跳到主要内容

Agent 运行时:一次生成的完整生命周期

30 秒导读: VoltAgent 里,你 new Agent({...}) 得到一个对象,然后调它的 generateText("...")。这一章讲的就是从你按下这个调用,到拿回结果的中间发生的一切:框架怎么把系统提示、历史记忆、你的输入拼成一串消息,交给底层模型,再在"模型说话 → 调工具 → 再说话"的循环里推进,最后把每一步落库、收尾返回。这是全库的主干,后面每一章(记忆、工具、护栏、子代理)都是往这条主干上挂的分支。


1. 这是什么(零基础也能懂)

  • 一句话定义: Agent 是 VoltAgent 的中枢类——你给它模型、指令、工具、记忆,它负责跑完一次"和模型对话并可能调工具"的完整流程,把杂事(拼提示、存历史、追踪、重试)都替你办了。

  • 它替你解决什么: 直接用底层 AI SDK,你得自己拼 system prompt、自己塞历史消息、自己写工具循环、自己存对话、自己做可观测。Agent 把这些固化成一条标准流水线,你只管配置。

  • 一个最小例子: 感受一下调用有多轻。

// 示意,非源码:一次最普通的生成
const agent = new Agent({
name: "assistant",
instructions: "你是一个乐于助人的助手。", // 会变成 system 消息
model: openai("gpt-4o-mini"),
tools: [weatherTool], // 可选:给模型装"手脚"
});

const result = await agent.generateText("北京今天天气怎么样?");
console.log(result.text);
  • 一句话直觉/类比:Agent 想成一个餐厅后厨的传菜流程。你(顾客)点一句话,后厨要先备料(拼消息)、下锅(调模型)、按菜谱可能多道工序(工具循环)、记账(落库),最后上菜(返回)。这一章讲的就是这条传菜线,不讲某道菜(记忆、工具)具体怎么做。

本节到此不碰代码细节。记住一句话就够:Agent = 一次生成的流水线管家。


2. 顶层全景(它大概怎么转)

2.1 四个对外入口

Agent 对外只有四个"发起一次生成"的方法,内部走的是同一条主干,只是最后交给底层 SDK 的动作不同。

入口定义位置干什么返回
generateTextagent.ts:1198一次性生成文本(可带工具循环)完整结果对象(.text)
streamTextagent.ts:1729流式生成文本,边生成边吐可迭代的流(.textStream / .fullStream)
generateObjectagent.ts:2848生成符合 schema 的结构化对象.object
streamObjectagent.ts:3188流式结构化对象(已废弃,建议改用带 outputstreamText)对象流

四者开头一模一样:都先 createOperationContext(agent.ts:1206173428573197),再进同一套"中间件 → 护栏 → prepareExecution → 调模型"的骨架。本章以 generateText 为主线讲透,其余三个只点出差异。

2.2 一次 generateText 的骨架图

怎么读这张图: 从上到下是时间顺序。左侧竖线是贯穿全程的 OperationContext(下一节讲)。中间的"工具循环"是可能重复多次的部分,由 stopWhen 决定何时停。

你调用 agent.generateText("...")


┌───────────────────────────┐
│ 建"随身背包" OperationContext │ createOperationContext (agent.ts:4119)
│ · operationId / logger │ ← userId、conversationId、abort、trace
│ · context / systemContext │ 都塞进这个背包,后面到处传它
└───────────────────────────┘


┌───────────────┐
│ 输入中间件+输入护栏 │ runInputMiddlewares → executeInputGuardrails
└───────────────┘ (改写/拦截你的输入)


┌────────────────────────────────┐
│ prepareExecution (agent.ts:3760) │ ← 组装"发给模型的一切"
│ 1. prepareMessages 拼消息 │
│ 2. getSystemMessage 拼系统提示 │
│ 3. convertToModelMessages │
│ 4. prepareTools 准备工具 │
│ 5. maxSteps 算步数上限 │
└────────────────────────────────┘
│ messages / tools / maxSteps

┌────────────────────────────────┐
│ 调底层 SDK generateText │ agent.ts:1384
│ ┌───── 工具循环(≤ maxSteps)──┐│
│ │ 模型说话 → 想调工具? ─是→ 执行 ││
│ │ ↑ │ ││ 每步触发
│ │ └── stopWhen 未满足 ──┘ ││ onStepFinish
│ └──────────────────────────────┘│ (createStepHandler)
└────────────────────────────────┘
│ result.text / steps

┌───────────────────────────────┐
│ 输出中间件 → 落库 → 输出护栏 → 收尾 │ runOutputMiddlewares (1469)
│ · recordStepResults 存每步 │ executeOutputGuardrails (1489)
│ · onEnd 钩子 / 结束 trace span │
└───────────────────────────────┘


返回 result

2.3 主要部件一句话职责

部件干什么在哪
Agent 构造AgentOptions 存成实例字段,建 memoryManager / toolManager / subAgentManageragent.ts:1068
createOperationContext这一次调用造一个隔离的"背包"agent.ts:4119
prepareExecution把消息、工具、步数上限一次性备齐agent.ts:3760
prepareMessages拉记忆 + 拼系统消息 + 拼输入,产出 UIMessage[]agent.ts:4969
getSystemMessage组装系统提示(指令 + 检索 + 工作记忆 + 工具包说明)agent.ts:5299
createStepHandler每个模型步骤结束时的回调:记步、增量落库、bail 检测agent.ts:7390
OperationContext一次调用的全部运行时状态类型见 types.ts:1316

3. 核心原理(逐个机制,由浅入深)

3.1 OperationContext:一次运行的"随身背包"

它要解决的小问题: 一次生成里,userIdconversationId、日志器、可观测 span、取消信号、临时缓冲……这些状态要在几十个方法间传来传去。到处传参数会爆炸,放实例字段又会被并发调用互相覆盖。

思路: 每次调用开头 new 一个 OperationContext(简称 oc),把这一次的所有状态都装进这个背包,然后把 oc 当唯一参数一路往下传。并发的两次调用各背各的包,互不干扰。

oc 里最关键的几格:

字段装什么备注
operationId本次调用的 UUIDagent.ts:4123 生成
context用户可见的上下文 Map(你传的、动态指令能读的)types.ts:1333
systemContext框架内部用的 Map:缓冲区、持久化队列、agent 引用等types.ts:1339
traceContextOpenTelemetry span 树的句柄详见第 6 章
abortController取消/中止的信号源(子代理 bail 也走它)agent.ts:4194
conversationSteps累积的步骤,供子代理"看历史"agent.ts:4233

真实实现: createOperationContext(agent.ts:4119)不只是 new 一个对象,它还处理一件微妙的事——context 的引用继承。当这是子代理调用(有 parentOperationContext)时,它复用父级的 context Map 实例而非克隆(agent.ts:4133),这样父子共享同一份可见上下文;只对缺失的键做填充。同理 abortController 也优先复用父级(agent.ts:4194),让"父一取消,子跟着停"自然成立。

systemContext 在这里被预装了几个关键对象(agent.ts:4208-4222):ConversationBuffer(本次的消息缓冲)、MemoryPersistQueue(落库的防抖队列)、以及指向 Agent 自己的引用。记住 context 是给你用的、systemContext 是给框架用的,这条边界后面处处出现。

3.2 组装"发给模型的一切":prepareExecution

它要解决的小问题: 底层 SDK 的 generateText 需要一个干净的 { messages, tools }。可这些东西分散在:你传的输入、Agent 的静态指令、数据库里的历史、工作记忆、检索结果、动态工具……得有人把它们按固定次序拼起来

思路/分工: prepareExecution(agent.ts:3760)是总调度,它自己不拼消息,而是编排四个子步骤:

prepareExecution

├─ resolveValue(dynamicTools) 解析动态工具 (agent.ts:3771)

├─ prepareMessages(...) 拼出 UIMessage[] (agent.ts:3780)
│ └─ getSystemMessage(...) 其中的系统提示部分 (agent.ts:5141)

├─ convertToModelMessages(...) UIMessage → ModelMessage (agent.ts:3784)
│ └─ 之后剥掉悬空的 OpenAI reasoning (agent.ts:3798)

├─ calculateMaxSteps() 算步数上限 (agent.ts:3801)

└─ prepareTools(...) 给工具注入执行上下文 (agent.ts:3806)

注意两个"钩子"挂在这一步:onPrepareModelMessages(agent.ts:3786)让你在消息转成模型格式后、发送前最后改一手。这是与外部对接的干预点。

消息的最终顺序prepareMessages 决定,读它(agent.ts:4969)可以看到拼装顺序是:

  1. 记忆上下文——若配了 userId,先从 memory 拉历史(可能走语义检索),agent.ts:4987 起。细节属第 3 章
  2. 系统消息——getSystemMessage 产出,push 到最前,agent.ts:5141
  3. 中间件重试反馈(若上一轮中间件要求重试)——作为一条 system 消息插入,agent.ts:5185
  4. 当前输入——你传的字符串/消息,agent.ts:5199

拼完还不算数,prepareMessages 结尾要过三道加工(agent.ts:5230-5253):sanitizeMessagesForModel(清理)、applySummarization(超长时压缩)、以及 onPrepareMessages 钩子,最后 validateUIMessages 校验。

3.3 系统提示怎么长出来:getSystemMessage + enrichInstructions

它要解决的小问题: "系统提示"看着就是一句 instructions,实际要把好几样东西叠加成一段:你的指令、工具包的用法说明、检索到的资料、工作记忆、还有子代理监督者的额外指令。

分两层: getSystemMessage(agent.ts:5299)负责取材——解析动态指令、跑检索器、读工作记忆;enrichInstructions(agent.ts:5556)负责叠加——把取到的料按固定顺序拼到基础指令后面。

enrichInstructions 的叠加顺序很明确(agent.ts:5563-5591):

顺序加什么条件源码
1基础指令总是入参 baseContent
2工具包说明工具包带 addInstructionsaddToolkitInstructions (agent.ts:5504)
3"用 markdown 回答"this.markdown 为真agent.ts:5569
4检索上下文配了 retrieveragent.ts:5574
5工作记忆有工作记忆支持agent.ts:5579
6监督者指令有子代理agent.ts:5584

第 6 步是主循环给子代理系统留的挂载点:一旦 subAgentManager.hasSubAgents(),基础指令会被 generateSupervisorSystemMessage 包一层(agent.ts:5586),把子代理列表和调用规则注进系统提示。多 agent 协作的细节在第 4 章,这里只需知道它在哪一步接进来。

指令本身还支持动态:this.instructions 可以是函数,getSystemMessageresolveValue(agent.ts:5323)在运行时求值,把 oc.context、请求头、VoltOps 的 prompt 助手喂进去。所以同一个 Agent 能按用户/请求动态换系统提示。

3.4 工具调用循环:模型说话 ↔ 执行工具

它要解决的小问题: 带工具的生成不是"一问一答",而是"模型说'我要调 X 工具' → 框架执行 X → 把结果喂回去 → 模型继续"这样循环多轮,直到模型给出最终答复或达到步数上限。

关键点:VoltAgent 不自己写这个循环。 它把循环交给底层 AI SDK 的 generateText(agent.ts:1384),自己只做三件事:给循环设停止条件、给每步挂回调、给循环前置控制。

① 停止条件 stopWhen 传给底层的是(agent.ts:1391):

// 真实调用里的这一行(agent.ts:1391)
stopWhen: options?.stopWhen ?? this.stopWhen ?? stepCountIs(maxSteps),

优先用调用级 → Agent 级 → 兜底 stepCountIs(maxSteps)maxSteps 怎么来的看 calculateMaxSteps:

情形maxSteps 默认源码
显式传了 maxSteps用你的值subagent/index.ts:222
有子代理10 × 子代理数subagent/index.ts:227
无子代理10subagent/index.ts:227
配了 workspace(构造时)100,否则 5agent.ts:1089

② 每步回调 onStepFinish 每当模型走完一步(说了话或调了工具),底层会回调 createStepHandler 产出的函数(agent.ts:1401)。它负责记步、增量落库、检测子代理 bail——见 3.5。

③ 循环前置控制 prepareStep AI SDK 允许在每一步开始前动态调整(比如某步之后禁用工具)。VoltAgent 在此之上包了一层 applyForcedToolChoice(agent.ts:813):如果 systemContext 里有"强制工具选择",它会包住你的 prepareStep,在第一步强制 toolChoice(agent.ts:822)。这是第 2 章工具路由往主循环里塞控制的方式。

3.5 步骤持久化:每走一步就记账

它要解决的小问题: 一次带工具的生成可能十几步。如果只在最后才存,中途崩了就全丢;而且子代理需要"看到"前面步骤才能协作。

思路:onStepFinish边跑边记createStepHandler(agent.ts:7390)返回的回调每步都做:

  1. processStepContent(agent.ts:7456)——把这一步的内容 push 进 oc.systemContextconversationSteps,并逐条打日志(文本/工具调用/工具结果);顺便检测子代理 bail(提前终止信号)。
  2. 把响应消息加进 ConversationBuffer(agent.ts:7406)。
  3. 若持久化模式是 "step",调 recordStepResults 增量存,并按需 flushscheduleSave 落库队列(agent.ts:7414-7443)。
  4. 若这步是 bail,直接 abortController.abort(agent.ts:7446)——用取消信号把整个循环停下,把子代理的答复当最终结果。
  5. 调用户的 onStepFinish 钩子(agent.ts:7452)。

recordStepResults(agent.ts:7568)把步骤转成可存的记录,只有配了 userId+conversationId 且非只读记忆时才真落库(agent.ts:7721)。它默认不阻塞——用 void persistStepsPromise(agent.ts:7742)后台存,除非调用方要求 awaitPersistence。落库的具体表结构属于第 3 章

3.6 中间件与重试:给输入输出"过安检 + 可回炉"

它要解决的小问题: 有时你想在发给模型前改写输入(脱敏、加前缀),或在拿到输出后加工/校验;更进一步,若输出不合格,想让模型带着反馈重跑一次

中间件 vs 护栏的分工: 两者都在主循环边缘挂载,但职责不同——中间件改写,护栏判定放行/拦截。本章讲中间件挂载点,护栏细节见第 6 章

挂载位置(以 generateText 为例):

输入 ──▶ runInputMiddlewares (agent.ts:1234) ──▶ executeInputGuardrails (1243)

prepareExecution → 调模型

result.text ──▶ runOutputMiddlewares (1469) ──▶ executeOutputGuardrails (1489) ──▶ 返回

中间件的合并与规范化。 Agent 级中间件(构造时的 inputMiddlewares)和调用级中间件(options 里的)会被 resolveMiddlewareSets(agent.ts:3696)合并:Agent 级在前,调用级追加在后。每个中间件先被 normalizeMiddlewareDefinition(middleware.ts:92)统一成 { name, handler, ... },函数式和对象式写法都支持。实际执行在 runInputMiddlewares / runOutputMiddlewares(middleware.ts:159 / 249),每个中间件包一个 span,并把改写前后的值记进 trace。

重试机制(妙点)。 中间件可以调 abort(reason, { retry: true }) 要求回炉。主循环外层是个 while (true)(agent.ts:1225),catch 到中间件的 abort 错误时,shouldRetryMiddleware(agent.ts:3750)判断能否重试(有 retry 标记且未超 maxMiddlewareRetries)。若能:

  1. storeMiddlewareRetryFeedback(agent.ts:3727)把反馈存进 systemContext
  2. middlewareRetryCount += 1,continue 回到循环顶。
  3. 下一轮 resetOperationAttemptState(agent.ts:4248)清掉缓冲、步数、上轮输出。
  4. 重新 prepareMessages 时,consumeMiddlewareRetryFeedback(agent.ts:3741)把上轮反馈作为一条 system 消息插进去(agent.ts:5185),模型这次就"看到批评"了。

这就是"输出不合格 → 带反馈重跑"的闭环,全靠这个 while + systemContext 传话实现。

3.7 消息规范化:喂给模型前的"洗净"

它要解决的小问题: 不同来源(用户、记忆、工具结果)、不同 provider(尤其 OpenAI 的 reasoning)拼出的消息可能有脏数据:半截的工具调用、悬空的 reasoning id、非法的工具输入。直接发给模型会报错。

思路:message-normalizer.ts 里集中"洗"。sanitizeMessagesForModel(在 prepareMessages 结尾 agent.ts:5230 调用)负责过滤不完整的工具调用、规范工具输入(normalizeToolInputForModel)。另有 stripDanglingOpenAIReasoningFromModelMessages(agent.ts:3798)专门剥掉转成模型消息后悬空的 OpenAI reasoning 片段——否则某些 provider 会拒收。

这一步很"脏活",但它保证了无论上游消息多乱,发出去的都是合法序列。具体清洗规则读 message-normalizer.tsWORKING_MEMORY_TOOL_NAMES(message-normalizer.ts:12)等常量与函数即可,本章不展开每条规则。


4. 四个入口的差异(主线之外的支线)

主干一致,尾部不同。挑差异说:

方面generateTextstreamTextgenerateObjectstreamObject
调底层generateText (1384)streamText (2041)generateObjectstreamObject(废弃)
结果何时定await 完就有流消费完才定await 完就有流消费完
输出加工时机主流程内 (1469/1489)onFinish 回调里 (2159)主流程内onFinish
返回完整结果立即返回流对象{ object }对象流

streamText 的关键差异: 因为要边生成边返回,它不能在主流程里等结果再做收尾。所以落库、输出中间件、护栏、onEnd 钩子都挪进底层的 onFinish 回调(agent.ts:2159)。同时它多一套"流管道":护栏可以对流式文本逐块判定(createGuardrailPipeline,agent.ts:2611),而不是等全文出来。这让"流式输出 + 实时护栏"能共存。

streamObject 已废弃(agent.ts:3187 注释建议改用带 outputstreamText),新代码不必用。


5. 巧妙之处(可借鉴)

  • "随身背包"模式隔离并发。OperationContext 而非实例字段承载运行时状态,让同一个 Agent 实例能安全地被多次并发调用——每次背各自的包(agent.ts:4119)。

  • 主循环用 while(true) 撑起"带反馈重试"。 不引入复杂状态机,靠一个外层循环 + systemContext 传递反馈,就实现了"输出不合格 → 插一条批评 → 重跑"的闭环(agent.ts:12255185)。

  • 循环本体外包给 AI SDK,自己只管钩子。 VoltAgent 不重造工具循环,而是给 SDK 的 generateTextstopWhen + onStepFinish + prepareStep 三个挂载点(agent.ts:1391/1401/1339)。框架的价值全在"钩子里做什么",而非循环本身。

  • context 引用继承而非克隆。 子代理复用父级的 contextabortController 实例(agent.ts:4133/4194),让"共享上下文"和"级联取消"零成本成立。

  • 子代理 bail 复用取消信号。 提前终止不发明新机制,直接 abortController.abort(bailError)(agent.ts:7446),再在外层 catch 里把 bail 结果当最终答复(agent.ts:1647)——一套 abort 通道两用。


6. 边界与局限

  • 工具循环的正确性依赖底层 AI SDK。 step 的推进、stopWhen 的判定都在 SDK 里;VoltAgent 只在边缘挂钩子。SDK 行为变了,循环行为跟着变。

  • 结构化输出可能"没生成"。 带工具时模型可能一直调工具、到 maxSteps 也没吐出符合 schema 的最终响应。ensureStructuredOutputGenerated(agent.ts:3834)专门检测这种情况并抛带指引的错误,提示"要么让模型给非工具的最终响应,要么拆成两次调用"。

  • maxSteps 默认值偏保守。 无子代理默认 10、无 workspace 仅 5(subagent/index.ts:227agent.ts:1090),复杂任务需显式调大,否则会被步数上限截断。

  • 本章不覆盖的: 记忆的读写与语义检索(第 3 章)、护栏的判定逻辑(第 6 章)、子代理协作与 Supervisor(第 4 章)、工具/MCP 的定义与执行(第 2 章)。本章只讲它们挂在主循环的哪一步


7. 代码地图(导航索引)

主题文件路径符号名
Agent 构造packages/core/src/agent/agent.ts:1068Agent constructor
生成入口(文本)packages/core/src/agent/agent.ts:1198generateText
生成入口(流式文本)packages/core/src/agent/agent.ts:1729streamText
生成入口(结构化)packages/core/src/agent/agent.ts:2848generateObject
生成入口(流式结构化,废弃)packages/core/src/agent/agent.ts:3188streamObject
建运行时背包packages/core/src/agent/agent.ts:4119createOperationContext
重置重试状态packages/core/src/agent/agent.ts:4248resetOperationAttemptState
组装执行素材packages/core/src/agent/agent.ts:3760prepareExecution
拼消息(记忆+系统+输入)packages/core/src/agent/agent.ts:4969prepareMessages
取系统提示素材packages/core/src/agent/agent.ts:5299getSystemMessage
叠加系统提示packages/core/src/agent/agent.ts:5556enrichInstructions
工具包说明拼接packages/core/src/agent/agent.ts:5504addToolkitInstructions
每步回调packages/core/src/agent/agent.ts:7390createStepHandler
处理步内容/检测 bailpackages/core/src/agent/agent.ts:7456processStepContent
步骤落库packages/core/src/agent/agent.ts:7568recordStepResults
步数上限packages/core/src/agent/subagent/index.ts:220calculateMaxSteps
强制工具选择包装packages/core/src/agent/agent.ts:813applyForcedToolChoice
中间件合并packages/core/src/agent/agent.ts:3696resolveMiddlewareSets
中间件重试判定packages/core/src/agent/agent.ts:3750shouldRetryMiddleware
存/取重试反馈packages/core/src/agent/agent.ts:3727storeMiddlewareRetryFeedback / consumeMiddlewareRetryFeedback
中间件执行packages/core/src/agent/middleware.ts:159runInputMiddlewares / runOutputMiddlewares
中间件规范化packages/core/src/agent/middleware.ts:92normalizeMiddlewareDefinition
消息清洗packages/core/src/agent/message-normalizer.tssanitizeMessagesForModel
类型:AgentOptionspackages/core/src/agent/types.ts:679AgentOptions
类型:OperationContextpackages/core/src/agent/types.ts:1316OperationContext