Claude Provider

The Claude provider implements the genai.Provider interface for the Anthropic Messages API. It supports all Claude models including Claude Opus, Sonnet, and Haiku.

Quick Start

import (
    "context"
    "fmt"

    "oss.nandlabs.io/golly/genai"
    "oss.nandlabs.io/golly/genai/impl"
)

provider := impl.NewClaudeProvider("sk-ant-...", nil)
defer provider.Close()

msg := genai.NewTextMessage(genai.RoleUser, "Hello, what can you do?")
resp, err := provider.Generate(context.Background(), "claude-sonnet-4-20250514", msg, nil)
if err != nil {
    panic(err)
}
for _, c := range resp.Candidates {
    for _, p := range c.Message.Parts {
        if p.Text != nil {
            fmt.Println(p.Text.Text)
        }
    }
}

Configuration

Simple Constructor

// Uses x-api-key header auth (Anthropic's standard mechanism)
provider := impl.NewClaudeProvider("sk-ant-...", nil)

The simple constructor wraps the API key with clients.NewAPIKeyAuth("x-api-key", apiKey).

Full Configuration

import (
    "oss.nandlabs.io/golly/clients"
    "oss.nandlabs.io/golly/genai/impl"
    "oss.nandlabs.io/golly/rest"
)

provider := impl.NewClaudeProviderWithConfig(&impl.ClaudeProviderConfig{
    Auth:       clients.NewAPIKeyAuth("x-api-key", "sk-ant-..."),
    APIVersion: "2023-06-01",
    BaseURL:    "https://api.anthropic.com",
    Models:     []string{"claude-sonnet-4-20250514", "claude-haiku-4-20250414", "claude-opus-4-20250514"},
    Description: "Production Claude",
    Version:     "2.0.0",
    ExtraHeaders: map[string]string{
        "anthropic-beta": "prompt-caching-2024-07-31",
    },
}, &rest.ClientOpts{
    // Optional: configure retry, circuit breaker, timeouts, etc.
})

Config Reference

FieldTypeDefaultDescription
Authclients.AuthProviderโ€”Authentication provider (required)
APIVersionstring"2023-06-01"Anthropic API version (sent as anthropic-version header)
BaseURLstringhttps://api.anthropic.comAPI base URL
Models[]stringnilList of available model IDs (informational)
Descriptionstring"Anthropic Claude API provider..."Provider description
Versionstring"1.0.0"Provider version
ExtraHeadersmap[string]stringnilAdditional HTTP headers on every request

Authentication

Standard Anthropic API

Anthropic uses an x-api-key header (not Bearer tokens):

// Convenience โ€” wraps key as x-api-key header automatically
provider := impl.NewClaudeProvider("sk-ant-...", nil)

// Equivalent explicit config
provider = impl.NewClaudeProviderWithConfig(&impl.ClaudeProviderConfig{
    Auth: clients.NewAPIKeyAuth("x-api-key", "sk-ant-..."),
}, nil)

Custom Auth Provider

For keys stored in external secret stores:

type SecretManagerAuth struct {
    client     *secretmanager.Client
    secretName string
}

func (s *SecretManagerAuth) Type() clients.AuthType { return clients.AuthTypeAPIKey }
func (s *SecretManagerAuth) User() (string, error)  { return "", nil }
func (s *SecretManagerAuth) Pass() (string, error)  { return "", nil }
func (s *SecretManagerAuth) Token() (string, error) {
    result, err := s.client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
        Name: s.secretName,
    })
    if err != nil {
        return "", err
    }
    return string(result.Payload.Data), nil
}

provider := impl.NewClaudeProviderWithConfig(&impl.ClaudeProviderConfig{
    Auth: &SecretManagerAuth{
        client:     smClient,
        secretName: "projects/my-proj/secrets/claude-key/versions/latest",
    },
}, nil)

Supported Options

GenAI OptionClaude ParameterTypeDescription
OptionMaxTokensmax_tokensintMaximum tokens in the response (default: 4096, required by API)
OptionTemperaturetemperaturefloat32Sampling temperature (0.0โ€“1.0)
OptionTopPtop_pfloat32Nucleus sampling threshold
OptionTopKtop_kintTop-K sampling (Claude-specific, not available in OpenAI)
OptionStopWordsstop_sequences[]stringStop sequences (up to 4)
OptionSystemInstructionssystemstringSystem prompt (top-level field, not a message)
โ„น๏ธ
Unlike OpenAI, Claude does not support frequency_penalty, presence_penalty, seed, candidate_count (n), or response_format. These options are silently ignored.

Generating Responses

Basic Generation

msg := genai.NewTextMessage(genai.RoleUser, "Explain quantum entanglement.")
opts := genai.NewOptionsBuilder().
    SetMaxTokens(1024).
    SetTemperature(0.5).
    Build()

resp, err := provider.Generate(ctx, "claude-sonnet-4-20250514", msg, opts)

System Instructions

Claude handles system instructions as a top-level system field (not as a message in the conversation). The provider handles this mapping automatically:

opts := genai.NewOptionsBuilder().
    SetMaxTokens(2048).
    Build()
opts.Set(genai.OptionSystemInstructions,
    "You are an expert Go programmer. Always provide idiomatic Go code with error handling.")

msg := genai.NewTextMessage(genai.RoleUser, "How do I implement a worker pool?")
resp, err := provider.Generate(ctx, "claude-sonnet-4-20250514", msg, opts)

Streaming

Claude’s streaming uses typed SSE events (content_block_delta, message_delta, message_start, message_stop) which are all mapped to genai.GenResponse automatically:

msg := genai.NewTextMessage(genai.RoleUser, "Write a detailed analysis of Go's concurrency model.")
opts := genai.NewOptionsBuilder().SetMaxTokens(2048).Build()

respCh, errCh := provider.GenerateStream(ctx, "claude-sonnet-4-20250514", msg, opts)
for resp := range respCh {
    for _, c := range resp.Candidates {
        for _, p := range c.Message.Parts {
            if p.Text != nil {
                fmt.Print(p.Text.Text) // prints token by token
            }
        }
    }
}
if err := <-errCh; err != nil {
    // handle streaming error
}

Streaming events emitted:

Claude SSE EventGenResponse Behaviour
message_startEmits Meta.InputTokens
content_block_deltaEmits text chunks as Candidate.Message.Parts
message_deltaEmits FinishReason and Meta.OutputTokens
message_stopStream ends
errorError sent to error channel

Multi-Modal (Vision)

Claude supports inline base64 images (JPEG, PNG, GIF, WebP). Unlike OpenAI, Claude does not support image URLs directly:

// Image from bytes (supported)
msg := genai.NewTextMessage(genai.RoleUser, "What's in this image?")
genai.AddBinPart(msg, "photo", jpegBytes, "image/jpeg")

resp, err := provider.Generate(ctx, "claude-sonnet-4-20250514", msg, nil)
โš ๏ธ
If you pass a FilePart with an image URL, the provider will include a text placeholder noting that Claude doesn’t support image URLs directly. Download the image first and use BinPart instead.

Tool / Function Calling

Tool calls are mapped between genai’s FuncCallPart/FuncResponsePart and Claude’s tool_use/tool_result content block types:

resp, _ := provider.Generate(ctx, "claude-sonnet-4-20250514", msg, opts)
for _, c := range resp.Candidates {
    for _, p := range c.Message.Parts {
        if p.FuncCall != nil {
            fmt.Printf("Tool call: %s(%v)\n", p.FuncCall.FunctionName, p.FuncCall.Arguments)
        }
    }
}

Response Structure

resp, _ := provider.Generate(ctx, "claude-sonnet-4-20250514", msg, opts)

// Claude returns a single candidate (no multi-candidate support)
candidate := resp.Candidates[0]
candidate.FinishReason // genai.FinishReasonEndTurn, FinishReasonLength, FinishReasonStop, FinishReasonToolCall
candidate.Message      // *genai.Message with Parts (text, tool_use)

// Token usage metadata
resp.Meta.InputTokens   // prompt tokens
resp.Meta.OutputTokens  // completion tokens
resp.Meta.TotalTokens   // input + output
resp.Meta.CachedTokens  // cache_read_input_tokens (if prompt caching is enabled)

Finish reason mapping:

Claude stop_reasonGenAI FinishReason
end_turnFinishReasonEndTurn
max_tokensFinishReasonLength
stop_sequenceFinishReasonStop
tool_useFinishReasonToolCall
null (streaming)FinishReasonInProgress

API Differences from OpenAI

The provider abstracts away these differences, but they’re useful to know:

AspectOpenAIClaude
Auth headerAuthorization: Bearer <key>x-api-key: <key>
System messagesIn messages[] with role: "system"Top-level system field
Content formatString or array of content partsAlways array of content blocks
Message rolessystem, user, assistant, tooluser, assistant only
max_tokensOptionalRequired (default: 4096)
top_kNot supportedSupported
n (candidates)SupportedNot supported
seedSupportedNot supported
frequency_penaltySupportedNot supported
Image URLsSupported directlyNot supported (use base64)
Streaming formatdata: {...} / data: [DONE]event: + data: typed SSE
API versionNot requiredanthropic-version header required

Error Handling

resp, err := provider.Generate(ctx, "claude-sonnet-4-20250514", msg, opts)
if err != nil {
    // Error format: "claude API error [error_type]: error message"
    log.Fatal(err)
}

Common error types:

Error TypeCause
authentication_errorInvalid or missing API key
invalid_request_errorBad request parameters
rate_limit_errorRate limit exceeded
overloaded_errorAPI temporarily overloaded
not_found_errorModel not found

Beta Features

Enable Anthropic beta features using ExtraHeaders:

// Prompt caching
provider := impl.NewClaudeProviderWithConfig(&impl.ClaudeProviderConfig{
    Auth: clients.NewAPIKeyAuth("x-api-key", "sk-ant-..."),
    ExtraHeaders: map[string]string{
        "anthropic-beta": "prompt-caching-2024-07-31",
    },
}, nil)

// Multiple beta features (comma-separated)
provider = impl.NewClaudeProviderWithConfig(&impl.ClaudeProviderConfig{
    Auth: clients.NewAPIKeyAuth("x-api-key", "sk-ant-..."),
    ExtraHeaders: map[string]string{
        "anthropic-beta": "prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
    },
}, nil)