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 |
|---|---|
reasoning | |
| OpenRouter + DeepSeek 模型 | reasoning |
| OpenAI/Azure + Responses API | reasoning |
| 其他 | 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 使用深度克隆的 toolRegistry(new 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 构建是必要的。
参考来源
- LangGraph Pregel 算法 — 图驱动 Agent 执行的理论基础
- LibreChat Agents 文档