From 3a30150a411183292df2fdae28627b14b57a0baa Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Thu, 16 Apr 2026 22:47:24 +0800 Subject: [PATCH] refactor(service): remove all hardcoded model-name inference from OpenRouter sync Drop all hard-coded model-name lookup tables that hardcoded specific model names and prices: - infer_context_length: remove GPT-4o/o1/Claude/etc. fallback table - infer_max_output: remove GPT-4o/o1/etc. output token limits - infer_pricing_fallback: remove entire hardcoded pricing table - infer_capability_list: derive from architecture.modality only, no longer uses model name strings Also fix stats: if upsert_version fails, skip counting and continue rather than counting model but not version (which caused versions_created=0 while pricing_created>0 inconsistency). --- libs/service/agent/sync.rs | 233 +++++++++++-------------------------- 1 file changed, 68 insertions(+), 165 deletions(-) diff --git a/libs/service/agent/sync.rs b/libs/service/agent/sync.rs index c803fe2..3e743f7 100644 --- a/libs/service/agent/sync.rs +++ b/libs/service/agent/sync.rs @@ -157,131 +157,32 @@ fn infer_capability(name: &str) -> ModelCapability { } } -fn infer_context_length(name: &str) -> i64 { - let lower = name.to_lowercase(); - // Hard-coded fallback table for known models - let fallbacks: &[(&str, i64)] = &[ - ("gpt-4o", 128_000), - ("chatgpt-4o", 128_000), - ("o1-preview", 128_000), - ("o1-mini", 65_536), - ("o1", 65_536), - ("o3-mini", 65_536), - ("gpt-4-turbo", 128_000), - ("gpt-4-32k", 32_768), - ("gpt-4", 8_192), - ("gpt-4o-mini", 128_000), - ("chatgpt-4o-mini", 128_000), - ("gpt-3.5-turbo-16k", 16_384), - ("gpt-3.5-turbo", 16_385), - ("text-embedding-3-large", 8_191), - ("text-embedding-3-small", 8_191), - ("text-embedding-ada", 8_191), - ("dall-e", 4_096), - ("whisper", 30_000), - ("gpt-image-1", 16_384), - ]; - for (prefix, ctx) in fallbacks { - if lower.starts_with(prefix) { - return *ctx; - } - } - 8_192 +fn infer_context_length(ctx: Option) -> i64 { + ctx.map(|c| c as i64).unwrap_or(8_192) } -fn infer_max_output(name: &str, top_provider_max: Option) -> Option { - if let Some(v) = top_provider_max { - return Some(v as i64); - } - let lower = name.to_lowercase(); - let fallbacks: &[(&str, i64)] = &[ - ("gpt-4o", 16_384), - ("chatgpt-4o", 16_384), - ("o1-preview", 32_768), - ("o1-mini", 65_536), - ("o1", 100_000), - ("o3-mini", 100_000), - ("gpt-4-turbo", 4_096), - ("gpt-4-32k", 32_768), - ("gpt-4", 8_192), - ("gpt-4o-mini", 16_384), - ("chatgpt-4o-mini", 16_384), - ("gpt-3.5-turbo", 4_096), - ("gpt-image-1", 1_024), - ]; - for (prefix, max) in fallbacks { - if lower.starts_with(prefix) { - return Some(*max); - } - } - if lower.starts_with("gpt") || lower.starts_with("o1") || lower.starts_with("o3") { - Some(4_096) - } else { - None - } +fn infer_max_output(top_provider_max: Option) -> Option { + top_provider_max.map(|v| v as i64) } -fn infer_capability_list(name: &str) -> Vec<(CapabilityType, bool)> { - let lower = name.to_lowercase(); - let mut caps = Vec::new(); - caps.push((CapabilityType::FunctionCall, true)); +fn infer_capability_list(arch: &OpenRouterArchitecture) -> Vec<(CapabilityType, bool)> { + // Derive capabilities purely from OpenRouter architecture data. + // FunctionCall is a safe baseline for chat models. + let mut caps = vec![(CapabilityType::FunctionCall, true)]; - if lower.contains("gpt-") || lower.contains("o1") || lower.contains("o3") { - caps.push((CapabilityType::ToolUse, true)); + // Vision capability from modality. + if let Some(m) = &arch.modality { + let m = m.to_lowercase(); + if m.contains("image") || m.contains("vision") { + caps.push((CapabilityType::Vision, true)); + } + if m.contains("text") || m.contains("chat") { + caps.push((CapabilityType::ToolUse, true)); + } } - - if lower.contains("vision") - || lower.contains("gpt-4o") - || lower.contains("gpt-image") - || lower.contains("dall-e") - { - caps.push((CapabilityType::Vision, true)); - } - - if lower.contains("o1") || lower.contains("o3") { - caps.push((CapabilityType::Reasoning, true)); - } - caps } -fn infer_pricing_fallback(name: &str) -> Option<(String, String)> { - let lower = name.to_lowercase(); - if lower.contains("gpt-4o-mini") || lower.contains("chatgpt-4o-mini") { - Some(("0.075".to_string(), "0.30".to_string())) - } else if lower.contains("gpt-4o") || lower.contains("chatgpt-4o") { - Some(("2.50".to_string(), "10.00".to_string())) - } else if lower.contains("gpt-4-turbo") { - Some(("10.00".to_string(), "30.00".to_string())) - } else if lower.contains("gpt-4") && !lower.contains("4o") { - Some(("15.00".to_string(), "60.00".to_string())) - } else if lower.contains("gpt-3.5-turbo") { - Some(("0.50".to_string(), "1.50".to_string())) - } else if lower.contains("o1-preview") { - Some(("15.00".to_string(), "60.00".to_string())) - } else if lower.contains("o1-mini") { - Some(("3.00".to_string(), "12.00".to_string())) - } else if lower.contains("o1") { - Some(("15.00".to_string(), "60.00".to_string())) - } else if lower.contains("o3-mini") { - Some(("1.50".to_string(), "6.00".to_string())) - } else if lower.contains("embedding-3-small") { - Some(("0.02".to_string(), "0.00".to_string())) - } else if lower.contains("embedding-3-large") { - Some(("0.13".to_string(), "0.00".to_string())) - } else if lower.contains("embedding-ada") { - Some(("0.10".to_string(), "0.00".to_string())) - } else if lower.contains("embedding") { - Some(("0.10".to_string(), "0.00".to_string())) - } else if lower.contains("dall-e") { - Some(("0.00".to_string(), "4.00".to_string())) - } else if lower.contains("whisper") { - Some(("0.00".to_string(), "0.006".to_string())) - } else { - None - } -} - // Provider helpers ----------------------------------------------------------- /// Extract provider slug from OpenRouter model ID (e.g. "anthropic/claude-3.5-sonnet" → "anthropic"). @@ -372,13 +273,10 @@ async fn upsert_model( let capability = infer_capability(model_id_str); // OpenRouter context_length takes priority; fall back to inference - let context_length = or_model - .context_length - .map(|c| c as i64) - .unwrap_or_else(|| infer_context_length(model_id_str)); + let context_length = infer_context_length(or_model.context_length); let max_output = - infer_max_output(model_id_str, or_model.top_provider.as_ref().and_then(|p| p.max_completion_tokens)); + infer_max_output(or_model.top_provider.as_ref().and_then(|p| p.max_completion_tokens)); use models::agents::model::Column as MCol; if let Some(existing) = ModelEntity::find() @@ -450,7 +348,6 @@ async fn upsert_pricing( db: &AppDatabase, version_uuid: Uuid, pricing: Option<&OpenRouterPricing>, - model_name: &str, ) -> Result { use models::agents::model_pricing::Column as PCol; let existing = PricingEntity::find() @@ -461,11 +358,9 @@ async fn upsert_pricing( return Ok(false); } + // OpenRouter prices are per-million-tokens strings; if missing, insert zero prices. let (input_str, output_str) = if let Some(p) = pricing { - // OpenRouter prices are per-million-tokens strings (p.prompt.clone(), p.completion.clone()) - } else if let Some((i, o)) = infer_pricing_fallback(model_name) { - (i, o) } else { ("0.00".to_string(), "0.00".to_string()) }; @@ -486,10 +381,16 @@ async fn upsert_pricing( async fn upsert_capabilities( db: &AppDatabase, version_uuid: Uuid, - model_name: &str, + arch: Option<&OpenRouterArchitecture>, ) -> Result { use models::agents::model_capability::Column as CCol; - let caps = infer_capability_list(model_name); + let caps = infer_capability_list(arch.unwrap_or(&OpenRouterArchitecture { + modality: None, + input_modalities: None, + output_modalities: None, + tokenizer: None, + instruct_type: None, + })); let now = Utc::now(); let mut created = 0i64; @@ -559,8 +460,7 @@ impl AppService { /// parameter-profile records. /// /// OpenRouter returns `context_length`, `pricing`, and `architecture.modality` - /// per model — these drive all inference-free field population. - /// Capabilities are still inferred from model name patterns. + /// per model — these drive all field population. No model names are hardcoded. pub async fn sync_upstream_models( &self, _ctx: &Session, @@ -603,26 +503,29 @@ impl AppService { } let (version_record, version_is_new) = - upsert_version(&self.db, model_record.id).await?; + match upsert_version(&self.db, model_record.id).await { + Ok(v) => v, + Err(e) => { + tracing::warn!("sync_upstream_models: upsert_version error: {:?}", e); + continue; + } + }; if version_is_new { versions_created += 1; } - if upsert_pricing( - &self.db, - version_record.id, - or_model.pricing.as_ref(), - &or_model.id, - ) - .await? - { + if let Err(e) = upsert_pricing(&self.db, version_record.id, or_model.pricing.as_ref()).await { + tracing::warn!("sync_upstream_models: upsert_pricing error: {:?}", e); + } else { pricing_created += 1; } capabilities_created += - upsert_capabilities(&self.db, version_record.id, &or_model.id).await?; + upsert_capabilities(&self.db, version_record.id, or_model.architecture.as_ref()) + .await + .unwrap_or(0); - if upsert_parameter_profile(&self.db, version_record.id, &or_model.id).await? { + if upsert_parameter_profile(&self.db, version_record.id, &or_model.id).await.unwrap_or(false) { profiles_created += 1; } } @@ -720,34 +623,34 @@ impl AppService { } }; - match upsert_version(db, model_record.id).await { - Ok((version_record, version_is_new)) => { - if version_is_new { - versions_created += 1; - } - - if upsert_pricing(db, version_record.id, or_model.pricing.as_ref(), &or_model.id) - .await - .unwrap_or(false) - { - pricing_created += 1; - } - - capabilities_created += - upsert_capabilities(db, version_record.id, &or_model.id) - .await - .unwrap_or(0); - - if upsert_parameter_profile(db, version_record.id, &or_model.id) - .await - .unwrap_or(false) - { - profiles_created += 1; - } - } + let (version_record, version_is_new) = match upsert_version(db, model_record.id).await { + Ok(v) => v, Err(e) => { tracing::warn!("OpenRouter model sync: upsert_version error: {:?}", e); + continue; } + }; + if version_is_new { + versions_created += 1; + } + + if upsert_pricing(db, version_record.id, or_model.pricing.as_ref()) + .await + .unwrap_or(false) + { + pricing_created += 1; + } + + capabilities_created += + upsert_capabilities(db, version_record.id, or_model.architecture.as_ref()) + .await + .unwrap_or(0); + + if upsert_parameter_profile(db, version_record.id, &or_model.id) + .await + .unwrap_or(false) + { + profiles_created += 1; } }