跳转到内容
搜索文档

生命周期钩子

最后更新 查看 MarkdownAgent 设置

Think 拥有 streamText 调用,并在聊天轮次各阶段提供钩子。无论入口路径——WebSocket 聊天、子 agent chat()saveMessages()、持久 submitMessages() 执行、continueLastTurn() 或工具结果后的自动续聊——钩子都会在每个轮次触发。

钩子汇总

Hook 触发时机 返回值 异步
configureSession(session) onStart 期间一次 Session
beforeTurn(ctx) streamText 之前 TurnConfig 或 void
beforeStep(ctx) 每个模型步骤之前 StepConfig 或 void
beforeToolCall(ctx) 服务端工具执行之前 ToolCallDecision 或 void
afterToolCall(ctx) 工具结果已知之后 void
onStepFinish(ctx) 每个步骤完成之后 void
onChunk(ctx) 每个流式分块 void
onChatResponse(result) 轮次完成且消息已持久化后 void
onChatError(error, ctx?) 轮次期间出错 要传播的错误
classifyChatError(error, ctx?) 轮次出错且启用 contextOverflow.reactive ChatErrorClassification 或 void

执行顺序

对于包含两次工具调用的轮次:

flowchart TD
    cfg["configureSession() — once at startup, not per-turn"] --> bt["beforeTurn() — inspect context, override model/tools/prompt"]
    bt --> bs

    subgraph loop ["streamText (repeats per step)"]
        bs["beforeStep()"] --> chunk["onChunk() — per streaming chunk"]
        chunk --> btc["beforeToolCall()"]
        btc --> exec["tool executes"]
        exec --> atc["afterToolCall()"]
        atc --> sf["onStepFinish()"]
        sf -->|"more steps"| bs
    end

    sf -->|"turn complete"| ocr["onChatResponse() — message persisted, turn lock released"]

beforeTurn

streamText 之前调用。接收完整组装的上下文——系统提示词、转换后的消息、合并的工具与模型。返回 TurnConfig 可覆盖任意部分,返回 void 则接受默认值。

beforeTurn(ctx: TurnContext): TurnConfig | void | Promise<TurnConfig | void>

TurnContext

字段 类型 描述
system string 组装的系统提示词(来自上下文块或 getSystemPrompt()
messages ModelMessage[] 组装的模型消息(已截断、修剪)
tools ToolSet 合并的工具集(工作区 + getTools + 会话 + 扩展 + MCP + 客户端)
model LanguageModel getModel() 返回的模型
continuation boolean 是否为续聊轮次(工具结果后自动续聊)
body Record<string, unknown> 客户端请求中的自定义 body 字段

TurnConfig

所有字段均为可选。仅返回需要修改的部分。

字段 类型 描述
model LanguageModel 覆盖本轮次的模型
system string 覆盖系统提示词
messages ModelMessage[] 覆盖组装的消息
tools ToolSet 额外合并的工具(叠加)
activeTools string[] 限制模型可调用的工具
toolChoice ToolChoice 强制调用特定工具
maxSteps number 覆盖本轮次的 maxSteps
sendReasoning boolean 本轮发送推理片段
chatStreamStallTimeoutMs number 覆盖本轮次的流停滞看门狗(0 禁用);轮次结束后自动重置。适用于已知慢工具的轮次——参阅 持久恢复
output Output 本轮次请求结构化输出
providerOptions Record<string, unknown> 提供商特定选项
experimental_telemetry object 本轮次的 AI SDK 遥测设置
experimental_transform StreamTextTransform | StreamTextTransform[] 本轮次的 AI SDK 流转换——检查或重写流部分(例如从工具结果派生 source 部分)。按顺序应用。

示例

续聊轮次切换到更便宜的模型:

beforeTurn(ctx: TurnContext) {
	if (ctx.continuation) {
		return { model: this.cheapModel };
	}
}

限制模型可调用的工具:

beforeTurn(ctx: TurnContext) {
	return { activeTools: ["read", "write", "getWeather"] };
}

从客户端 body 添加每轮次上下文:

beforeTurn(ctx: TurnContext) {
	if (ctx.body?.selectedFile) {
		return {
			system: ctx.system + `\n\nUser is editing: ${ctx.body.selectedFile}`,
		};
	}
}

内部续聊轮次隐藏推理:

beforeTurn(ctx: TurnContext) {
	if (ctx.continuation) {
		return { sendReasoning: false };
	}
}

为轮次强制结构化输出:

import { Output } from "ai";
import { z } from "zod";

const ResultSchema = z.object({ severity: z.enum(["low", "high"]) });

beforeTurn(ctx: TurnContext) {
	if (ctx.body?.mode === "structured-answer") {
		return {
			output: Output.object({ schema: ResultSchema }),
			activeTools: [],
		};
	}
}

output 仅为轮次级设置。AI SDK 的 prepareStep 不接受 output 覆盖,因此 beforeStep 无法在单个步骤切换结构化输出。

beforeStep

在 agentic 循环中每个 AI SDK 步骤之前调用。Think 将此钩子转发给 streamTextprepareStep,因此接收 AI SDK 完整的 prepare-step 上下文,并可返回每步覆盖。轮次级组装用 beforeTurn,当决策依赖步骤编号或前一步结果时用 beforeStep

beforeStep(ctx: PrepareStepContext): StepConfig | void {
	if (ctx.stepNumber > 0) {
		return { activeTools: [] };
	}
}

beforeToolCall

在服务端工具的 execute 运行之前调用。Think 包装每个服务端工具,使钩子可在模型收到工具结果前允许、修改、阻止或替换调用。

beforeToolCall(ctx: ToolCallContext): ToolCallDecision | void {
	if (ctx.toolName === "delete" && this.isReadOnlyMode) {
		return { action: "block", reason: "delete is disabled in read-only mode" };
	}

	if (ctx.toolName === "weather") {
		const cached = this.weatherCache.get(JSON.stringify(ctx.input));
		if (cached) return { action: "substitute", output: cached };
	}
}
字段 类型 描述
toolName string 被调用工具的名称
input unknown 模型提供的输入
toolCallId string 本次工具调用的 ID
messages ModelMessage[] 工具执行时可见的消息
abortSignal AbortSignal | undefined 轮次取消时中止的信号

返回 ToolCallDecision 控制执行:

Decision 行为
void{ action: "allow" } 用原始输入运行原始工具
{ action: "allow", input } 用修改后的输入运行原始工具
{ action: "block", reason } 跳过原始工具,将 reason 作为工具结果返回
{ action: "substitute", output } 跳过原始工具,将 output 作为工具结果返回

若包装工具为初步工具结果返回 AsyncIterable,Think 会在 beforeToolCall 之后将 iterable 折叠为最终 yield 值。若需要该工具的真正初步流式输出,避免用 beforeToolCall 拦截。

afterToolCall

在工具结果已知后调用,包括真实执行、被阻止、被替换以及工具抛错。

afterToolCall(ctx: ToolCallResultContext) {
	if (!ctx.success) return;

	this.env.ANALYTICS.writeDataPoint({
		blobs: [ctx.toolName],
		doubles: [JSON.stringify(ctx.output).length],
	});
}
字段 类型 描述
toolName string 被调用工具的名称
input unknown 模型提供的输入
toolCallId string 本次工具调用的 ID
messages ModelMessage[] 工具执行时可见的消息
durationMs number 工具执行耗时(毫秒)
success boolean 模型是否收到成功的工具结果
output unknown successtrue 时存在
error unknown successfalse 时存在

对于被阻止和替换的工具调用,successtrue,因为模型收到有效工具结果。仅原始工具执行抛错时 successfalse

onStepFinish

在 agentic 循环中每个步骤完成后调用。StepContext 是 AI SDK 的 step-finish 事件,包含完整步骤记录:生成文本、推理、文件、来源、类型化工具调用与结果、用量、警告、请求/响应元数据及提供商元数据。

onStepFinish(ctx: StepContext) {
	console.log(
		`Step ${ctx.stepNumber} (${ctx.finishReason}): ` +
			`${ctx.usage.inputTokens}in/${ctx.usage.outputTokens}out`,
	);
}
字段 描述
stepNumber 步骤的从零开始索引
text 本步骤生成的文本
reasoning 模型发出的推理部分
files 步骤期间生成的文件
sources 模型使用的引用或来源
toolCalls 本步骤的类型化工具调用
toolResults 本步骤收到的类型化工具结果
finishReason 步骤结束原因
usage token 用量,含缓存与推理 token
providerMetadata 提供商特定元数据

onChunk

每个流式分块调用。高频——每个 token 触发。用于流式分析、进度指示或 token 计数。仅观察,不可修改。

onChatResponse

聊天轮次生成并持久化 assistant 消息之后调用。轮次锁在此钩子运行前释放,因此可在内部安全调用 saveMessages 或其他方法。

对所有持久化 assistant 消息的轮次路径触发:WebSocket、子 agent RPC、saveMessages 与自动续聊。若轮次在产生任何 assistant 部分前失败,由 onChatError 处理。

onChatResponse(result: ChatResponseResult) {
	if (result.status === "completed") {
		console.log(`Turn ${result.requestId}: ${result.message.parts.length} parts`);
	}
}
字段 类型 描述
message UIMessage 已持久化的 assistant 消息
requestId string 本轮次的唯一 ID
continuation boolean 是否为续聊轮次
status "completed" | "error" | "aborted" 轮次如何结束
error string? 错误消息(status"error" 时)

onChatError

聊天轮次期间出错时调用。返回错误以传播,或返回不同错误。可选上下文描述失败位置及用户消息是否已持久化。部分 assistant 消息(若有)在此钩子前持久化。

onChatError(error: unknown, ctx?: ChatErrorContext): unknown

ChatErrorContext 包含:

字段 类型 描述
requestId string | undefined 聊天请求 ID(可用时)
stage "parse" | "persist" | "turn" | "stream" | "recovery" | "transcript" 失败阶段
messagesPersisted boolean 入站用户消息是否已存储
classification ChatErrorClassification | undefined 上下文溢出无法恢复时,终端 onChatError 设为 "context_overflow"(参阅 classifyChatError);否则为 undefined

Think 还会在 agents:chat 可观测性通道发出 chat:request:failed,含相同阶段与持久化信息。

onChatError(error: unknown, ctx?: ChatErrorContext) {
	console.error("Chat turn failed:", ctx?.stage, error);
	if (ctx?.classification === "context_overflow") {
		return new Error("This conversation is too long to continue. Please start a new one.");
	}
	return new Error("Something went wrong. Please try again.");
}

classifyChatError

轮次期间出错时、在 onChatError 之前调用。将原始提供商错误映射为与提供商无关的类别,使 Think 无需在框架中硬编码提供商字符串即可响应——与传给 compactAfter()tokenCounter 分工相同。应用负责映射,因为它知道使用的提供商与模型。

classifyChatError(error: unknown, ctx?: ChatErrorContext): ChatErrorClassification | void

ChatErrorClassification"context_overflow" | "rate_limit" | "transient" | "fatal" | "unknown"。目前此钩子仅驱动上下文溢出恢复。轮次出错且启用 contextOverflow.reactive 时 Think 会调用;reactive 关闭则不调用。

返回 "context_overflow" 会运行压缩并重试兜底(参阅上下文窗口溢出恢复)。若恢复无法挽救轮次,该分类通过 ChatErrorContext.classification 浮现到终端 onChatError

其他类别保留供将来使用。目前返回其中之一为无操作,不会转发到 onChatError。返回 void(默认)保持现有终端行为。

参数可能是 Error、AI SDK APICallError(含 statusCode/responseBody),或——对流内提供商错误以流错误部分而非抛出出现时——错误消息字符串。请相应收窄类型。提供商上下文溢出错误以流内错误部分到达,因此此钩子收到字符串形式,而非抛出的异常。

第二参数为 ChatErrorContext。溢出恢复期间为 { stage: "stream", requestId },分类器可将错误与进行中的轮次关联——例如调用 cancelChat(requestId) 并退出恢复。

示例

常见做法是赋值内置 defaultContextOverflowClassifier,匹配 Anthropic、OpenAI、Google、Bedrock 等的上下文溢出错误:

import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think {
	classifyChatError = defaultContextOverflowClassifier;
}
import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	override classifyChatError = defaultContextOverflowClassifier;
}

或编写自定义分类器,可选委托给内置分类器:

import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think {
	classifyChatError(error) {
		if (error instanceof Error && /rate.?limit/i.test(error.message)) {
			return "rate_limit";
		}
		return defaultContextOverflowClassifier(error);
	}
}
import type { ChatErrorClassification } from "@cloudflare/think";
import { Think, defaultContextOverflowClassifier } from "@cloudflare/think";

export class MyAgent extends Think<Env> {
	override classifyChatError(error: unknown): ChatErrorClassification | void {
		if (error instanceof Error && /rate.?limit/i.test(error.message)) {
			return "rate_limit";
		}
		return defaultContextOverflowClassifier(error);
	}
}

这篇文档对您有帮助吗?