Skip to content

取消智能体运行

Supported in ADKTypeScript v1.0.0

当智能体运行时间过长、遇到变化的条件或不再需要时,你可能希望取消它而不丢失已完成的工作。ADK 中的取消是非破坏性的:已提交到会话的事件仍会保留。

ADK 支持使用 AbortControllerAbortSignal 进行优雅取消。将 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 };
  },
});