Skip to content

模型配置与 Model 工厂

本章目标:

  • 讲清 models[] 配置项如何通过反射(reflection)实例化任意 LangChain 兼容的 chat model
  • 拆解 create_chat_model 工厂在 thinking_enabled / supports_vision 等开关下的覆盖逻辑
  • 给出新增一个自定义模型 provider 的最小模板与从代码校验逻辑反推出的约束清单

TL;DR

DeerFlow 的模型层不绑定任何固定 SDK:每个 models[] 条目用 use 字段声明一个 module:Class 类路径,工厂在运行时用反射 resolve_class 导入并实例化。create_chat_model 负责把配置字典转成构造参数,并按 thinking_enabledwhen_thinking_enabled / when_thinking_disabled / thinking 之间做深合并覆盖,自适应 Anthropic 原生、OpenAI 兼容网关、vLLM 三种 thinking 表达方式。VllmChatModel 是为 vLLM 0.19.0 定制的子类,跨多轮保留非标准 reasoning 字段。缺失 provider 包时,reflection 层会给出可直接复制的 uv add 安装提示。

Overview

为什么需要一个 Model 工厂,而不是直接 ChatOpenAI(...)?

DeerFlow 是一个 super agent harness,要同时支持 OpenAI、Anthropic、Gemini、Doubao、DeepSeek、Ollama、vLLM、MindIE 等异构后端。这些 provider 在三件事上彼此不兼容:

  1. 构造类不同 —— langchain_openai:ChatOpenAIlangchain_anthropic:ChatAnthropicdeerflow.models.vllm_provider:VllmChatModel 等。硬编码 import 会把所有 SDK 变成强依赖,且无法扩展。
  2. thinking / reasoning 的开关方式不同 —— Anthropic 用构造参数 thinking={"type": ...};OpenAI 兼容网关用 extra_body.thinking;vLLM 用 extra_body.chat_template_kwargs.enable_thinking
  3. 流式与多轮语义不同 —— vLLM 的 reasoning 字段是非标准的,LangChain 默认适配器会丢弃它,导致交错式 thinking/tool-call 流程在下一轮失去上文。

工厂模式把"读配置 → 选类 → 调参 → 实例化"收敛到一个入口 create_chat_model,配置层只声明 use 类路径与字段,反射层负责按需 import 并在缺包时给出安装提示。

Architecture

模型层只有三个核心文件,职责清晰:

Source 列表

Components / Subsystems

ModelConfig:声明式模型 schema

ModelConfig 是一个 extra="allow" 的 Pydantic 模型,这意味着除了显式声明的字段外,任何 provider 特有参数(如 api_basetemperaturenum_predict)都会被收集进来。显式字段中有一组"元字段"不会传给构造器:usenamedisplay_namedescriptionsupports_thinkingsupports_reasoning_effortwhen_thinking_enabledwhen_thinking_disabledthinkingsupports_vision,它们在工厂里被 model_dump(exclude=...) 排除backend/packages/harness/deerflow/models/factory.py:66

thinking 字段是 when_thinking_enabled 的快捷写法,两者都提供时会被合并backend/packages/harness/deerflow/config/model_config.py:35

resolve_class / resolve_variable:反射加载与安装提示

use 字段格式为 module.path:ClassName,由 resolve_variablemodule_path, variable_name = variable_path.rsplit(":", 1) 拆分后 import_module,再 getattr 取出类backend/packages/harness/deerflow/reflection/resolvers.py:44-62resolve_class 在其上追加两层校验:必须是 type,且必须是 base_class(工厂传入 BaseChatModel)的子类backend/packages/harness/deerflow/reflection/resolvers.py:87-93

当 provider 包未安装时,_build_missing_dependency_hint 会把已知 provider 模块映射到正确的 pip/uv 包名(如 langchain_google_genailangchain-google-genai),返回一句可直接复制执行的提示backend/packages/harness/deerflow/reflection/resolvers.py:11-22

create_chat_model:工厂核心

工厂入口签名为 create_chat_model(name=None, thinking_enabled=False, *, app_config=None, **kwargs)backend/packages/harness/deerflow/models/factory.py:50nameNone 时取 config.models[0].name 作为默认模型backend/packages/harness/deerflow/models/factory.py:60-61。它还对若干特殊 provider 做针对性处理:Codex Responses API 模型把 thinking 映射为 reasoning_effort 并移除 max_tokensbackend/packages/harness/deerflow/models/factory.py:120-133;MindIEChatModel 强制 max_retries 默认为 1 防止超时级联backend/packages/harness/deerflow/models/factory.py:137-139

VllmChatModel:保留 reasoning 的 vLLM provider

VllmChatModel 继承 langchain_openai:ChatOpenAI,解决 vLLM 0.19.0 的一个具体问题:LangChain 默认 OpenAI 适配器会丢弃非标准的 reasoning 字段,导致交错式 thinking/tool-call 流程在后续轮次失去之前的推理上文backend/packages/harness/deerflow/models/vllm_provider.py:1-13。它在三处保留 reasoning:

此外 _normalize_vllm_chat_template_kwargs 把 DeerFlow 早期文档的 chat_template_kwargs.thinking 别名归一化为 vLLM 0.19.0 Qwen reasoning parser 实际读取的 enable_thinking,保证旧配置仍可用backend/packages/harness/deerflow/models/vllm_provider.py:39-62

Data Flow

下图展示一次 create_chat_model("doubao-seed-1.8", thinking_enabled=True) 的完整调用链。

关键编号步骤说明:

  1. 调用方(如 lead_agent)传入模型名与 thinking_enabledbackend/packages/harness/deerflow/agents/lead_agent/agent.py:435
  2. 工厂用 get_model_config(name) 取出该模型的 ModelConfigbackend/packages/harness/deerflow/models/factory.py:62
  3. resolve_classuse 类路径反射导入并做 BaseChatModel 子类校验backend/packages/harness/deerflow/models/factory.py:65
  4. thinking_enabled 为真且 supports_thinking 为真时,把 effective_wte 合并进构造参数backend/packages/harness/deerflow/models/factory.py:88-92
  5. 用解析出的类与合并后的参数实例化模型backend/packages/harness/deerflow/models/factory.py:150

Implementation Details

thinking 覆盖逻辑是工厂里最精细的部分。先合并出"生效的 enabled 设置"effective_wte,再分场景处理:

python
# 把 thinking 快捷字段合并进 when_thinking_enabled
has_thinking_settings = (model_config.when_thinking_enabled is not None) or (model_config.thinking is not None)
effective_wte: dict = dict(model_config.when_thinking_enabled) if model_config.when_thinking_enabled else {}
if model_config.thinking is not None:
    merged_thinking = {**(effective_wte.get("thinking") or {}), **model_config.thinking}
    effective_wte = {**effective_wte, "thinking": merged_thinking}
if thinking_enabled and has_thinking_settings:
    if not model_config.supports_thinking:
        raise ValueError(f"Model {name} does not support thinking. ...")
    if effective_wte:
        model_settings_from_config.update(effective_wte)

backend/packages/harness/deerflow/models/factory.py:83-92

解读:thinking 字段优先级高于 when_thinking_enabled.thinking({**old, **new} 中后者覆盖前者)。开启 thinking 但模型未声明 supports_thinking 时直接抛错,而非静默忽略。

关闭 thinking(not thinking_enabled)时的分支按"用户显式 disable → OpenAI 兼容网关 → vLLM → Anthropic 原生"依次匹配:

python
if not thinking_enabled:
    if model_config.when_thinking_disabled is not None:
        model_settings_from_config.update(model_config.when_thinking_disabled)  # 用户显式优先
    elif has_thinking_settings and effective_wte.get("extra_body", {}).get("thinking", {}).get("type"):
        model_settings_from_config["extra_body"] = _deep_merge_dicts(..., {"thinking": {"type": "disabled"}})
        model_settings_from_config["reasoning_effort"] = "minimal"
    elif has_thinking_settings and (disable_ctk := _vllm_disable_chat_template_kwargs(...)):
        model_settings_from_config["extra_body"] = _deep_merge_dicts(..., {"chat_template_kwargs": disable_ctk})
    elif has_thinking_settings and effective_wte.get("thinking", {}).get("type"):
        model_settings_from_config["thinking"] = {"type": "disabled"}  # Anthropic 原生

backend/packages/harness/deerflow/models/factory.py:93-112

解读:工厂从 when_thinking_enabled结构推断 provider 类型——出现 extra_body.thinking.type 判为 OpenAI 兼容网关,出现 extra_body.chat_template_kwargs.{thinking,enable_thinking} 判为 vLLM,出现顶层 thinking.type 判为 Anthropic 原生。这样无需新增字段就能自动给出正确的"关闭"表达。

supports_reasoning_effort 为假时,reasoning_effort 会同时从 kwargs 和配置里弹出,避免传给不支持的模型backend/packages/harness/deerflow/models/factory.py:113-115

supports_vision 不影响工厂构造(被 exclude),而是在工具装配处读取:为真时把 view_image_tool 加入 agent 工具集backend/packages/harness/deerflow/tools/tools.py:108-111

扩展指南:新增一个自定义模型 provider

适用于需要重写 LangChain 默认行为的场景(例如保留某个后端的非标准字段、改写流式解析)。参考 VllmChatModel 的最小模板:

python
# backend/packages/harness/deerflow/models/my_provider.py
from typing import Any
from langchain_openai import ChatOpenAI          # 或任意 BaseChatModel 子类
from langchain_core.outputs import ChatResult


class MyChatModel(ChatOpenAI):
    """自定义 provider:在标准 OpenAI 兼容协议上做增量改写。"""

    model_config = {"arbitrary_types_allowed": True}

    @property
    def _llm_type(self) -> str:
        return "my-openai-compatible"

    def _create_chat_result(self, response, generation_info=None) -> ChatResult:
        result = super()._create_chat_result(response, generation_info=generation_info)
        # 在此保留你的非标准字段 ...
        return result

对应 config.yaml 条目:

yaml
models:
  - name: my-model
    display_name: My Model
    use: deerflow.models.my_provider:MyChatModel   # module:Class
    model: my-model-id
    api_key: $MY_API_KEY
    base_url: http://localhost:8000/v1
    supports_thinking: false
    supports_vision: false

约束清单(均从工厂与反射的校验代码读出,不是建议而是硬性要求):

  1. use 必须是 module.path:ClassName 格式;缺少 : 会被 resolve_variable 判为非法路径并抛 ImportErrorbackend/packages/harness/deerflow/reflection/resolvers.py:44-46
  2. 解析出的对象必须是 type 且是 BaseChatModel 的子类,否则 resolve_classValueErrorbackend/packages/harness/deerflow/reflection/resolvers.py:89-93
  3. name 必填且应唯一;usemodel 必填(ModelConfig 中为 Field(...))backend/packages/harness/deerflow/config/model_config.py:7-14
  4. 构造函数会收到除元字段外的全部配置项(model_dump(exclude_none=True, exclude={...})),provider 类必须能接受这些 kwargs(或继承自能接受 extra 的基类)backend/packages/harness/deerflow/models/factory.py:66-80
  5. 若要支持 thinking,必须设置 supports_thinking: true,否则在 thinking_enabled=True 时工厂抛错backend/packages/harness/deerflow/models/factory.py:88-90
  6. 若类的 model_fieldsstream_usage,工厂会默认置 True 以保证 token 用量可被中间件统计backend/packages/harness/deerflow/models/factory.py:146-148
  7. 已知 provider 模块名建议补进 MODULE_TO_PACKAGE_HINTS,缺包提示才能给出正确包名backend/packages/harness/deerflow/reflection/resolvers.py:3-8

Configuration

models[] 每个条目对应一个 ModelConfig。下表字段均来自 schema 与 example 文件:

字段含义必填Source
name模型唯一名,运行时按此选择model_config.py:7
display_name前端展示名model_config.py:8
description模型描述model_config.py:9
useprovider 类路径 module:Classmodel_config.py:10
model后端模型 IDmodel_config.py:14
use_responses_api是否走 OpenAI /v1/responsesmodel_config.py:16
output_version结构化输出版本(如 responses/v1)model_config.py:20
supports_thinking是否支持 thinking;为假时开 thinking 会抛错否(默认 false)model_config.py:24
supports_reasoning_effort是否支持 reasoning_effort;为假时该参数被剔除否(默认 false)model_config.py:25
when_thinking_enabledthinking 开启时合并的额外设置model_config.py:26
when_thinking_disabledthinking 关闭时合并的额外设置(用户显式优先)model_config.py:30
supports_vision为真时 agent 工具集加入 view_image_tool否(默认 false)model_config.py:34
thinkingwhen_thinking_enabled 的快捷写法,二者会合并model_config.py:35
api_key / base_url / 其他extra="allow" 透传给 provider 构造器视 providermodel_config.py:15

$ENV 与安全标注:任何以 $ 开头的配置值会被 resolve_env_variablesos.getenv 解析,环境变量缺失时直接抛 ValueErrorbackend/packages/harness/deerflow/config/app_config.py:280-285。务必用 api_key: $OPENAI_API_KEY 这类引用,不要把明文密钥写进 config.yaml,因为该文件常被纳入版本控制。

OpenAI 兼容网关的 vLLM 配置示例(注意 enable_thinkingextra_body.chat_template_kwargs 下):

yaml
- name: qwen3-32b-vllm
  use: deerflow.models.vllm_provider:VllmChatModel
  model: Qwen/Qwen3-32B
  api_key: $VLLM_API_KEY
  base_url: http://localhost:8000/v1
  supports_thinking: true
  when_thinking_enabled:
    extra_body:
      chat_template_kwargs:
        enable_thinking: true

config.example.yaml:316-330

Common Pitfalls / Tips

References

页面关系
./04-配置系统与AppConfig.md上游:AppConfig 如何加载与缓存 config.yamlget_model_config$ENV 解析的归属
./34-反射与动态加载机制.md平行:resolve_class / resolve_variable 的通用反射机制,本章是其在模型层的应用
./10-LeadAgent与Agent工厂.md下游:Agent 工厂如何调用 create_chat_model 并按请求/agent/默认三级解析模型名
./35-可观测性-Tracing与Token用量.md下游:stream_usage 自动开启与 tracing callbacks 附加如何支撑 token 用量统计

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析