# 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-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 描述的 `TaskRequirements` 含 `min_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_context`),`min_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` ```rust pub struct AiProviderRecord { pub provider_type: String, // "openai_compat" | "anthropic_compat" pub base_url: String, pub default_model: String, // 唯一实际使用的模型名 pub models: Option, // JSON array of model names(展示用,未消费) pub config: Option, // JSON extra config(几乎没用) pub is_default: bool, ... } ``` **问题**: 1. `models` 存了模型列表但**没有能力标记**,不知道哪个支持 vision / tool use 2. **没有启用/禁用开关**,配了的模型无法控制哪些参与路由 3. **没有权重/优先级**,无法表达"优先用 A,A 不可用时 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(每个模型的完整描述) ```rust /// 单个模型的完整配置(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, // ── 维度 1:模态(能接收什么输入)── pub modalities: Vec, // ── 维度 2:能力(能做什么)── pub capabilities: Vec, // ── 维度 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, } fn default_true() -> bool { true } fn default_weight() -> u32 { 50 } fn default_context_window() -> usize { 8192 } ``` ### 2.2 枚举定义 ```rust /// 模态(输入类型) #[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"]` 升级为: ```json [ { "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"]` → 自动转成默认配置列表。 ```rust fn deserialize_models<'de, D>(d: D) -> Result, 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::>() .pipe(Ok) } // 新格式:ModelConfig 数组 serde_json::Value::Array(_) => { serde_json::from_value::>(v).map_err(D::Error::custom) } _ => Ok(vec![]), } } ``` **否决方案**:新建 `ai_models` 独立表 — 增量复杂度高(迁移 + 新 Repo),`models` 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 多源探测 每个模型的信息从多个来源获取,交叉验证: ```rust 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 探测结果带置信度 ```rust pub struct ModelProfile { pub model_id: String, pub modalities: SourcedField>, pub capabilities: SourcedField>, pub cost_tier: SourcedField, pub intelligence: SourcedField, pub context_window: SourcedField, } pub struct SourcedField { pub value: T, pub source: Source, pub confidence: Confidence, } pub enum Source { PresetTable, Heuristic, UserSet } pub enum Confidence { High, Medium, Low } ``` ### 4.4 内置预设表 ```rust pub struct ModelPresetTable { exact: HashMap, // 精确匹配 patterns: Vec, // 模糊匹配 } // 精确匹配示例 "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 统一拉取接口 ```rust /// 模型拉取结果(统一格式) pub struct FetchedModel { pub model_id: String, pub display_name: Option, } /// 按 provider_type 分派 pub async fn fetch_models( provider_type: &str, base_url: &str, api_key: &str, ) -> Result, 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 智能拼接 ```rust 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 噪音过滤 ```rust 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 路由逻辑 ```rust pub struct TaskRequirements { pub modalities: Vec, // 任务需要什么模态 pub needs_tool_use: bool, // 是否需要工具调用 pub min_intelligence: IntelligenceTier, // 最低智力要求 pub max_cost: Option, // 成本上限(可选) 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 逻辑 | ### 阶段 5:7 调用点接入(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` | 新增 IPC:ai_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(纯设计)