Files
vikingowl 0d2d825e52 feat: add dynamic model discovery within providers
- OpenAI provider: use Models.ListAutoPaging() to discover available models
- Anthropic provider: use Models.ListAutoPaging() to discover available models
- Google provider: use Models.All() iterator to discover available models
- All providers fall back to hardcoded lists if API calls fail
- Add capability inference functions for each provider based on model ID
- Add tests for model discovery fallback behavior

This enables gnoma to dynamically discover new models as they become available
from cloud providers, while maintaining backward compatibility with fallback
lists for offline use or API failures.
2026-05-07 22:27:24 +02:00

109 lines
2.7 KiB
Go

package openai
import (
"context"
"testing"
"somegit.dev/Owlibou/gnoma/internal/provider"
)
func TestModels_Fallback(t *testing.T) {
// Test with invalid API key - should fall back to hardcoded list
cfg := provider.ProviderConfig{
APIKey: "invalid-key",
BaseURL: "https://api.openai.com/v1",
}
p, err := New(cfg)
if err != nil {
t.Fatalf("New() error = %v", err)
}
models, err := p.Models(context.Background())
if err != nil {
t.Fatalf("Models() error = %v", err)
}
// Should return fallback models
if len(models) == 0 {
t.Fatal("Models() returned empty list, expected fallback models")
}
// Check that we have the expected fallback models
modelIDs := make(map[string]bool)
for _, m := range models {
modelIDs[m.ID] = true
}
// Verify some expected models are present
expectedModels := []string{"gpt-4o", "gpt-4o-mini", "o3", "o3-mini"}
for _, expected := range expectedModels {
if !modelIDs[expected] {
t.Errorf("Expected model %q not found in fallback list", expected)
}
}
}
func TestInferOpenAIModelCapabilities(t *testing.T) {
tests := []struct {
modelID string
want provider.Capabilities
}{
{
modelID: "gpt-4o",
want: provider.Capabilities{
ToolUse: true,
JSONOutput: true,
Vision: true,
ContextWindow: 128000,
MaxOutput: 16384,
},
},
{
modelID: "o3",
want: provider.Capabilities{
ToolUse: true,
JSONOutput: true,
Vision: true,
ThinkingModes: []provider.EffortLevel{provider.EffortLow, provider.EffortMedium, provider.EffortHigh},
ContextWindow: 200000,
MaxOutput: 100000,
},
},
{
modelID: "gpt-3.5-turbo",
want: provider.Capabilities{
ToolUse: false,
JSONOutput: true,
Vision: false,
ContextWindow: 16384,
MaxOutput: 4096,
},
},
}
for _, tt := range tests {
t.Run(tt.modelID, func(t *testing.T) {
got := inferOpenAIModelCapabilities(tt.modelID)
if got.ToolUse != tt.want.ToolUse {
t.Errorf("ToolUse = %v, want %v", got.ToolUse, tt.want.ToolUse)
}
if got.JSONOutput != tt.want.JSONOutput {
t.Errorf("JSONOutput = %v, want %v", got.JSONOutput, tt.want.JSONOutput)
}
if got.Vision != tt.want.Vision {
t.Errorf("Vision = %v, want %v", got.Vision, tt.want.Vision)
}
if got.ContextWindow != tt.want.ContextWindow {
t.Errorf("ContextWindow = %v, want %v", got.ContextWindow, tt.want.ContextWindow)
}
if got.MaxOutput != tt.want.MaxOutput {
t.Errorf("MaxOutput = %v, want %v", got.MaxOutput, tt.want.MaxOutput)
}
// Check ThinkingModes length
if len(got.ThinkingModes) != len(tt.want.ThinkingModes) {
t.Errorf("ThinkingModes length = %v, want %v", len(got.ThinkingModes), len(tt.want.ThinkingModes))
}
})
}
}