Skip to content
Back to skills

GoAI SDK - AI Agent Skill

ASecurity

> 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`.

  • 216 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 26, 2026
data-aigobashawsazuregitapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 18 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add zendev-sh/goai --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of GoAI SDK - AI Agent Skill?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for GoAI SDK - AI Agent Skill
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zendev-sh-goai-sdk-ai-agent-skill/badge)](https://www.skillsdirectory.com/skills/zendev-sh-goai-sdk-ai-agent-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# 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/oauth2` for Vertex AI. Optional `observability/otel` submodule 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
```

```go
import (
    "github.com/zendev-sh/goai"
    "github.com/zendev-sh/goai/provider/openai"     // or any provider
)
```

## Core API

### 7 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)`     |

### Model Constructors

Each provider has `Chat()`, and optionally `Embedding()` and `Image()`:

```go
// 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")
```

### 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    | `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:

```go
model := openai.Chat("gpt-4o", openai.WithAPIKey("sk-..."))
```

Most providers support these options (Bedrock uses AWS credential options; Ollama requires no auth):

```go
provider.WithAPIKey(key)         // static API key
provider.WithTokenSource(ts)     // dynamic auth (OAuth, service accounts)
provider.WithBaseURL(url)        // override endpoint (Azure uses WithEndpoint)
provider.WithHeaders(h)          // custom HTTP headers
provider.WithHTTPClient(c)       // custom HTTP transport
```

---

## Patterns and Examples

### 1. Basic Text Generation

```go
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

```go
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)

```go
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 name
- `json:"-"` - exclude field
- `jsonschema:"description=..."` - adds description
- `jsonschema:"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

```go
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

```go
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, &params); 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 in `result.ToolCalls`
- `WithMaxSteps(n)` where n > 1 = auto loop: generate -> execute tools -> re-generate -> repeat
- Tools without `Execute` are sent as definitions only (for manual handling)
- Tool errors are passed back to the model as `"error: ..."` messages

### 6. Conversation History

```go
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 message
- `goai.UserMessage(text)` - user message
- `goai.AssistantMessage(text)` - assistant message
- `goai.ToolMessage(toolCallID, toolName, output)` - tool result

### 7. Embeddings

```go
// 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

```go
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. Provider-Defined Tools (Server-Side)

Built-in tools executed by the provider, not your code:

```go
// 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()` (+ versioned variants)
- **Google**: `GoogleSearch()`, `URLContext()`, `CodeExecution()`
- **xAI**: `WebSearch()`, `XSearch()`
- **Groq**: `BrowserSearch()`

### 10. Observability Hooks

```go
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`)

```go
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.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.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`)

```go
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
```

---

## Result Types

### TextResult (from GenerateText / StreamText)

```go
type TextResult struct {
    Text             string                         // accumulated generated 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
}
```

### ObjectResult[T] (from GenerateObject / StreamObject)

```go
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
    Steps            []StepResult                   // results from each generation step (multi-step tool loops)
}
```

### EmbedResult / EmbedManyResult

```go
type EmbedResult struct {
    Embedding []float64
    Usage     provider.Usage
}

type EmbedManyResult struct {
    Embeddings [][]float64
    Usage      provider.Usage
}
```

### ImageResult

```go
type ImageResult struct {
    Images []provider.ImageData  // .Data = []byte, .MediaType = "image/png"
}
```

### provider.Usage

```go
type Usage struct {
    InputTokens      int
    OutputTokens     int
    TotalTokens      int
    ReasoningTokens  int
    CacheReadTokens  int
    CacheWriteTokens int
}
```

---

## Error Handling

```go
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:

```go
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 string
- `provider.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  | -     | -     | 10             |
| Google Gemini | `provider/google`    | Yes  | Yes   | Yes   | 3              |
| 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   | `localhost:11434/v1` |
| 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

1. **Don't use `WithPrompt` and `WithMessages` with user message together carelessly** - `WithPrompt("x")` is auto-wrapped as a `UserMessage` and prepended to `Messages`. Use one or the other for the initial user input.

2. **Don't call both `Stream()` and `TextStream()`** - they are mutually exclusive on `TextStream`. The second call returns a closed channel.

3. **Don't forget `WithMaxSteps` for tool loops** - default is 1 (no loop). Set `WithMaxSteps(n)` where n > 1 to enable automatic tool execution.

4. **Don't type-assert errors** - always use `errors.As(err, &target)`, never `err.(*goai.APIError)`.

5. **Don't mix `Option` and `ImageOption`** - `GenerateImage` uses `ImageOption`, all other functions use `Option`.

6. **Don't forget error handling on streaming** - always check the error from `StreamText`/`StreamObject` before consuming the stream.

7. **`Result()` on streams is blocking** - it waits for the entire stream to complete. Call it after consuming `TextStream()`/`Stream()`, or on its own if you don't need incremental output.

Files in this skill

  • .zenflow/pr-review.yaml5.7 KB
  • AGENTS.md4.7 KB
  • CLAUDE.md4.9 KB
  • CONTRIBUTING.md3.7 KB
  • Makefile776 B
  • ROADMAP.md14.4 KB
  • SKILL.md20 KB
  • agent_state.go14.3 KB
  • agent_state_test.go93.8 KB
  • bench/.gitignore75 B
  • bench/BADGE.md1.1 KB
  • bench/Makefile1 KB
  • bench/RESULTS.md4 KB
  • bench/collect.sh15.2 KB
  • caching.go810 B
  • caching_test.go2.3 KB
  • cmd_azure_test.go1.7 KB
  • codecov.yml199 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.