This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/capabilities/intrinsic.go
2026-03-10 13:38:01 +00:00

241 lines
7.4 KiB
Go

package capabilities
import (
"regexp"
"strings"
"git.gobha.me/xcaliber/chat-switchboard/models"
)
// ── Known Model Defaults ────────────────────
//
// 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 override — manual correction (shipped v0.22.0)
//
// 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.
// LookupKnownModel always returns false — kept for interface compatibility.
// Callers fall through to heuristic inference.
func LookupKnownModel(modelID string) (models.ModelCapabilities, bool) {
return models.ModelCapabilities{}, false
}
// ── Heuristic Capability Detection ──────────
var (
toolPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)llama[_-]?[34]`),
regexp.MustCompile(`(?i)granite[_-]?[34]`),
regexp.MustCompile(`(?i)hermes[_-]?[23]?`),
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[_-]?[345]`),
regexp.MustCompile(`(?i)^openai[_-]gpt`),
regexp.MustCompile(`(?i)^claude`),
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{
regexp.MustCompile(`(?i)llava|bakllava`),
regexp.MustCompile(`(?i)moondream`),
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|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{
regexp.MustCompile(`(?i)codellama|code[_-]?llama`),
regexp.MustCompile(`(?i)deepseek[_-]?coder`),
regexp.MustCompile(`(?i)starcoder`),
regexp.MustCompile(`(?i)coder|codestral`),
regexp.MustCompile(`(?i)wizardcoder`),
regexp.MustCompile(`(?i)codegemma`),
}
)
func matchesAny(id string, patterns []*regexp.Regexp) bool {
for _, p := range patterns {
if p.MatchString(id) {
return true
}
}
return false
}
// InferCapabilities guesses model capabilities from the model ID string.
// Best-effort fallback when the provider API hasn't reported capabilities.
func InferCapabilities(modelID string) models.ModelCapabilities {
id := normalizeModelID(modelID)
return models.ModelCapabilities{
Streaming: true, // virtually everything streams
ToolCalling: matchesAny(id, toolPatterns),
Vision: matchesAny(id, visionPatterns),
Reasoning: matchesAny(id, reasoningPatterns),
CodeOptimized: matchesAny(id, codePatterns),
}
}
// ResolveIntrinsic determines the intrinsic capabilities of a model.
// Priority (highest wins):
// 1. Admin overrides (manual corrections from capability_overrides table)
// 2. Catalog (from model_catalog DB — synced from provider API)
// 3. Heuristic inference (regex patterns on model ID)
//
// The catalog is the source of truth per provider. Heuristics fill gaps
// for models that haven't been synced yet. Admin overrides correct
// provider-reported or heuristic errors. No hardcoded model table —
// the same model can have different capabilities on different providers.
func ResolveIntrinsic(modelID string, catalogCaps *models.ModelCapabilities, overrides []models.CapabilityOverride) models.ModelCapabilities {
// Start with catalog data if available
var base models.ModelCapabilities
if catalogCaps != nil && catalogCaps.HasProviderData() {
base = *catalogCaps
}
// Fill gaps from heuristic inference
inferred := InferCapabilities(modelID)
mergeGaps(&base, &inferred)
// Apply admin overrides (highest priority — can flip any field)
applyOverrides(&base, overrides)
return base
}
// applyOverrides applies admin capability corrections to the resolved capabilities.
func applyOverrides(caps *models.ModelCapabilities, overrides []models.CapabilityOverride) {
for _, o := range overrides {
boolVal := o.Value == "true" || o.Value == "1"
switch o.Field {
case "streaming":
caps.Streaming = boolVal
case "tool_calling":
caps.ToolCalling = boolVal
case "vision":
caps.Vision = boolVal
case "thinking":
caps.Thinking = boolVal
case "reasoning":
caps.Reasoning = boolVal
case "code_optimized":
caps.CodeOptimized = boolVal
case "web_search":
caps.WebSearch = boolVal
case "max_context":
if n := parseInt(o.Value); n > 0 {
caps.MaxContext = n
}
case "max_output_tokens":
if n := parseInt(o.Value); n > 0 {
caps.MaxOutputTokens = n
}
}
}
}
// parseInt parses a string to int, returning 0 on failure.
func parseInt(s string) int {
n := 0
for _, c := range s {
if c < '0' || c > '9' {
return 0
}
n = n*10 + int(c-'0')
}
return n
}
// ResolveMaxOutput returns the max output tokens for a model.
// Priority: explicit caps → derive from context → 4096 fallback.
func ResolveMaxOutput(modelID string, caps models.ModelCapabilities) int {
if caps.MaxOutputTokens > 0 {
return caps.MaxOutputTokens
}
if caps.MaxContext > 0 {
derived := caps.MaxContext / 8
if derived < 2048 {
derived = 2048
}
if derived > 16384 {
derived = 16384
}
return derived
}
return 4096
}
// mergeGaps fills zero/false fields in dst from src. Never overrides existing data.
func mergeGaps(dst, src *models.ModelCapabilities) {
if !dst.Streaming && src.Streaming {
dst.Streaming = true
}
if !dst.ToolCalling && src.ToolCalling {
dst.ToolCalling = true
}
if !dst.Vision && src.Vision {
dst.Vision = true
}
if !dst.Thinking && src.Thinking {
dst.Thinking = true
}
if !dst.Reasoning && src.Reasoning {
dst.Reasoning = true
}
if !dst.CodeOptimized && src.CodeOptimized {
dst.CodeOptimized = true
}
if !dst.WebSearch && src.WebSearch {
dst.WebSearch = true
}
if dst.MaxContext == 0 && src.MaxContext > 0 {
dst.MaxContext = src.MaxContext
}
if dst.MaxOutputTokens == 0 && src.MaxOutputTokens > 0 {
dst.MaxOutputTokens = src.MaxOutputTokens
}
}
// normalizeModelID strips provider prefix and lowercases.
func normalizeModelID(modelID string) string {
id := strings.ToLower(modelID)
if idx := strings.Index(id, "/"); idx >= 0 {
id = id[idx+1:]
}
return id
}