新增: F-01模型能力阶段1+2(ModelConfig数据模型+model_probe探测器+df-storage兼容V18)
This commit is contained in:
172
crates/df-ai/presets/models.json
Normal file
172
crates/df-ai/presets/models.json
Normal file
@@ -0,0 +1,172 @@
|
||||
[
|
||||
{
|
||||
"model_id": "glm-4",
|
||||
"enabled": true,
|
||||
"modalities": ["text"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "medium",
|
||||
"intelligence": "plus",
|
||||
"weight": 60,
|
||||
"context_window": 128000
|
||||
},
|
||||
{
|
||||
"model_id": "glm-4-air",
|
||||
"enabled": true,
|
||||
"modalities": ["text"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "standard",
|
||||
"weight": 80,
|
||||
"context_window": 128000
|
||||
},
|
||||
{
|
||||
"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-4-plus",
|
||||
"enabled": true,
|
||||
"modalities": ["text"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "high",
|
||||
"intelligence": "plus",
|
||||
"weight": 50,
|
||||
"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_id": "glm-4v-flash",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "lite",
|
||||
"weight": 85,
|
||||
"context_window": 128000
|
||||
},
|
||||
{
|
||||
"model_id": "gpt-4o",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "high",
|
||||
"intelligence": "ultra",
|
||||
"weight": 50,
|
||||
"context_window": 128000
|
||||
},
|
||||
{
|
||||
"model_id": "gpt-4o-mini",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "standard",
|
||||
"weight": 80,
|
||||
"context_window": 128000
|
||||
},
|
||||
{
|
||||
"model_id": "claude-3-5-sonnet-20241022",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "high",
|
||||
"intelligence": "ultra",
|
||||
"weight": 50,
|
||||
"context_window": 200000
|
||||
},
|
||||
{
|
||||
"model_id": "claude-3-opus-20240229",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "high",
|
||||
"intelligence": "ultra",
|
||||
"weight": 45,
|
||||
"context_window": 200000
|
||||
},
|
||||
{
|
||||
"model_id": "claude-3-haiku-20240307",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "standard",
|
||||
"weight": 80,
|
||||
"context_window": 200000
|
||||
},
|
||||
{
|
||||
"model_id": "gemini-1.5-pro",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "high",
|
||||
"intelligence": "plus",
|
||||
"weight": 55,
|
||||
"context_window": 1000000
|
||||
},
|
||||
{
|
||||
"model_id": "gemini-1.5-flash",
|
||||
"enabled": true,
|
||||
"modalities": ["text", "vision"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "lite",
|
||||
"weight": 85,
|
||||
"context_window": 1000000
|
||||
},
|
||||
{
|
||||
"model_id": "deepseek-chat",
|
||||
"enabled": true,
|
||||
"modalities": ["text"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "standard",
|
||||
"weight": 75,
|
||||
"context_window": 64000
|
||||
},
|
||||
{
|
||||
"model_id": "deepseek-coder",
|
||||
"enabled": true,
|
||||
"modalities": ["text"],
|
||||
"capabilities": ["tool_use", "code_gen"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "plus",
|
||||
"weight": 75,
|
||||
"context_window": 64000
|
||||
},
|
||||
{
|
||||
"model_id": "qwen-2.5",
|
||||
"enabled": true,
|
||||
"modalities": ["text"],
|
||||
"capabilities": ["tool_use"],
|
||||
"cost_tier": "medium",
|
||||
"intelligence": "standard",
|
||||
"weight": 65,
|
||||
"context_window": 128000
|
||||
},
|
||||
{
|
||||
"model_id": "embedding-3",
|
||||
"enabled": true,
|
||||
"modalities": [],
|
||||
"capabilities": ["embedding"],
|
||||
"cost_tier": "low",
|
||||
"intelligence": "lite",
|
||||
"weight": 50,
|
||||
"context_window": 8192
|
||||
}
|
||||
]
|
||||
@@ -4,6 +4,7 @@ pub mod ai_tools;
|
||||
pub mod anthropic_compat;
|
||||
pub mod context;
|
||||
pub mod coordinator;
|
||||
pub mod model_probe;
|
||||
pub mod openai_compat;
|
||||
pub mod provider;
|
||||
// CR-30-1: 流前重试退避对外复用。complete() 的 retry_with_backoff 仍 crate 内用,
|
||||
|
||||
434
crates/df-ai/src/model_probe.rs
Normal file
434
crates/df-ai/src/model_probe.rs
Normal file
@@ -0,0 +1,434 @@
|
||||
//! 模型探测器 — F-01 阶段2
|
||||
//!
|
||||
//! 给定模型名,产出完整 `ModelConfig`(4 维度 + 路由控制 + 探测来源标注)。
|
||||
//!
|
||||
//! 多源探测,高优先源命中即返(短路):
|
||||
//! 1. 内置预设表精确匹配(name 完全相等) → `ProbeSource::PresetTable`
|
||||
//! 2. 内置预设表模糊匹配(子串包含) → `ProbeSource::PresetTable`
|
||||
//! 3. 模型名启发式推断(命名模式) → `ProbeSource::Heuristic`
|
||||
//! 4. 默认值兜底(`ModelConfig::with_defaults`) → `ProbeSource::Default`
|
||||
//!
|
||||
//! 设计来源:docs/02-架构设计/F-01-模型能力系统与智能路由设计-2026-06-16.md §4
|
||||
//! (设计文档 §4.1 写的是"启发式先行 + 预设表合并";本实施按阶段 2 任务规格
|
||||
//! 收敛为"预设优先 > 启发式"链 — 预设表精确数据可信度高于命名猜测,优先短路)。
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
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<Vec<ModelConfig>> = OnceLock::new();
|
||||
PRESETS.get_or_init(|| {
|
||||
// include_str! 内容由仓库控制,解析失败属编译期/仓库错误,panic 合理。
|
||||
serde_json::from_str::<Vec<ModelConfig>>(PRESETS_JSON)
|
||||
.expect("presets/models.json 解析失败 — 检查 JSON 格式与 ModelConfig serde 映射")
|
||||
})
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// 公共入口
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
/// 探测单个模型,返回完整 `ModelConfig`(已填充 `probe_source`)。
|
||||
///
|
||||
/// 多源探测顺序(高优先源命中即返):
|
||||
/// 1. 预设表精确匹配(`model_id` 完全相等,大小写敏感)
|
||||
/// 2. 预设表模糊匹配(`model_id` 双向子串包含,大小写不敏感)
|
||||
/// 3. 启发式推断(模型名命名模式)
|
||||
/// 4. 默认值兜底
|
||||
///
|
||||
/// 返回的 `ModelConfig.probe_source` 标注实际命中来源。
|
||||
pub fn probe(model_id: &str) -> ModelConfig {
|
||||
// 1. 预设表精确匹配
|
||||
if let Some(mut hit) = presets().iter().find(|m| m.model_id == model_id).cloned() {
|
||||
hit.probe_source = Some(ProbeSource::PresetTable);
|
||||
return hit;
|
||||
}
|
||||
|
||||
// 2. 预设表模糊匹配(双向子串包含,大小写不敏感)
|
||||
// 多个候选命中时,选预设 model_id 最长者(最具体:glm-4v > glm-4)。
|
||||
let needle = model_id.to_lowercase();
|
||||
let fuzzy = presets()
|
||||
.iter()
|
||||
.filter(|m| {
|
||||
let cand = m.model_id.to_lowercase();
|
||||
!cand.is_empty() && (cand.contains(&needle) || needle.contains(&cand))
|
||||
})
|
||||
.max_by_key(|m| m.model_id.len());
|
||||
|
||||
if let Some(mut hit) = fuzzy.cloned() {
|
||||
// 模糊命中:保留预设维度,但用入参的实际模型名覆盖 model_id(避免回写错名)
|
||||
hit.model_id = model_id.to_string();
|
||||
hit.probe_source = Some(ProbeSource::PresetTable);
|
||||
return hit;
|
||||
}
|
||||
|
||||
// 3. 启发式推断
|
||||
let mut heuristic = heuristic_infer(model_id);
|
||||
heuristic.probe_source = Some(ProbeSource::Heuristic);
|
||||
heuristic
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// 启发式推断(模型名命名模式 → 4 维度)
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
/// 模型名启发式推断。命名是行业惯例(flash/mini/v/embed/code),可信度 Medium。
|
||||
///
|
||||
/// 规则(任务规格,设计文档 §4.4 模糊匹配规则对齐):
|
||||
/// - `embed`/`embedding`/`e-`(前缀) → Embedding 模型:capabilities=[Embedding],modalities=[](去 Text)
|
||||
/// - `v`/`vision`/`vl`(词素) → modalities 加 Vision
|
||||
/// - `flash`/`mini`/`lite`/`nano`/`air` → IntelligenceTier::Lite(air 归 Lite,对齐设计 §4.4 air→Standard 但与 Lite 规则并存时取 Lite;
|
||||
/// 此处 air 单独命中归 Standard — 见 issues)
|
||||
/// - `plus`/`pro`/`max`/`ultra` → IntelligenceTier::Plus/Ultra
|
||||
/// - `code`/`coder` → capabilities 加 CodeGen
|
||||
/// - `4o`/`4.5`/`5`(高价旗舰标识) → CostTier::High
|
||||
/// - 默认 → Standard / Medium / [Text] / [ToolUse]
|
||||
fn heuristic_infer(model_id: &str) -> ModelConfig {
|
||||
let name = model_id.to_lowercase();
|
||||
let mut modalities: Vec<Modality> = Vec::new();
|
||||
let mut capabilities: Vec<Capability> = Vec::new();
|
||||
let mut cost_tier = CostTier::Medium;
|
||||
let mut 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: CostTier::Low, // embedding 普遍低价
|
||||
intelligence: IntelligenceTier::Lite, // embedding 无智力概念,归 Lite
|
||||
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);
|
||||
|
||||
// —— 智力分级 ——
|
||||
if has_lite_token(&name) {
|
||||
intelligence = IntelligenceTier::Lite;
|
||||
} else if has_ultra_token(&name) {
|
||||
intelligence = IntelligenceTier::Ultra;
|
||||
} else if has_plus_token(&name) {
|
||||
intelligence = IntelligenceTier::Plus;
|
||||
} else if has_standard_token(&name) {
|
||||
intelligence = IntelligenceTier::Standard;
|
||||
}
|
||||
|
||||
// —— 价格分级 ——
|
||||
if has_high_cost_token(&name) {
|
||||
cost_tier = CostTier::High;
|
||||
} else if has_lite_token(&name) {
|
||||
cost_tier = CostTier::Low;
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/// Lite 智力词素:flash / mini / lite / nano / air(air 对齐设计 §4.4 模糊规则 Lite)。
|
||||
/// 注:设计 §4.4 文本规则列了 air,但 §4.4 精确例 glm-4-air→Standard。
|
||||
/// 本启发式取模糊规则(air→Lite)以保持一致 — 见 issues。
|
||||
fn has_lite_token(name: &str) -> bool {
|
||||
let toks = ["flash", "mini", "lite", "nano", "air", "haiku", "small"];
|
||||
toks.iter().any(|t| name.contains(t))
|
||||
}
|
||||
|
||||
/// Plus 智力词素:plus / pro / max / sonnet。
|
||||
fn has_plus_token(name: &str) -> bool {
|
||||
let toks = ["plus", "pro", "max", "sonnet"];
|
||||
toks.iter().any(|t| name.contains(t))
|
||||
}
|
||||
|
||||
/// Ultra 智力词素:ultra / opus / largest。
|
||||
fn has_ultra_token(name: &str) -> bool {
|
||||
let toks = ["ultra", "opus", "largest"];
|
||||
toks.iter().any(|t| name.contains(t))
|
||||
}
|
||||
|
||||
/// Standard 智力词素:standard / chat(对话主力模型默认归 Standard)。
|
||||
fn has_standard_token(name: &str) -> bool {
|
||||
let toks = ["standard", "chat", "haiku"];
|
||||
toks.iter().any(|t| name.contains(t))
|
||||
}
|
||||
|
||||
/// 高价旗舰词素:4o / 4.5 / 5 / opus / o1 / o3(OpenAI/Anthropic 旗舰命名)。
|
||||
fn has_high_cost_token(name: &str) -> bool {
|
||||
let toks = ["4o", "4.5", "opus", "o1", "o3"];
|
||||
// "5" 单字符误伤大(如 "5b"),仅匹配词边界 -5 / 5- 或以 5 结尾
|
||||
toks.iter().any(|t| name.contains(t))
|
||||
|| name.ends_with("-5")
|
||||
|| name.contains("-5-")
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// 测试
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use df_ai_core::model::{Capability, CostTier, IntelligenceTier, Modality, ProbeSource};
|
||||
|
||||
// ── 预设表加载 ──
|
||||
|
||||
#[test]
|
||||
fn presets_loaded_and_nonempty() {
|
||||
let p = presets();
|
||||
assert!(!p.is_empty(), "预设表应非空");
|
||||
// 确保每个预设都有 model_id
|
||||
assert!(p.iter().all(|m| !m.model_id.is_empty()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presets_contain_expected_models() {
|
||||
let p = presets();
|
||||
let ids: Vec<&str> = p.iter().map(|m| m.model_id.as_str()).collect();
|
||||
assert!(ids.contains(&"glm-4"), "glm-4 应在预设表: {ids:?}");
|
||||
assert!(ids.contains(&"glm-4-flash"));
|
||||
assert!(ids.contains(&"glm-4v"));
|
||||
assert!(ids.contains(&"gpt-4o"));
|
||||
assert!(ids.contains(&"claude-3-5-sonnet-20241022"));
|
||||
}
|
||||
|
||||
// ── 预设精确匹配 ──
|
||||
|
||||
#[test]
|
||||
fn probe_preset_exact_match_glm4() {
|
||||
let m = probe("glm-4");
|
||||
assert_eq!(m.model_id, "glm-4");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::PresetTable));
|
||||
assert_eq!(m.modalities, vec![Modality::Text]);
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Plus);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_preset_exact_match_glm4v_has_vision() {
|
||||
let m = probe("glm-4v");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::PresetTable));
|
||||
assert!(m.modalities.contains(&Modality::Vision));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_preset_exact_match_deepseek_coder_has_codegen() {
|
||||
let m = probe("deepseek-coder");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::PresetTable));
|
||||
assert!(m.capabilities.contains(&Capability::CodeGen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_preset_exact_match_embedding_no_text_modality() {
|
||||
let m = probe("embedding-3");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::PresetTable));
|
||||
assert!(m.capabilities.contains(&Capability::Embedding));
|
||||
assert!(
|
||||
!m.modalities.contains(&Modality::Text),
|
||||
"embedding 模型不应有 Text 模态"
|
||||
);
|
||||
}
|
||||
|
||||
// ── 预设模糊匹配 ──
|
||||
|
||||
#[test]
|
||||
fn probe_preset_fuzzy_match_glm4v_variant() {
|
||||
// "glm-4v-x" 不在预设表精确命中,但 "glm-4v" 是其子串 → 模糊命中
|
||||
let m = probe("glm-4v-x");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::PresetTable));
|
||||
assert_eq!(m.model_id, "glm-4v-x", "模糊命中后 model_id 应用入参名");
|
||||
assert!(
|
||||
m.modalities.contains(&Modality::Vision),
|
||||
"应继承 glm-4v 的 vision 模态"
|
||||
);
|
||||
}
|
||||
|
||||
// ── 启发式:Vision ──
|
||||
|
||||
#[test]
|
||||
fn heuristic_vision_from_v_suffix() {
|
||||
// glm-4v 不走启发式(精确命中预设);用未知名验证启发式
|
||||
let m = probe("custom-model-v");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert!(
|
||||
m.modalities.contains(&Modality::Vision),
|
||||
"v 后缀应推断 Vision"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_vision_from_vl_token() {
|
||||
let m = probe("qwen-vl-unknown");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert!(m.modalities.contains(&Modality::Vision));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_vision_from_vision_word() {
|
||||
let m = probe("some-vision-7b");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert!(m.modalities.contains(&Modality::Vision));
|
||||
}
|
||||
|
||||
// ── 启发式:Lite 智力 ──
|
||||
|
||||
#[test]
|
||||
fn heuristic_lite_from_flash() {
|
||||
let m = probe("unknown-flash");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Lite);
|
||||
assert_eq!(m.cost_tier, CostTier::Low);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_lite_from_mini() {
|
||||
let m = probe("test-mini-model");
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Lite);
|
||||
}
|
||||
|
||||
// ── 启发式:CodeGen ──
|
||||
|
||||
#[test]
|
||||
fn heuristic_codegen_from_code() {
|
||||
let m = probe("acme-code-7b");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert!(m.capabilities.contains(&Capability::CodeGen));
|
||||
// 对话模型仍应保留 ToolUse
|
||||
assert!(m.capabilities.contains(&Capability::ToolUse));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_codegen_from_coder() {
|
||||
let m = probe("acme-coder");
|
||||
assert!(m.capabilities.contains(&Capability::CodeGen));
|
||||
}
|
||||
|
||||
// ── 启发式:Embedding(特殊路径:无 Text 模态) ──
|
||||
|
||||
#[test]
|
||||
fn heuristic_embedding_no_text_modality() {
|
||||
let m = probe("acme-embedding-v3");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert!(m.capabilities.contains(&Capability::Embedding));
|
||||
assert!(!m.modalities.contains(&Modality::Text));
|
||||
assert!(!m.capabilities.contains(&Capability::ToolUse));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_embedding_from_text_embedding_prefix() {
|
||||
let m = probe("text-embedding-3-large");
|
||||
assert!(m.capabilities.contains(&Capability::Embedding));
|
||||
assert!(m.modalities.is_empty());
|
||||
}
|
||||
|
||||
// ── 启发式:Plus/Ultra 智力 + High 价格 ──
|
||||
|
||||
#[test]
|
||||
fn heuristic_plus_from_pro() {
|
||||
let m = probe("acme-pro");
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Plus);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_ultra_from_opus() {
|
||||
let m = probe("acme-opus");
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Ultra);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heuristic_high_cost_from_4o() {
|
||||
let m = probe("acme-4o");
|
||||
assert_eq!(m.cost_tier, CostTier::High);
|
||||
}
|
||||
|
||||
// ── 启发式:默认(Standard/Medium/Text/ToolUse) ──
|
||||
|
||||
#[test]
|
||||
fn heuristic_default_for_unknown_name() {
|
||||
let m = probe("acme-unknown-model");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::Heuristic));
|
||||
assert_eq!(m.modalities, vec![Modality::Text]);
|
||||
assert_eq!(m.capabilities, vec![Capability::ToolUse]);
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Standard);
|
||||
assert_eq!(m.cost_tier, CostTier::Medium);
|
||||
}
|
||||
|
||||
// ── 优先级链:预设精确 > 启发式 ──
|
||||
|
||||
#[test]
|
||||
fn probe_preset_beats_heuristic() {
|
||||
// "glm-4-flash" 在预设表 = Lite;若走启发式也会是 Lite,但 source 应为 PresetTable
|
||||
let m = probe("glm-4-flash");
|
||||
assert_eq!(m.probe_source, Some(ProbeSource::PresetTable));
|
||||
assert_eq!(m.intelligence, IntelligenceTier::Lite);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_priority_chain_returns_config() {
|
||||
// 任何名字都应返回完整 ModelConfig,不 panic
|
||||
for name in ["glm-4", "unknown-xyz", "qwen-vl", "acme-embedding", "gpt-5"] {
|
||||
let m = probe(name);
|
||||
assert_eq!(m.model_id, name);
|
||||
assert!(
|
||||
m.probe_source.is_some(),
|
||||
"probe_source 应被填充: {name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user