Agent 编排与流式:AgentClient 深入
30 秒导读:
AgentClient是 LibreChat 现代 agent 主路径的核心类(约 1900 行)。它做四件事:组装 一次请求的上下文 → 调外部包@librechat/agents把 agent 图跑起来 → 把图流式吐出的事件转成 SSE 帧推给前端 → 把 token 用量累计并落账。一句话记住边界: 图怎么走、LLM 调用循环怎么转,是外部包
@librechat/agents(一个 LangGraph 运行器)的事;本仓只是集成层。分不清这条边界,读这一章会一直找错地方。
本章只讲 agent 运行时与流式。工具怎么装载见 04;上下文/文件/记忆/token 预算的细节见 05;这条消息在到达 AgentClient 之前的完整生命周期见 01。
1. 这是什么(先建立直觉)
AgentClient 继承自老的 BaseClient(api/app/clients/BaseClient.js)。你可以把它想成一个「聊天客户端」的实现:上层路由拿到一条用户消息后,构造一个 AgentClient 实例,调它的 sendCompletion,它负责把这条消息变成一次完整的 agent 应答( 可能带思考、带工具调用、带子 agent),边生成边推流,最后把用量记账。
它自己不发 LLM 请求、不跑 agent 循环。真正「给模型发消息、收 tool_call、再发、再收」这个循环,发生在 Run 对象里——Run 来自外部包 @librechat/agents。AgentClient 的活是「把料备好、把 Run 起起来、把 Run 吐的东西接住」。
一句类比:AgentClient 是剧务,@librechat/agents 的 Run 是导演。 剧务准备道具(上下文)、开机(createRun)、把导演喊的每句话转播出去(SSE)、结束后结账(usage);戏怎么演是导演的事。
2. 顶层全景(它大概怎么转)
2.1 一次应答的主流程
sendCompletion 是入口(api/server/controllers/agents/client.js:895),它几乎立刻转交给 chatCompletion(client.js:1141)。下面这张图是从「一条已经组装好的 payload」到「流完+落账」的骨架——从上往下是时间顺序,右侧标出跨过边界进入外部包的那一步:
sendCompletion(payload) ┌ 本仓(集成层)
└─ chatCompletion({ payload }) │
1. 组装 config(thread_id/user/signal…) │ client.js:1159
2. formatAgentMessages(payload) ←────────┼─ 外部包:把 DB 消息转成 BaseMessage[]
3. runAgents(messages): │
run = await createRun({ … }) ←───────┼─ 本仓 packages/api:搭 graphConfig
└─ Run.create(runConfig) ←──────┼════ 跨边界 → @librechat/agents(LangGraph)
this.run = run │ client.js:1411
this._resolveRun(run) │ ← 握手:唤醒等 run 的 titleConvo
await run.processStream(…) ←──────────┼════ 跨边界 → 图执行 + LLM 循环在这里跑
每个图事件 → customHandlers ────┼─ callbacks.js 把事件转 SSE 帧回流
4. finally:
finalizeSubagentContent() │ client.js:1520
flush 子 agent usage 的 emit │
recordCollectedUsage() ← 落账 │ client.js:1540
└─ 回到 sendCompletion: │
completion = filterMalformedContentParts(this.contentParts)
metadata = buildResponseMetadata() │ client.js:919
return { completion, metadata } └
关键:createRun 和 run.processStream 就是那条边界。 createRun 还在本仓(packages/api/src/agents/run.ts),它把 agent 列表、子 agent 配置、图类型、自定义处理器打包成 runConfig,最后一行 Run.create(runConfig)(run.ts:1173)才真正跨进 @librechat/agents。processStream 里发生的一切(给模型发消息、收 tool_call、递归子图)全在外部包。