Agent 核准
Agent 呼叫處理敏感操作(例如刪除資源或執行耗時流程)的 Tool 時,有時也需要 Workflow 所使用的人機協作監督機制。透過 Agent 核准,你可以在 Tool 呼叫執行前將其暫停,讓人員核准或拒絕;也可以讓 Tool 自行暫停,以向使用者要求額外資訊。
何時使用 Agent 核准「何時使用 Agent 核准」的直接連結
- 破壞性或無法復原的動作,例如刪除紀錄、傳送電子郵件或處理付款。
- 高成本操作,例如呼叫昂貴的第三方 API,並希望先確認引數。
- 條件式確認:Tool 開始執行後,才發現完成前需要使用者確認或提供額外資料。
快速入門「快速入門」的直接連結
將 Tool 標記為 requireApproval: true,接著檢查串流中的 tool-call-approval 區塊,以核准或拒絕:
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,否則會看到「snapshot not found」錯誤。
Agent 執行的快照是精簡的恢復成品:只保存恢復已暫停執行所需的內容,並會在執行完成後刪除。請使用 tracing 保存執行紀錄,並使用 memory 保存對話記錄。
核准的運作方式「核准的運作方式」的直接連結
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() 即可繼續:
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:
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 就不應沿用先前的核准來執行。
tool-call-approval 區塊已包含 toolName、toolCallId 與 args。顯示核准請求時,你可以為這些欄位建立指紋。下列範例使用 JSON 字串作為指紋,但在正式環境中,應使用 Tool 名稱與引數的穩定雜湊值:
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<string>()
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.`,
}
}
},
},
})
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() 在執行階段暫停「runtime-suspension-with-suspend」的直接連結
Tool 也可以在其 execute 函式執行_期間_呼叫 suspend() 來暫停。當 Tool 開始執行後才發現需要更多使用者輸入或確認才能完成時,此功能相當實用。
串流會發出 tool-call-suspended 區塊,其中包含由 Tool 的 suspendSchema 定義的自訂酬載。呼叫 resumeStream() 並傳入符合 Tool resumeSchema 的資料,即可恢復執行。
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-approval-with-generate」的直接連結
Tool 核准也適用於非串流情境的 generate()。當 Tool 需要核准時,generate() 會立即傳回,其中包含 finishReason: 'suspended'、含有 Tool 呼叫詳細資訊(toolCallId、toolName、args)的 suspendPayload,以及 runId:
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 層級核准「Tool 層級核准」的直接連結
除了在 Agent 層級暫停每次 Tool 呼叫,也可以將個別 Tool 標記為需要核准。這能提供細部控制:只有特定 Tool 會暫停,其他 Tool 則會立即執行。
使用 requireApproval 核准「approval-using-requireapproval」的直接連結
在 Tool 定義上設定 requireApproval: true。無論 Agent 是否設定 requireToolApproval,Tool 都會在執行前暫停:
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() 即可繼續:
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() 核准「approval-using-suspend」的直接連結
採用此方式時,Agent 和 Tool 都不會使用 requireApproval。Tool 的 execute 函式會改為呼叫 suspend(),在特定位置暫停,並將情境資訊或確認提示傳回給使用者。當核准取決於執行階段條件,而非無條件要求核准時,此方式相當實用。
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 即可繼續:
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「自動恢復 Tool」的直接連結
使用會呼叫 suspend() 的 Tool 時,可以啟用自動恢復,讓 Agent 根據使用者的下一則訊息恢復已暫停的 Tool。在 Agent 的預設選項或個別請求中,將 autoResumeSuspendedTools 設為 true:
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。
下列範例呈現完整的對話流程:
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}` }
},
})
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)
}
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(),即可從儲存空間重新找出對話中待處理的執行:
// 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() 使用,因此瀏覽器型核准 UI 可以直接重新找出待處理的執行。
只有在 Mastra 執行個體設定了永久性儲存 Provider時,已暫停的執行才能在重新啟動後繼續存在。預設的記憶體內儲存區會在處理程序結束時遺失快照。
Tool 核准:Supervisor Agent「Tool 核准:Supervisor Agent」的直接連結
Supervisor Agent 會使用 .stream() 或 .generate() 協調多個 Subagent。當 Subagent 呼叫需要核准的 Tool 時,請求會沿著委派鏈向上傳遞,並出現在 Supervisor 層級:
- Supervisor 將工作委派給 Subagent。
- Subagent 呼叫設有
requireApproval: true或使用suspend()的 Tool。 - 核准請求向上傳遞至 Supervisor。
- 你在 Supervisor 層級核准或拒絕。
- 決定向下傳回 Subagent。
Tool 核准也會經由多層委派傳遞。如果 Supervisor 委派給 Subagent A,而 Subagent A 又委派給 Subagent B,且後者有設為 requireApproval: true 的 Tool,核准請求仍會出現在最上層 Supervisor。
在 Supervisor Agent 中核准與拒絕「在 Supervisor Agent 中核准與拒絕」的直接連結
下列範例會建立具有需核准 Tool 的 Subagent。Tool 觸發核准請求時,該請求會以 tool-call-approval 區塊出現在 Supervisor 的串流中:
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()「use-suspend-in-supervisor-agents」的直接連結
Tool 也可以使用 suspend() 暫停執行,並將情境資訊傳回給使用者。此方式會像 requireApproval 一樣沿著 Supervisor 委派鏈運作:暫停會出現在 Supervisor 層級:
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)
},
})
// 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 核准「supervisor-approval-with-generate」的直接連結
Tool 核准傳遞也適用於 Supervisor Agent 中的 generate():
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)
}