diff --git a/crates/df-ai/src/lib.rs b/crates/df-ai/src/lib.rs index 81821c2..1d166ab 100644 --- a/crates/df-ai/src/lib.rs +++ b/crates/df-ai/src/lib.rs @@ -11,6 +11,9 @@ pub mod model_fetch; // crate 内复用,private mod — 外部经 model_fetch 间接访问,无直接路径需求。 mod model_fetch_helpers; pub mod model_probe; +// model_probe 的纯逻辑子模块(预设表加载 / 启发式推断 / 词素判定), +// crate 内复用,private mod — 外部经 model_probe 间接访问,无直接路径需求。 +mod model_probe_helpers; pub mod openai_compat; pub mod openai_helpers; pub mod provider; diff --git a/crates/df-ai/src/model_probe.rs b/crates/df-ai/src/model_probe.rs index 9a4705f..ed39182 100644 --- a/crates/df-ai/src/model_probe.rs +++ b/crates/df-ai/src/model_probe.rs @@ -12,28 +12,11 @@ //! (设计文档 §4.1 写的是"启发式先行 + 预设表合并";本实施按阶段 2 任务规格 //! 收敛为"预设优先 > 启发式"链 — 预设表精确数据可信度高于命名猜测,优先短路)。 -use std::sync::OnceLock; +use df_ai_core::model::{ModelConfig, ProbeSource}; -use df_ai_core::model::{ - Capability, CostTier, IntelligenceTier, Modality, ModelConfig, ProbeSource, -}; - -// ──────────────────────────────────────────────────────────── -// 预设表:编译期嵌入(include_str! 相对 crate 根),零运行时文件依赖 -// ──────────────────────────────────────────────────────────── - -/// 预设表原始 JSON(编译期从 `crates/df-ai/presets/models.json` 嵌入)。 -const PRESETS_JSON: &str = include_str!("../presets/models.json"); - -/// 解析后的预设表(进程内单例,首次访问惰性解析一次)。 -fn presets() -> &'static [ModelConfig] { - static PRESETS: OnceLock> = OnceLock::new(); - PRESETS.get_or_init(|| { - // include_str! 内容由仓库控制,解析失败属编译期/仓库错误,panic 合理。 - serde_json::from_str::>(PRESETS_JSON) - .expect("presets/models.json 解析失败 — 检查 JSON 格式与 ModelConfig serde 映射") - }) -} +// 纯逻辑子模块(预设表加载 / 启发式推断 / 词素判定)抽离至此 — +// private mod,crate 内复用,外部经 model_probe::probe 间接访问(对齐 model_fetch_helpers)。 +use crate::model_probe_helpers::{heuristic_infer, presets}; // ──────────────────────────────────────────────────────────── // 公共入口 @@ -79,95 +62,6 @@ pub fn probe(model_id: &str) -> ModelConfig { heuristic } -// ──────────────────────────────────────────────────────────── -// 启发式推断(模型名命名模式 → 4 维度) -// ──────────────────────────────────────────────────────────── - -/// 模型名启发式推断。仅推断功能性维度(modalities/capabilities), -/// cost_tier / intelligence 一律返中性默认(Medium / Standard)。 -/// -/// 取舍:B-260618-04 — 模型名关键词猜档位(flash→Lite、4o→High、pro→Plus)无依据, -/// 厂商定价/智力与命名无关,瞎填会污染路由器过滤(intelligence >= min / cost_tier <= max)。 -/// 改中性默认,真实档位由用户手填或更高阶探测源(如厂商 API/定价表)提供。 -/// -/// 规则(任务规格,设计文档 §4.4 模糊匹配规则对齐): -/// - `embed`/`embedding`/`e-`(前缀) → Embedding 模型:capabilities=[Embedding],modalities=[](去 Text) -/// - `v`/`vision`/`vl`(词素) → modalities 加 Vision -/// - `code`/`coder` → capabilities 加 CodeGen -/// - cost_tier / intelligence → 中性默认 Medium / Standard(不靠名字猜) -fn heuristic_infer(model_id: &str) -> ModelConfig { - let name = model_id.to_lowercase(); - let mut modalities: Vec = Vec::new(); - let mut capabilities: Vec = Vec::new(); - // 中性默认:不靠模型名猜档位(B-260618-04) - let cost_tier = CostTier::Medium; - let intelligence = IntelligenceTier::Standard; - - // —— Embedding 优先判定(改变模态集合,且通常独占) —— - // 规则:含 embed / embedding / 以 "e-" / "embedding-" 起首 - let is_embedding = name.contains("embed") - || name.contains("embedding") - || name.starts_with("e-") - || name.starts_with("embedding-") - || name.starts_with("text-embedding"); - - if is_embedding { - capabilities.push(Capability::Embedding); - // embedding 模型不走对话模态,modalities 留空(设计 §4.4 embedding-3 例:modalities=[]) - return ModelConfig { - model_id: model_id.to_string(), - enabled: true, - label: None, - modalities, - capabilities, - cost_tier, - intelligence, - weight: 50, - context_window: 8192, - probe_source: None, - }; - } - - // —— 对话模型默认 Text 模态 —— - modalities.push(Modality::Text); - - // —— Vision 词素:含 "v"(独立词素:glm-4v / qwen-vl)、"vision"、"vl" —— - // 注:裸 "v" 子串误伤大(如 "review"),故仅在边界词素命中: - // 以 "v" 结尾、含 "-v" / "v-" 分隔、含 "vl" / "vision" - if has_vision_token(&name) { - modalities.push(Modality::Vision); - } - - // —— CodeGen:含 code / coder —— - if name.contains("code") || name.contains("coder") { - capabilities.push(Capability::CodeGen); - } - - // —— 对话模型默认 ToolUse(主流支持 function calling) —— - capabilities.push(Capability::ToolUse); - - ModelConfig { - model_id: model_id.to_string(), - enabled: true, - label: None, - modalities, - capabilities, - cost_tier, - intelligence, - weight: 50, - context_window: 8192, - probe_source: None, - } -} - -// —— 词素判定助手(命名约定式启发式,集中维护便于增删) —— - -/// Vision 词素:`-v`(尾缀如 glm-4v)、以 `v` 结尾、含 `vl` / `vision`。 -/// 不直接 `contains('v')`(避免误伤 review/verbal 等)。 -fn has_vision_token(name: &str) -> bool { - name.contains("vision") || name.contains("vl") || name.ends_with('v') || name.contains("-v") -} - // ──────────────────────────────────────────────────────────── // 测试 // ──────────────────────────────────────────────────────────── diff --git a/crates/df-ai/src/model_probe_helpers.rs b/crates/df-ai/src/model_probe_helpers.rs new file mode 100644 index 0000000..d530086 --- /dev/null +++ b/crates/df-ai/src/model_probe_helpers.rs @@ -0,0 +1,119 @@ +//! 模型探测器 — 纯逻辑子模块(F-01 阶段2) +//! +//! 从 `model_probe.rs` 抽离的纯函数实现(预设表加载 / 启发式推断 / 词素判定)。 +//! 无 IO、无外部状态,crate 内经 `model_probe::probe` 间接复用 — +//! 对齐 `model_fetch_helpers` 拆分先例(private mod,外部零直接路径需求)。 +//! +//! 设计来源:docs/02-架构设计/F-01-模型能力系统与智能路由设计-2026-06-16.md §4 + +use std::sync::OnceLock; + +use df_ai_core::model::{ + Capability, CostTier, IntelligenceTier, Modality, ModelConfig, +}; + +// ──────────────────────────────────────────────────────────── +// 预设表:编译期嵌入(include_str! 相对 crate 根),零运行时文件依赖 +// ──────────────────────────────────────────────────────────── + +/// 预设表原始 JSON(编译期从 `crates/df-ai/presets/models.json` 嵌入)。 +pub(super) const PRESETS_JSON: &str = include_str!("../presets/models.json"); + +/// 解析后的预设表(进程内单例,首次访问惰性解析一次)。 +pub(super) fn presets() -> &'static [ModelConfig] { + static PRESETS: OnceLock> = OnceLock::new(); + PRESETS.get_or_init(|| { + // include_str! 内容由仓库控制,解析失败属编译期/仓库错误,panic 合理。 + serde_json::from_str::>(PRESETS_JSON) + .expect("presets/models.json 解析失败 — 检查 JSON 格式与 ModelConfig serde 映射") + }) +} + +// ──────────────────────────────────────────────────────────── +// 启发式推断(模型名命名模式 → 4 维度) +// ──────────────────────────────────────────────────────────── + +/// 模型名启发式推断。仅推断功能性维度(modalities/capabilities), +/// cost_tier / intelligence 一律返中性默认(Medium / Standard)。 +/// +/// 取舍:B-260618-04 — 模型名关键词猜档位(flash→Lite、4o→High、pro→Plus)无依据, +/// 厂商定价/智力与命名无关,瞎填会污染路由器过滤(intelligence >= min / cost_tier <= max)。 +/// 改中性默认,真实档位由用户手填或更高阶探测源(如厂商 API/定价表)提供。 +/// +/// 规则(任务规格,设计文档 §4.4 模糊匹配规则对齐): +/// - `embed`/`embedding`/`e-`(前缀) → Embedding 模型:capabilities=[Embedding],modalities=[](去 Text) +/// - `v`/`vision`/`vl`(词素) → modalities 加 Vision +/// - `code`/`coder` → capabilities 加 CodeGen +/// - cost_tier / intelligence → 中性默认 Medium / Standard(不靠名字猜) +pub(super) fn heuristic_infer(model_id: &str) -> ModelConfig { + let name = model_id.to_lowercase(); + let mut modalities: Vec = Vec::new(); + let mut capabilities: Vec = Vec::new(); + // 中性默认:不靠模型名猜档位(B-260618-04) + let cost_tier = CostTier::Medium; + let intelligence = IntelligenceTier::Standard; + + // —— Embedding 优先判定(改变模态集合,且通常独占) —— + // 规则:含 embed / embedding / 以 "e-" / "embedding-" 起首 + let is_embedding = name.contains("embed") + || name.contains("embedding") + || name.starts_with("e-") + || name.starts_with("embedding-") + || name.starts_with("text-embedding"); + + if is_embedding { + capabilities.push(Capability::Embedding); + // embedding 模型不走对话模态,modalities 留空(设计 §4.4 embedding-3 例:modalities=[]) + return ModelConfig { + model_id: model_id.to_string(), + enabled: true, + label: None, + modalities, + capabilities, + cost_tier, + intelligence, + weight: 50, + context_window: 8192, + probe_source: None, + }; + } + + // —— 对话模型默认 Text 模态 —— + modalities.push(Modality::Text); + + // —— Vision 词素:含 "v"(独立词素:glm-4v / qwen-vl)、"vision"、"vl" —— + // 注:裸 "v" 子串误伤大(如 "review"),故仅在边界词素命中: + // 以 "v" 结尾、含 "-v" / "v-" 分隔、含 "vl" / "vision" + if has_vision_token(&name) { + modalities.push(Modality::Vision); + } + + // —— CodeGen:含 code / coder —— + if name.contains("code") || name.contains("coder") { + capabilities.push(Capability::CodeGen); + } + + // —— 对话模型默认 ToolUse(主流支持 function calling) —— + capabilities.push(Capability::ToolUse); + + ModelConfig { + model_id: model_id.to_string(), + enabled: true, + label: None, + modalities, + capabilities, + cost_tier, + intelligence, + weight: 50, + context_window: 8192, + probe_source: None, + } +} + +// —— 词素判定助手(命名约定式启发式,集中维护便于增删) —— + +/// Vision 词素:`-v`(尾缀如 glm-4v)、以 `v` 结尾、含 `vl` / `vision`。 +/// 不直接 `contains('v')`(避免误伤 review/verbal 等)。 +pub(super) fn has_vision_token(name: &str) -> bool { + name.contains("vision") || name.contains("vl") || name.ends_with('v') || name.contains("-v") +}