squash合并: - 意图识别层论证(8维度+10业界佐证) - 多主题上下文管理愿景+并存论证+补充论证(多轮agentic) - 架构设计文档物理分类(四子目录+INDEX+命名规范+引用同步+边界清晰化) - 前端架构技术债清单归档
639 lines
24 KiB
Markdown
639 lines
24 KiB
Markdown
# 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<String>, // JSON array of model names(展示用,未消费)
|
||
pub config: Option<String>, // 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<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 枚举定义
|
||
|
||
```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<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` 独立表 — 增量复杂度高(迁移 + 新 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<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 内置预设表
|
||
|
||
```rust
|
||
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 统一拉取接口
|
||
|
||
```rust
|
||
/// 模型拉取结果(统一格式)
|
||
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 智能拼接
|
||
|
||
```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<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 逻辑 |
|
||
|
||
### 阶段 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(纯设计)
|