第 5 章:模型工厂与 Prompt —— 把供应商差异关进边界层
模型调用看似只是一行
create_chat_model(),真正困难的是配置、能力、重试、思考轨迹、统计和上下文缓存如何保持一致。
ModelConfig 是能力声明
config.yaml 中的模型不只是 base_url + api_key + model。ModelConfig 还描述 provider、API 类型、思考支持、视觉支持、最大 token、采样参数和自定义实现。Agent 工厂根据这些能力决定:
- 是否允许
thinking_enabled; - 是否注册
view_image及对应 middleware; - 是否传 reasoning effort;
- 摘要时用哪个模型做 token counter 与生成;
- 工具调用格式需要哪种 provider patch。
能力判断放在工厂而不是 prompt 里,是为了让模型看不到它根本无法执行的接口。
创建链路
create_chat_model() 负责把声明式配置转成 LangChain BaseChatModel。特殊 provider 被隔离在 patched_openai.py、patched_deepseek.py、claude_provider.py、vllm_provider.py 等文件里,Agent 不需要知道某家如何编码 reasoning 或 tool call。
为什么需要 patched provider
“OpenAI-compatible”只说明大体协议相似,并不保证:
- reasoning 内容放在同一字段;
- tool call arguments 的流式 chunk 总是合法 JSON;
- usage 在 chunk、final response 还是 header 中;
- safety/length stop reason 使用相同枚举;
- assistant payload 能被另一个供应商原样回放。
DeerFlow 把这些差异收敛在模型层,再用 assistant_payload_replay.py 处理历史 assistant message 的供应商私有载荷。原则是:ThreadState 存统一消息,但保留必要的可回放元数据。
Prompt 的四个来源
Lead Agent 的 system prompt 不是一段常量字符串,而是由四类片段组合:
- 基础行为:文件工作流、回答规范、工具使用原则;
- 能力描述:可用子代理、MCP 路由提示、记忆工具、ACP;
- Agent 身份:custom agent 的
SOUL.md与自更新说明; - Skills 元数据:名称、描述、位置,不直接塞入完整正文。
apply_prompt_template() 汇总这些片段。它接收已经过 allowlist 过滤的技能名和延迟工具信息,因此 prompt 与实际能力表保持一致。
静态前缀与动态 reminder
模型前缀缓存通常对 system prompt 的字节级变化敏感。如果每天日期、每个用户 memory、每轮 workspace 路径都拼进 system prompt,缓存几乎无法命中。
DeerFlow 的策略是:
- system prompt 放跨轮稳定内容;
DynamicContextMiddleware把日期/记忆变成隐藏 reminder;DurableContextMiddleware在调用模型前注入 summary、delegation ledger 和 skill refs;SystemMessageCoalescingMiddleware最后把系统消息合并成严格 provider 可接受的单个 leading message。
这是一种工程上的折中:逻辑上仍是系统上下文,物理上选择更利于缓存和兼容性的消息位置。
Skills 的渐进式披露
基础 prompt 只列技能元数据。完整 SKILL.md 有三种进入上下文的方式:
- 用户用
/skill-name显式激活; - 模型先调用
describe_skill发现技能,再读取文件; - 子代理按自身配置在会话启动时加载。
完整正文不常驻,既节省 token,也让 allowed-tools 策略只在技能真的激活后生效。ThreadState 的 skill_context 只保存路径、描述与加载次序,不复制正文,防止每个 checkpoint 膨胀。
摘要模型的回退
摘要是“模型调用中的模型调用”。配置可指定独立 summary model;构建失败或调用失败时,按候选顺序回退到当前 run model,再到默认模型。摘要调用加 nostream 标签,避免它的 token 被前端误认为主回答流。
这揭示一个通用原则:旁路 LLM 任务(标题、摘要、记忆抽取)要有独立归因与流隔离,否则 token、事件和错误都会污染主 Agent。
源码锚点
models/factory.pyagents/lead_agent/prompt.pymodels/assistant_payload_replay.pyconfig/model_config.py
下一章把重点从“模型能看见什么”转向“模型真的允许执行什么”。