> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-TW/llms.txt # Agent 核准 Agent 呼叫處理敏感操作(例如刪除資源或執行耗時流程)的 Tool 時,有時也需要 Workflow 所使用的[人機協作](https://mastra.zisheng.pro/zh-TW/docs/workflows/human-in-the-loop)監督機制。透過 Agent 核准,你可以在 Tool 呼叫執行前將其暫停,讓人員核准或拒絕;也可以讓 Tool 自行暫停,以向使用者要求額外資訊。 ## 何時使用 Agent 核准 - **破壞性或無法復原的動作**,例如刪除紀錄、傳送電子郵件或處理付款。 - **高成本操作**,例如呼叫昂貴的第三方 API,並希望先確認引數。 - **條件式確認**:Tool 開始執行後,才發現完成前需要使用者確認或提供額外資料。 ## 快速入門 將 Tool 標記為 `requireApproval: true`,接著檢查串流中的 `tool-call-approval` 區塊,以核准或拒絕: ```typescript import { Agent } from '@mastra/core/agent' import { createTool } from '@mastra/core/tools' import { z } from 'zod' const deleteTool = createTool({ id: 'delete-record', description: 'Delete a record by ID', inputSchema: z.object({ id: z.string() }), outputSchema: z.object({ deleted: z.boolean() }), requireApproval: true, execute: async ({ id }) => { await db.delete(id) return { deleted: true } }, }) const agent = new Agent({ id: 'my-agent', name: 'My Agent', model: 'openai/gpt-5-mini', tools: { deleteTool }, }) const stream = await agent.stream('Delete record abc-123') for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-approval') { const approved = await agent.approveToolCall({ runId: stream.runId }) for await (const c of approved.textStream) process.stdout.write(c) } } ``` > **備註:** Agent 核准會使用快照擷取請求狀態。請在 Mastra 執行個體上設定[儲存 Provider](https://mastra.zisheng.pro/zh-TW/docs/storage/overview),否則會看到「snapshot not found」錯誤。 > > Agent 執行的快照是精簡的恢復成品:只保存恢復已暫停執行所需的內容,並會在執行完成後刪除。請使用 [tracing](https://mastra.zisheng.pro/zh-TW/docs/observability/overview) 保存執行紀錄,並使用 [memory](https://mastra.zisheng.pro/zh-TW/docs/memory/overview) 保存對話記錄。 ## 核准的運作方式 Mastra 提供兩種不同的 Tool 呼叫暫停機制:**執行前核准**與**執行階段暫停**。 ### 執行前核准 執行前核准會在 Tool 呼叫的 `execute` 函式執行\_之前\_將其暫停。LLM 仍會決定要呼叫哪個 Tool 並提供引數,但在你明確核准之前,`execute` 不會執行。 下列旗標以 OR 邏輯共同控制此行為。只要\_任一\_旗標為 `true`,呼叫就會暫停: | 旗標 | 設定位置 | 範圍 | | --------------------------- | ---------------------------- | --------------------- | | `requireToolApproval: true` | `stream()` / `generate()` 選項 | 暫停該請求的**每一次** Tool 呼叫 | | `requireApproval: true` | `createTool()` 定義 | 暫停對**該特定 Tool** 的呼叫 | 串流會發出包含 `toolCallId`、`toolName` 和 `args` 的 `tool-call-approval` 區塊。使用串流的 `runId` 呼叫 `approveToolCall()` 或 `declineToolCall()` 即可繼續: ```typescript const stream = await agent.stream("What's the weather in London?", { requireToolApproval: true, }) for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-approval') { console.log('Tool:', chunk.payload.toolName) console.log('Args:', chunk.payload.args) // Approve const approved = await agent.approveToolCall({ runId: stream.runId }) for await (const c of approved.textStream) process.stdout.write(c) // Or decline const declined = await agent.declineToolCall({ runId: stream.runId }) for await (const c of declined.textStream) process.stdout.write(c) } } ``` #### 使用函式進行條件式核准 除了布林值之外,`requireToolApproval` 也接受能針對每次 Tool 呼叫做出決定的函式。它會接收模型傳入的 `toolName`、`args`、`requestContext` 與 `workspace`。傳回 `true` 代表該呼叫需要核准,傳回 `false` 則允許執行。如此便能在執行階段設定核准條件,例如只核准名稱符合特定模式的 Tool: ```typescript const stream = await agent.stream('Clean up old records', { requireToolApproval: ({ toolName }) => /^delete_/.test(toolName), }) ``` Tool 本身的 `requireApproval` 設定優先於上述函式,並由該規則決定此 Tool 是否需要核准。如果函式擲回錯誤,為確保安全,該呼叫會要求核准。 > **備註:** 以函式為基礎的 `requireToolApproval` 僅適用於一般的 `stream()` / `generate()` 呼叫。Durable Agent 和已儲存的 Agent 會保存其選項,而函式無法序列化,因此它們只接受布林值。如果在這些情境中傳入函式,系統會改為要求核准每一次 Tool 呼叫。 #### 將核准綁定至確切的 Tool 引數 對於敏感 Tool,應將核准綁定至審核者看到的確切 Tool 名稱與引數。如果這些引數在執行前發生變動,Tool 就不應沿用先前的核准來執行。 `tool-call-approval` 區塊已包含 `toolName`、`toolCallId` 與 `args`。顯示核准請求時,你可以為這些欄位建立指紋。下列範例使用 JSON 字串作為指紋,但在正式環境中,應使用 Tool 名稱與引數的穩定雜湊值: ```typescript import { Agent } from '@mastra/core/agent' // For your production usecase, build a stable hash of the tool name and args function actionFingerprint(toolName: string, args: unknown) { const payload = JSON.stringify({ toolName, args }) return `fingerprint-${payload}` } const sensitiveTools = new Set(['issue_refund', 'delete_record']) const approvedFingerprints = new Set() export const approvalBoundAgent = new Agent({ id: 'approval-bound-agent', name: 'Approval Bound Agent', model: 'openai/gpt-5.6-sol', tools: { issueRefundTool, deleteRecordTool }, hooks: { beforeToolCall: ({ toolName, input }) => { if (!sensitiveTools.has(toolName)) return const fingerprint = actionFingerprint(toolName, input) if (!approvedFingerprints.delete(fingerprint)) { return { proceed: false, output: `Tool call blocked: approval did not match ${toolName} arguments.`, } } }, }, }) ``` ```typescript const stream = await approvalBoundAgent.stream('Refund order ord-1042', { requireToolApproval: ({ toolName }) => sensitiveTools.has(toolName), }) async function consumeApprovalStream(currentStream: typeof stream) { for await (const chunk of currentStream.fullStream) { if (chunk.type === 'tool-call-approval') { const { toolName, toolCallId, args } = chunk.payload const fingerprint = actionFingerprint(toolName, args) // Present toolName, args, and fingerprint to your approval UI. const approved = await showApprovalDialog({ toolName, args, fingerprint }) const nextStream = approved ? await approveReviewedToolCall(currentStream.runId, toolCallId, fingerprint) : await approvalBoundAgent.declineToolCall({ runId: currentStream.runId, toolCallId }) await consumeApprovalStream(nextStream) } } } async function approveReviewedToolCall(runId: string, toolCallId: string, fingerprint: string) { approvedFingerprints.add(fingerprint) return approvalBoundAgent.approveToolCall({ runId, toolCallId }) } await consumeApprovalStream(stream) ``` 在正式環境中,請將已核准的指紋儲存在永久性儲存空間,並以使用者、執行、Tool 呼叫和政策版本限定範圍。上述 `Set` 刻意維持精簡,讓界線清楚可見:核准只能使用一次,而且僅適用於已審核的相同標準化 Tool 引數。 ### 使用 `suspend()` 在執行階段暫停 Tool 也可以在其 `execute` 函式執行\_期間\_呼叫 `suspend()` 來暫停。當 Tool 開始執行後才發現需要更多使用者輸入或確認才能完成時,此功能相當實用。 串流會發出 `tool-call-suspended` 區塊,其中包含由 Tool 的 `suspendSchema` 定義的自訂酬載。呼叫 `resumeStream()` 並傳入符合 Tool `resumeSchema` 的資料,即可恢復執行。 ```typescript const weatherTool = createTool({ id: 'get-weather', inputSchema: z.object({ location: z.string().optional(), }), suspendSchema: z.object({ question: z.string(), }), resumeSchema: z.object({ location: z.string(), }), execute: async ({ location }, context) => { if (!location) { return await context?.agent?.suspend({ question: 'Which city would you like the weather for?', }) } return await fetchWeather(location) }, }) ``` > **備註:** `suspend()` 不會擲回錯誤,請在呼叫後立即傳回(例如 `return await suspend({ ... })`)。Tool 暫停前,`await suspend(...)` 之後的程式碼仍會執行。 ## 搭配 `generate()` 使用 Tool 核准 Tool 核准也適用於非串流情境的 `generate()`。當 Tool 需要核准時,`generate()` 會立即傳回,其中包含 `finishReason: 'suspended'`、含有 Tool 呼叫詳細資訊(`toolCallId`、`toolName`、`args`)的 `suspendPayload`,以及 `runId`: ```typescript const output = await agent.generate('Find user John', { requireToolApproval: true, }) if (output.finishReason === 'suspended') { console.log('Tool requires approval:', output.suspendPayload.toolName) // Approve const result = await agent.approveToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }) console.log('Final result:', result.text) // Or decline const result = await agent.declineToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }) } ``` ### 串流與產生結果的比較 | 項目 | `stream()` | `generate()` | | ---- | ---------------------------- | ------------------------------------------------ | | 回應類型 | 串流區塊 | 完整回應 | | 核准偵測 | `tool-call-approval` 區塊 | `finishReason: 'suspended'` | | 核准方法 | `approveToolCall({ runId })` | `approveToolCallGenerate({ runId, toolCallId })` | | 拒絕方法 | `declineToolCall({ runId })` | `declineToolCallGenerate({ runId, toolCallId })` | | 結果 | 可逐一走訪的串流 | 完整輸出物件 | > **備註:** 四個方法的 `toolCallId` 都是選用項目。可能有多個待處理的 Tool 呼叫時(Supervisor Agent 中很常見),請傳入此值。省略時,Agent 會恢復最近暫停的 Tool 呼叫。 ## Tool 層級核准 除了在 Agent 層級暫停每次 Tool 呼叫,也可以將個別 Tool 標記為需要核准。這能提供細部控制:只有特定 Tool 會暫停,其他 Tool 則會立即執行。 ### 使用 `requireApproval` 核准 在 Tool 定義上設定 `requireApproval: true`。無論 Agent 是否設定 `requireToolApproval`,Tool 都會在執行前暫停: ```typescript export const testTool = createTool({ id: 'test-tool', description: 'Fetches weather for a location', inputSchema: z.object({ location: z.string(), }), outputSchema: z.object({ weather: z.string(), }), resumeSchema: z.object({ approved: z.boolean(), }), execute: async inputData => { const response = await fetch(`https://wttr.in/${inputData.location}?format=3`) const weather = await response.text() return { weather } }, requireApproval: true, }) ``` 當 `requireApproval` 為 `true` 時,串流會以與 Agent 層級核准相同的方式發出 `tool-call-approval` 區塊。使用 `approveToolCall()` 或 `declineToolCall()` 即可繼續: ```typescript const stream = await agent.stream("What's the weather in London?") for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-approval') { console.log('Approval required for:', chunk.payload.toolName) } } const handleApproval = async () => { const approvedStream = await agent.approveToolCall({ runId: stream.runId }) for await (const chunk of approvedStream.textStream) { process.stdout.write(chunk) } process.stdout.write('\n') } ``` ### 使用 `suspend()` 核准 採用此方式時,Agent 和 Tool 都不會使用 `requireApproval`。Tool 的 `execute` 函式會改為呼叫 `suspend()`,在特定位置暫停,並將情境資訊或確認提示傳回給使用者。當核准取決於執行階段條件,而非無條件要求核准時,此方式相當實用。 ```typescript export const testToolB = createTool({ id: 'test-tool-b', description: 'Fetches weather for a location', inputSchema: z.object({ location: z.string(), }), outputSchema: z.object({ weather: z.string(), }), resumeSchema: z.object({ approved: z.boolean(), }), suspendSchema: z.object({ reason: z.string(), }), execute: async (inputData, context) => { const { resumeData: { approved } = {}, suspend } = context?.agent ?? {} if (!approved) { return suspend?.({ reason: 'Approval required.' }) } const response = await fetch(`https://wttr.in/${inputData.location}?format=3`) const weather = await response.text() return { weather } }, }) ``` 採用此方式時,串流會包含 `tool-call-suspended` 區塊,且 `suspendPayload` 會包含 Tool `suspendSchema` 所定義的 `reason`。使用 `resumeSchema` 資料與 `runId` 呼叫 `resumeStream` 即可繼續: ```typescript const stream = await agent.stream("What's the weather in London?") for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-suspended') { console.log(chunk.payload.suspendPayload) } } const handleResume = async () => { const resumedStream = await agent.resumeStream({ approved: true }, { runId: stream.runId }) for await (const chunk of resumedStream.textStream) { process.stdout.write(chunk) } process.stdout.write('\n') } ``` ## 自動恢復 Tool 使用會呼叫 `suspend()` 的 Tool 時,可以啟用自動恢復,讓 Agent 根據使用者的下一則訊息恢復已暫停的 Tool。在 Agent 的預設選項或個別請求中,將 `autoResumeSuspendedTools` 設為 `true`: ```typescript import { Agent } from '@mastra/core/agent' import { Memory } from '@mastra/memory' const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model: 'openai/gpt-5-mini', tools: { weatherTool }, memory: new Memory(), defaultOptions: { autoResumeSuspendedTools: true, }, }) ``` 啟用後,Agent 會在收到下一則使用者訊息時,從訊息記錄中偵測已暫停的 Tool。它會根據 Tool 的 `resumeSchema` 擷取 `resumeData`,接著自動恢復 Tool。 下列範例呈現完整的對話流程: ```typescript import { createTool } from '@mastra/core/tools' import { z } from 'zod' const weatherTool = createTool({ id: 'weather-tool', description: 'Fetches weather for a city', inputSchema: z.object({ city: z.string(), }), outputSchema: z.object({ weather: z.string(), }), suspendSchema: z.object({ message: z.string(), }), resumeSchema: z.object({ city: z.string(), }), execute: async (inputData, context) => { const { resumeData, suspend } = context?.agent ?? {} // If no city provided, ask the user if (!inputData.city && !resumeData?.city) { return suspend?.({ message: 'What city do you want to know the weather for?' }) } const city = resumeData?.city ?? inputData.city const response = await fetch(`https://wttr.in/${city}?format=3`) const weather = await response.text() return { weather: `${city}: ${weather}` } }, }) ``` ```typescript const stream = await agent.stream("What's the weather like?") for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-suspended') { console.log(chunk.payload.suspendPayload) } } // User sends follow-up on the same thread const resumedStream = await agent.stream('San Francisco') for await (const chunk of resumedStream.textStream) { process.stdout.write(chunk) } ``` ```text User: "What's the weather like?" Agent: "What city do you want to know the weather for?" User: "San Francisco" Agent: "The weather in San Francisco is: San Francisco: ☀️ +72°F" ``` 第二則訊息會自動恢復已暫停的 Tool。Agent 會從使用者訊息中擷取 `{ city: "San Francisco" }`,並將其作為 `resumeData` 傳入。 ### 必要條件 若要使用 Tool 自動恢復功能: - **已設定 Memory**:Agent 需要 Memory,才能跨訊息追蹤已暫停的 Tool - **相同對話串**:後續訊息必須使用相同的 Memory 對話串與資源識別碼 - **已定義 `resumeSchema`**:Tool 必須定義 `resumeSchema`,讓 Agent 知道要從使用者訊息中擷取何種資料結構 ### 手動與自動恢復的比較 | 方式 | 使用情境 | | ------------------------------ | ------------------------- | | 手動(`resumeStream()`) | 程式化控制、Webhook、按鈕點擊、外部觸發流程 | | 自動(`autoResumeSuspendedTools`) | 使用者以自然語言提供恢復資料的對話流程 | 兩種方式都能搭配相同的 Tool 定義。只有當訊息記錄中存在已暫停的 Tool,且使用者在相同對話串傳送新訊息時,才會觸發自動恢復。 ## 重新啟動後恢復執行 上述範例會在暫停與核准之間保留 `stream.runId`。處理程序持續執行時,這種方式可以正常運作;但在正式環境中,核准往往會稍後才送達,例如重新整理頁面、伺服器重新啟動,或由負載平衡器後方的另一個伺服器執行個體接收核准。 使用 [`listSuspendedRuns()`](https://mastra.zisheng.pro/zh-TW/reference/agents/listSuspendedRuns),即可從儲存空間重新找出對話中待處理的執行: ```typescript // In the request handler that receives the user's decision const { runs } = await agent.listSuspendedRuns({ threadId: 'thread-123', resourceId: 'user-456', }) const run = runs[0] const toolCall = run?.toolCalls[0] if (run && toolCall) { let stream if (toolCall.requiresApproval) { // Suspended by requireApproval — approve or decline the tool call stream = await agent.approveToolCall({ runId: run.runId, toolCallId: toolCall.toolCallId }) } else { // Suspended by suspend() — resume with the data the tool asked for console.log('Tool asked:', toolCall.suspendPayload) stream = await agent.resumeStream({ name: 'San Francisco' }, { runId: run.runId }) } for await (const chunk of stream.textStream) process.stdout.write(chunk) } ``` 傳回的每次執行都包含已暫停的 Tool 呼叫(`toolCallId`、`toolName`、`args` 與 `requiresApproval`)。核准造成的暫停(`requiresApproval: true`)需使用 `approveToolCall()` / `declineToolCall()` 回應,而由 `suspend()` 造成的暫停則會攜帶 `suspendPayload`,並要求以恢復資料呼叫 `resumeStream()`。因此,即使未在記憶體中保留任何狀態,你仍能針對任一流程重建正確的 UI。 `sendToolApproval()` 會自動使用相同的儲存空間探索機制:如果在記憶體中找不到該對話串的有效執行,它會先在儲存空間查詢已暫停的執行,之後才會失敗。若有數個已暫停的執行符合該對話串,請傳入 `toolCallId` 以消除歧義。 相同的探索功能也可透過 HTTP 的 `GET /agents/:agentId/suspended-runs`,以及用戶端 SDK 的 [`agent.listSuspendedRuns()`](https://mastra.zisheng.pro/zh-TW/reference/client-js/agents) 使用,因此瀏覽器型核准 UI 可以直接重新找出待處理的執行。 > **備註:** 只有在 Mastra 執行個體設定了永久性[儲存 Provider](https://mastra.zisheng.pro/zh-TW/docs/storage/overview)時,已暫停的執行才能在重新啟動後繼續存在。預設的記憶體內儲存區會在處理程序結束時遺失快照。 ## Tool 核准:Supervisor Agent [Supervisor Agent](https://mastra.zisheng.pro/zh-TW/docs/capabilities/subagents) 會使用 `.stream()` 或 `.generate()` 協調多個 Subagent。當 Subagent 呼叫需要核准的 Tool 時,請求會沿著委派鏈向上傳遞,並出現在 Supervisor 層級: 1. Supervisor 將工作委派給 Subagent。 2. Subagent 呼叫設有 `requireApproval: true` 或使用 `suspend()` 的 Tool。 3. 核准請求向上傳遞至 Supervisor。 4. 你在 Supervisor 層級核准或拒絕。 5. 決定向下傳回 Subagent。 Tool 核准也會經由多層委派傳遞。如果 Supervisor 委派給 Subagent A,而 Subagent A 又委派給 Subagent B,且後者有設為 `requireApproval: true` 的 Tool,核准請求仍會出現在最上層 Supervisor。 ### 在 Supervisor Agent 中核准與拒絕 下列範例會建立具有需核准 Tool 的 Subagent。Tool 觸發核准請求時,該請求會以 `tool-call-approval` 區塊出現在 Supervisor 的串流中: ```typescript import { Agent } from '@mastra/core/agent' import { createTool } from '@mastra/core/tools' import { Memory } from '@mastra/memory' import { z } from 'zod' const findUserTool = createTool({ id: 'find-user', description: 'Finds user by ID in the database', inputSchema: z.object({ userId: z.string(), }), outputSchema: z.object({ user: z.object({ id: z.string(), name: z.string(), email: z.string(), }), }), requireApproval: true, execute: async input => { const user = await database.findUser(input.userId) return { user } }, }) const dataAgent = new Agent({ id: 'data-agent', name: 'Data Agent', description: 'Handles database queries and user data retrieval', model: 'openai/gpt-5-mini', tools: { findUserTool }, }) const supervisorAgent = new Agent({ id: 'supervisor', name: 'Supervisor Agent', instructions: `You coordinate data retrieval tasks. Delegate to data-agent for user lookups.`, model: 'openai/gpt-5.6-sol', agents: { dataAgent }, memory: new Memory(), }) const stream = await supervisorAgent.stream('Find user with ID 12345') for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-approval') { console.log('Tool requires approval:', chunk.payload.toolName) console.log('Arguments:', chunk.payload.args) // Approve the tool call const resumeStream = await supervisorAgent.approveToolCall({ runId: stream.runId, toolCallId: chunk.payload.toolCallId, }) for await (const resumeChunk of resumeStream.textStream) { process.stdout.write(resumeChunk) } // To decline instead, use: const declineStream = await supervisorAgent.declineToolCall({ runId: stream.runId, toolCallId: chunk.payload.toolCallId, }) } } ``` ### 在 Supervisor Agent 中使用 `suspend()` Tool 也可以使用 [`suspend()`](#approval-using-suspend) 暫停執行,並將情境資訊傳回給使用者。此方式會像 `requireApproval` 一樣沿著 Supervisor 委派鏈運作:暫停會出現在 Supervisor 層級: ```typescript const conditionalTool = createTool({ id: 'conditional-operation', description: 'Performs an operation that may require confirmation', inputSchema: z.object({ operation: z.string(), }), suspendSchema: z.object({ message: z.string(), }), resumeSchema: z.object({ confirmed: z.boolean(), }), execute: async (input, context) => { const { resumeData } = context?.agent ?? {} if (!resumeData?.confirmed) { return context?.agent?.suspend({ message: `Confirm: ${input.operation}?`, }) } // Proceed with operation return await performOperation(input.operation) }, }) ``` ```typescript // When using this tool through a subagent in supervisor agents for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-suspended') { console.log('Tool suspended:', chunk.payload.suspendPayload.message) // Resume with confirmation const resumeStream = await supervisorAgent.resumeStream( { confirmed: true }, { runId: stream.runId }, ) for await (const resumeChunk of resumeStream.textStream) { process.stdout.write(resumeChunk) } } } ``` ### 搭配 `generate()` 使用 Supervisor 核准 Tool 核准傳遞也適用於 Supervisor Agent 中的 `generate()`: ```typescript const output = await supervisorAgent.generate('Find user with ID 12345', { maxSteps: 10, }) if (output.finishReason === 'suspended') { console.log('Tool requires approval:', output.suspendPayload.toolName) // Approve const result = await supervisorAgent.approveToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }) console.log('Final result:', result.text) } ``` ## 相關內容 - [Tools](https://mastra.zisheng.pro/zh-TW/docs/agents/using-tools) - [Agent 概觀](https://mastra.zisheng.pro/zh-TW/docs/agents/overview) - [MCP 概觀](https://mastra.zisheng.pro/zh-TW/docs/mcp/overview) - [Memory](https://mastra.zisheng.pro/zh-TW/docs/memory/overview) - [請求情境](https://mastra.zisheng.pro/zh-TW/docs/server/request-context)