GoAI SDK - AI Agent Skill
Instructions for AI coding agents helping developers use the GoAI SDK. Load this file when a user's project imports
github.com/zendev-sh/goai.
Overview
GoAI is a Go SDK for AI applications. One unified API across 25+ LLM providers. Inspired by the Vercel AI SDK, adapted to Go idioms (generics, interfaces, channels).
- Package:
github.com/zendev-sh/goai - Go version: 1.25+
- Dependencies: stdlib +
golang.org/x/oauth2for Vertex AI. Optionalobservability/otelsubmodule adds OTel SDK (separate go.mod, not pulled unless imported). - Docs: https://goai.sh
- GoDoc: https://pkg.go.dev/github.com/zendev-sh/goai
Quick Reference
go get github.com/zendev-sh/goai@latest
import (
"github.com/zendev-sh/goai"
"github.com/zendev-sh/goai/provider/openai" // or any provider
)
Core API
8 Top-Level Functions
| Function | Purpose | Returns |
|---|---|---|
goai.GenerateText(ctx, model, opts...) |
Non-streaming text generation | (*TextResult, error) |
goai.StreamText(ctx, model, opts...) |
Streaming text via channels | (*TextStream, error) |
goai.GenerateObject[T](ctx, model, opts...) |
Typed structured output (JSON) | (*ObjectResult[T], error) |
goai.StreamObject[T](ctx, model, opts...) |
Streaming structured output | (*ObjectStream[T], error) |
goai.Embed(ctx, model, text, opts...) |
Single text embedding | (*EmbedResult, error) |
goai.EmbedMany(ctx, model, texts, opts...) |
Batch embeddings (auto-chunked) | (*EmbedManyResult, error) |
goai.GenerateImage(ctx, model, imgOpts...) |
Image generation | (*ImageResult, error) |
goai.GenerateVideo(ctx, model, vidOpts...) |
Video generation | (*VideoResult, error) |
Model Constructors
Each provider has Chat(), and optionally Embedding(), Image(), and Video():
// Language models
openai.Chat("gpt-4o")
anthropic.Chat("claude-sonnet-4-20250514")
google.Chat("gemini-2.5-flash")
bedrock.Chat("anthropic.claude-sonnet-4-20250514-v1:0")
azure.Chat("gpt-4o", azure.WithEndpoint("https://my-resource.openai.azure.com"))
vertex.Chat("gemini-2.5-flash", vertex.WithProject("my-project"), vertex.WithLocation("us-central1"))
groq.Chat("llama-3.3-70b-versatile")
ollama.Chat("llama3.2")
// Embedding models
openai.Embedding("text-embedding-3-small")
google.Embedding("text-embedding-004")
cohere.Embedding("embed-english-v3.0")
ollama.Embedding("nomic-embed-text")
// Image models
openai.Image("gpt-image-1")
google.Image("imagen-4.0-generate-001")
// Video models
google.Video("veo-3.1-generate-preview")
Auth - Auto-Resolved from Environment
Providers auto-read API keys from env vars. No explicit config needed:
| Provider | Env Var |
|---|---|
| OpenAI | OPENAI_API_KEY |
| Anthropic | ANTHROPIC_API_KEY |
GOOGLE_GENERATIVE_AI_API_KEY or GEMINI_API_KEY |
|
| Bedrock | AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_REGION |
| Azure | AZURE_OPENAI_API_KEY |
| Vertex AI | Application Default Credentials (ADC) |
| xAI | XAI_API_KEY |
| Groq | GROQ_API_KEY |
| Cohere | COHERE_API_KEY |
| Mistral | MISTRAL_API_KEY |
| DeepSeek | DEEPSEEK_API_KEY |
Or set explicitly:
model := openai.Chat("gpt-4o", openai.WithAPIKey("sk-..."))
Most providers support these options (Bedrock uses AWS credential options; Ollama requires no auth):
openai.WithAPIKey(key) // static API key
openai.WithTokenSource(ts) // dynamic auth (OAuth, service accounts)
openai.WithBaseURL(url) // override endpoint (Azure uses WithEndpoint)
openai.WithHeaders(h) // custom HTTP headers
openai.WithHTTPClient(c) // custom HTTP transport
Patterns and Examples
1. Basic Text Generation
result, err := goai.GenerateText(ctx, openai.Chat("gpt-4o"),
goai.WithSystem("You are a helpful assistant."),
goai.WithPrompt("What is Go?"),
)
if err != nil {
return err
}
fmt.Println(result.Text)
2. Streaming
stream, err := goai.StreamText(ctx, openai.Chat("gpt-4o"),
goai.WithPrompt("Write a poem about Go."),
)
if err != nil {
return err
}
// Option A: text-only channel
for text := range stream.TextStream() {
fmt.Print(text)
}
// Option B: raw chunks (mutually exclusive with TextStream)
// for chunk := range stream.Stream() { ... }
// Always available after streaming completes:
result := stream.Result()
fmt.Printf("\nTokens: %d in, %d out\n", result.TotalUsage.InputTokens, result.TotalUsage.OutputTokens)
Important: Stream() and TextStream() are mutually exclusive. Only call one. Result() can always be called after either.
3. Structured Output (Generics)
type Recipe struct {
Name string `json:"name" jsonschema:"description=Recipe name"`
Ingredients []string `json:"ingredients"`
Steps []string `json:"steps"`
Difficulty string `json:"difficulty" jsonschema:"enum=easy|medium|hard"`
}
result, err := goai.GenerateObject[Recipe](ctx, openai.Chat("gpt-4o"),
goai.WithPrompt("Chocolate chip cookies recipe"),
)
if err != nil {
return err
}
fmt.Println(result.Object.Name) // typed access
fmt.Println(result.Object.Difficulty) // "easy", "medium", or "hard"
Schema is auto-generated from struct tags. Supported tags:
json:"name"- property namejson:"-"- exclude fieldjsonschema:"description=..."- adds descriptionjsonschema:"enum=a|b|c"- restricts to enum values
Supported types: string, bool, int/uint (all sizes), float32/64, slices, maps (string keys), structs (embedded structs flattened), pointers (nullable).
4. Streaming Structured Output
stream, err := goai.StreamObject[Recipe](ctx, model,
goai.WithPrompt("Chocolate chip cookies recipe"),
)
if err != nil {
return err
}
for partial := range stream.PartialObjectStream() {
fmt.Printf("Name so far: %s\n", partial.Name)
}
result, err := stream.Result()
// result.Object is the final validated Recipe
5. Tool Calling
weatherTool := goai.Tool{
Name: "get_weather",
Description: "Get weather for a city.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}`),
Execute: func(ctx context.Context, input json.RawMessage) (string, error) {
var params struct {
City string `json:"city"`
}
if err := json.Unmarshal(input, ¶ms); err != nil {
return "", err
}
return fmt.Sprintf("72F and sunny in %s", params.City), nil
},
}
result, err := goai.GenerateText(ctx, model,
goai.WithPrompt("What's the weather in Tokyo?"),
goai.WithTools(weatherTool),
goai.WithMaxSteps(3), // enable auto tool loop
)
Key rules:
WithMaxSteps(1)(default) = no auto loop, tool calls returned inresult.ToolCallsWithMaxSteps(n)where n > 1 = auto loop: generate -> execute tools -> re-generate -> repeat- Tools without
Executeare sent as definitions only (for manual handling) - Tool errors are passed back to the model as
"error: ..."messages
6. Conversation History
result, err := goai.GenerateText(ctx, model,
goai.WithSystem("You are a helpful assistant."),
goai.WithMessages(
goai.UserMessage("Hi, my name is Alice."),
goai.AssistantMessage("Hello Alice! How can I help you?"),
goai.UserMessage("What's my name?"),
),
)
Message builders:
goai.SystemMessage(text)- system messagegoai.UserMessage(text)- user messagegoai.AssistantMessage(text)- assistant messagegoai.ToolMessage(toolCallID, toolName, output)- tool result
7. Embeddings
// Single embedding
model := openai.Embedding("text-embedding-3-small")
result, err := goai.Embed(ctx, model, "Hello world")
fmt.Printf("Dimensions: %d\n", len(result.Embedding))
// Batch embeddings (auto-chunked, parallel)
results, err := goai.EmbedMany(ctx, model, []string{"text1", "text2", "text3"},
goai.WithMaxParallelCalls(4),
)
8. Image Generation
result, err := goai.GenerateImage(ctx, openai.Image("gpt-image-1"),
goai.WithImagePrompt("A gopher writing Go code"),
goai.WithImageSize("1024x1024"),
goai.WithImageCount(1),
)
// result.Images[0].Data = raw bytes, result.Images[0].MediaType = "image/png"
Note: GenerateImage uses ImageOption (not Option): WithImagePrompt, WithImageCount, WithImageSize, WithAspectRatio, WithImageMaxRetries, WithImageTimeout, WithImageProviderOptions.
9. Video Generation
result, err := goai.GenerateVideo(ctx, google.Video("veo-3.1-generate-preview"),
goai.WithVideoPrompt("A gopher writing Go code"),
goai.WithVideoAspectRatio("16:9"),
goai.WithVideoDuration(8*time.Second),
)
// result.Video.Data = raw bytes, result.Video.MediaType = "video/mp4"
Video generation handles asynchronous operation polling and authenticated downloads. Use WithVideoImage for image-to-video and WithVideoPollTimeout to bound long-running operations.
10. Provider-Defined Tools (Server-Side)
Built-in tools executed by the provider, not your code:
// OpenAI web search
result, err := goai.GenerateText(ctx, openai.Chat("gpt-4o"),
goai.WithPrompt("Latest Go release?"),
goai.WithTools(providerTool(openai.Tools.WebSearch())),
)
// Google Search grounding
result, err := goai.GenerateText(ctx, google.Chat("gemini-2.5-flash"),
goai.WithPrompt("Latest news about Go"),
goai.WithTools(providerTool(google.Tools.GoogleSearch())),
)
// Anthropic code execution
result, err := goai.GenerateText(ctx, anthropic.Chat("claude-sonnet-4-20250514"),
goai.WithPrompt("Calculate fibonacci(10)"),
goai.WithTools(providerTool(anthropic.Tools.CodeExecution())),
)
// Helper to convert provider.ToolDefinition → goai.Tool:
func providerTool(td provider.ToolDefinition) goai.Tool {
return goai.Tool{
Name: td.Name, ProviderDefinedType: td.ProviderDefinedType,
ProviderDefinedOptions: td.ProviderDefinedOptions,
}
}
Available provider tools:
- OpenAI:
WebSearch(),CodeInterpreter(),ImageGeneration(),FileSearch(opts...) - Anthropic:
WebSearch(),WebFetch(),Computer(opts),Bash(),TextEditor(),CodeExecution(),ToolSearchToolRegex(),ToolSearchToolBM25()(+ versioned variants) - Google:
GoogleSearch(),URLContext(),CodeExecution(),ComputerUse(opts...) - xAI:
WebSearch(),XSearch() - Groq:
BrowserSearch()
11. Observability Hooks
result, err := goai.GenerateText(ctx, model,
goai.WithPrompt("Hello"),
goai.WithOnRequest(func(info goai.RequestInfo) {
log.Printf("Calling %s with %d messages", info.Model, info.MessageCount)
}),
goai.WithOnResponse(func(info goai.ResponseInfo) {
log.Printf("Response in %v, tokens: %d", info.Latency, info.Usage.TotalTokens)
}),
goai.WithOnStepFinish(func(step goai.StepResult) {
log.Printf("Step %d: %s, tools: %d", step.Number, step.FinishReason, len(step.ToolCalls))
}),
goai.WithOnToolCall(func(info goai.ToolCallInfo) {
log.Printf("Tool %s (step %d) took %v, input: %s", info.ToolName, info.Step, info.Duration, info.Input)
}),
)
Complete Options Reference
Core Options (type goai.Option)
goai.WithSystem(s string) // system prompt
goai.WithPrompt(s string) // single user message (shorthand)
goai.WithMessages(msgs ...provider.Message) // conversation history
goai.WithPromptCaching(bool) // enable prompt caching
goai.WithTools(tools ...goai.Tool) // available tools
goai.WithMaxSteps(n int) // auto tool loop iterations (default: 1)
goai.WithToolChoice(tc string) // "auto" | "none" | "required" | "<tool_name>"
goai.WithMaxOutputTokens(n int) // response length limit
goai.WithTemperature(t float64) // randomness (0.0 - 2.0)
goai.WithTopP(p float64) // nucleus sampling
goai.WithTopK(k int) // top-K sampling
goai.WithFrequencyPenalty(p float64) // frequency penalty
goai.WithPresencePenalty(p float64) // presence penalty
goai.WithSeed(s int) // deterministic generation
goai.WithStopSequences(seqs ...string) // stop sequences
goai.WithMaxRetries(n int) // retry count (default: 2)
goai.WithRetryObserver(fn RetryObserver) // observe retry attempts
goai.WithTimeout(d time.Duration) // request timeout
goai.WithHeaders(h map[string]string) // additional HTTP headers
goai.WithProviderOptions(opts map[string]any) // provider-specific params
goai.WithOnRequest(fn func(RequestInfo)) // before each API call
goai.WithOnResponse(fn func(ResponseInfo)) // after each API call
goai.WithOnStepFinish(fn func(StepResult)) // after each step
goai.WithOnToolCallStart(fn func(ToolCallStartInfo)) // before each tool execution
goai.WithOnToolCall(fn func(ToolCallInfo)) // after each tool execution
goai.WithOnFinish(fn func(FinishInfo)) // after generation terminates
goai.WithOnPanic(fn func(PanicInfo)) // observe recovered callback panics
goai.WithOnBeforeStep(fn func(BeforeStepInfo) BeforeStepResult)
goai.WithOnBeforeToolExecute(fn func(BeforeToolExecuteInfo) BeforeToolExecuteResult)
goai.WithOnAfterToolExecute(fn func(AfterToolExecuteInfo) AfterToolExecuteResult)
goai.WithExplicitSchema(schema json.RawMessage) // override auto-generated schema
goai.WithSchemaName(name string) // schema name (default: "response")
goai.WithMaxParallelCalls(n int) // EmbedMany parallelism (default: 4)
goai.WithEmbeddingProviderOptions(opts map[string]any) // embedding-specific params
Image Options (type goai.ImageOption)
goai.WithImagePrompt(prompt string) // text prompt
goai.WithImageCount(n int) // number of images (default: 1)
goai.WithImageSize(size string) // e.g. "1024x1024"
goai.WithAspectRatio(ratio string) // e.g. "16:9"
goai.WithImageMaxRetries(n int) // retries on 429/5xx
goai.WithImageTimeout(d time.Duration) // overall timeout
goai.WithImageProviderOptions(opts map[string]any) // provider-specific params
Video Options (type goai.VideoOption)
goai.WithVideoPrompt(prompt string)
goai.WithVideoImage(image provider.MediaData)
goai.WithVideoCount(n int)
goai.WithVideoAspectRatio(ratio string)
goai.WithVideoResolution(resolution string)
goai.WithVideoDuration(d time.Duration)
goai.WithVideoFPS(fps int)
goai.WithVideoSeed(seed int64)
goai.WithVideoFrameImages(frames ...provider.VideoFrame)
goai.WithVideoInputReferences(refs ...provider.MediaData)
goai.WithVideoAudio(enabled bool)
goai.WithVideoProviderOptions(opts map[string]any)
goai.WithVideoMaxRetries(n int)
goai.WithVideoTimeout(d time.Duration)
goai.WithVideoPollInterval(d time.Duration)
goai.WithVideoPollTimeout(d time.Duration)
Result Types
TextResult (from GenerateText / StreamText)
type TextResult struct {
Text string // accumulated generated text
Reasoning string // accumulated reasoning text
ToolCalls []provider.ToolCall // tool calls from final step
Steps []StepResult // per-step results
TotalUsage provider.Usage // aggregated token usage
FinishReason provider.FinishReason // "stop", "tool-calls", "length", etc.
Response provider.ResponseMetadata // provider metadata (ID, Model)
ProviderMetadata map[string]map[string]any // provider-specific response data
Sources []provider.Source // citations/references
StepsExhausted bool // whether MaxSteps was exhausted
ResponseMessages []provider.Message // messages for the next turn
}
ObjectResult[T] (from GenerateObject / StreamObject)
type ObjectResult[T any] struct {
Object T // the parsed typed object
Usage provider.Usage // total token consumption across all steps
FinishReason provider.FinishReason
Response provider.ResponseMetadata
ProviderMetadata map[string]map[string]any // provider-specific response data
ResponseMessages []provider.Message // messages for the next turn
Steps []StepResult // results from each generation step (multi-step tool loops)
}
EmbedResult / EmbedManyResult
type EmbedResult struct {
Embedding []float64
Usage provider.Usage
ProviderMetadata map[string]map[string]any
Response provider.ResponseMetadata
}
type EmbedManyResult struct {
Embeddings [][]float64
Usage provider.Usage
ProviderMetadata map[string]map[string]any
Response provider.ResponseMetadata
}
ImageResult
type ImageResult struct {
Images []provider.ImageData
ProviderMetadata map[string]map[string]any
Usage provider.Usage
Response provider.ResponseMetadata
}
VideoResult
type VideoResult struct {
Video provider.VideoData
Videos []provider.VideoData
ProviderMetadata map[string]map[string]any
Response provider.ResponseMetadata
}
provider.Usage
type Usage struct {
InputTokens int
OutputTokens int
TotalTokens int
ReasoningTokens int
CacheReadTokens int
CacheWriteTokens int
}
Error Handling
import "errors"
result, err := goai.GenerateText(ctx, model, goai.WithPrompt("..."))
if err != nil {
var apiErr *goai.APIError
var overflowErr *goai.ContextOverflowError
if errors.As(err, &overflowErr) {
// Prompt too long for context window - truncate messages and retry
log.Printf("Context overflow: %s", overflowErr.Message)
} else if errors.As(err, &apiErr) {
log.Printf("API error %d: %s (retryable: %v)", apiErr.StatusCode, apiErr.Message, apiErr.IsRetryable)
} else {
log.Printf("Error: %v", err)
}
}
Always use errors.As(), never type assertion.
Built-in retry: transient errors (429, 5xx) are retried automatically up to WithMaxRetries(2) times with exponential backoff.
TokenSource (Dynamic Auth)
For OAuth, service accounts, or rotating credentials:
ts := provider.CachedTokenSource(func(ctx context.Context) (*provider.Token, error) {
// Fetch token from your auth system
token, err := myAuthClient.GetToken(ctx)
if err != nil {
return nil, err
}
return &provider.Token{
Value: token.AccessToken,
ExpiresAt: token.ExpiresAt,
}, nil
})
model := openai.Chat("gpt-4o", openai.WithTokenSource(ts))
provider.StaticToken(key)- wraps a static stringprovider.CachedTokenSource(fetchFn)- caches until expiry, thread-safe
Supported Providers
Tier 1 (dedicated implementations)
| Provider | Import | Chat | Embed | Image | Provider Tools |
|---|---|---|---|---|---|
| OpenAI | provider/openai |
Yes | Yes | Yes | 4 |
| Anthropic | provider/anthropic |
Yes | - | - | 12 |
| Google Gemini | provider/google |
Yes | Yes | Yes | 4 |
| AWS Bedrock | provider/bedrock |
Yes | Yes | - | - |
| Azure OpenAI | provider/azure |
Yes | - | Yes | - |
| Vertex AI | provider/vertex |
Yes | Yes | Yes | - |
Tier 2
| Provider | Import | Chat | Embed | Provider Tools |
|---|---|---|---|---|
| Cohere | provider/cohere |
Yes | Yes | - |
| Mistral | provider/mistral |
Yes | - | - |
| xAI (Grok) | provider/xai |
Yes | - | 2 |
| Groq | provider/groq |
Yes | - | 1 |
| DeepSeek | provider/deepseek |
Yes | - | - |
Tier 3 (OpenAI-compatible)
Fireworks, Together, DeepInfra, OpenRouter, Perplexity, Cerebras
Local
| Provider | Import | Chat | Embed | Default URL |
|---|---|---|---|---|
| Ollama | provider/ollama |
Yes | Yes | http://localhost:11434 |
| vLLM | provider/vllm |
Yes | Yes | localhost:8000/v1 |
| RunPod | provider/runpod |
Yes | - | RUNPOD_ENDPOINT_ID |
| Custom | provider/compat |
Yes | Yes | user-defined |
Common Mistakes to Avoid
Don't use
WithPromptandWithMessageswith user message together carelessly -WithPrompt("x")is auto-wrapped as aUserMessageand prepended toMessages. Use one or the other for the initial user input.Don't call both
Stream()andTextStream()- they are mutually exclusive onTextStream. The second call returns a closed channel.Don't forget
WithMaxStepsfor tool loops - default is 1 (no loop). SetWithMaxSteps(n)where n > 1 to enable automatic tool execution.Don't type-assert errors - always use
errors.As(err, &target), nevererr.(*goai.APIError).Don't mix option families - text/object/embed functions use
Option,GenerateImageusesImageOption, andGenerateVideousesVideoOption.Don't forget error handling on streaming - always check the error from
StreamText/StreamObjectbefore consuming the stream.Result()on streams is blocking - it waits for the entire stream to complete. Call it after consumingTextStream()/Stream(), or on its own if you don't need incremental output.