主题
Lead Agent 设计与图构建
本章目标:
- 看懂
make_lead_agent(config)工厂如何在一次请求里动态拼出一个 Agent- 掌握
ThreadState状态 schema 与它的两个自定义 reducer- 理解
config.configurable这组运行时开关如何驱动模型/中间件/工具的选择
TL;DR
Lead Agent 不是一个固定对象,而是 make_lead_agent(config) 在每次请求时按 config.configurable 动态装配的:解析 model_name(请求 → custom agent 配置 → 全局默认)、按 thinking_enabled/is_plan_mode/subagent_enabled 选模型与中间件、get_available_tools() 装工具、apply_prompt_template() 注入技能/记忆/子代理指令,最后 create_agent(model, tools, middleware, system_prompt, state_schema=ThreadState)。ThreadState 在 LangChain AgentState 上扩展 sandbox/thread_data/artifacts/todos/viewed_images,artifacts 与 viewed_images 用自定义 reducer 合并。
Overview(为什么 Agent 是"工厂"而不是单例)
一个对话可能要求:这次用 doubao 开 thinking、下次用 kimi、A 用户走默认 agent、B 用户走自定义"产品专家"agent、有的开 plan mode 有的开子代理。如果 Agent 是启动时建好的单例,这些组合就得提前枚举或运行时改全局状态——既不可扩展也不线程安全。
deer-flow 的解法是 per-request factory:make_lead_agent(config) 注册在 langgraph.json/aegra.json,Aegra/LangGraph 每次 run 调一次,从 config.configurable 读出这次请求的所有开关,当场拼一个专属 Agent。模型、工具集、中间件链、系统提示全是这次请求的快照,请求之间互不影响。这也是为什么前端切模型、开 plan mode 这些操作能即时生效——它们只是改 config.configurable。
Architecture:make_lead_agent 装配流程
agents/lead_agent/agent.py:290-398 的核心逻辑:
| 步骤 | 代码 | 说明 | Source |
|---|---|---|---|
| 读 configurable/context | cfg = config.get("configurable",{}) | 所有运行时开关入口 | agent.py:295-305 |
| 直读 user_id/thread_id | cfg.get("user_id") / cfg.get("thread_id") | factory 阶段 langgraph.config.get_config() 不可用,前端 hooks 显式注入 | agent.py:307-315 |
| 加载 custom agent 配置 | load_agent_config(agent_name) | 非 bootstrap 时按 agent_name 取定制配置 | agent.py:325-327 |
| 解析最终模型 | _resolve_model_name(requested or agent_model) | 请求 → agent 配置 → 全局默认,未知名 fallback | agent.py:329-336 |
| thinking 兜底 | model 不支持 thinking 时降级 | 发 warning 不报错 | agent.py:337-339 |
| 注入 trace metadata | config["metadata"].update({...}) | LangSmith trace 打标 | agent.py:352-365 |
| 组装并返回 | create_agent(model, tools, middleware, system_prompt, state_schema=ThreadState) | bootstrap 与默认两条路径 | agent.py:367-398 |
_resolve_model_name(agent.py:30)三级优先级:请求 model_name/model > custom agent 配置的 model > 全局默认(config.yaml models[0])。is_bootstrap(agent.py:367)是创建自定义 agent 的特殊流程,用最小 prompt + setup_agent 工具,可用技能只 {"bootstrap"}。
Components:ThreadState 状态 schema
agents/thread_state.py:48-55 在 LangChain AgentState 上扩展:
| 字段 | 类型 | 含义 | reducer | Source |
|---|---|---|---|---|
sandbox | SandboxState | 当前 sandbox_id | 默认覆盖 | thread_state.py:6-7,49 |
thread_data | ThreadDataState | workspace/uploads/outputs 路径 | 默认覆盖 | thread_state.py:10-13,50 |
title | str | 自动生成的对话标题 | 默认覆盖 | thread_state.py:51 |
artifacts | list[str] | 产出文件 | merge_artifacts(去重合并) | thread_state.py:21-28,52 |
todos | list | plan mode 任务 | 默认覆盖 | thread_state.py:53 |
uploaded_files | list[dict] | 已上传文件 | 默认覆盖 | thread_state.py:54 |
viewed_images | dict | image_path → | merge_viewed_images(空 dict 清空) | thread_state.py:31-46,55 |
两个自定义 reducer 是关键:merge_artifacts(thread_state.py:21-28)合并并去重——因为多个中间件/工具都可能往 artifacts 追加;merge_viewed_images(thread_state.py:31-46)有特例:传空 dict {} 表示"清空所有已看图片"(用于 vision 内存管理)。LangGraph 用 reducer 决定状态怎么合并,选对 reducer 是状态机正确性的核心。
Data Flow:configurable 驱动的一次请求
config.configurable 的关键开关(agent.py:298-305):thinking_enabled(默认 True)、reasoning_effort、model_name/model、is_plan_mode(开 TodoMiddleware)、subagent_enabled(开 task 工具)、max_concurrent_subagents(默认 3)、agent_name(custom agent)、is_bootstrap。user_id/thread_id 由前端 core/threads/hooks.ts 显式注入到 configurable——因为 graph factory 阶段 langgraph.config.get_config() 不可用(agent.py:307-315 注释,2026-05-09 D' read gate)。
扩展指南:自定义 Agent
a-cdm 支持 custom agent(前端"智能专家"创建流程)。最小约束:
yaml
# agents/<agent-name>/config.yaml(由 load_agent_config 读)
model: kimi-k2.6 # 可选,不写则用全局默认
tool_groups: [web, file:read] # 可选,限制工具组
skills: ["weekly-report"] # 可选,限制可用技能(空=全禁,省略=全开)约束(从代码读出):
- agent_name 经
validate_agent_name(agent.py:305)校验;红线规定user-/project-前缀是平台专用(前端创 agent UI 拦双前缀 422)。 - custom agent 的 model 仍走
_resolve_model_name三级 fallback,不存在的模型名会 fallback 到默认而非报错。 - bootstrap agent 是创建流程专用,用
setup_agent工具 +{"bootstrap"}技能,不要直接复用其路径。
Common Pitfalls / 实战 Tips
- Agent 是 per-request 拼的:别期望"改一次全局生效"。改模型/技能要走 configurable 或 custom agent config。
- user_id 必须前端显式注入 configurable:factory 阶段拿不到 langgraph config,靠
cfg.get("user_id")(agent.py:311)。前端 hooks 不注入则记忆/项目上下文 prompt 注入失效。 - thinking 不支持会静默降级:model 不支持 thinking 时只 warning + 关 thinking(
agent.py:337-339),不是报错。 - artifacts 用了去重 reducer:多次 append 同一 artifact 不会重复,但依赖"覆盖"语义的代码会出错——它是合并不是替换。
References
deer-flow/backend/packages/harness/deerflow/agents/lead_agent/agent.py:290-398— make_lead_agent 工厂(本章主源)deer-flow/backend/packages/harness/deerflow/agents/lead_agent/agent.py:30-91— 模型解析/摘要/todo 中间件构造deer-flow/backend/packages/harness/deerflow/agents/thread_state.py:1-55— ThreadState + 两个 reducerdeer-flow/backend/packages/harness/deerflow/agents/lead_agent/prompt.py— apply_prompt_template(技能/记忆/子代理注入)deer-flow/backend/packages/harness/deerflow/agents/__init__.py:1-18— agents 包导出deer-flow/backend/packages/harness/deerflow/agents/lead_agent/agent.py:216-289—_build_middlewares装配
Related Pages
| Page | Relationship |
|---|---|
| Agent 中间件链机制 | 本章 _build_middlewares 在该章逐个展开 |
| MCP 集成与工具装配 | 本章 get_available_tools 在该章详解 |
| 长期记忆机制 | 本章 apply_prompt_template 注入的记忆在该章 |
| deer-flow 引擎配置体系 | 本章模型解析依赖该章 config.yaml models |
| AI 消息流与 SSE 基础设施 | 本章 configurable 由该章前端 hooks 注入 |