Files
DevFlow/docs/02-架构设计/已编号方案/F-01-模型能力系统与智能路由设计-2026-06-16.md
绝尘 998a2f243d 文档: 架构方案文档(意图识别论证+多主题愿景/论证+文档物理分类+边界清晰化)
squash合并:
- 意图识别层论证(8维度+10业界佐证)
- 多主题上下文管理愿景+并存论证+补充论证(多轮agentic)
- 架构设计文档物理分类(四子目录+INDEX+命名规范+引用同步+边界清晰化)
- 前端架构技术债清单归档
2026-06-19 15:04:04 +08:00

24 KiB
Raw Blame History

F-01 模型能力系统 Phase 1 — 模型配置与智能路由设计

状态:📐 设计定稿2026-06-16含多角度佐证 + 厂商适配 + 用户流程) 类型:架构设计文档(不碰任何 code 关联F-260614-05 多模态 Phase 2 / F-260614-04 多 Provider 负载均衡池 前置F-07 df-ai-core trait 下沉 已完成batch61·2069f79 决策记录:见 功能决策记录-2026-06-14.md §「模型能力系统」行


⚠️ 实施状态2026-06-18 核对:已落地,路由部分改方向)

阶段 1-6 全部落地(数据模型 / 探测器 / 厂商拉取 / 路由器 / 7+ 调用点接入 / 前端):

  • 阶段 1 数据模型:crates/df-ai-core/src/model.rs:106 ModelConfig:20/35/55/71 四维度枚举Modality/Capability/CostTier/IntelligenceTier:211 deserialize_model_configs 向后兼容;crates/df-storage/src/models.rs:159-160 AiProviderRecord.model_configs 字段(注意:实际落地字段名 model_configs 而非本文档 §1.1/§2.3 描述的老 models JSON 字段扩展,新字段经 V18 迁移幂等补列 crates/df-storage/src/migrations.rs:317-325)。
  • 阶段 2 探测器:crates/df-ai/src/model_probe.rs(已建)+ crates/df-ai/presets/models.json(已建)。注意预设表/PatternRule 未拆独立 preset_table.rs,合并在 model_probe 内。
  • 阶段 3 厂商拉取:crates/df-ai/src/model_fetch.rs已建fetch_models 分派)。
  • 阶段 4 路由器:crates/df-ai/src/router.rs:40/56/76 ModelRouter::select / select_model_id
  • 阶段 5 调用点:主对话 src-tauri/src/commands/ai/agentic.rs:418/423、标题 title.rs:86/91、知识提炼/嵌入 knowledge_inject.rs:58-63/359-364、压缩 compress.rs:59-64、项目扫描 project.rs:533-538/627-632、灵感评估 crates/df-ideas/src/adversarial.rs:158-163、AiNode crates/df-nodes/src/ai_node.rs:221-226

§6.1 路由逻辑已改方向2026-06-18 决策 B-260618-03:本文档 §6.1 描述的 TaskRequirementsmin_intelligence/max_cost 两字段、select 含「智力达标」「成本可控」两过滤步、max_by_key((weight, Reverse(cost_tier))) 同权重选便宜——均已删除

  • 实际形态:crates/df-ai/src/router.rs:27-35 TaskRequirements 仅 3 字段(modalities/needs_tool_use/estimated_contextmin_intelligence/max_cost 已删;:56-63 select 过滤链仅 4 步enabled / 模态 / 能力 / 窗口),:62 排序纯 max_by_key(m.weight),无 cost tie-break。
  • 根因provider /v1/models API 不返回 cost_tier/intelligence两维度 100% 靠预设表写死 + 模型名启发式猜数据无客观依据不可信不参与硬路由。枚举CostTier/IntelligenceTier保留在 model.rs 供未来出现真实判别源再接回。

§6.2 场景路由表 / §6.3 调用点表:表中行号(如 agentic.rs:50/title.rs:63/project.rs:378)已漂移,实际调用点见上方阶段 5 行号清单。


0. 摘要

将当前「一个 Provider 一个 default_model 跑全场」升级为「多模型池 + 能力感知 + 智能路由」。 用户只需填 Provider 基本信息 → 自动拉取模型列表 → 自动探测每个模型的模态/能力/价格/智力 → 路由器按场景自动选最优模型。

核心价值:含图消息自动选 Vision 模型、标题生成自动选便宜模型、复杂推理自动选强模型、限流自动 fallback。


1. 现状盘点

1.1 数据模型AiProviderRecord

crates/df-storage/src/models.rs:127-140

pub struct AiProviderRecord {
    pub provider_type: String,     // "openai_compat" | "anthropic_compat"
    pub base_url: String,
    pub default_model: String,     // 唯一实际使用的模型名
    pub models: Option<String>,    // JSON array of model names展示用未消费
    pub config: Option<String>,    // JSON extra config几乎没用
    pub is_default: bool,
    ...
}

问题

  1. models 存了模型列表但没有能力标记,不知道哪个支持 vision / tool use
  2. 没有启用/禁用开关,配了的模型无法控制哪些参与路由
  3. 没有权重/优先级,无法表达"优先用 AA 不可用时 fallback 到 B"
  4. default_model 是唯一实际使用的模型名,models 列表形同虚设

1.2 模型选择机制(当前)

用户在 Settings 配置 Provider含 base_url + default_model
  ↓
AiSession.active_provider_id  ← 全局唯一活跃 Provider
  ↓
get_active_provider() → 取 Provider 配置
  ↓
CompletionRequest { model: provider.default_model }  ← 直接用固定 model
  • 一个时刻只有一个活跃 Provider
  • 所有场景(对话/标题/知识提炼/项目扫描)共用同一个 model
  • 无 ModelCapability / ModelRouter / 按场景路由机制

2. 模型配置数据模型

2.1 ModelConfig每个模型的完整描述

/// 单个模型的完整配置4 维度 + 路由控制)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelConfig {
    // ── 基础 ──
    /// 模型名(如 "glm-4v-flash"
    pub model_id: String,
    /// 启用/禁用false = 配了但不参与路由,相当于"备档"
    #[serde(default = "default_true")]
    pub enabled: bool,
    /// 用户自定义别名(可选)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub label: Option<String>,

    // ── 维度 1模态能接收什么输入──
    pub modalities: Vec<Modality>,

    // ── 维度 2能力能做什么──
    pub capabilities: Vec<Capability>,

    // ── 维度 3价格成本分级──
    pub cost_tier: CostTier,

    // ── 维度 4聪明程度智力分级──
    pub intelligence: IntelligenceTier,

    // ── 路由控制 ──
    /// 权重0-100同能力候选中优先选权重高的
    #[serde(default = "default_weight")]
    pub weight: u32,
    /// 上下文窗口大小tokens
    #[serde(default = "default_context_window")]
    pub context_window: usize,

    // ── 探测元数据(只读,由 ModelProbe 填充)──
    #[serde(skip_serializing_if = "Option::is_none")]
    pub probe_source: Option<ProbeSource>,
}

fn default_true() -> bool { true }
fn default_weight() -> u32 { 50 }
fn default_context_window() -> usize { 8192 }

2.2 枚举定义

/// 模态(输入类型)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Modality {
    Text,
    Vision,
    // 未来扩展Audio, Video
}

/// 能力
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Capability {
    ToolUse,    // function calling / tool use
    Embedding,  // 向量嵌入
    CodeGen,    // 代码生成强项
}

/// 价格分级
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum CostTier {
    Free,   // 免费
    Low,    // flash / mini 系列
    Medium, // 标准定价
    High,   // 旗舰定价
}

/// 聪明程度(智力分级)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum IntelligenceTier {
    Lite,      // flash/mini/lite/nano — 快但简单任务
    Standard,  // air/standard — 日常够用
    Plus,      // plus/pro/max — 复杂推理
    Ultra,     // 最强模型 — 兜底用
}

2.3 存储方式:扩展 models JSON 字段(零迁移)

AiProviderRecord.models["glm-4-flash", "glm-4v"] 升级为:

[
  {
    "model_id": "glm-4-flash",
    "enabled": true,
    "modalities": ["text"],
    "capabilities": ["tool_use"],
    "cost_tier": "low",
    "intelligence": "lite",
    "weight": 90,
    "context_window": 128000
  },
  {
    "model_id": "glm-4v",
    "enabled": true,
    "modalities": ["text", "vision"],
    "capabilities": ["tool_use"],
    "cost_tier": "medium",
    "intelligence": "plus",
    "weight": 70,
    "context_window": 128000
  }
]

向后兼容:自定义反序列化器,老格式 ["model-a"] → 自动转成默认配置列表。

fn deserialize_models<'de, D>(d: D) -> Result<Vec<ModelConfig>, D::Error>
where D: serde::Deserializer<'de> {
    let v = serde_json::Value::deserialize(d)?;
    match v {
        // 老格式:字符串数组 → 每个转默认 ModelConfig
        serde_json::Value::Array(arr) if arr.iter().all(|v| v.is_string()) => {
            arr.into_iter()
                .filter_map(|v| v.as_str().map(|s| ModelConfig::with_defaults(s)))
                .collect::<Vec<_>>()
                .pipe(Ok)
        }
        // 新格式ModelConfig 数组
        serde_json::Value::Array(_) => {
            serde_json::from_value::<Vec<ModelConfig>>(v).map_err(D::Error::custom)
        }
        _ => Ok(vec![]),
    }
}

否决方案:新建 ai_models 独立表 — 增量复杂度高(迁移 + 新 Repomodels JSON 字段已够用。


3. 多角度佐证

3.1 行业对标

产品 模型配置方式 对照
OpenRouter 每个模型标 modality/pricing/context_length 4 维度完全覆盖,且加了 intelligence 分级
Cursor 内置模型能力感知(自动判断图片/工具) 预设表 + 启发式探测本质相同
LangChain ModelRouter 按 max_tokens/supports_tool_use 路由 capabilities + modalities 路由一致
One-API / New-API 纯渠道管理,无能力感知 能力标记是显著增量

结论4 维度配置是行业标配的超集,非过度设计。

3.2 路由器实际需求倒推

路由决策 必须知道 对应维度 缺了会怎样
含图消息选哪个 是否支持 Vision 模态 发给纯文本模型 → 400
Agentic loop 选哪个 是否支持 tool_use 能力 发 tool 定义给不支持的 → 忽略/报错
标题生成选哪个 够便宜够快 价格+智力 用旗舰模型 → 烧钱
复杂推理选哪个 够聪明 智力 用 flash → 质量差
上下文超长选哪个 窗口够大 context_window 超窗口 → 截断

结论:每个维度直接对应路由器必须做的判断,缺一不可。

3.3 用户认知负担

信息 用户负担 说明
模型名 从文档复制
模态 文档一眼可见,或名字带 v
能力 文档明确标注
价格 查文档,但分级别直觉可判
智力 flash < air < plus 是行业共识

结论4 个维度都是用户本来就知道的,加上自动填充,门槛极低。

3.4 可扩展性

未来需求 支持 扩展方式
音频输入 Modality 加 Audio
视频输入 Modality 加 Video
精确价格控制 cost_tier 升级为精确数值
流式支持差异 Capability 加 Streaming
自定义能力标签 Capability 加 Custom(String)

结论enum + Vec 开放式,未来加维度只需加变体。

3.5 数据兼容性

models: ["model-a"] → 自定义反序列化自动补默认值。default_model 字段保留向后兼容。零迁移脚本。

3.6 竞品缺陷反证

问题 竞品现状 我们的方案
含图发给纯文本模型 One-API 不感知能力 路由器自动选 Vision
标题生成用旗舰模型 固定模型手动切 按 intelligence+cost 自动选 Lite
新模型不知道能不能用 用户自己试 预设表 + 启发式自动推断
限流后无 fallback 手动切换 按 weight 自动 fallback

3.7 实施风险

风险 评估 缓解
预设表维护 低(主流 20-30 个,半年更新) JSON 配置文件可热更新
启发式误判 低(命名是行业惯例) 标注 Medium 可信度,用户可修正
路由器选错 weight fallback + 降级兜底

7 个角度一致指向:方案可行且合理。


4. 模型探测器ModelProbe

4.1 多源探测

每个模型的信息从多个来源获取,交叉验证:

pub struct ModelProbe {
    preset_table: ModelPresetTable,
}

impl ModelProbe {
    pub async fn probe(&self, provider: &AiProviderRecord, model_id: &str) -> ModelProfile {
        // 1. 启发式:从模型名推断
        let heuristic = self.heuristic_infer(model_id);

        // 2. 查预设表(精确匹配 > 模糊匹配)
        let preset = self.preset_table.lookup(model_id);

        // 3. 多源合并(预设表 > 启发式)
        let merged = self.merge_sources(heuristic, preset);

        // 4. 最终结果 + 标注每个维度的置信度
        self.finalize(merged)
    }
}

4.2 探测优先级链

用户手动设定 (UserSet)          ← 最高优先,永不被覆盖
    ↓
内置预设表精确匹配 (PresetTable) ← 高可信
    ↓
模型名启发式 (Heuristic)         ← 中可信,标注"建议确认"
    ↓
默认值 (Default)                 ← 低可信

4.3 探测结果带置信度

pub struct ModelProfile {
    pub model_id: String,
    pub modalities: SourcedField<Vec<Modality>>,
    pub capabilities: SourcedField<Vec<Capability>>,
    pub cost_tier: SourcedField<CostTier>,
    pub intelligence: SourcedField<IntelligenceTier>,
    pub context_window: SourcedField<usize>,
}

pub struct SourcedField<T> {
    pub value: T,
    pub source: Source,
    pub confidence: Confidence,
}

pub enum Source { PresetTable, Heuristic, UserSet }

pub enum Confidence { High, Medium, Low }

4.4 内置预设表

pub struct ModelPresetTable {
    exact: HashMap<String, ModelPreset>,     // 精确匹配
    patterns: Vec<PatternRule>,              // 模糊匹配
}

// 精确匹配示例
"glm-4-flash"   => { modalities:[Text], capabilities:[ToolUse], cost:Low, intelligence:Lite, context:128K }
"glm-4-air"     => { modalities:[Text], capabilities:[ToolUse], cost:Low, intelligence:Standard }
"glm-4-plus"    => { modalities:[Text], capabilities:[ToolUse], cost:Medium, intelligence:Plus }
"glm-4v"        => { modalities:[Text,Vision], capabilities:[ToolUse], cost:Medium, intelligence:Plus }
"glm-4v-flash"  => { modalities:[Text,Vision], capabilities:[ToolUse], cost:Low, intelligence:Lite }
"deepseek-chat" => { modalities:[Text], capabilities:[ToolUse], cost:Low, intelligence:Standard }
"deepseek-coder"=> { modalities:[Text], capabilities:[ToolUse,CodeGen], cost:Low, intelligence:Plus }
"claude-3-5-sonnet-20241022" => { modalities:[Text,Vision], capabilities:[ToolUse], cost:High, intelligence:Ultra }
"claude-3-haiku-20240307"    => { modalities:[Text,Vision], capabilities:[ToolUse], cost:Low, intelligence:Standard }
"gpt-4o"        => { modalities:[Text,Vision], capabilities:[ToolUse], cost:High, intelligence:Ultra }
"gpt-4o-mini"   => { modalities:[Text,Vision], capabilities:[ToolUse], cost:Low, intelligence:Standard }
"embedding-3"   => { modalities:[], capabilities:[Embedding], cost:Low, intelligence:Lite }

// 模糊匹配规则
PatternRule { keyword: "flash|mini|lite|nano",  Intelligence(Lite) }
PatternRule { keyword: "plus|pro|max|ultra",   Intelligence(Plus) }
PatternRule { keyword: "v$|vl|vision|-v\\d",    Modality(Vision) }
PatternRule { keyword: "embed",                 Capability(Embedding) }
PatternRule { keyword: "code|coder",            Capability(CodeGen) }

预设表以 JSON 配置文件形式存储(crates/df-ai/presets/models.json),可热更新不随版本绑。


5. 厂商模型列表适配

5.1 差异盘点

厂商/协议 端点 鉴权 返回格式
OpenAI 兼容GLM/DeepSeek/Kimi/通义) GET /v1/models Bearer {data:[{id, owned_by}]}
Anthropic GET /v1/models x-api-key + version {data:[{id, display_name}]}
中转站 GET /v1/models Bearer 同 OpenAI但可能混入噪音或返回 404
Ollama GET /api/tags {models:[{name, size}]} — 完全不同

5.2 统一拉取接口

/// 模型拉取结果(统一格式)
pub struct FetchedModel {
    pub model_id: String,
    pub display_name: Option<String>,
}

/// 按 provider_type 分派
pub async fn fetch_models(
    provider_type: &str,
    base_url: &str,
    api_key: &str,
) -> Result<Vec<FetchedModel>, FetchError> {
    match provider_type {
        "openai_compat" => fetch_openai_compat(base_url, api_key).await,
        "anthropic_compat" => fetch_anthropic_compat(base_url, api_key).await,
        _ => Err(FetchError::Unsupported),
    }
}

5.3 URL 智能拼接

fn build_models_url(base_url: &str) -> String {
    let url = base_url.trim_end_matches('/');
    if url.ends_with("/chat/completions") {
        url.replace("/chat/completions", "/models")
    } else if url.ends_with("/v1") || url.ends_with("/v4") {
        format!("{}/models", url)
    } else if url.ends_with("/models") {
        url.to_string()
    } else {
        format!("{}/v1/models", url)
    }
}

5.4 噪音过滤

fn is_non_chat_model(id: &str) -> bool {
    let id = id.to_lowercase();
    id.contains("dall-e") || id.contains("midjourney")      // 图片生成
    || id.contains("tts") || id.contains("whisper")          // 语音
    || id.contains("moderation")                              // 审查
    // embedding 保留(知识库需要)
}

5.5 失败降级

错误 处理
404 / 不支持 静默降级到手动输入(批量文本)
401 / 403 提示密钥问题
网络/超时 提示检查连接
解析失败 提示格式不兼容,降级手动

核心原则:自动拉取是锦上添花,手动输入永远保底。


6. 模型路由器ModelRouter

6.1 路由逻辑

pub struct TaskRequirements {
    pub modalities: Vec<Modality>,     // 任务需要什么模态
    pub needs_tool_use: bool,          // 是否需要工具调用
    pub min_intelligence: IntelligenceTier, // 最低智力要求
    pub max_cost: Option<CostTier>,    // 成本上限(可选)
    pub estimated_context: usize,      // 预估上下文大小
}

impl ModelRouter {
    pub fn select(&self, req: &TaskRequirements, pool: &[ModelConfig]) -> Option<&ModelConfig> {
        pool.iter()
            .filter(|m| m.enabled)                                              // 1. 只选启用的
            .filter(|m| req.modalities.iter().all(|r| m.modalities.contains(r))) // 2. 模态匹配
            .filter(|m| !req.needs_tool_use || m.capabilities.contains(&Capability::ToolUse)) // 3. 能力匹配
            .filter(|m| m.intelligence >= req.min_intelligence)                 // 4. 智力达标
            .filter(|m| req.max_cost.map_or(true, |max| m.cost_tier <= max))    // 5. 成本可控
            .filter(|m| m.context_window >= req.estimated_context)              // 6. 窗口够大
            .max_by_key(|m| (m.weight, -(m.cost_tier as i32)))                  // 7. 权重优先,同权重选便宜的
    }
}

6.2 场景路由表

场景 路由偏好 典型选择
标题生成 Lite + Low cost glm-4-flash
日常对话 Standard glm-4-air
写代码 Standard/Plus + CodeGen deepseek-coder
含图片 Vision + Standard glm-4v
复杂推理 Plus/Ultra glm-4-plus
知识嵌入 Embedding embedding-3

6.3 7 个调用点接入

调用点 当前代码位置 TaskRequirements
主对话 stream_llm agentic.rs:50 动态按消息内容含图→Vision
标题生成 title.rs:63 Lite + Low省钱
知识提炼 knowledge_inject.rs:33,323 Standard
知识嵌入 knowledge_inject.rs embedding Embedding
项目扫描描述 project.rs:378 Standard含图→Vision
灵感评估 df-ideas adversarial.rs Standard
AiNode 工作流 df-nodes ai_node.rs:118 按节点配置

7. 用户配置与使用流程

7.1 配置流程(做一次)

Step 1: 填 Provider 基本信息(名称/类型/地址/密钥)
  ↓
Step 2: 点「测试连接并拉取模型」
  ↓ 自动调 /v1/models按协议分派
  ↓ 自动过滤非 chat 模型
  ↓ 自动探测每个模型特征(预设表 + 启发式)
  ↓
Step 3: 显示模型列表(已标注特征 + 可信度)
  ↓ 用户确认或微调(大多数不需要进这步)
  ↓
Step 4: 完成

拉取失败时降级:手动输入模型名(批量文本,每行一个)→ 同样走探测。

7.2 日常使用(零配置)

用户发消息 → 路由器自动选模型 → 用户无感 → 出结果

手动覆盖AiChat 顶部下拉选「自动(推荐)」或指定具体模型。

自动 fallback:模型限流/失败 → 按 weight 选次高 → 自动重试。

7.3 简洁性指标

传统做法 我们的做法 省了什么
手动填每个模型能力 预设表自动填充 省 90% 配置
自己记哪个支持图片 路由器自动选 省记忆
每次对话手动切模型 自动路由 省操作
限流后手动换 自动 fallback 省故障处理

用户最小操作路径:填 3 个字段 → 点 1 个按钮 → 完成。


8. 实施分阶段

阶段 1数据模型df-ai-core + df-storage

文件 改动
crates/df-ai-core/src/provider.rs 新增 ModelConfig / Modality / Capability / CostTier / IntelligenceTier
crates/df-storage/src/models.rs AiProviderRecord.models 反序列化升级(兼容老格式)
crates/df-ai/src/model_config.rs(新建) deserialize_models 兼容函数 + ModelConfig::with_defaults

阶段 2预设表 + 探测器df-ai

文件 改动
crates/df-ai/presets/models.json(新建) 主流模型预设数据
crates/df-ai/src/model_probe.rs(新建) ModelProbe + 启发式 + 多源合并
crates/df-ai/src/preset_table.rs(新建) 预设表加载 + 精确/模糊匹配

阶段 3厂商模型列表拉取df-ai

文件 改动
crates/df-ai/src/model_fetch.rs(新建) fetch_models + openai_compat/anthropic_compat 分派 + URL 拼接 + 噪音过滤

阶段 4路由器df-ai

文件 改动
crates/df-ai/src/router.rs(新建) ModelRouter + TaskRequirements + select 逻辑

阶段 57 调用点接入src-tauri

文件 改动
src-tauri/src/commands/ai/agentic.rs 主对话路由(按 has_image 动态选)
src-tauri/src/commands/ai/title.rs 标题生成路由Lite + Low
src-tauri/src/commands/ai/knowledge_inject.rs 知识提炼/嵌入路由
src-tauri/src/commands/project.rs 项目扫描路由
src-tauri/src/commands/ai/commands.rs 新增 IPCai_fetch_models / ai_probe_model

阶段 6前端src

文件 改动
src/api/types.ts ModelConfig 类型对齐
src/components/settings/ Provider 配置页加模型列表 + 探测结果展示 + 启用/禁用/权重
src/components/AiChat.vue 顶部模型下拉(自动/指定)

9. 与 F-05多模态的关系

子能力 依赖 F-01 说明
ContentPart 数据模型 纯类型升级,与 ModelConfig 解耦
provider content 数组化 协议层改造,与路由器无关
前端粘贴/拖拽/渲染 纯前端
vision 模型自动路由 ModelRouter 按 modalities 含 Vision 筛选

F-05 可独立先做 80%(数据模型 + provider + 前端vision 路由留到 F-01 落地后接入。 过渡期靠 ModelConfig.modalities 手动标注 + 路由器筛选。


10. 不做的事(显式排除)

  • 不做模型自动下载/安装(仅 API 模型,不含本地模型管理)
  • 不做模型 benchmark 自动跑分(智力分级靠预设表 + 用户确认)
  • 不做多 Provider 负载均衡(属 F-04本任务只做单 Provider 内多模型路由)
  • 不做模型用量统计/成本告警(属运维增强,后续独立做)
  • 不新建 ai_models 独立表(扩展 models JSON 字段够用)
  • 本文档不实施任何 code纯设计