Skip to main content

Provider & Model Management

When you work with multiple providers and models, it is often desirable to manage them in a central place and access the models through simple string IDs.

The Go AI SDK offers a provider registry and middleware-based custom providers for this purpose:

  • With middleware, you can pre-configure model settings, provide model name aliases, and enhance model behavior.
  • The provider registry lets you mix multiple providers and access them through simple string IDs.

You can mix and match middleware, the provider registry, and custom provider configurations in your application.

Provider Registry​

The provider registry allows you to register multiple providers and access their models through simple string identifiers in the format provider:model.

Basic Setup​

package main

import (
"os"

"github.com/digitallysavvy/go-ai/pkg/registry"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)

func init() {
// Register providers
registry.RegisterProvider("openai", openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
}))

registry.RegisterProvider("anthropic", anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
}))
}

Using the Registry​

Language Models​

Access language models using the provider:model format:

import (
"context"
"github.com/digitallysavvy/go-ai/pkg/registry"
"github.com/digitallysavvy/go-ai/pkg/ai"
)

// Resolve model from registry
model, err := registry.ResolveLanguageModel("openai:gpt-4")
if err != nil {
log.Fatal(err)
}

// Use with AI SDK functions
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Explain quantum computing",
})

Embedding Models​

embeddingModel, err := registry.ResolveEmbeddingModel("openai:text-embedding-3-small")
if err != nil {
log.Fatal(err)
}

result, err := ai.Embed(ctx, ai.EmbedOptions{
Model: embeddingModel,
Input: "sunny day at the beach",
})

Model Aliases​

Create short aliases for commonly used models:

func init() {
// Register providers
registry.RegisterProvider("openai", openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
}))

registry.RegisterProvider("anthropic", anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
}))

// Register aliases for easy access
registry.RegisterAlias("opus", "anthropic:claude-opus-4-5")
registry.RegisterAlias("sonnet", "anthropic:claude-sonnet-4-5")
registry.RegisterAlias("haiku", "anthropic:claude-haiku-4-5")
registry.RegisterAlias("gpt-4", "openai:gpt-4")
registry.RegisterAlias("gpt-4-turbo", "openai:gpt-4-turbo-preview")
}

// Use aliases
model, err := registry.ResolveLanguageModel("opus")

Custom Registry Instances​

You can create custom registry instances instead of using the global registry:

import "github.com/digitallysavvy/go-ai/pkg/registry"

// Create custom registry
myRegistry := registry.NewRegistry()

// Register providers
myRegistry.RegisterProvider("openai", openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
}))

// Use custom registry
model, err := myRegistry.ResolveLanguageModel("openai:gpt-4")

Registry Options​

NewRegistry accepts functional options:

myRegistry := registry.NewRegistry(
// Use a different provider:model separator (default ":").
registry.WithSeparator("/"),

// Apply middleware to every language model resolved through the registry.
registry.WithLanguageModelMiddleware(loggingMiddleware),

// Apply middleware to every image model resolved through the registry.
registry.WithImageModelMiddleware(imageLoggingMiddleware),
)

model, err := myRegistry.ResolveLanguageModel("openai/gpt-4") // custom separator

NoSuchModelError​

A model ID that has no provider:model separator (or names an unregistered provider or model) returns a typed error from pkg/provider/errors:

import providererrors "github.com/digitallysavvy/go-ai/pkg/provider/errors"

model, err := registry.ResolveLanguageModel("gpt-4") // missing "provider:" prefix
if err != nil {
var noSuchModel *providererrors.NoSuchModelError
if errors.As(err, &noSuchModel) {
log.Printf("no such model: %s (%s)", noSuchModel.ModelID, noSuchModel.ModelType)
}
}

Custom Providers with Middleware​

You can create custom provider configurations using middleware to pre-configure settings, create aliases, or limit available models.

Example: Pre-configured Model Settings​

import (
"github.com/digitallysavvy/go-ai/pkg/middleware"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/registry"
)

func setupCustomProviders() {
openaiProvider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})

// Create a base model
baseModel, _ := openaiProvider.LanguageModel("gpt-4")

// Wrap with default settings
temperature := 0.7
maxTokens := 2000

customModel := middleware.WrapLanguageModel(
baseModel,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &temperature,
MaxTokens: &maxTokens,
}),
},
nil,
nil,
)

// Register the custom model
// Note: Since we can't register a single model, we create a wrapper provider
// (implementation shown below)
}

Example: Model Name Aliases with Different Settings​

Create a configuration package that exposes different model configurations:

package models

import (
"os"

"github.com/digitallysavvy/go-ai/pkg/middleware"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)

var (
// Fast model with lower temperature for quick, focused responses
Fast provider.LanguageModel

// Creative model with high temperature for diverse outputs
Creative provider.LanguageModel

// Precise model with low temperature for deterministic responses
Precise provider.LanguageModel
)

func init() {
anthropicProvider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})

// Fast: Claude Haiku with optimized settings
haiku, _ := anthropicProvider.LanguageModel("claude-haiku-4-5")
temp := 0.5
Fast = middleware.WrapLanguageModel(
haiku,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &temp,
}),
},
nil,
nil,
)

// Creative: Claude Sonnet with high temperature
sonnet, _ := anthropicProvider.LanguageModel("claude-sonnet-4-5")
creativeTemp := 0.9
Creative = middleware.WrapLanguageModel(
sonnet,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &creativeTemp,
}),
},
nil,
nil,
)

// Precise: Claude Opus with low temperature
opus, _ := anthropicProvider.LanguageModel("claude-opus-4-5")
preciseTemp := 0.1
Precise = middleware.WrapLanguageModel(
opus,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &preciseTemp,
}),
},
nil,
nil,
)
}

Usage:

import "myapp/models"

// Use pre-configured models
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: models.Creative,
Prompt: "Write a creative story",
})

Example: Limited Model Set​

Create a constrained provider that only exposes specific models:

package providers

import (
"fmt"
"os"

"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
)

type LimitedProvider struct {
models map[string]provider.LanguageModel
}

func NewLimitedProvider() *LimitedProvider {
openaiProvider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})

anthropicProvider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})

// Only expose specific models
models := make(map[string]provider.LanguageModel)

textMedium, _ := anthropicProvider.LanguageModel("claude-sonnet-4-5")
textSmall, _ := openaiProvider.LanguageModel("gpt-4-turbo")

models["text-medium"] = textMedium
models["text-small"] = textSmall

return &LimitedProvider{models: models}
}

func (p *LimitedProvider) LanguageModel(modelID string) (provider.LanguageModel, error) {
model, ok := p.models[modelID]
if !ok {
return nil, fmt.Errorf("model not available: %s (available: text-medium, text-small)", modelID)
}
return model, nil
}

func (p *LimitedProvider) EmbeddingModel(modelID string) (provider.EmbeddingModel, error) {
return nil, fmt.Errorf("embedding models not available")
}

func (p *LimitedProvider) ImageModel(modelID string) (provider.ImageModel, error) {
return nil, fmt.Errorf("image models not available")
}

func (p *LimitedProvider) SpeechModel(modelID string) (provider.SpeechModel, error) {
return nil, fmt.Errorf("speech models not available")
}

func (p *LimitedProvider) TranscriptionModel(modelID string) (provider.TranscriptionModel, error) {
return nil, fmt.Errorf("transcription models not available")
}

func (p *LimitedProvider) RerankingModel(modelID string) (provider.RerankingModel, error) {
return nil, fmt.Errorf("reranking models not available")
}

Usage:

limited := providers.NewLimitedProvider()
registry.RegisterProvider("limited", limited)

// Only "text-medium" and "text-small" are available
model, _ := registry.ResolveLanguageModel("limited:text-medium")

Centralized Provider Configuration​

Create a central configuration file that sets up all providers and models:

package config

import (
"os"

"github.com/digitallysavvy/go-ai/pkg/middleware"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
"github.com/digitallysavvy/go-ai/pkg/providers/google"
"github.com/digitallysavvy/go-ai/pkg/registry"
)

func init() {
setupProviders()
setupAliases()
setupCustomModels()
}

func setupProviders() {
// Register standard providers
registry.RegisterProvider("openai", openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
}))

registry.RegisterProvider("anthropic", anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
}))

registry.RegisterProvider("google", google.New(google.Config{
APIKey: os.Getenv("GOOGLE_GENERATIVE_AI_API_KEY"),
}))
}

func setupAliases() {
// Short aliases for common models
registry.RegisterAlias("fast", "anthropic:claude-haiku-4-5")
registry.RegisterAlias("balanced", "anthropic:claude-sonnet-4-5")
registry.RegisterAlias("powerful", "anthropic:claude-opus-4-5")

registry.RegisterAlias("gpt-4", "openai:gpt-4")
registry.RegisterAlias("gpt-4-turbo", "openai:gpt-4-turbo-preview")

registry.RegisterAlias("gemini", "google:gemini-2.0-flash")
}

func setupCustomModels() {
// Set up models with pre-configured settings
// (Implementation depends on your needs)
}

Combining Registry, Middleware, and Custom Configuration​

Here's a comprehensive example combining all concepts:

package main

import (
"context"
"fmt"
"os"

"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/middleware"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/providers/anthropic"
"github.com/digitallysavvy/go-ai/pkg/registry"
)

func init() {
// 1. Register base providers
registry.RegisterProvider("openai", openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
}))

registry.RegisterProvider("anthropic", anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
}))

// 2. Set up aliases for common models
registry.RegisterAlias("default", "anthropic:claude-sonnet-4-5")
registry.RegisterAlias("fast", "anthropic:claude-haiku-4-5")
registry.RegisterAlias("smart", "anthropic:claude-opus-4-5")

// 3. Set up wrapped models with custom settings
// (Shown in CustomModels below)
}

// CustomModels provides pre-configured models with specific settings
type CustomModels struct {
Creative provider.LanguageModel // High temperature for creative tasks
Factual provider.LanguageModel // Low temperature for factual responses
Concise provider.LanguageModel // Limited tokens for brief responses
}

func GetCustomModels() *CustomModels {
anthropicProvider := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
})

baseModel, _ := anthropicProvider.LanguageModel("claude-sonnet-4-5")

// Creative model
creativeTemp := 0.9
creative := middleware.WrapLanguageModel(
baseModel,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &creativeTemp,
}),
},
nil,
nil,
)

// Factual model
factualTemp := 0.1
factual := middleware.WrapLanguageModel(
baseModel,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &factualTemp,
}),
},
nil,
nil,
)

// Concise model
conciseTemp := 0.5
conciseTokens := 100
concise := middleware.WrapLanguageModel(
baseModel,
[]*middleware.LanguageModelMiddleware{
middleware.DefaultSettingsMiddleware(&provider.GenerateOptions{
Temperature: &conciseTemp,
MaxTokens: &conciseTokens,
}),
},
nil,
nil,
)

return &CustomModels{
Creative: creative,
Factual: factual,
Concise: concise,
}
}

func main() {
ctx := context.Background()
customModels := GetCustomModels()

// Use registry with aliases
model1, _ := registry.ResolveLanguageModel("default")
result1, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model1,
Prompt: "Explain quantum computing",
})
fmt.Println("Default:", result1.Text)

// Use custom pre-configured models
result2, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: customModels.Creative,
Prompt: "Write a creative story",
})
fmt.Println("Creative:", result2.Text)

// Use direct provider:model syntax
model3, _ := registry.ResolveLanguageModel("openai:gpt-4")
result3, _ := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model3,
Prompt: "Explain photosynthesis",
})
fmt.Println("GPT-4:", result3.Text)
}

Registry Utility Functions​

List Registered Providers​

ListProviders and ListAliases are methods on a *registry.Registry, not package-level functions. Use registry.GetGlobalRegistry() to reach the global registry that registry.RegisterProvider et al. populate:

providers := registry.GetGlobalRegistry().ListProviders()
fmt.Println("Available providers:", providers)
// Output: Available providers: [openai anthropic google]

List Registered Aliases​

aliases := registry.GetGlobalRegistry().ListAliases()
for alias, target := range aliases {
fmt.Printf("%s -> %s\n", alias, target)
}
// Output:
// default -> anthropic:claude-sonnet-4-5
// fast -> anthropic:claude-haiku-4-5
// smart -> anthropic:claude-opus-4-5

Get Provider Directly​

provider, err := registry.GetProvider("openai")
if err != nil {
log.Fatal(err)
}

model, _ := provider.LanguageModel("gpt-4")

Best Practices​

1. Centralize Configuration​

Keep all provider setup in a single configuration package:

myapp/
├── config/
│ └── providers.go # All provider setup here
├── models/
│ └── custom.go # Custom model configurations
└── main.go

2. Use Environment Variables​

Store API keys and configuration in environment variables:

openaiProvider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
BaseURL: os.Getenv("OPENAI_BASE_URL"), // Optional custom endpoint
})

3. Create Semantic Aliases​

Use meaningful names that reflect the model's purpose:

// Good: Semantic aliases
registry.RegisterAlias("writing-assistant", "anthropic:claude-sonnet-4-5")
registry.RegisterAlias("code-reviewer", "openai:gpt-4")
registry.RegisterAlias("quick-qa", "anthropic:claude-haiku-4-5")

// Bad: Technical aliases
registry.RegisterAlias("model1", "anthropic:claude-sonnet-4-5")
registry.RegisterAlias("fast-one", "anthropic:claude-haiku-4-5")

4. Document Available Models​

Create documentation for your team about available models:

// models/README.md
//
// Available Models:
//
// default - Claude Sonnet 4.5 (balanced performance)
// fast - Claude Haiku 4.5 (quick responses)
// smart - Claude Opus 4.5 (complex reasoning)
// writing - GPT-4 optimized for writing
// code - GPT-4 optimized for code generation

5. Handle Errors Gracefully​

Always check for errors when resolving models:

model, err := registry.ResolveLanguageModel("nonexistent:model")
if err != nil {
// Log error and fall back to default
log.Printf("Failed to resolve model: %v, using default", err)
model, _ = registry.ResolveLanguageModel("default")
}

6. Use Type-Safe Model References​

For critical applications, create constants for model references:

package models

const (
DefaultModel = "anthropic:claude-sonnet-4-5"
FastModel = "anthropic:claude-haiku-4-5"
PowerfulModel = "anthropic:claude-opus-4-5"
CodeModel = "openai:gpt-4"
)

// Usage
model, _ := registry.ResolveLanguageModel(models.DefaultModel)

See Also​