取消智能体运行¶
当智能体运行时间过长、遇到变化的条件或不再需要时,你可能希望取消它而不丢失已完成的工作。ADK 中的取消是非破坏性的:已提交到会话的事件仍会保留。
ADK 支持使用 AbortController 和 AbortSignal 进行优雅取消。将 AbortSignal 传递给 runner.runAsync(),以在执行堆栈的任何点取消整个调用,包括智能体执行、LLM 生成、工具执行和插件回调。
入门¶
创建一个 AbortController,将它的 signal 传递给 runner.runAsync(),并在要取消执行时调用 controller.abort():
import { Runner, InMemorySessionService, LlmAgent, FunctionTool } from '@google/adk';
import { z } from 'zod';
const getInfo = new FunctionTool({
name: 'get_info',
description: 'Gets information about a topic.',
parameters: z.object({ topic: z.string() }),
execute: (args) => ({ result: `Info about ${args.topic}` }),
});
const agent = new LlmAgent({
name: 'my_agent',
model: 'gemini-flash-latest',
instruction: 'Always use the get_info tool before answering.',
tools: [getInfo],
});
const sessionService = new InMemorySessionService();
const runner = new Runner({ agent, appName: 'my_app', sessionService });
const session = await sessionService.createSession({ appName: 'my_app', userId: 'user_1' });
const controller = new AbortController();
const run = runner.runAsync({
userId: session.userId,
sessionId: session.id,
newMessage: { role: 'user', parts: [{ text: 'Tell me about quantum computing.' }] },
abortSignal: controller.signal,
});
let count = 0;
for await (const event of run) {
count++;
console.log('Event:', event.author);
controller.abort(); // Without this, 3+ events; with it, only 1.
}
console.log(`Done. Received ${count} event(s).`);
取消如何传播¶
当中止信号被触发时,取消会向下传播到整个执行堆栈。每个组件在关键生命周期点检查 abortSignal.aborted,并在检测到取消时提前终止:
| 组件 | 中止时发生的情况 |
|---|---|
| Runner | 在会话获取之前、插件回调之后以及事件流循环中停止。 |
| LlmAgent | 在执行步骤之间、模型回调之前/之后以及响应流中停止。 |
| LoopAgent | 在循环迭代之间和子智能体执行之间停止。 |
| ParallelAgent | 在合并并发子智能体运行的结果时停止。 |
| 模型(Gemini) | 信号通过 config.abortSignal 传递给底层的 Google GenAI SDK,取消正在进行的 HTTP 请求。 |
| AgentTool | 将信号传递给子智能体运行器,并在会话创建后检查中止状态。 |
| MCPTool | 将信号传递给 MCP 客户端的 callTool 方法。 |
InvocationContext 还会在信号上注册一个监听器,当触发时自动设置 endInvocation = true,通知所有组件结束运行。
取消时的行为¶
当 AbortSignal 被触发时,适用以下规则:
- 优雅终止:
runner.runAsync()返回的异步生成器完成(停止产生事件)而不抛出错误。 - 已提交的事件保留: 在中止之前已经产生并由 Runner 处理的任何事件仍会提交到会话历史中。
- 无部分事件: 正在进行但尚未产生的事件被丢弃。
- 资源清理: 对 Gemini API 的正在进行中的 LLM 请求通过 SDK 原生的
AbortSignal支持被取消,释放网络资源。
高级示例¶
以下示例展示了除基本 AbortController 用法之外的额外取消模式。
带超时的取消¶
使用 AbortSignal.timeout() 在指定持续时间后自动取消智能体运行。这对于强制执行智能体执行的时限非常有用。
使用入门示例中相同的智能体和运行器设置,将 const controller 之后的所有内容替换为:
const run = runner.runAsync({
userId: session.userId,
sessionId: session.id,
newMessage: { role: 'user', parts: [{ text: 'Tell me about quantum computing.' }] },
abortSignal: AbortSignal.timeout(2_000), // Cancel after 2 seconds
});
let count = 0;
for await (const event of run) {
count++;
console.log('Event:', event.author);
}
console.log(`Done. Received ${count} event(s).`);
你还可以使用 AbortSignal.any() 将超时与编程取消结合起来。使用相同的设置,将 const controller 之后的所有内容替换为:
const controller = new AbortController();
// Cancel on timeout OR programmatically via controller.abort()
// e.g.: cancelButton.addEventListener('click', () => controller.abort());
const combinedSignal = AbortSignal.any([
controller.signal,
AbortSignal.timeout(60_000),
]);
const run = runner.runAsync({
userId: session.userId,
sessionId: session.id,
newMessage: { role: 'user', parts: [{ text: 'Tell me about quantum computing.' }] },
abortSignal: combinedSignal,
});
自定义工具中的 AbortSignal¶
当你将 AbortSignal 传递给 runner.runAsync() 时,它可以在自定义工具内部通过 toolContext.abortSignal 获取。以下示例展示了在自定义工具中检查中止信号的模式:
import { FunctionTool } from '@google/adk';
import { z } from 'zod';
const fetchItems = async (id: string) => ['item1', 'item2', 'item3'];
const processItem = async (item: string) => ({ processed: item });
const longRunningTool = new FunctionTool({
name: 'process_data',
description: 'Processes data in multiple steps.',
parameters: z.object({
dataId: z.string(),
}),
execute: async (args, toolContext) => {
const items = await fetchItems(args.dataId);
const results = [];
for (const item of items) {
// Check the abort signal before each step
if (toolContext?.abortSignal?.aborted) {
return { status: 'cancelled', processed: results.length };
}
results.push(await processItem(item));
}
return { status: 'complete', processed: results.length };
},
});