模型选择器实现指南

在这里插入图片描述

本文档详细记录了在 DeerFlow 项目中增加模型选择器的完整方法和过程。


一、功能概述

模型选择器允许用户在前端界面选择不同的 LLM 模型,并在运行时使用选定的模型执行任务。主要特性包括:

  • 实时模型切换,无需重启服务
  • 支持配置文件中的所有模型
  • 中间件(标题生成、摘要、记忆)可独立配置模型
  • 配置热重载机制

二、实现架构

2.1 整体数据流

用户选择模型
       │
       ▼
模型切换器 UI (localStorage + 事件通知)
       │
       ▼
前端状态管理 (useLocalSettings hook)
       │
       ▼
ChatPage 传递 context 到 useThreadStream
       │
       ▼
thread.submit(config.configurable)
       │
       ▼
后端 make_lead_agent 解析 model_name
       │
       ▼
create_chat_model(name=model_name)
       │
       ▼
执行任务时使用选定模型

2.2 关键组件

组件 位置 职责
模型切换器 UI public/model-switcher/index.html 展示模型列表,处理用户选择
后端 API backend/app/gateway/routers/models.py 提供模型列表、默认模型设置、配置重载接口
配置管理 backend/packages/harness/deerflow/config/app_config.py 管理应用配置,支持热重载
前端状态 frontend/src/core/hooks/useLocalSettings.ts 管理本地设置状态和事件通知
线程通信 frontend/src/core/threads/hooks.ts 将模型名称传递到后端
代理创建 backend/packages/harness/deerflow/agents/lead_agent/agent.py 根据运行时配置创建代理和模型

三、详细实现步骤

3.1 前端模型切换器 UI

文件: frontend/public/model-switcher/index.html

功能:

  • 从后端 API 获取模型列表
  • 显示当前选中的模型
  • 允许用户切换模型
  • 支持配置重载

关键代码:

// 获取模型列表
async function fetchModels() {
    const res = await fetch(apiBase + '/api/models');
    const data = await res.json();
    renderModelList(data);
}

// 切换模型
async function switchModel(name) {
    await fetch(apiBase + '/api/model', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ model_name: name })
    });
    
    // 更新本地存储并触发事件
    localStorage.setItem('df_model_name', name);
    dispatchEvent(new CustomEvent('df-settings-changed', { detail: { model_name: name } }));
}

// 重载配置
async function reloadConfig() {
    await fetch(apiBase + '/api/model/reload', { method: 'POST' });
    fetchModels();
}

部署:
model-switcher 目录放置在 frontend/public/ 下,通过 iframe 嵌入主应用。


3.2 后端 API 路由

文件: backend/app/gateway/routers/models.py

新增接口:

接口 方法 功能
/api/models GET 获取所有可用模型列表
/api/model POST 设置默认模型
/api/model/reload POST 热重载配置文件

关键代码:

@router.post("/model", response_model=SetDefaultModelResponse)
async def set_default_model(request: SetDefaultModelRequest) -> SetDefaultModelResponse:
    success = set_default_model(request.model_name)
    return SetDefaultModelResponse(success=success)

@router.post("/model/reload", response_model=ReloadConfigResponse)
async def reload_model_config() -> ReloadConfigResponse:
    config = reload_app_config()
    return ReloadConfigResponse(
        success=True,
        models_count=len(config.models),
    )

3.3 配置管理增强

文件: backend/packages/harness/deerflow/config/app_config.py

关键修改:

  1. set_default_model() - 运行时设置默认模型
  2. reload_app_config() - 热重载配置文件
  3. reset_app_config() - 重置配置缓存
def set_default_model(model_name: str) -> bool:
    """Set the default model at runtime."""
    global _app_config
    if _app_config is None:
        _app_config = AppConfig.from_file()
    
    model_config = _app_config.get_model_config(model_name)
    if model_config is None:
        return False
    
    _app_config.default_model = model_name
    return True

def reload_app_config(config_path: str | None = None) -> AppConfig:
    """Reload the config from file and update the cached instance."""
    global _app_config
    _app_config = AppConfig.from_file(config_path)
    return _app_config

导出配置: backend/packages/harness/deerflow/config/__init__.py

from .app_config import get_app_config, reload_app_config, set_default_model

__all__ = [
    "get_app_config",
    "reload_app_config", 
    "set_default_model",
    # ... other exports
]

3.4 前端状态管理

文件: frontend/src/core/hooks/useLocalSettings.ts

功能:

  • 监听 df-settings-changed 事件
  • 同步 localStorage 中的模型名称
  • 更新全局设置状态

关键代码:

useEffect(() => {
    const handleSettingsChanged = (e: Event) => {
        const detail = (e as CustomEvent).detail;
        if (detail?.model_name) {
            setSettings(prev => ({
                ...prev,
                context: { ...prev.context, model_name: detail.model_name }
            }));
        }
    };
    
    window.addEventListener('df-settings-changed', handleSettingsChanged);
    return () => window.removeEventListener('df-settings-changed', handleSettingsChanged);
}, []);

3.5 线程通信参数传递

文件: frontend/src/core/threads/hooks.ts

关键修改:
model_name 通过 config.configurable 传递到后端。

await thread.submit(
    { messages: [...] },
    {
        threadId: threadId,
        config: {
            recursion_limit: 1000,
            configurable: {
                ...context,
                model_name: context.model_name,
                // ... other configurable options
            },
        },
    },
);

注意事项:

  • 参数必须放在 config.configurable 中,LangGraph SDK 才能正确传递
  • 不能放在顶层 context 参数中

3.6 后端代理模型解析

文件: backend/packages/harness/deerflow/agents/lead_agent/agent.py

关键修改:

  1. make_lead_agent() 中解析运行时模型名称
def make_lead_agent(config: RunnableConfig) -> LeadAgent:
    cfg = config.get("configurable", {})
    requested_model_name = cfg.get("model_name")
    
    # 解析最终使用的模型名称
    agent_model_name = agent_config.model if agent_config and agent_config.model else _resolve_model_name(requested_model_name)
    
    # 创建模型实例
    model = create_chat_model(name=agent_model_name, thinking_enabled=thinking_enabled)
  1. _resolve_model_name() 函数
def _resolve_model_name(requested_name: str | None = None) -> str:
    """Resolve the model name based on request and config."""
    if requested_name:
        return requested_name
    
    config = get_app_config()
    return config.default_model if config.default_model else config.models[0].name

3.7 中间件模型配置

文件: config.yaml

三个中间件支持独立配置模型:

# 标题生成中间件
title:
  enabled: true
  model_name: deepseek-r1:1.5b  # 使用本地模型,节省成本

# 摘要中间件
summarization:
  enabled: true
  model_name: deepseek-r1:1.5b
  trigger:
    - type: tokens
      value: 15564

# 记忆中间件
memory:
  enabled: true
  model_name: deepseek-r1:1.5b

四、模型配置示例

4.1 远程 API 模型(火山引擎豆包)

models:
  - name: doubao-seed-evolving
    display_name: doubao-seed-evolving
    use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
    model: doubao-seed-evolving
    api_base: https://ark.cn-beijing.volces.com/api/v3
    api_key: $VOLCENGINE_API_KEY
    supports_thinking: true
    supports_vision: false

4.2 本地 Ollama 模型

models:
  - name: deepseek-r1:1.5b
    display_name: DeepSeek-R1 1.5B Local Ollama
    use: langchain_openai:ChatOpenAI
    model: deepseek-r1:1.5b
    api_key: dummy
    base_url: http://localhost:11434/v1
    max_tokens: 2048
    supports_vision: false

注意事项:

  • 本地 Ollama 模型必须使用 langchain_openai:ChatOpenAI
  • api_key 可以是任意值(Ollama 不需要认证)
  • base_url 指向本地 Ollama 服务

五、测试验证

5.1 验证配置加载


# 验证模型列表
python -c "from deerflow.config import get_app_config; cfg = get_app_config(); print([m.name for m in cfg.models])"

# 验证中间件配置
python -c "from deerflow.config import reload_app_config; reload_app_config(); from deerflow.config.summarization_config import get_summarization_config; print(get_summarization_config().model_name)"

5.2 验证模型创建

python -c "from deerflow.models import create_chat_model; m = create_chat_model(name='deepseek-r1:1.5b'); print(type(m).__name__)"
# 输出: ChatOpenAI

5.3 验证 API 接口

# 获取模型列表
curl http://localhost:8001/api/models

# 设置默认模型
curl -X POST http://localhost:8001/api/model \
  -H "Content-Type: application/json" \
  -d '{"model_name": "doubao-seed-evolving"}'

# 重载配置
curl -X POST http://localhost:8001/api/model/reload

七、代码修改清单

文件 修改类型 说明
frontend/public/model-switcher/index.html 新增 模型切换器 UI
backend/app/gateway/routers/models.py 新增 模型管理 API
backend/packages/harness/deerflow/config/app_config.py 修改 增加配置重载和默认模型设置
backend/packages/harness/deerflow/config/__init__.py 修改 导出新函数
frontend/src/core/hooks/useLocalSettings.ts 修改 监听模型切换事件
frontend/src/core/threads/hooks.ts 修改 通过 configurable 传递模型名称
backend/packages/harness/deerflow/agents/lead_agent/agent.py 修改 解析运行时模型名称
config.yaml 修改 配置中间件模型

八、总结

模型选择器的实现涉及前端 UI、状态管理、API 通信和后端配置解析等多个环节。核心要点是:

  1. 参数传递: 通过 config.configurable 将模型名称从前端传递到后端
  2. 配置热重载: 支持不重启服务更新配置
  3. 中间件独立: 中间件可以使用与主代理不同的模型,降低成本
  4. 本地模型支持: 通过 OpenAI 兼容接口连接本地 Ollama 服务

通过这些修改,用户可以在运行时灵活切换模型,同时中间件使用轻量级本地模型来节省 API 费用。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐