Providers
Configure specific providers. Provider Setup →
This page explains the core building blocks of Iris. Understanding these concepts will help you design effective AI applications and use the SDK efficiently.
A Provider implements the core.Provider interface and adapts a specific LLM API to Iris’s
unified interface. Providers handle the translation between Iris’s common types and provider-specific
API formats.
type Provider interface { // Identity ID() string // "openai", "anthropic", etc. Models() []ModelInfo // Available models with metadata
// Capabilities Supports(feature Feature) bool // Check if feature is supported
// Core operations Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error) StreamChat(ctx context.Context, req *ChatRequest) (*ChatStream, error)}Each provider advertises what it supports via the Supports() method:
// Feature capability flagsconst ( FeatureChat Feature = "chat" FeatureChatStreaming Feature = "chat_streaming" FeatureToolCalling Feature = "tool_calling" FeatureReasoning Feature = "reasoning" FeatureBuiltInTools Feature = "built_in_tools" FeatureResponseChain Feature = "response_chain" FeatureEmbeddings Feature = "embeddings" FeatureContextualizedEmbeddings Feature = "contextualized_embeddings" FeatureReranking Feature = "reranking")
// Check capabilities before using featuresif provider.Supports(core.FeatureToolCalling) { // Safe to use tool calling}Each provider package exports a New() function:
// Cloud providers require API keysopenaiProvider := openai.New(os.Getenv("OPENAI_API_KEY"))anthropicProvider := anthropic.New(os.Getenv("ANTHROPIC_API_KEY"))geminiProvider := gemini.New(os.Getenv("GEMINI_API_KEY"))
// Local providers require base URLsollamaProvider := ollama.New("http://localhost:11434")Providers accept configuration options:
provider := openai.New(apiKey, openai.WithBaseURL("https://custom-endpoint.example.com/v1"), openai.WithOrganization("org-xxx"), openai.WithHTTPClient(customHTTPClient), openai.WithTimeout(60 * time.Second),)The core.Client wraps a provider with middleware features: retry logic, telemetry hooks, and
the fluent builder API. The client is the primary entry point for SDK usage.
// Basic clientclient := core.NewClient(provider)
// Client with optionsclient := core.NewClient(provider, core.WithRetryPolicy(customPolicy), core.WithTelemetry(telemetryHook), core.WithTimeout(30 * time.Second),)core.Client is safe for concurrent use. Create one client and share it across goroutines:
// Create once at startupclient := core.NewClient(provider)
// Use from multiple goroutinesvar wg sync.WaitGroupfor i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() resp, err := client.Chat("gpt-4o"). User(fmt.Sprintf("Query %d", id)). GetResponse(ctx) // Handle response... }(i)}wg.Wait()// Start a chat builderbuilder := client.Chat("gpt-4o")
// Access underlying providerprovider := client.Provider()
// For embeddings, use the provider directly (if it supports them)if embedProvider, ok := provider.(core.EmbeddingProvider); ok { resp, _ := embedProvider.CreateEmbeddings(ctx, &core.EmbeddingRequest{...})}The ChatBuilder constructs chat requests through a fluent API. Each method returns the builder
for chaining. Builders are not thread-safe—use Clone() when sharing configurations.
resp, err := client.Chat("gpt-4o"). // Message methods System("You are a helpful assistant."). // System message User("What is the capital of France?"). // User message Assistant("Paris is the capital of France."). // Assistant message (for context)
// Configuration methods Temperature(0.7). // 0.0-2.0, lower = more deterministic MaxTokens(1000). // Maximum tokens in response Timeout(30 * time.Second). // Request timeout (alternative to context timeout)
// Tool methods Tools(tool1, tool2). // Available tools
// Responses API methods (GPT-4o, GPT-5, etc.) Instructions("System-level instructions"). // System instructions ReasoningEffort(core.ReasoningEffortMedium). // Reasoning level: low/medium/high/xhigh WebSearch(). // Enable web search built-in tool FileSearch("vs_abc123"). // Enable file search with vector store IDs CodeInterpreter(). // Enable code interpreter built-in tool ContinueFrom(previousRespID). // Chain to previous response Truncation("auto"). // Truncation mode
// Execute GetResponse(ctx) // Blocking response // or Stream(ctx) // Streaming responseYou can set a timeout on the builder instead of creating a context manually:
// Using Timeout() on builderresp, err := client.Chat("gpt-4o"). User("Hello"). Timeout(30 * time.Second). GetResponse(context.Background())
// Equivalent to manual context timeoutctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()resp, err := client.Chat("gpt-4o").User("Hello").GetResponse(ctx)For messages with images or files, use the multimodal builder:
resp, err := client.Chat("gpt-4o"). System("Analyze images in detail."). UserMultimodal(). Text("What objects are in this image?"). ImageURL("https://example.com/photo.jpg"). ImageURLWithDetail("https://example.com/diagram.png", core.ImageDetailHigh). ImageFileID("file-abc123"). // From Files API FileURL("https://example.com/doc.pdf"). // Document URL FileID("file-xyz789"). // Document from Files API FileBase64("report.pdf", base64Data). // Base64-encoded file Done(). GetResponse(ctx)For common single-image or single-file cases:
// Image from URLresp, _ := client.Chat("gpt-4o"). UserWithImageURL("Describe this image", "https://example.com/photo.jpg"). GetResponse(ctx)
// Image from Files APIresp, _ := client.Chat("gpt-4o"). UserWithImageFileID("Analyze this screenshot", "file-abc123"). GetResponse(ctx)
// Document from URLresp, _ := client.Chat("gpt-4o"). UserWithFileURL("Summarize this PDF", "https://example.com/doc.pdf"). GetResponse(ctx)
// Document from Files APIresp, _ := client.Chat("gpt-4o"). UserWithFileID("Extract data from this file", "file-xyz789"). GetResponse(ctx)Use Clone() to create a copy with the current configuration:
// Base configurationbase := client.Chat("gpt-4o"). System("You are a helpful assistant."). Temperature(0.7)
// Create specialized builders from basecodeHelper := base.Clone().System("You are a coding expert.")mathHelper := base.Clone().System("You are a math tutor.")
// Use independentlycodeResp, _ := codeHelper.User("How do I reverse a string in Go?").GetResponse(ctx)mathResp, _ := mathHelper.User("What is the derivative of x^2?").GetResponse(ctx)Build multi-turn conversations by adding message history:
builder := client.Chat("gpt-4o"). System("You are a helpful assistant.")
// First turnresp1, _ := builder.Clone(). User("What is Python?"). GetResponse(ctx)
// Second turn with historyresp2, _ := builder.Clone(). User("What is Python?"). Assistant(resp1.Output). User("How do I install it?"). GetResponse(ctx)The response from a chat completion contains the generated content and metadata:
type ChatResponse struct { ID string // Response identifier Model ModelID // Actual model used Output string // Generated text content Usage TokenUsage // Token counts ToolCalls []ToolCall // Tool invocations (if tools were provided)
// Responses API fields Reasoning *ReasoningOutput // Reasoning summary (models with reasoning) Status string // Response status}
type TokenUsage struct { PromptTokens int CompletionTokens int TotalTokens int}
type ReasoningOutput struct { ID string Summary []string // Reasoning steps}ChatResponse provides convenience methods for common checks:
// Check for tool callsif resp.HasToolCalls() { tc := resp.FirstToolCall() // Get first tool call (common for single-tool scenarios) // or iterate all for _, tc := range resp.ToolCalls { executeToolCall(tc) }}
// Check for reasoning outputif resp.HasReasoning() { for _, step := range resp.Reasoning.Summary { fmt.Println("Reasoning:", step) }}resp, err := client.Chat("gpt-4o"). User("Hello!"). GetResponse(ctx)
if err != nil { // Handle error (see Error Handling section) return err}
// Print outputfmt.Println(resp.Output)
// Check for tool calls using helper methodif resp.HasToolCalls() { for _, tc := range resp.ToolCalls { result := executeToolCall(tc) // Continue conversation with tool result (see Tools section) }}
// Check token usagefmt.Printf("Tokens used: %d\n", resp.Usage.TotalTokens)Streaming delivers response tokens as they’re generated, enabling real-time UI updates. The
ChatStream type provides channels for incremental processing.
type ChatStream struct { Ch <-chan ChatChunk // Incremental content chunks Err <-chan error // Error channel (receives at most one) Final <-chan *ChatResponse // Aggregated final response}
type ChatChunk struct { Delta string // Incremental text content}stream, err := client.Chat("gpt-4o"). User("Tell me a story."). Stream(ctx)
if err != nil { return err}
// Process chunks as they arrivefor chunk := range stream.Ch { fmt.Print(chunk.Delta) // Real-time output}
// Check for errorsif err := <-stream.Err; err != nil { return err}
// Get final aggregated responsefinal := <-stream.Finalfmt.Printf("\nTotal tokens: %d\n", final.Usage.TotalTokens)ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()
stream, err := client.Chat("gpt-4o"). User("Write a long essay."). Stream(ctx)
if err != nil { return err}
for { select { case chunk, ok := <-stream.Ch: if !ok { // Stream finished break } fmt.Print(chunk.Delta) case <-ctx.Done(): // Timeout or cancellation return ctx.Err() }}Use core.DrainStream to collect a stream into a full response:
stream, err := client.Chat("gpt-4o"). User("Hello"). Stream(ctx)
if err != nil { return err}
// Drain stream to get full responseresp, err := core.DrainStream(ctx, stream)if err != nil { return err}
// Now you have a regular ChatResponsefmt.Println(resp.Output)When streaming with tools, consume the text stream normally and get complete tool calls from the Final response:
stream, err := client.Chat("gpt-4o"). User("What's the weather in Tokyo?"). Tools(weatherTool). Stream(ctx)
if err != nil { return err}
// Consume text chunksfor chunk := range stream.Ch { fmt.Print(chunk.Delta)}
// Check for errorsif err := <-stream.Err; err != nil { return err}
// Get complete tool calls from final responsefinal := <-stream.Finalif final != nil && final.HasToolCalls() { for _, tc := range final.ToolCalls { executeToolCall(tc) }}Tools enable models to invoke external functions with structured arguments. This is the foundation for building agents that can take actions in the real world.
Tools implement the core.Tool interface:
type Tool interface { Name() string // Unique identifier Description() string // What the tool does (used by model)}Tool implementations typically include JSON Schema for parameters. See the tools package
for helper types and builders.
When a model decides to use a tool, it returns tool calls instead of text:
type ToolCall struct { ID string // Unique call identifier Name string // Tool name Arguments json.RawMessage // JSON-encoded arguments}After executing tools, send results back using the immutable ToolResults API:
type ToolResult struct { CallID string // Must match ToolCall.ID from the response Content any // Result data (will be JSON marshaled) IsError bool // True if this represents an error}Complete tool calling requires multiple turns:
// Turn 1: Initial request with toolsresp, err := client.Chat("gpt-4o"). System("You are a helpful assistant with access to weather data."). User("What's the weather in New York?"). Tools(weatherTool). GetResponse(ctx)
if err != nil { return err}
// Check if model wants to call a toolif resp.HasToolCalls() { tc := resp.FirstToolCall()
// Parse arguments var args struct { Location string `json:"location"` Unit string `json:"unit"` } json.Unmarshal(tc.Arguments, &args)
// Execute the tool (your implementation) weatherResult := getWeather(args.Location, args.Unit)
// Turn 2: Send tool result back // Note: ToolResult returns a NEW builder (immutable) finalResp, err := client.Chat("gpt-4o"). System("You are a helpful assistant with access to weather data."). User("What's the weather in New York?"). ToolResult(resp, tc.ID, weatherResult). // Pass original response + result Tools(weatherTool). GetResponse(ctx)
if err != nil { return err }
fmt.Println(finalResp.Output) // "The current weather in New York is 72°F with partly cloudy skies."}The ChatBuilder provides three convenience methods for tool results:
// Single successful resultnewBuilder := builder.ToolResult(resp, callID, content)
// Single error resultnewBuilder := builder.ToolError(resp, callID, err)
// Multiple results at oncenewBuilder := builder.ToolResults(resp, []core.ToolResult{ {CallID: call1.ID, Content: result1, IsError: false}, {CallID: call2.ID, Content: result2, IsError: false},})When a model returns multiple tool calls, execute them and return all results:
if resp.HasToolCalls() { // Execute all tool calls results := make([]core.ToolResult, 0, len(resp.ToolCalls)) for _, tc := range resp.ToolCalls { result, err := executeToolCall(tc) if err != nil { results = append(results, core.ToolResult{ CallID: tc.ID, Content: err.Error(), IsError: true, }) } else { results = append(results, core.ToolResult{ CallID: tc.ID, Content: result, IsError: false, }) } }
// Send all results back finalResp, err := client.Chat("gpt-4o"). User("What's the weather in New York and London?"). ToolResults(resp, results). Tools(weatherTool). GetResponse(ctx)}Some models (GPT-5, Claude with extended thinking) provide reasoning summaries explaining their thought process. Additionally, certain models have built-in tools like web search.
type Reasoning struct { Summary string // High-level explanation Steps []string // Step-by-step reasoning (if available)}Access reasoning in responses:
resp, err := client.Chat("gpt-5"). User("Solve: If a train leaves Chicago at 9am going 60mph..."). GetResponse(ctx)
if resp.Reasoning != nil { fmt.Println("Model reasoning:") fmt.Println(resp.Reasoning.Summary) for i, step := range resp.Reasoning.Steps { fmt.Printf("%d. %s\n", i+1, step) }}
fmt.Println("\nAnswer:", resp.Output)GPT-5+ models support built-in tools that the model can invoke internally:
resp, err := client.Chat("gpt-5"). User("Search the web for the latest news about AI."). BuiltInTools( core.BuiltInToolWebSearch, // Web search core.BuiltInToolFileSearch, // File search in uploaded files ). GetResponse(ctx)Vision-capable models can process images alongside text. Iris provides a consistent interface across providers.
// From URLresp, err := client.Chat("gpt-4o"). UserMultimodal(). Text("Describe this image."). ImageURL("https://example.com/photo.jpg"). Done(). GetResponse(ctx)
// From base64imageData, _ := os.ReadFile("photo.png")base64Data := base64.StdEncoding.EncodeToString(imageData)
resp, err := client.Chat("gpt-4o"). UserMultimodal(). Text("What's in this image?"). ImageBase64(base64Data, "image/png"). Done(). GetResponse(ctx)
// Multiple imagesresp, err := client.Chat("gpt-4o"). UserMultimodal(). Text("Compare these two images."). ImageURL("https://example.com/before.jpg"). ImageURL("https://example.com/after.jpg"). Done(). GetResponse(ctx)Control the detail level for image analysis:
resp, err := client.Chat("gpt-4o"). UserMultimodal(). Text("Analyze this diagram in detail."). ImageURL("https://example.com/diagram.png", core.ImageDetailHigh). Done(). GetResponse(ctx)
// Detail levels:// - core.ImageDetailAuto (default) - Model decides// - core.ImageDetailLow - Faster, lower token usage// - core.ImageDetailHigh - More detailed analysis| Format | OpenAI | Anthropic | Gemini | Ollama |
|---|---|---|---|---|
| PNG | ✓ | ✓ | ✓ | ✓ |
| JPEG | ✓ | ✓ | ✓ | ✓ |
| GIF | ✓ | ✓ | ✓ | - |
| WebP | ✓ | ✓ | ✓ | - |
Embeddings convert text into dense vectors for semantic search, clustering, and RAG pipelines.
Providers that support embeddings implement the EmbeddingProvider interface.
Embeddings are created directly on providers that support them:
// Cast provider to EmbeddingProviderembedProvider, ok := provider.(core.EmbeddingProvider)if !ok { return errors.New("provider does not support embeddings")}
// Single text embeddingresp, err := embedProvider.CreateEmbeddings(ctx, &core.EmbeddingRequest{ Model: "text-embedding-3-small", Input: []core.EmbeddingInput{ {Text: "The quick brown fox jumps over the lazy dog."}, },})
if err != nil { return err}
fmt.Printf("Dimensions: %d\n", len(resp.Vectors[0].Vector))// Dimensions: 1536
// Batch embeddingresp, err := embedProvider.CreateEmbeddings(ctx, &core.EmbeddingRequest{ Model: "text-embedding-3-small", Input: []core.EmbeddingInput{ {Text: "First document"}, {Text: "Second document"}, {Text: "Third document"}, },})
for i, vec := range resp.Vectors { fmt.Printf("Document %d: %d dimensions\n", i, len(vec.Vector))}dimensions := 512resp, err := embedProvider.CreateEmbeddings(ctx, &core.EmbeddingRequest{ Model: "text-embedding-3-small", Input: []core.EmbeddingInput{{Text: "Query text"}}, Dimensions: &dimensions, // Reduce dimensions (if supported) EncodingFormat: core.EncodingFormatFloat, // "float" or "base64" InputType: core.InputTypeQuery, // Optimize for queries vs documents})// Generate query embeddingresp, _ := embedProvider.CreateEmbeddings(ctx, &core.EmbeddingRequest{ Model: "text-embedding-3-small", Input: []core.EmbeddingInput{{Text: "What is machine learning?"}}, InputType: core.InputTypeQuery,})queryVector := resp.Vectors[0].Vector
// Search vector store (Qdrant example)results, err := qdrantClient.Search(&qdrant.SearchRequest{ Vector: queryVector, Limit: 5,})
// Use results in RAG promptvar ragContext strings.Builderfor _, r := range results { ragContext.WriteString(r.Payload["text"].(string)) ragContext.WriteString("\n---\n")}
chatResp, _ := client.Chat("gpt-4o"). System("Answer based on the provided context."). User(fmt.Sprintf("Context:\n%s\n\nQuestion: What is machine learning?", ragContext.String())). GetResponse(ctx)Telemetry hooks let you instrument Iris requests for observability, debugging, and cost tracking. The telemetry system is designed to never include sensitive data like API keys, prompts, or responses.
type TelemetryHook interface { OnRequestStart(e RequestStartEvent) OnRequestEnd(e RequestEndEvent)}
type RequestStartEvent struct { Provider string // Provider identifier (e.g., "openai", "anthropic") Model ModelID // Model being called Start time.Time // When the request started}
type RequestEndEvent struct { Provider string // Provider identifier Model ModelID // Model that was called Start time.Time // When the request started End time.Time // When the request completed Usage TokenUsage // Token consumption Err error // Error if request failed, nil on success}
// RequestEndEvent has a convenience methodfunc (e RequestEndEvent) Duration() time.Durationtype LoggingHook struct { logger *log.Logger}
func (h *LoggingHook) OnRequestStart(e core.RequestStartEvent) { h.logger.Printf("Starting request to %s/%s", e.Provider, e.Model)}
func (h *LoggingHook) OnRequestEnd(e core.RequestEndEvent) { if e.Err != nil { h.logger.Printf("Request failed after %v: %v", e.Duration(), e.Err) return } h.logger.Printf("Request completed in %v, tokens: %d", e.Duration(), e.Usage.TotalTokens)}
// Use the hookclient := core.NewClient(provider, core.WithTelemetry(&LoggingHook{logger: log.Default()}),)For cases where no telemetry is needed, use the built-in no-op implementation:
var _ core.TelemetryHook = core.NoopTelemetryHook{}type CostTracker struct { mu sync.Mutex costs map[string]float64}
func (t *CostTracker) OnRequestStart(e core.RequestStartEvent) {}
func (t *CostTracker) OnRequestEnd(e core.RequestEndEvent) { if e.Err != nil { return }
cost := calculateCost(string(e.Model), e.Usage.PromptTokens, e.Usage.CompletionTokens)
t.mu.Lock() t.costs[string(e.Model)] += cost t.mu.Unlock()}
func calculateCost(model string, promptTokens, outputTokens int) float64 { // Model-specific pricing prices := map[string]struct{ prompt, output float64 }{ "gpt-4o": {0.005, 0.015}, // per 1K tokens "gpt-4o-mini": {0.00015, 0.0006}, }
p, ok := prices[model] if !ok { return 0 }
return (float64(promptTokens)/1000)*p.prompt + (float64(outputTokens)/1000)*p.output}import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace")
type OTelHook struct { tracer trace.Tracer spans sync.Map // Track spans by request}
func (h *OTelHook) OnRequestStart(e core.RequestStartEvent) { ctx, span := h.tracer.Start(context.Background(), "llm.request", trace.WithAttributes( attribute.String("llm.provider", e.Provider), attribute.String("llm.model", string(e.Model)), ), ) // Store span for matching OnRequestEnd h.spans.Store(e.Start.UnixNano(), span)}
func (h *OTelHook) OnRequestEnd(e core.RequestEndEvent) { spanVal, ok := h.spans.LoadAndDelete(e.Start.UnixNano()) if !ok { return } span := spanVal.(trace.Span)
span.SetAttributes( attribute.Int("llm.tokens.total", e.Usage.TotalTokens), attribute.Int64("llm.duration_ms", e.Duration().Milliseconds()), ) if e.Err != nil { span.RecordError(e.Err) } span.End()}Retry policies handle transient failures with configurable exponential backoff.
type RetryPolicy interface { // NextDelay returns the delay before the next retry attempt and whether to retry. // If ok is false, no more retries should be attempted. // attempt starts at 0 for the first retry after the initial failure. NextDelay(attempt int, err error) (delay time.Duration, ok bool)}// Iris includes a sensible default: exponential backoff with jitter,// max 3 retries, 30s max delayclient := core.NewClient(provider) // Uses DefaultRetryPolicy()
// Or explicitlyclient := core.NewClient(provider, core.WithRetryPolicy(core.DefaultRetryPolicy()),)Use RetryConfig to create a policy with custom settings:
type RetryConfig struct { MaxRetries int // Maximum number of retry attempts (default: 3) BaseDelay time.Duration // Initial delay before first retry (default: 1s) MaxDelay time.Duration // Maximum delay cap (default: 30s) Jitter float64 // Jitter factor 0.0-1.0 (default: 0.2)}
policy := core.NewRetryPolicy(core.RetryConfig{ MaxRetries: 5, BaseDelay: 500 * time.Millisecond, MaxDelay: 60 * time.Second, Jitter: 0.3,})
client := core.NewClient(provider, core.WithRetryPolicy(policy),)The default retry policy automatically classifies errors:
| Error Type | Retryable | Reason |
|---|---|---|
ErrRateLimited (429) | ✓ | Temporary, will resolve |
ErrServer (500+) | ✓ | Transient infrastructure issue |
ErrNetwork | ✓ | Network glitch |
ErrUnauthorized (401) | ✗ | Invalid credentials won’t change |
ErrBadRequest (400) | ✗ | Request itself is malformed |
ErrDecode | ✗ | Response parsing failure |
context.Canceled | ✗ | Explicit cancellation |
context.DeadlineExceeded | ✗ | Timeout by caller |
Implement the RetryPolicy interface for full control:
type MyRetryPolicy struct { maxAttempts int}
func (p *MyRetryPolicy) NextDelay(attempt int, err error) (time.Duration, bool) { if attempt >= p.maxAttempts { return 0, false }
// Only retry rate limits if !errors.Is(err, core.ErrRateLimited) { return 0, false }
// Fixed delay return 5 * time.Second, true}
client := core.NewClient(provider, core.WithRetryPolicy(&MyRetryPolicy{maxAttempts: 3}),)// Pass nil to disable retries entirelyclient := core.NewClient(provider, core.WithRetryPolicy(nil),)The core.Secret type wraps sensitive values to prevent accidental logging or exposure.
type Secret struct { value string}
func NewSecret(value string) Secret { return Secret{value: value}}
func (s Secret) String() string { return "[REDACTED]" // Never exposes actual value}
func (s Secret) Expose() string { return s.value // Explicit access required}
func (s Secret) IsEmpty() bool { return s.value == ""}// Create a secretapiKey := core.NewSecret("sk-actual-api-key-here")
// Safe to loglog.Printf("Using API key: %s", apiKey)// Output: Using API key: [REDACTED]
// Explicit access for actual useprovider := openai.New(apiKey.Expose())The encrypted keystore returns secrets:
keystore, err := core.LoadKeystore()if err != nil { return err}
// Returns core.Secret, not stringapiKey, err := keystore.Get("openai")if err != nil { return err}
// Safe to pass around - won't leak in logsprovider := openai.New(apiKey.Expose())Iris provides structured errors for provider failures and sentinel errors for classification.
The primary error type for API failures with full context:
type ProviderError struct { Provider string // Provider identifier (e.g., "openai") Status int // HTTP status code RequestID string // Provider request ID (for support) Code string // Provider error code Message string // Error description Err error // Underlying/wrapped error}
// Error implements the error interfacefunc (e *ProviderError) Error() string// Unwrap returns the underlying error for error chainingfunc (e *ProviderError) Unwrap() errorUse sentinel errors for error classification with errors.Is():
var ( ErrUnauthorized = errors.New("unauthorized") // Invalid API key (401) ErrRateLimited = errors.New("rate limited") // Too many requests (429) ErrBadRequest = errors.New("bad request") // Invalid request (400) ErrNotFound = errors.New("not found") // Resource not found (404) ErrServer = errors.New("server error") // Provider issue (5xx) ErrNetwork = errors.New("network error") // Connection failure ErrDecode = errors.New("decode error") // Response parsing failed ErrNotSupported = errors.New("operation not supported"))
// Validation errors with guidancevar ( ErrModelRequired = errors.New("model required: pass a model ID to Client.Chat()") ErrNoMessages = errors.New("no messages: add at least one message using .User()"))resp, err := client.Chat("gpt-4o").User("Hello").GetResponse(ctx)if err != nil { // Check for sentinel errors (most common) switch { case errors.Is(err, core.ErrRateLimited): // Retry with backoff (handled automatically if retry policy enabled) log.Println("Rate limited, waiting...") time.Sleep(time.Second) return retry(ctx)
case errors.Is(err, core.ErrUnauthorized): // Invalid credentials - don't retry return fmt.Errorf("check API key: %w", err)
case errors.Is(err, core.ErrBadRequest): // Request is malformed - don't retry return fmt.Errorf("invalid request: %w", err)
case errors.Is(err, core.ErrServer): // Provider issue - may retry log.Println("Server error, retrying...") return retry(ctx)
case errors.Is(err, core.ErrNetwork): // Connection issue - may retry log.Println("Network error, retrying...") return retry(ctx) }
// Get full error details if available var pe *core.ProviderError if errors.As(err, &pe) { log.Printf("Provider %s error: status=%d code=%s request_id=%s", pe.Provider, pe.Status, pe.Code, pe.RequestID) }
return fmt.Errorf("request failed: %w", err)}Handle context cancellation and timeouts:
resp, err := client.Chat("gpt-4o").User("Hello").GetResponse(ctx)if err != nil { if errors.Is(err, context.Canceled) { return fmt.Errorf("request canceled by caller") } if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("request timed out") } return err}Providers
Configure specific providers. Provider Setup →
Streaming Guide
Advanced streaming patterns. Streaming →
Tools Guide
Build tool-augmented agents. Tools →
Examples
See complete working examples. Examples →