Changeset 0.9.1 (#51)
This commit is contained in:
@@ -8,185 +8,23 @@ import (
|
||||
)
|
||||
|
||||
// ── Known Model Defaults ────────────────────
|
||||
// Authoritative capabilities for models where the provider API
|
||||
// doesn't report them. Keyed by exact model ID or prefix.
|
||||
//
|
||||
// Sources:
|
||||
// Anthropic: https://docs.anthropic.com/en/docs/about-claude/models
|
||||
// OpenAI: https://platform.openai.com/docs/models
|
||||
// Meta: Model cards on Hugging Face
|
||||
// Google: https://ai.google.dev/gemini-api/docs/models
|
||||
// REMOVED in 0.9.1: The static known model table was removed because the same
|
||||
// model ID can have different capabilities depending on the provider hosting it
|
||||
// (e.g. DeepSeek on Venice has no tool_calling, same model on OpenRouter does).
|
||||
//
|
||||
// Capability resolution chain:
|
||||
// 1. Catalog (provider API sync) — authoritative per-provider
|
||||
// 2. Heuristic inference (regex on model ID) — best effort
|
||||
// 3. Admin/user override — manual correction (TODO: 0.9.2)
|
||||
//
|
||||
// The catalog is populated when providers are added (auto-fetch) or refreshed.
|
||||
// Heuristics cover broad model families (llama→tools, gemini→vision, etc.)
|
||||
// until catalog data is available.
|
||||
|
||||
var knownModels = map[string]models.ModelCapabilities{
|
||||
// ── Anthropic ────────────────────────────
|
||||
"claude-opus-4": {
|
||||
Streaming: true, ToolCalling: true, Vision: true, Thinking: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 32000,
|
||||
},
|
||||
"claude-sonnet-4": {
|
||||
Streaming: true, ToolCalling: true, Vision: true, Thinking: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 16000,
|
||||
},
|
||||
"claude-3-5-sonnet": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 8192,
|
||||
},
|
||||
"claude-3-5-haiku": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 8192,
|
||||
},
|
||||
"claude-3-opus": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 4096,
|
||||
},
|
||||
"claude-3-haiku": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 4096,
|
||||
},
|
||||
|
||||
// ── OpenAI ──────────────────────────────
|
||||
"gpt-4o": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 128000, MaxOutputTokens: 16384,
|
||||
},
|
||||
"gpt-4o-mini": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 128000, MaxOutputTokens: 16384,
|
||||
},
|
||||
"gpt-4-turbo": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 128000, MaxOutputTokens: 4096,
|
||||
},
|
||||
"gpt-4": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 8192, MaxOutputTokens: 4096,
|
||||
},
|
||||
"o1": {
|
||||
Streaming: true, Reasoning: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 100000,
|
||||
},
|
||||
"o1-mini": {
|
||||
Streaming: true, Reasoning: true,
|
||||
MaxContext: 128000, MaxOutputTokens: 65536,
|
||||
},
|
||||
"o3": {
|
||||
Streaming: true, ToolCalling: true, Reasoning: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 100000,
|
||||
},
|
||||
"o3-mini": {
|
||||
Streaming: true, ToolCalling: true, Reasoning: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 65536,
|
||||
},
|
||||
"o4-mini": {
|
||||
Streaming: true, ToolCalling: true, Reasoning: true,
|
||||
MaxContext: 200000, MaxOutputTokens: 100000,
|
||||
},
|
||||
|
||||
// ── Meta Llama ──────────────────────────
|
||||
"llama-3.1-405b": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 4096,
|
||||
},
|
||||
"llama-3.1-70b": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 4096,
|
||||
},
|
||||
"llama-3.1-8b": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 4096,
|
||||
},
|
||||
"llama-3.3-70b": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 4096,
|
||||
},
|
||||
"llama-4-maverick": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 1048576, MaxOutputTokens: 16384,
|
||||
},
|
||||
"llama-4-scout": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 524288, MaxOutputTokens: 16384,
|
||||
},
|
||||
|
||||
// ── Google Gemini ───────────────────────
|
||||
"gemini-2.5-pro": {
|
||||
Streaming: true, ToolCalling: true, Vision: true, Thinking: true,
|
||||
MaxContext: 1048576, MaxOutputTokens: 65536,
|
||||
},
|
||||
"gemini-2.5-flash": {
|
||||
Streaming: true, ToolCalling: true, Vision: true, Thinking: true,
|
||||
MaxContext: 1048576, MaxOutputTokens: 65536,
|
||||
},
|
||||
"gemini-2.0-flash": {
|
||||
Streaming: true, ToolCalling: true, Vision: true,
|
||||
MaxContext: 1048576, MaxOutputTokens: 8192,
|
||||
},
|
||||
|
||||
// ── DeepSeek ────────────────────────────
|
||||
"deepseek-r1": {
|
||||
Streaming: true, Reasoning: true,
|
||||
MaxContext: 65536, MaxOutputTokens: 8192,
|
||||
},
|
||||
"deepseek-v3": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 65536, MaxOutputTokens: 8192,
|
||||
},
|
||||
"deepseek-chat": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 65536, MaxOutputTokens: 8192,
|
||||
},
|
||||
|
||||
// ── Mistral ─────────────────────────────
|
||||
"mistral-large": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 8192,
|
||||
},
|
||||
"mistral-small": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 8192,
|
||||
},
|
||||
"codestral": {
|
||||
Streaming: true, ToolCalling: true, CodeOptimized: true,
|
||||
MaxContext: 262144, MaxOutputTokens: 8192,
|
||||
},
|
||||
|
||||
// ── Qwen ────────────────────────────────
|
||||
"qwen-2.5-72b": {
|
||||
Streaming: true, ToolCalling: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 8192,
|
||||
},
|
||||
"qwen-2.5-coder-32b": {
|
||||
Streaming: true, ToolCalling: true, CodeOptimized: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 8192,
|
||||
},
|
||||
"qwq-32b": {
|
||||
Streaming: true, Reasoning: true,
|
||||
MaxContext: 131072, MaxOutputTokens: 8192,
|
||||
},
|
||||
}
|
||||
|
||||
// LookupKnownModel finds capabilities for a model by exact ID or prefix match.
|
||||
// LookupKnownModel always returns false — kept for interface compatibility.
|
||||
// Callers fall through to heuristic inference.
|
||||
func LookupKnownModel(modelID string) (models.ModelCapabilities, bool) {
|
||||
id := normalizeModelID(modelID)
|
||||
|
||||
// Exact match first
|
||||
if caps, ok := knownModels[id]; ok {
|
||||
return caps, true
|
||||
}
|
||||
|
||||
// Prefix match: "claude-sonnet-4-20250514" matches "claude-sonnet-4"
|
||||
var bestKey string
|
||||
var bestCaps models.ModelCapabilities
|
||||
for key, caps := range knownModels {
|
||||
if strings.HasPrefix(id, key) && len(key) > len(bestKey) {
|
||||
bestKey = key
|
||||
bestCaps = caps
|
||||
}
|
||||
}
|
||||
if bestKey != "" {
|
||||
return bestCaps, true
|
||||
}
|
||||
|
||||
return models.ModelCapabilities{}, false
|
||||
}
|
||||
|
||||
@@ -197,17 +35,21 @@ var (
|
||||
regexp.MustCompile(`(?i)llama[_-]?[34]`),
|
||||
regexp.MustCompile(`(?i)granite[_-]?[34]`),
|
||||
regexp.MustCompile(`(?i)hermes[_-]?[23]?`),
|
||||
regexp.MustCompile(`(?i)qwen[_-]?2`),
|
||||
regexp.MustCompile(`(?i)qwen[_-]?[23]`),
|
||||
regexp.MustCompile(`(?i)mistral|mixtral`),
|
||||
regexp.MustCompile(`(?i)command[_-]?r`),
|
||||
regexp.MustCompile(`(?i)phi[_-]?[34]`),
|
||||
regexp.MustCompile(`(?i)deepseek[_-]?v[23]`),
|
||||
regexp.MustCompile(`(?i)functionary`),
|
||||
regexp.MustCompile(`(?i)^gpt[_-]?[34]`),
|
||||
regexp.MustCompile(`(?i)^gpt[_-]?[345]`),
|
||||
regexp.MustCompile(`(?i)^openai[_-]gpt`),
|
||||
regexp.MustCompile(`(?i)^claude`),
|
||||
regexp.MustCompile(`(?i)gemma[_-]?2`),
|
||||
regexp.MustCompile(`(?i)glm[_-]?4`),
|
||||
regexp.MustCompile(`(?i)gemma[_-]?[23]`),
|
||||
regexp.MustCompile(`(?i)glm[_-]?[45]`),
|
||||
regexp.MustCompile(`(?i)gemini`),
|
||||
regexp.MustCompile(`(?i)grok`),
|
||||
regexp.MustCompile(`(?i)kimi`),
|
||||
regexp.MustCompile(`(?i)minimax`),
|
||||
}
|
||||
|
||||
visionPatterns = []*regexp.Regexp{
|
||||
@@ -216,18 +58,24 @@ var (
|
||||
regexp.MustCompile(`(?i)llama.*vision|vision.*llama`),
|
||||
regexp.MustCompile(`(?i)minicpm[_-]?v`),
|
||||
regexp.MustCompile(`(?i)^gpt[_-]?4[_-]?o|^gpt[_-]?4[_-]?turbo`),
|
||||
regexp.MustCompile(`(?i)^claude[_-]?3`),
|
||||
regexp.MustCompile(`(?i)^claude[_-]?(3|opus|sonnet)`),
|
||||
regexp.MustCompile(`(?i)gemini`),
|
||||
regexp.MustCompile(`(?i)qwen.*vl|vl.*qwen`),
|
||||
regexp.MustCompile(`(?i)phi[_-]?[34].*vision`),
|
||||
regexp.MustCompile(`(?i)internvl`),
|
||||
regexp.MustCompile(`(?i)glm[_-]?4v`),
|
||||
regexp.MustCompile(`(?i)gemma[_-]?3`),
|
||||
regexp.MustCompile(`(?i)grok[_-]?4`),
|
||||
regexp.MustCompile(`(?i)mistral.*24b`),
|
||||
}
|
||||
|
||||
reasoningPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?i)deepseek[_-]?r1`),
|
||||
regexp.MustCompile(`(?i)qwq`),
|
||||
regexp.MustCompile(`(?i)^o[1234][_-]|^o[1234]$`),
|
||||
regexp.MustCompile(`(?i)thinking`),
|
||||
regexp.MustCompile(`(?i)grok`),
|
||||
regexp.MustCompile(`(?i)glm[_-]?[45]`),
|
||||
}
|
||||
|
||||
codePatterns = []*regexp.Regexp{
|
||||
@@ -250,7 +98,7 @@ func matchesAny(id string, patterns []*regexp.Regexp) bool {
|
||||
}
|
||||
|
||||
// InferCapabilities guesses model capabilities from the model ID string.
|
||||
// Fallback when neither the known model table nor the provider API has data.
|
||||
// Best-effort fallback when the provider API hasn't reported capabilities.
|
||||
func InferCapabilities(modelID string) models.ModelCapabilities {
|
||||
id := normalizeModelID(modelID)
|
||||
return models.ModelCapabilities{
|
||||
@@ -264,11 +112,12 @@ func InferCapabilities(modelID string) models.ModelCapabilities {
|
||||
|
||||
// ResolveIntrinsic determines the intrinsic capabilities of a model.
|
||||
// Priority:
|
||||
// 1. catalogCaps (from model_catalog DB — provider API data)
|
||||
// 2. Known model table (static, compiled-in)
|
||||
// 3. Heuristic inference (regex patterns)
|
||||
// 1. catalogCaps (from model_catalog DB — synced from provider API)
|
||||
// 2. Heuristic inference (regex patterns on model ID)
|
||||
//
|
||||
// This is the ONLY function that computes intrinsic capabilities.
|
||||
// The catalog is the source of truth per provider. Heuristics fill gaps
|
||||
// for models that haven't been synced yet. No hardcoded model table —
|
||||
// the same model can have different capabilities on different providers.
|
||||
func ResolveIntrinsic(modelID string, catalogCaps *models.ModelCapabilities) models.ModelCapabilities {
|
||||
// Start with catalog data if available
|
||||
var base models.ModelCapabilities
|
||||
@@ -276,12 +125,6 @@ func ResolveIntrinsic(modelID string, catalogCaps *models.ModelCapabilities) mod
|
||||
base = *catalogCaps
|
||||
}
|
||||
|
||||
// Fill gaps from known model table
|
||||
if known, found := LookupKnownModel(modelID); found {
|
||||
mergeGaps(&base, &known)
|
||||
return base
|
||||
}
|
||||
|
||||
// Fill gaps from heuristic inference
|
||||
inferred := InferCapabilities(modelID)
|
||||
mergeGaps(&base, &inferred)
|
||||
@@ -289,14 +132,11 @@ func ResolveIntrinsic(modelID string, catalogCaps *models.ModelCapabilities) mod
|
||||
}
|
||||
|
||||
// ResolveMaxOutput returns the max output tokens for a model.
|
||||
// Priority: explicit caps → known model table → derive from context → 4096 fallback.
|
||||
// Priority: explicit caps → derive from context → 4096 fallback.
|
||||
func ResolveMaxOutput(modelID string, caps models.ModelCapabilities) int {
|
||||
if caps.MaxOutputTokens > 0 {
|
||||
return caps.MaxOutputTokens
|
||||
}
|
||||
if known, ok := LookupKnownModel(modelID); ok && known.MaxOutputTokens > 0 {
|
||||
return known.MaxOutputTokens
|
||||
}
|
||||
if caps.MaxContext > 0 {
|
||||
derived := caps.MaxContext / 8
|
||||
if derived < 2048 {
|
||||
|
||||
Reference in New Issue
Block a user