aisdk

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 10 Imported by: 0

README

aisdk-go

A single Go interface for calling Anthropic, OpenAI, and Gemini — one request/response shape, one streaming API, one tool-calling contract, one structured-output API, instead of three different provider SDKs with three different shapes. It's for Go developers building LLM-backed applications who want to write against one aisdk.Model interface and swap providers (or add retry/fallback across them) without rewriting call sites. It's modeled on the developer experience of Vercel's AI SDK, translated into idiomatic Go.

What you get beyond calling each provider's official SDK directly: a unified Generate/Stream request and response shape across all three providers, a unified tool-calling contract, generic structured-output extraction (GenerateStructured[T]), a provider-agnostic retry/fallback decorator (Fallback), and an OpenTelemetry observability decorator (otel.Wrap) — all implemented today, not on a roadmap, and all conformance-tested against fake HTTP transports so behavior is verified identically across providers.

Status

Feature-complete and pre-1.0: Generate, Stream, tool-calling, structured output, fallback/retry, and OpenTelemetry observability are all implemented and conformance-tested for Anthropic, OpenAI, and Gemini alike. No v1.0.0 tag has been cut yet — treat the API as stable in shape but not yet formally versioned.

Requirements

  • Go 1.26.5 or newer (see go.mod)
go get github.com/habibiramadhan-dev/aisdk-go

Quickstart

Get an API key at https://console.anthropic.com, then:

export ANTHROPIC_API_KEY=sk-ant-...
go run github.com/habibiramadhan-dev/aisdk-go/examples/basic-chat
import anthropicprovider "github.com/habibiramadhan-dev/aisdk-go/providers/anthropic"

provider := anthropicprovider.New(os.Getenv("ANTHROPIC_API_KEY"))
model := provider.Model("claude-sonnet-5")

answer, err := aisdk.Ask(context.Background(), model, "hello")

Providers

Every provider is constructed with a New(...) on its package, then bound to a specific model name via .Model(name), returning an aisdk.Model — the same interface regardless of provider.

Provider Import path Env var Example model name
Anthropic github.com/habibiramadhan-dev/aisdk-go/providers/anthropic ANTHROPIC_API_KEY claude-sonnet-5
OpenAI github.com/habibiramadhan-dev/aisdk-go/providers/openai OPENAI_API_KEY gpt-4o
Gemini github.com/habibiramadhan-dev/aisdk-go/providers/gemini GEMINI_API_KEY gemini-2.0-flash

Note: unlike the Anthropic and OpenAI adapters, gemini.New takes a context.Context and returns an error — its constructor is fallible.

Features

  • Generate — single-shot request/response, unified across providers. See examples/basic-chat.
  • Stream — token-level streaming via a <-chan StreamEvent. See examples/basic-chat.
  • Tool-calling — declare aisdk.Tool{Name, Description, Parameters} on a request, inspect ToolCalls in the response. See examples/tool-calling.
  • Structured outputaisdk.GenerateStructured[T] reflects a Go type into a JSON Schema and unmarshals the constrained response into it. See examples/structured-output.
  • Fallback / retryaisdk.Fallback(models, opts...) wraps a chain of models with configurable retry, backoff, and a time budget. See examples/fallback.
  • Observabilityotel.Wrap(model, opts...) decorates any aisdk.Model with OpenTelemetry GenAI semantic-convention spans. See examples/otel-tracing.

Coming from Vercel AI SDK

Vercel AI SDK aisdk-go Notes
generateText() model.Generate(ctx, req), or aisdk.Ask(ctx, model, prompt) for the simple case
streamText() model.Stream(ctx, req) returns <-chan aisdk.StreamEvent
generateObject() aisdk.GenerateStructured[T](ctx, model, req) T is any Go struct; its JSON Schema is derived via reflection
tool() aisdk.Tool{Name, Description, Parameters} in GenerateRequest.Tools Parameters is a raw JSON Schema (json.RawMessage)
wrapLanguageModel() aisdk.Fallback(...) / otel.Wrap(...) no dedicated middleware type — just Go interface composition, since every decorator implements the same aisdk.Model interface

No automatic multi-step tool-calling loop. Unlike some higher-level SDKs, v1 of aisdk-go does not run the tool-call loop for you. The caller drives it manually: call Generate, check resp.FinishReason == aisdk.FinishReasonToolCalls, pull the ToolCall out of resp.Message.Parts, execute the tool yourself, append the result as a RoleTool message, and call Generate again. See examples/tool-calling/main.go for the full reference implementation of this pattern.

Examples

All examples live under examples/ and are runnable with go run:

Security

See SECURITY.md for the vulnerability reporting process.

One fact worth calling out directly here: ToolCall.Arguments is untrusted, model-generated JSON. The SDK never validates, sandboxes, or executes it — that responsibility is entirely the caller's, as shown in examples/tool-calling/main.go.

Contributing

See CONTRIBUTING.md for how to get set up and submit changes.

License

MIT — see LICENSE.

Documentation

Overview

aisdk.go

fallback.go

stream.go

structured.go

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Ask

func Ask(ctx context.Context, model Model, prompt string) (string, error)

Ask wraps Generate for the single-message, no-tools case: send one string, get one string back, no GenerateRequest assembly required.

func GenerateStructured

func GenerateStructured[T any](ctx context.Context, model Model, req GenerateRequest) (T, error)

GenerateStructured reflects T into a JSON Schema (internal/schema), constrains model's response to match it, and unmarshals the result into T. It calls Model.Generate, never Model.Stream — streaming structured output ("streamObject") is out of scope for v1 (design.md §3).

T must not be self-referential — see internal/schema.Reflect's doc comment for why (an unrecoverable crash during reflection, not a returnable error).

Types

type ContentPart

type ContentPart struct {
	Type       ContentPartType
	Text       string      // set when Type is Text or Reasoning
	ToolCall   *ToolCall   // set when Type is ToolCall
	ToolResult *ToolResult // set when Type is ToolResult
}

ContentPart is one piece of a Message's content.

func TextPart

func TextPart(text string) ContentPart

TextPart is a convenience constructor for the common case of a plain text part.

type ContentPartType

type ContentPartType string

ContentPartType discriminates the variants of ContentPart.

const (
	ContentPartTypeText       ContentPartType = "text"
	ContentPartTypeReasoning  ContentPartType = "reasoning"
	ContentPartTypeToolCall   ContentPartType = "tool_call"
	ContentPartTypeToolResult ContentPartType = "tool_result"
)

type Error

type Error struct {
	Provider  string
	Code      ErrorCode
	Retryable bool
	RequestID string
	// RetryAfter is the provider's requested wait time before retrying, parsed
	// from a Retry-After response header when present. Zero means the
	// provider didn't supply one (or, for Gemini, that its SDK's error type
	// structurally has no access to response headers at all — an honest gap,
	// not a bug; see providers/gemini's mapError, which never sets this).
	RetryAfter time.Duration
	Cause      error
}

Error is the common error type every provider adapter maps its native SDK errors into. Cause is sanitized by the adapter before wrapping — it must never carry raw HTTP headers or request/response bodies (see design.md §8).

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorCode

type ErrorCode string

ErrorCode classifies an Error independent of which provider produced it.

const (
	ErrorCodeRateLimited      ErrorCode = "rate_limited"
	ErrorCodeAuthFailed       ErrorCode = "auth_failed"
	ErrorCodePermissionDenied ErrorCode = "permission_denied"
	ErrorCodeInvalidRequest   ErrorCode = "invalid_request"
	ErrorCodeOverloaded       ErrorCode = "overloaded"
	ErrorCodeServerError      ErrorCode = "server_error"
	ErrorCodeTimeout          ErrorCode = "timeout"
	ErrorCodeContextLength    ErrorCode = "context_length"
)

type FallbackOption

type FallbackOption func(*fallbackConfig)

FallbackOption configures a Fallback-wrapped Model.

func WithBackoff

func WithBackoff(fn func(attempt int) time.Duration) FallbackOption

WithBackoff sets the wait-time function used between retry attempts on the same model. It's called with the zero-based attempt number (0 for the wait before the first retry). Ignored for a given attempt when the failing error carries a non-zero RetryAfter — that value is honored instead. Default: full-jitter exponential backoff, 500ms base, 10s cap.

func WithBudget

func WithBudget(d time.Duration) FallbackOption

WithBudget sets the overall wall-clock time budget for the whole fallback chain — retries and fallbacks across every model combined, not a per-model limit. Once the budget is exhausted, Fallback stops trying further models/retries and returns whatever errors it has collected so far. This exists so a stuck request can't retry-storm across every configured provider for an unbounded amount of time. Default: 2 minutes.

func WithMaxRetries

func WithMaxRetries(n int) FallbackOption

WithMaxRetries sets how many additional attempts Fallback makes on the SAME model after a retryable error, before moving to the next model in the chain. 0 means no retries — a single failure moves straight to fallback. Default: 2.

type FinishReason

type FinishReason string

FinishReason explains why generation stopped. A Refusal or MaxTokens result is a successful response, not an error.

const (
	FinishReasonStop      FinishReason = "stop"
	FinishReasonMaxTokens FinishReason = "max_tokens"
	FinishReasonToolCalls FinishReason = "tool_calls"
	FinishReasonRefusal   FinishReason = "refusal"
)

type GenerateRequest

type GenerateRequest struct {
	// System is the system prompt/instruction. Never a Message with a system
	// role — Anthropic and Gemini both expose this as a dedicated top-level
	// field natively; the OpenAI adapter folds it into Messages itself.
	System   string
	Messages []Message
	Tools    []Tool
	// ResponseSchema, when set, constrains the response to valid JSON
	// matching this JSON Schema document. Callers normally don't set this
	// directly — GenerateStructured (structured.go) sets it after
	// reflecting a Go type via internal/schema.
	ResponseSchema  json.RawMessage
	MaxTokens       int
	Temperature     float64
	ProviderOptions map[string]any
}

GenerateRequest is the unified request shape across all providers.

type GenerateResponse

type GenerateResponse struct {
	Message      Message
	FinishReason FinishReason
	Usage        Usage
}

GenerateResponse is the unified response shape across all providers.

type Message

type Message struct {
	Role  Role
	Parts []ContentPart
}

Message is one turn in a conversation.

type Model

type Model interface {
	Generate(ctx context.Context, req GenerateRequest) (GenerateResponse, error)
	Stream(ctx context.Context, req GenerateRequest) (<-chan StreamEvent, error)
}

Model is the unified interface every provider adapter implements.

func Fallback

func Fallback(models []Model, opts ...FallbackOption) Model

Fallback wraps models so that a Generate/Stream call tries modelA first, retrying transient failures on it with backoff before moving to modelB, and so on. See design.md §6 for the full retry/fallback contract this implements — in short: a Retryable error (other than Overloaded) is retried on the same model up to WithMaxRetries times; ErrorCodeOverloaded skips straight to the next model without burning retries on this one; a non-Retryable error (or any error that doesn't unwrap to *aisdk.Error at all) fails the whole chain immediately, since those tend to fail identically on every provider. If every model is exhausted, the returned error is errors.Join of every attempt's error — errors.As/errors.Is still work through it.

Fallback panics if models is empty — there is no sane way to satisfy a Generate/Stream call with zero models, and returning a Model that would always silently misbehave (an empty errors.Join is nil, which would look like success) is worse than failing loudly at construction time.

Fallback only covers Generate calls and the initial connection for Stream — once Stream has returned a channel to the caller, Fallback does not intercept, retry, or replay anything flowing through it; a mid-stream failure surfaces as StreamEvent{Type: Error} exactly as it would from a bare adapter, and retrying the whole call is left to the caller.

type ModelInfo

type ModelInfo interface {
	// Provider returns the provider name (e.g. "anthropic", "openai", "gemini").
	Provider() string
	// ModelName returns the specific model name (e.g. "claude-sonnet-5").
	ModelName() string
}

ModelInfo is an optional interface a Model MAY implement to expose its provider/model identity for introspection. It is deliberately NOT part of the required Model interface — a test fake, or a decorator composing multiple providers (like Fallback), has no single meaningful answer to "what provider/model is this." otel.Wrap uses this (via a type assertion, gracefully degrading when absent) to populate GenAI semantic convention attributes (gen_ai.system, gen_ai.request.model) — but this interface itself has no otel dependency; it's a general-purpose introspection hook any code can use.

type Provider

type Provider interface {
	Model(name string) Model
}

Provider produces Models by name (e.g. "claude-sonnet-5", "gpt-5").

type Role

type Role string

Role identifies who sent a Message. There is no "system" role — see GenerateRequest.System.

const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
)

type StreamEvent

type StreamEvent struct {
	Type         StreamEventType
	Delta        string
	ToolCall     *ToolCall
	FinishReason FinishReason // set when Type is StreamEventTypeFinish
	Usage        Usage        // set when Type is StreamEventTypeFinish
	Err          error        // set when Type is StreamEventTypeError
}

StreamEvent is one item from a Model.Stream channel. Defined in Fase 1; no adapter emits these yet until Fase 2 (design.md §10).

type StreamEventType

type StreamEventType string

StreamEventType discriminates the variants of StreamEvent.

const (
	StreamEventTypeTextDelta      StreamEventType = "text_delta"
	StreamEventTypeReasoningDelta StreamEventType = "reasoning_delta"
	StreamEventTypeToolCallDelta  StreamEventType = "tool_call_delta"
	StreamEventTypeFinish         StreamEventType = "finish"
	StreamEventTypeError          StreamEventType = "error"
)

type Tool

type Tool struct {
	Name        string
	Description string
	Parameters  json.RawMessage
}

Tool describes a function the model may call. Parameters is a JSON Schema.

type ToolCall

type ToolCall struct {
	ID        string
	Name      string
	Arguments json.RawMessage
}

ToolCall is model-generated output naming a Tool and its arguments. Arguments is untrusted, model-generated data — the SDK never executes it (see design.md §8).

type ToolResult

type ToolResult struct {
	ToolCallID string
	Content    string
	IsError    bool
}

ToolResult is the caller's response to a ToolCall, sent back in a follow-up Message with Role RoleTool.

type Usage

type Usage struct {
	InputTokens  int
	OutputTokens int
}

Usage reports token consumption for a single Generate call.

Directories

Path Synopsis
aisdktest/conformance.go
aisdktest/conformance.go
examples
basic-chat command
examples/basic-chat/main.go
examples/basic-chat/main.go
fallback command
examples/fallback/main.go
examples/fallback/main.go
otel-tracing command
examples/otel-tracing/main.go
examples/otel-tracing/main.go
structured-output command
examples/structured-output/main.go
examples/structured-output/main.go
tool-calling command
examples/tool-calling/main.go
examples/tool-calling/main.go
internal
httpretry
internal/httpretry/retryafter.go
internal/httpretry/retryafter.go
schema
internal/schema/schema.go
internal/schema/schema.go
otel/otel.go
otel/otel.go
providers
anthropic
providers/anthropic/convert.go
providers/anthropic/convert.go
gemini
providers/gemini/convert.go
providers/gemini/convert.go
openai
providers/openai/convert.go
providers/openai/convert.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL