# Error Types

Error types returned by Go AI SDK functions.

## Typed Errors

Many SDK functions return specific error types instead of (or in addition to)
plain `errors.New`/`fmt.Errorf` values. Use `errors.As` to detect them. Most
live in `pkg/ai`; a few tool/provider-level ones live in `pkg/provider/types`
and `pkg/provider`.

| Type | Package | Key Fields | When it's returned |
|------|---------|------------|---------------------|
| `NoSuchToolError` | ai | ToolName, AvailableTools | Model called a tool that isn't available |
| `InvalidToolInputError` | ai | ToolName, ToolInput, Cause | Tool input doesn't satisfy the tool's schema |
| `ToolCallRepairError` | ai | Cause, OriginalError | `RepairToolCall` failed to fix a bad tool call |
| `ToolChoiceViolationError` | ai | ToolChoice, FinishReason, Provider, ModelID | Response didn't honor a required/specific `ToolChoice` |
| `ToolCallNotFoundForApprovalError` | ai | ToolCallID, ApprovalID | Approval references a tool call missing from history |
| `InvalidToolApprovalError` | ai | ApprovalID | Approval response has no matching request |
| `InvalidToolApprovalSignatureError` | ai | ApprovalID, ToolCallID, Reason | Signed approval could not be verified |
| `NoObjectGeneratedError` | ai | Message, Cause, Text, Response, Usage, FinishReason | `GenerateObject`/`StreamObject` produced no valid object |
| `NoOutputGeneratedError` | ai | Message, Cause | Stream ended before any output or finish chunk |
| `NoImageGeneratedError` | ai | Responses, Calls | `GenerateImage` produced zero images |
| `NoSpeechGeneratedError` | ai | Responses | `GenerateSpeech` produced no audio |
| `NoVideoGeneratedError` | ai | Responses | `GenerateVideo` produced no videos |
| `NoTranscriptGeneratedError` | ai | Responses | `Transcribe` produced no text |
| `NoTranslationGeneratedError` | ai | Response | Speech translation produced no audio or text |
| `MessageConversionError` | ai | OriginalMessage, Message, Cause | A UI message couldn't convert to a model message |
| `TimeoutError` | ai | Reason, Err | A configured `TimeoutConfig` deadline was hit |
| `UIMessageStreamError` | ai | ChunkType, ChunkID, Message | A UI message stream received an out-of-sequence chunk |
| `types.MissingToolResultError` | provider/types | ToolCallID, ToolName, Message | A provider-executed tool didn't return a result |
| `types.MissingToolResultsError` | provider/types | ToolCallIDs, Message | Multiple provider-executed tools are missing results |
| `types.ToolExecutionError` | provider/types | ToolCallID, ToolName, Err, ProviderExecuted | A tool's `Execute` function returned an error |
| `provider.BatchError` | provider | Message, Type, Code, StatusCode | Serializable error info for a failed batch item |
| `provider.NoSuchUploadAPIError` | provider | API | Provider has no upload API of the requested kind |

The SDK also exposes `Is*Error(err) bool` helper functions for most of the
types above as an alternative to `errors.As`, for example
`ai.IsNoSuchToolError`, `ai.IsInvalidToolInputError`,
`ai.IsToolChoiceViolationError`, `ai.IsNoImageGeneratedError`,
`ai.IsNoOutputGeneratedError`, and `ai.IsMessageConversionError`.
`NoObjectGeneratedError` and `TimeoutError` have no `Is*` helper; use
`errors.As` for those.

### Example: errors.As

```go
package main

import (
    "context"
    "errors"
    "fmt"
    "log"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/provider/types"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
    p := openai.New(openai.Config{APIKey: "your-api-key"})
    model, _ := p.LanguageModel("gpt-4")

    weatherTool := types.Tool{Name: "get_weather"}

    result, err := ai.GenerateText(context.Background(), ai.GenerateTextOptions{
        Model:  model,
        Prompt: "Use the weather tool",
        Tools:  []types.Tool{weatherTool},
        StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
    })
    if err != nil {
        var noSuchTool *ai.NoSuchToolError
        var invalidInput *ai.InvalidToolInputError
        switch {
        case errors.As(err, &noSuchTool):
            log.Printf("model tried calling unknown tool %q", noSuchTool.ToolName)
        case errors.As(err, &invalidInput):
            log.Printf("tool %q got invalid input: %v", invalidInput.ToolName, invalidInput.Cause)
        default:
            log.Fatal(err)
        }
        return
    }

    fmt.Println(result.Text)
}
```

## Untyped Errors

Many lower-level validation and provider failures are still plain Go errors
with descriptive messages rather than a dedicated type, for example:

```text
"model is required"
"prompt is required"
provider-specific HTTP errors wrapped from the provider's API response
```

Check these with `strings.Contains(err.Error(), "...")` as shown below, or
prefer the typed errors above and `ai.Is*Error` helpers where one exists.

## Error Handling Patterns

### Basic Error Handling

```go
package main

import (
    "context"
    "log"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/providers/openai"
)

func main() {
    p := openai.New(openai.Config{APIKey: "your-api-key"})
    model, _ := p.LanguageModel("gpt-4")

    opts := ai.GenerateTextOptions{Model: model, Prompt: "Hello"}

    result, err := ai.GenerateText(context.Background(), opts)
    if err != nil {
        log.Fatal(err)
    }
    log.Println(result.Text)
}
```

### Timeout Errors

```go
result, err := ai.GenerateText(ctx, opts)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        log.Println("Request timed out")
        return
    }
    log.Fatal(err)
}
```

### Cancellation Errors

```go
ctx, cancel := context.WithCancel(context.Background())

go func() {
    time.Sleep(5 * time.Second)
    cancel()
}()

result, err := ai.GenerateText(ctx, opts)
if err != nil {
    if errors.Is(err, context.Canceled) {
        log.Println("Request was canceled")
        return
    }
    log.Fatal(err)
}
```

### Validation Errors

```go
result, err := ai.GenerateText(ctx, opts)
if err != nil {
    switch {
    case strings.Contains(err.Error(), "model is required"):
        log.Println("Must provide a model")
    case strings.Contains(err.Error(), "prompt is required"):
        log.Println("Must provide a prompt or messages")
    default:
        log.Println("Unknown validation error:", err)
    }
    return
}
```

### Provider-Specific Errors

```go
result, err := ai.GenerateText(ctx, opts)
if err != nil {
    switch {
    case strings.Contains(err.Error(), "API key"):
        log.Println("Invalid or missing API key")
    case strings.Contains(err.Error(), "rate limit"):
        log.Println("Rate limited - retry later")
        time.Sleep(time.Minute)
        // Retry logic
    case strings.Contains(err.Error(), "content policy"):
        log.Println("Content policy violation")
    case strings.Contains(err.Error(), "invalid model"):
        log.Println("Model not found or not supported")
    default:
        log.Println("Provider error:", err)
    }
    return
}
```

### Retry Logic

```go
func generateWithRetry(ctx context.Context, opts ai.GenerateTextOptions, maxRetries int) (*ai.GenerateTextResult, error) {
    var result *ai.GenerateTextResult
    var err error

    for attempt := 0; attempt < maxRetries; attempt++ {
        result, err = ai.GenerateText(ctx, opts)
        if err == nil {
            return result, nil
        }

        // Check if error is retryable
        if strings.Contains(err.Error(), "rate limit") {
            waitTime := time.Duration(attempt+1) * time.Second
            log.Printf("Rate limited, retrying in %v (attempt %d/%d)",
                waitTime, attempt+1, maxRetries)
            time.Sleep(waitTime)
            continue
        }

        // Non-retryable error
        return nil, err
    }

    return nil, fmt.Errorf("max retries exceeded: %w", err)
}
```

### Tool Execution Errors

```go
weatherTool := types.Tool{
    Name:        "get_weather",
    Description: "Get weather",
    Parameters:  weatherSchema,
    Execute: func(ctx context.Context, input map[string]interface{}, opts types.ToolExecutionOptions) (interface{}, error) {
        location, ok := input["location"].(string)
        if !ok {
            return nil, fmt.Errorf("location must be a string")
        }

        weather, err := fetchWeather(location)
        if err != nil {
            return nil, fmt.Errorf("failed to fetch weather: %w", err)
        }

        return weather, nil
    },
}

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    Prompt: "What's the weather?",
    Tools:  []types.Tool{weatherTool},
    StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
})
if err != nil {
    if strings.Contains(err.Error(), "tool execution failed") {
        log.Println("A tool failed during execution:", err)
    }
}
```

### Stream Errors

```go
result, err := ai.StreamText(ctx, opts)
if err != nil {
    log.Fatal("Failed to start stream:", err)
}
defer result.Close()

for {
    chunk, err := result.Stream().Next()
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Printf("Stream error: %v", err)
        break
    }

    // Process chunk
}

// Check for accumulated errors
if result.Err() != nil {
    log.Printf("Stream had errors: %v", result.Err())
}
```

### Schema Validation Errors

```go
result, err := ai.GenerateObject(ctx, ai.GenerateObjectOptions{
    Model:  model,
    Prompt: "Generate user data",
    Schema: schema,
})
if err != nil {
    if strings.Contains(err.Error(), "validation failed") {
        log.Println("Generated object doesn't match schema:", err)
    }
    return
}
```

## See Also

- [GenerateText](https://goaisdk.com/docs/reference/ai/generate-text.md) - Text generation
- [Usage Types](https://goaisdk.com/docs/reference/types/usage.md) - Token usage tracking
- [Tool Types](https://goaisdk.com/docs/reference/types/tools.md) - Tool-related types
