跳转到内容

Agent 图驱动循环

Agent 图驱动循环

学习目标

本章聚焦 LibreChat 如何通过 createRun() 函数构建图驱动的 Agent 执行流。你将了解:

  • Run.create() 如何根据 Agent 数量自动选择图类型
  • AgentInputs 的构建流程与 per-agent 配置策略
  • summarization 配置的形状与跨端点注入

前置知识

下文假设你已理解上述概念,直接聚焦 LibreChat 的具体实现。


项目实践

createRun:统一入口

LibreChat 的 createRun 函数(位于 packages/api/src/agents/run.ts)是所有 Agent 执行的统一入口。它接收一组 RunAgent 对象,将它们转换为 AgentInputs[],然后调用 Run.create()

createRun({
agents: RunAgent[], // 一个或多个 Agent
signal: AbortSignal, // 用于取消执行
streaming: true, // 是否启用流式输出
messages: BaseMessage[], // 消息历史(用于工具发现提取)
summarizationConfig, // 摘要配置
customHandlers, // 自定义事件处理器
...
}) → Run<IState>

图类型自动选择

createRun 根据 Agent 数量和边(edges)数量自动选择图类型:

如果 agentInputs.length > 1 或 edges.length > 0:
graphConfig.type = 'multi-agent'
否则:
graphConfig.type = 'standard'

这种自动选择的设计意味着调用方不需要显式声明”我要用多 Agent 模式”——只要传入多个 Agent 或定义了边,系统自动进入多 Agent 图执行。

buildAgentInput:Per-Agent 配置构建

每个 Agent 通过 buildAgentInput 函数独立构建自己的 AgentInputs。这个函数是理解 LibreChat Agent 系统的关键,它处理:

1. Provider 映射

provider = providerEndpointMap[agent.provider] ?? agent.provider

自定义端点名(如 "Ollama""OpenRouter")映射到 SDK 识别的标准提供商名。

2. Summarization 配置形状

shapeSummarizationConfig 将用户配置的摘要参数转换为 SDK 期望的格式:

  • provider:摘要使用的模型提供商(可不同于主 Agent)
  • model:摘要使用的模型名
  • trigger:触发条件(Token 数或消息数)
  • parameters:LLM 客户端参数
  • reserveRatio:上下文预算储备比例

3. 上下文预算计算

effectiveMaxContextTokens = max(1024, round(baseContextTokens * (1 - reserveRatio)))

当设置了 reserveRatio 时,实际可用的上下文预算会按比例缩减,为摘要输出预留空间。

4. Reasoning 字段选择

不同提供商和模型使用不同的 reasoning 字段名:

场景reasoningKey
Googlereasoning
OpenRouter + DeepSeek 模型reasoning
OpenAI/Azure + Responses APIreasoning
其他reasoning_content

子 Agent 配置构建

buildSubagentConfigs 递归展开子 Agent 配置:

buildSubagentConfigs(agent, agentInput, toInput, state, ancestors, depth)
→ SubagentConfig[]

关键设计:

  • ancestors 集合确保循环安全(A → B → A 会在第二个 A 处停止)
  • depth 计数器防止过深嵌套(MAX_SUBAGENT_DEPTH
  • isSubagent: true 标志确保子 Agent 的工具上下文隔离

问题与规避

问题 1:子 Agent 泄漏父 Agent 的工具搜索状态

如果子 Agent 直接复用父 Agent 的 toolRegistry 引用,父 Agent 对 defer_loading 的修改会泄漏到子 Agent。

规避:子 Agent 使用深度克隆的 toolRegistrynew Map + 每个 LCTool 浅拷贝),并且跳过 defer_loading 覆盖逻辑。

问题 2:摘要配置使用了无法解析的自定义端点

summarization.provider 指向一个自定义端点但该端点的 API Key 未配置时,SDK 会抛出 “Unsupported LLM provider”。

规避resolveSummarizationProvider 在构建摘要配置前验证 API Key 和 baseURL 的可用性,无法解析时回退到原始提供商,让 SDK 给出清晰的错误信息。


设计取舍

自动图类型选择 vs 显式声明

LibreChat 选择了自动选择——调用方只需传入 Agent 列表,系统决定图类型。

方案优势代价
自动选择调用方简单,不易出错调用方对执行模式不够透明
显式声明完全可控需要调用方理解图类型语义

LibreChat 的选择适合其场景:调用方(AgentClient)不需要理解图执行细节,只需配置 Agent。

Per-Agent 配置构建 vs 全局共享配置

每个 Agent 独立构建自己的 AgentInputs,而非共享一个全局配置。

方案优势代价
Per-Agent每个 Agent 可有不同的 provider、模型、摘要配置构建逻辑复杂
全局共享简单一致灵活性差

LibreChat 的多 Agent 场景中,不同 Agent 可能使用不同的模型和提供商,Per-Agent 构建是必要的。


参考来源