> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-HK/llms.txt # 儲存 儲存 API 已標準化,所有方法均採用一致的分頁和命名模式。 ## 資料庫遷移 請透過你平常的遷移流程(例如 Prisma Migrate、Drizzle Kit 或 DBA 審核流程)執行以下 SQL 遷移。 ### 重新命名 Scorers 資料表欄位 `mastra_scorers` 中的 `runtimeContext` 欄位已重新命名為 `requestContext`。 > **誰需要執行此遷移:** 只有在你將 `@mastra/pg` 或 `@mastra/libsql` 配合 Evals/評分使用,而且 `runtimeContext` 欄位中已有資料時才需要。 > **不執行會導致甚麼問題:** 現有評分記錄的請求內容資料將無法存取。 部署 v1(初始化時會加入新的 `requestContext` 欄位)後,複製資料並移除舊欄位: ```sql UPDATE mastra_scorers SET "requestContext" = "runtimeContext" WHERE "runtimeContext" IS NOT NULL; ALTER TABLE mastra_scorers DROP COLUMN "runtimeContext"; ``` ### 重複 span 遷移 如果你從較舊版本的 Mastra 升級,`mastra_spans` 資料表中可能有重複的 `(traceId, spanId)` 項目。v1 會為這些欄位加入唯一約束以確保資料完整性,但如果存在重複項目,就無法加入此約束。 > **誰需要執行此遷移:** 只有在你有來自 v1 之前 Mastra 版本的現有 span 資料,並遇到重複鍵違規或建立約束失敗的錯誤時才需要。 > **不執行會導致甚麼問題:** 儲存初始化可能會在嘗試加入唯一約束時失敗,或你可能會看到「duplicate key value violates unique constraint」之類的錯誤。 #### 選項 1:使用 CLI(建議) 執行遷移命令,它會自動移除重複的 span 並加入約束: ```bash npx mastra migrate ``` CLI 會先打包你的項目並連接至已設定的儲存,然後執行遷移。移除重複項目時,它會保留最完整的記錄(根據 `endTime` 和 attributes 判斷)。 #### 選項 2:手動執行 SQL(PostgreSQL) 如果你想手動執行遷移: ```sql -- Remove duplicates, keeping the most complete record DELETE FROM mastra_spans a USING mastra_spans b WHERE a.ctid < b.ctid AND a."traceId" = b."traceId" AND a."spanId" = b."spanId"; -- Add the unique constraint ALTER TABLE mastra_spans ADD CONSTRAINT mastra_spans_trace_span_unique UNIQUE ("traceId", "spanId"); ``` #### 選項 3:手動遷移(其他資料庫) 如使用 ClickHouse、LibSQL、MongoDB 或 MSSQL,請使用編程 API: ```typescript const storage = mastra.getStorage() const observabilityStore = await storage.getStore('observability') // Check if migration is needed const status = await observabilityStore?.checkSpansMigrationStatus() console.log(status) // Run the migration const result = await observabilityStore?.migrateSpans() console.log(result) ``` ### JSON 欄位(TEXT → JSONB) **僅限 PostgreSQL。**`mastra_threads` 中的 `metadata` 欄位和 `mastra_workflow_snapshot` 中的 `snapshot` 欄位已由 TEXT 改為 JSONB。 > **建議:** 遷移至 JSONB 後,可使用 PostgreSQL 原生 JSON 運算子和 GIN 索引,提升 JSON 欄位的查詢效能。 ```sql ALTER TABLE mastra_threads ALTER COLUMN metadata TYPE jsonb USING metadata::jsonb; ALTER TABLE mastra_workflow_snapshot ALTER COLUMN snapshot TYPE jsonb USING snapshot::jsonb; ``` ## 新增 ### 在 `MastraCompositeStore` 中組合儲存 `MastraCompositeStore` 現在可以組合來自不同 adapter 的儲存 domain。當你需要針對不同用途使用不同資料庫時,可以使用它——例如以 PostgreSQL 儲存記憶和 Workflow,並以專門的資料庫處理 observability。 ```typescript import { MastraCompositeStore } from '@mastra/core/storage' import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg' import { MemoryLibSQL } from '@mastra/libsql' import { Mastra } from '@mastra/core' // Compose domains from different stores const mastra = new Mastra({ storage: new MastraCompositeStore({ id: 'composite', domains: { memory: new MemoryLibSQL({ url: 'file:./local.db' }), workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }), scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }), }, }), }) ``` 詳情請參閱[儲存組合參考資料](https://mastra.zisheng.pro/zh-HK/reference/storage/composite)。 ## 已變更 ### `MastraStorage` 重新命名為 `MastraCompositeStore` `MastraStorage` 類別已重新命名為 `MastraCompositeStore`,以更準確反映它作為複合儲存實作的角色:將不同 domain 路由至不同的底層 store。這可避免它與一般的「Mastra Storage」概念(即 Mastra instance 上的 `storage` 屬性)混淆。 為向後兼容,舊名稱 `MastraStorage` 仍可作為已棄用的 alias 使用,但將於未來版本移除。 要進行遷移,請更新 import 和 instance 化方式: ```diff - import { MastraStorage } from "@mastra/core/storage"; + import { MastraCompositeStore } from "@mastra/core/storage"; import { MemoryLibSQL } from "@mastra/libsql"; import { WorkflowsPG } from "@mastra/pg"; export const mastra = new Mastra({ - storage: new MastraStorage({ + storage: new MastraCompositeStore({ id: "composite", domains: { memory: new MemoryLibSQL({ url: "file:./memory.db" }), workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }), }, }), }); ``` > **備註:** 如果你直接使用單一 store 實作(例如 `PostgresStore` 或 `LibSQLStore`),請保留現有設定。此變更只影響明確使用 `MastraStorage` 組合儲存的程式碼。 ### 儲存 instance 必須提供 `id` 屬性 儲存 instance 現在必須提供 `id` 屬性。這個唯一識別碼用於在 Mastra 中追蹤和管理儲存 instance。應為應用程式中的每個儲存 instance 設定一個具描述性且唯一的 `id` 字串。 要進行遷移,請在儲存 constructor 中加入 `id` 欄位。 ```diff const storage = new PostgresStore({ + id: 'main-postgres-store', connectionString: process.env.POSTGRES_CONNECTION_STRING, schemaName: 'public', }); const upstashStore = new UpstashStore({ + id: 'upstash-cache-store', url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }); ``` ### 分頁由 `offset/limit` 改為 `page/perPage` 所有分頁 API 現在都使用 `page` 和 `perPage`,而非 `offset` 和 `limit`,以配合以頁面為基礎的網絡分頁方式。 要進行遷移,請將所有分頁參數由 `offset/limit` 更新為 `page/perPage`。請注意,`page` 的索引由 0 開始。 ```diff memoryStore.listMessages({ threadId: 'thread-123', - offset: 0, - limit: 20, + page: 0, + perPage: 20, }); ``` ### `getMessagesPaginated` 改為 `listMessages` `getMessagesPaginated()` 方法已由 `listMessages()` 取代。新方法支援以 `perPage: false` 擷取所有記錄而不分頁。此變更符合 `list*` 命名慣例,亦讓擷取所有記錄的方式更靈活。 要進行遷移,請重新命名方法並更新分頁參數。你現在可以使用 `perPage: false` 擷取所有記錄。 ```diff + const memoryStore = await storage.getStore('memory'); + // Paginated - const result = await storage.getMessagesPaginated({ + const result = await memoryStore?.listMessages({ threadId: 'thread-123', - offset: 0, - limit: 20, + page: 0, + perPage: 20, }); // Fetch all records (no pagination limit) + const allMessages = await memoryStore?.listMessages({ + threadId: 'thread-123', + page: 0, + perPage: false, + }); ``` > **Codemod:** 你可以使用 Mastra 的 codemod CLI 自動更新程式碼: > > ```bash > npx @mastra/codemod@latest v1/storage-get-messages-paginated . > ``` ### 透過 `getStore()` 存取指定 domain 的儲存 儲存操作現在要透過指定 domain 的 store 存取,而非直接在儲存 instance 上存取。 domain 包括: - **`memory`** - thread、訊息和資源 - **`workflows`** - Workflow snapshot - **`scores`** - 評估分數 - **`observability`** - Trace 和 span - **`agents`** - 已儲存的 Agent 資料 要進行遷移,請以 domain 名稱呼叫 `getStore()`,然後在傳回的 store 上呼叫方法。 ```diff const storage = mastra.getStorage(); // Memory operations (threads, messages, resources) - const thread = await storage.getThread({ threadId: '123' }); - await storage.saveThread({ thread }); + const memoryStore = await storage.getStore('memory'); + const thread = await memoryStore?.getThreadById({ threadId: '123' }); + await memoryStore?.saveThread({ thread }); // Workflow operations (snapshots) - const snapshot = await storage.loadWorkflowSnapshot({ runId, workflowName }); - await storage.persistWorkflowSnapshot({ runId, workflowName, snapshot }); + const workflowStore = await storage.getStore('workflows'); + const snapshot = await workflowStore?.loadWorkflowSnapshot({ runId, workflowName }); + await workflowStore?.persistWorkflowSnapshot({ runId, workflowName, snapshot }); // Observability operations (traces, spans) - const traces = await storage.listTraces({ page: 0, perPage: 20 }); + const observabilityStore = await storage.getStore('observability'); + const traces = await observabilityStore?.listTraces({ page: 0, perPage: 20 }); // Score operations (evaluations) - const scores = await storage.listScoresByScorerId({ scorerId: 'helpfulness' }); + const scoresStore = await storage.getStore('scores'); + const scores = await scoresStore?.listScoresByScorerId({ scorerId: 'helpfulness' }); ``` ### `getThreadsByResourceId` 改為 `listThreads` `getThreadsByResourceId()` 方法已由 `listThreads()` 取代。新方法加入分頁支援,亦可按 `resourceId`、`metadata` 或同時按兩者篩選。 > **Important:** 舊有的 `getThreadsByResourceId()` 會傳回所有符合條件的 thread,而不會分頁。新的 `listThreads()` 必須提供分頁參數。要保留擷取所有 thread 的舊有行為,請使用 `perPage: false`。 要進行遷移,請使用 memory store 和新的 `listThreads()` 方法,並提供分頁參數及可選的 filter object。 ```diff - const threads = await storage.getThreadsByResourceId({ - resourceId: 'res-123', - }); + const memoryStore = await storage.getStore('memory'); + + // Paginated (recommended for large datasets) + const result = await memoryStore?.listThreads({ + filter: { resourceId: 'res-123' }, + page: 0, + perPage: 20, + }); + const threads = result?.threads; + + // Or fetch all threads like before (use perPage: false) + const allResult = await memoryStore?.listThreads({ + filter: { resourceId: 'res-123' }, + perPage: false, + }); + const allThreads = allResult?.threads; ``` 新方法亦支援: - 列出所有 thread(省略 filter) - 只按 metadata 篩選 - 結合 resourceId + metadata 篩選 ```typescript // List all threads await memoryStore?.listThreads({ page: 0, perPage: 20 }) // Filter by metadata only await memoryStore?.listThreads({ filter: { metadata: { status: 'active' } }, page: 0, perPage: 20, }) // Combined filter await memoryStore?.listThreads({ filter: { resourceId: 'user-123', metadata: { category: 'support' }, }, page: 0, perPage: 20, }) ``` > **Codemod:** 你可以使用 Mastra 的 codemod CLI 自動更新程式碼: > > ```bash > npx @mastra/codemod@latest v1/storage-list-threads-by-resource-to-list-threads . > ``` ### `getWorkflowRuns` 改為 `listWorkflowRuns` `getWorkflowRuns()` 方法已重新命名為 `listWorkflowRuns()`。此變更符合以 `list*` 方法傳回集合的慣例。 要進行遷移,請使用 workflows store、重新命名方法呼叫,並更新分頁參數。 ```diff - const runs = await storage.getWorkflowRuns({ + const workflowStore = await storage.getStore('workflows'); + const runs = await workflowStore?.listWorkflowRuns({ fromDate, toDate, + page: 0, + perPage: 20, }); ``` > **Codemod:** 你可以使用 Mastra 的 codemod CLI 自動更新程式碼: > > ```bash > npx @mastra/codemod@latest v1/storage-list-workflow-runs . > ``` ### `getMessagesById` 改為 `listMessagesById` `getMessagesById()` 方法已重新命名為 `listMessagesById()`。此變更符合以 `list*` 方法傳回集合的慣例。 要進行遷移,請使用 memory store 並重新命名方法呼叫。 ```diff + const memoryStore = await storage.getStore('memory'); - const result = await storage.getMessagesById({ + const result = await memoryStore?.listMessagesById({ messageIds: ['msg-1', 'msg-2'], }); ``` > **Codemod:** 你可以使用 Mastra 的 codemod CLI 自動更新程式碼: > > ```bash > npx @mastra/codemod@latest v1/storage-list-messages-by-id . > ``` ### 儲存 `getMessages` 和 `saveMessages` 的 signature `getMessages()` 和 `saveMessages()` 方法的 signature 和傳回類型已變更。format overload 已移除,而這些方法現在一律使用 `MastraDBMessage`。此變更移除格式差異,令 API 更簡單。 要進行遷移,請使用 memory store、移除 format 參數,並更新程式碼以配合一致的傳回類型。 ```diff + const memoryStore = await storage.getStore('memory'); + // Always returns { messages: MastraDBMessage[] } - const v1Messages = await storage.getMessages({ threadId, format: 'v1' }); - const v2Messages = await storage.getMessages({ threadId, format: 'v2' }); + const result = await memoryStore?.getMessages({ threadId }); + const messages = result?.messages; // MastraDBMessage[] // SaveMessages always uses MastraDBMessage - await storage.saveMessages({ messages: v1Messages, format: 'v1' }); - await storage.saveMessages({ messages: v2Messages, format: 'v2' }); + const saveResult = await memoryStore?.saveMessages({ messages: mastraDBMessages }); + const saved = saveResult?.messages; // MastraDBMessage[] ``` ### Vector store API 由位置參數改為具名參數 所有 vector store 方法現在都使用 arguments object,而非位置參數。這讓每個值的用途在呼叫位置一目了然,亦令 method signature 可在不依賴參數順序的情況下演進。 要進行遷移,請更新所有 vector store 方法呼叫,改用 arguments object。 ```diff - await vectorDB.createIndex(indexName, 3, 'cosine'); + await vectorDB.createIndex({ + indexName: indexName, + dimension: 3, + metric: 'cosine', + }); - await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]); + await vectorDB.upsert({ + indexName: indexName, + vectors: [[1, 2, 3]], + metadata: [{ test: 'data' }], + }); - await vectorDB.query(indexName, [1, 2, 3], 5); + await vectorDB.query({ + indexName: indexName, + queryVector: [1, 2, 3], + topK: 5, + }); ``` ### 重新命名 Vector store 方法 `updateIndexById` 和 `deleteIndexById` 方法已分別重新命名為 `updateVector` 和 `deleteVector`。新名稱清楚表示這些方法是對 vector 執行操作。 要進行遷移,請重新命名方法並傳入 arguments object。 ```diff - await vectorDB.updateIndexById(indexName, id, update); - await vectorDB.deleteIndexById(indexName, id); + await vectorDB.updateVector({ indexName, id, update }); + await vectorDB.deleteVector({ indexName, id }); ``` ### PGVector constructor 由連接字串改為 object PGVector constructor 現在需要 object 參數,而非連接字串。此變更令所有儲存 adapter 的 API 更一致。 要進行遷移,請將連接字串作為 object property 傳入。 ```diff - const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); + const pgVector = new PgVector({ + connectionString: process.env.POSTGRES_CONNECTION_STRING, + }); ``` > **Codemod:** 你可以使用 Mastra 的 codemod CLI 自動更新程式碼: > > ```bash > npx @mastra/codemod@latest v1/vector-pg-constructor . > ``` ### PGVector `defineIndex` 改為 `buildIndex` `defineIndex()` 方法已移除,並以 `buildIndex()` 取代,清楚表示該方法會建立 index。 要進行遷移,請重新命名方法並傳入 arguments object。 ```diff - await vectorDB.defineIndex(indexName, 'cosine', { type: 'flat' }); + await vectorDB.buildIndex({ + indexName: indexName, + metric: 'cosine', + indexConfig: { type: 'flat' }, + }); ``` ### `PostgresStore`:`schema` 改為 `schemaName` PostgresStore constructor 中的 `schema` 參數已重新命名為 `schemaName`。新名稱清楚表示該值是資料庫 schema 的名稱。 要進行遷移,請重新命名參數。 ```diff const pgStore = new PostgresStore({ connectionString: process.env.POSTGRES_CONNECTION_STRING, - schema: customSchema, + schemaName: customSchema, }); ``` > **Codemod:** 你可以使用 Mastra 的 codemod CLI 自動更新程式碼: > > ```bash > npx @mastra/codemod@latest v1/storage-postgres-schema-name . > ``` ### 評分儲存方法改用 `listScoresBy*` 模式 評分儲存 API 已重新命名,以遵循 `listScoresBy*` 模式。此變更令它與更廣泛的 API 命名慣例保持一致。 要進行遷移,請將方法名稱由 `getScores` 更新為 `listScoresByScorerId` 及相關變體。 ```diff - const scores = await storage.getScores({ scorerName: 'helpfulness-scorer' }); + const scores = await storage.listScoresByScorerId({ + scorerId: 'helpfulness-scorer', + }); + // Also available: listScoresByRunId, listScoresByEntityId, listScoresBySpan ``` ## 已移除 ### 非分頁儲存函數 非分頁儲存函數已移除,並由分頁版本取代。所有 list 操作現在都使用分頁,但你可以透過 `perPage: false` 擷取所有記錄。此變更令 API 保持一致,並防止意外載入大型資料集。 要進行遷移,請透過 domain store 使用分頁方法。要擷取所有記錄,請使用 `perPage: false`。 ```diff - // Non-paginated direct access - const messages = await storage.getMessages({ threadId }); + // Use paginated methods via domain stores + const memoryStore = await storage.getStore('memory'); + const result = await memoryStore?.listMessages({ threadId, page: 0, perPage: 20 }); + // Or fetch all + const allMessages = await memoryStore?.listMessages({ + threadId, + page: 0, + perPage: false, + }); ``` ### `getTraces` 和 `getTracesPaginated` `getTraces()` 和 `getTracesPaginated()` 方法已從儲存中移除。請使用 observability 套件存取 Trace,而非 core storage。 要進行遷移,請改用 observability 儲存方法。 ```diff - const traces = await storage.getTraces({ traceId: 'trace-123' }); - const paginated = await storage.getTracesPaginated({ page: 0, perPage: 20 }); + // Use observability API for traces + import { initObservability } from '@mastra/observability'; + const observability = initObservability({ config: { ... } }); + // Access traces through observability API ``` ### Evals 測試工具 Evals domain 測試工具已從 `@internal/test-utils` 移除。此變更反映舊有 Evals 功能已遭移除。 要進行遷移,請在測試時直接使用儲存 API,而非專門的 Evals 測試工具。 ```diff - import { createEvalsTests } from '@internal/test-utils/domains/evals'; - createEvalsTests({ storage }); + // Use storage APIs directly for testing ``` ### 從 MSSQL 儲存移除 TABLE\_EVALS MSSQL 儲存實作中的 `TABLE_EVALS` 資料表已移除。此變更反映舊有 Evals 功能已遭移除。 如果你正在將 MSSQL 儲存配合 Evals 使用,請遷移至另一個儲存 adapter,或移除 Evals 功能。