Files
GopherGate/internal/models/registry.go
T
hobokenchicken d2b9da89d9
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled
fix FindModel: prioritize canonical providers to prevent reseller limit overrides
FindModel iterates providers in random map order, so when deepseek-v4-pro
exists in both 'deepseek' (output=384000) and 'ollama-cloud' (output=1048576),
it sometimes returned the wrong metadata. The proxy then injected
max_tokens=1048576 into DeepSeek's API, which rejected it with 400
(valid range is [1, 393216]).

Fix: define CanonicalProviders list (deepseek, openai, google, xai, etc.)
and search them in priority order before falling back to all providers.
Each of the four lookup strategies (exact key, metadata ID, reverse fuzzy,
forward fuzzy) checks canonical providers first.
2026-05-07 14:47:17 -04:00

211 lines
5.7 KiB
Go

package models
import "strings"
// CanonicalProviders lists the original model creators in priority order.
// When a model name exists in multiple providers (e.g. deepseek-v4-pro in
// deepseek, ollama-cloud, openrouter, etc.), these providers take precedence
// so the proxy uses authoritative metadata (pricing, limits) rather than a
// reseller's values.
var CanonicalProviders = []string{
"openai",
"google",
"deepseek",
"xai",
"moonshotai",
"moonshotai-cn",
"anthropic",
"mistral",
"cohere",
"minimax",
}
type ModelRegistry struct {
Providers map[string]ProviderInfo `json:"-"`
}
type ProviderInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Models map[string]ModelMetadata `json:"models"`
}
type ModelMetadata struct {
ID string `json:"id"`
Name string `json:"name"`
Cost *ModelCost `json:"cost,omitempty"`
Limit *ModelLimit `json:"limit,omitempty"`
Modalities *ModelModalities `json:"modalities,omitempty"`
ToolCall *bool `json:"tool_call,omitempty"`
Reasoning *bool `json:"reasoning,omitempty"`
}
type ModelCost struct {
Input float64 `json:"input"`
Output float64 `json:"output"`
CacheRead *float64 `json:"cache_read,omitempty"`
CacheWrite *float64 `json:"cache_write,omitempty"`
}
type ModelLimit struct {
Context uint32 `json:"context"`
Output uint32 `json:"output"`
}
type ModelModalities struct {
Input []string `json:"input"`
Output []string `json:"output"`
}
// findInCanonical searches the canonical providers in order for an exact model
// key match. Returns the metadata and true if found.
func (r *ModelRegistry) findInCanonical(modelID string) (*ModelMetadata, bool) {
for _, key := range CanonicalProviders {
if p, ok := r.Providers[key]; ok {
if m, ok := p.Models[modelID]; ok {
return &m, true
}
}
}
return nil, false
}
// findInAll searches all providers (map iteration, random order) for an exact
// model key match. Used as fallback when canonical search fails.
func (r *ModelRegistry) findInAll(modelID string) (*ModelMetadata, bool) {
for _, p := range r.Providers {
if m, ok := p.Models[modelID]; ok {
return &m, true
}
}
return nil, false
}
// findInCanonicalByID searches canonical providers for a model whose metadata
// ID field matches modelID.
func (r *ModelRegistry) findInCanonicalByID(modelID string) (*ModelMetadata, bool) {
for _, key := range CanonicalProviders {
if p, ok := r.Providers[key]; ok {
for _, m := range p.Models {
if m.ID == modelID {
return &m, true
}
}
}
}
return nil, false
}
// findInAllByID searches all providers for a model whose metadata ID field
// matches modelID.
func (r *ModelRegistry) findInAllByID(modelID string) (*ModelMetadata, bool) {
for _, p := range r.Providers {
for _, m := range p.Models {
if m.ID == modelID {
return &m, true
}
}
}
return nil, false
}
// findCanonicalReverseFuzzy searches canonical providers for any model whose
// key starts with modelID.
func (r *ModelRegistry) findCanonicalReverseFuzzy(modelID string) (*ModelMetadata, bool) {
for _, key := range CanonicalProviders {
if p, ok := r.Providers[key]; ok {
for id, m := range p.Models {
if strings.HasPrefix(id, modelID) {
return &m, true
}
}
}
}
return nil, false
}
// findAllReverseFuzzy searches all providers for any model whose key starts
// with modelID.
func (r *ModelRegistry) findAllReverseFuzzy(modelID string) (*ModelMetadata, bool) {
for _, p := range r.Providers {
for id, m := range p.Models {
if strings.HasPrefix(id, modelID) {
return &m, true
}
}
}
return nil, false
}
// findCanonicalForwardFuzzy searches canonical providers for any model whose
// key is a prefix of modelID.
func (r *ModelRegistry) findCanonicalForwardFuzzy(modelID string) (*ModelMetadata, bool) {
for _, key := range CanonicalProviders {
if p, ok := r.Providers[key]; ok {
for id, m := range p.Models {
if strings.HasPrefix(modelID, id) {
return &m, true
}
}
}
}
return nil, false
}
// findAllForwardFuzzy searches all providers for any model whose key is a
// prefix of modelID.
func (r *ModelRegistry) findAllForwardFuzzy(modelID string) (*ModelMetadata, bool) {
for _, p := range r.Providers {
for id, m := range p.Models {
if strings.HasPrefix(modelID, id) {
return &m, true
}
}
}
return nil, false
}
// FindModel looks up model metadata by ID. It searches canonical providers
// first at each strategy level (exact key, metadata ID, reverse fuzzy,
// forward fuzzy) and falls back to all providers only when canonical search
// yields no result. This prevents reseller entries (ollama-cloud, openrouter,
// etc.) from overriding the original provider's authoritative pricing and
// limits.
func (r *ModelRegistry) FindModel(modelID string) *ModelMetadata {
// 1. Exact key match — canonical first, then all
if m, ok := r.findInCanonical(modelID); ok {
return m
}
if m, ok := r.findInAll(modelID); ok {
return m
}
// 2. Match by metadata ID field — canonical first, then all
if m, ok := r.findInCanonicalByID(modelID); ok {
return m
}
if m, ok := r.findInAllByID(modelID); ok {
return m
}
// 3. Reverse fuzzy: model key starts with modelID
// e.g. 'gpt-5.4-mini' matching 'gpt-5.4-mini-2026-04-01'
if m, ok := r.findCanonicalReverseFuzzy(modelID); ok {
return m
}
if m, ok := r.findAllReverseFuzzy(modelID); ok {
return m
}
// 4. Forward fuzzy: modelID starts with model key
// e.g. 'gpt-4o-2024-05-13' matching 'gpt-4o'
if m, ok := r.findCanonicalForwardFuzzy(modelID); ok {
return m
}
if m, ok := r.findAllForwardFuzzy(modelID); ok {
return m
}
return nil
}