# OpenAI Provider

OpenAI provides industry-leading language models including GPT-4o, GPT-5, and reasoning models like o1 and o3. Known for high-quality responses, extensive capabilities, and comprehensive API features.

## Setup

### Installation

The OpenAI provider is included in the Go-AI SDK:

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

### Configuration

```go
provider := openai.New(openai.Config{
    APIKey: os.Getenv("OPENAI_API_KEY"),
})

model, err := provider.LanguageModel("gpt-4o")
if err != nil {
    log.Fatal(err)
}
```

`LanguageModel` matches the TypeScript OpenAI provider default and returns a Responses API model. Use `ChatModel` when you need the Chat Completions endpoint or `CompletionModel` for legacy instruct-style completions:

```go
chatModel, err := provider.ChatModel("gpt-4o")
completionModel, err := provider.CompletionModel("gpt-3.5-turbo-instruct")
```

### Get API Key

1. Sign up at [platform.openai.com](https://platform.openai.com)
2. Navigate to API Keys section
3. Create new secret key
4. Set environment variable:

```bash
export OPENAI_API_KEY=sk-...
```

## Available Models

### GPT-4 Series (Latest Generation)

| Model ID | Context | Input Price | Output Price | Best For |
|----------|---------|-------------|--------------|----------|
| gpt-4o | 128K | $2.50/1M | $10.00/1M | General purpose, multimodal |
| gpt-4o-mini | 128K | $0.15/1M | $0.60/1M | Fast, cost-effective tasks |
| gpt-4-turbo | 128K | $10.00/1M | $30.00/1M | Complex tasks, vision |
| gpt-4 | 8K | $30.00/1M | $60.00/1M | Legacy high-quality |

### GPT-5 Series (Frontier)

| Model ID | Context | Input Price | Output Price | Best For |
|----------|---------|-------------|--------------|----------|
| gpt-5 | 200K | TBA | TBA | Next-generation reasoning |
| gpt-5.5 | 200K | TBA | TBA | Frontier reasoning and coding |
| gpt-5.5-2026-04-23 | 200K | TBA | TBA | Pinned GPT-5.5 release |

### o-Series (Reasoning Models)

| Model ID | Context | Input Price | Output Price | Best For |
|----------|---------|-------------|--------------|----------|
| o1 | 200K | $15.00/1M | $60.00/1M | Complex reasoning, math |
| o1-mini | 128K | $3.00/1M | $12.00/1M | Fast reasoning tasks |
| o3 | 200K | TBA | TBA | Advanced reasoning (preview) |
| o3-mini | 128K | TBA | TBA | Efficient reasoning |

### GPT-3.5 Series (Legacy)

| Model ID | Context | Input Price | Output Price | Best For |
|----------|---------|-------------|--------------|----------|
| gpt-3.5-turbo | 16K | $0.50/1M | $1.50/1M | Simple, fast tasks |

### Embedding Models

| Model ID | Dimensions | Price | Best For |
|----------|-----------|-------|----------|
| text-embedding-3-large | 3072 | $0.13/1M | High-quality embeddings |
| text-embedding-3-small | 1536 | $0.02/1M | Cost-effective embeddings |
| text-embedding-ada-002 | 1536 | $0.10/1M | Legacy embeddings |

### Image Generation Models

| Model ID | Quality | Speed | Price | Best For |
|----------|---------|-------|-------|----------|
| gpt-image-2 | Excellent | Medium | Per image | Latest image generation |
| gpt-image-1 | Excellent | Medium | Per image | Multimodal image generation |
| dall-e-3 | Excellent | Medium | $0.040/image | High-quality images |
| dall-e-2 | Good | Fast | $0.020/image | Quick generations |

### Speech Models

| Model ID | Type | Quality | Price | Best For |
|----------|------|---------|-------|----------|
| tts-1 | TTS | Good | $15/1M chars | Fast synthesis |
| tts-1-hd | TTS | High | $30/1M chars | High-quality voices |
| whisper-1 | STT | Excellent | $0.006/min | Transcription |

## Provider-Specific Features

### Transport and Headers

OpenAI provider configuration supports the same transport flexibility as the TypeScript SDK: custom base URL, custom HTTP client, custom provider name, organization/project headers, and additional headers.

```go
provider := openai.New(openai.Config{
    APIKey:       os.Getenv("OPENAI_API_KEY"),
    BaseURL:      "https://proxy.example.com/v1",
    Organization: "org_...",
    Project:      "proj_...",
    Headers: map[string]string{
        "X-Trace-ID": "trace-123",
    },
    HTTPClient: http.DefaultClient,
})
```

Provider-level headers are forwarded to language, Responses, embeddings, image, speech, and transcription requests. Request-level headers override provider-level headers when both are present, matching TypeScript `headers` merge behavior.

### Structured Output (JSON Mode)

OpenAI supports native JSON mode for reliable structured output:

```go
import (
    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/schema"
)

personSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
    "type": "object",
    "properties": map[string]interface{}{
        "name": map[string]string{"type": "string"},
        "age":  map[string]string{"type": "number"},
        "email": map[string]string{"type": "string"},
    },
    "required": []string{"name", "age"},
})

result, err := ai.GenerateObject(ctx, ai.GenerateObjectOptions{
    Model:  model,
    Schema: personSchema,
    Prompt: "Extract person info: John Doe, 30 years old, john@example.com",
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Structured data: %+v\n", result.Object)
```

JSON schemas are normalized for OpenAI's structured-outputs dialect before
being sent: `propertyNames` and lookaround `pattern`s are stripped (with a
`compatibility` warning), and a singleton-reference `allOf` wrapper (as
recursive schemas commonly produce) is rewritten to a direct `$ref`, or
inlined at the schema root, since OpenAI rejects `allOf` and requires an
object at the root.

### Vision Capabilities

GPT-4o and GPT-4-turbo support image understanding:

```go
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model: model,
    Messages: []types.Message{
        {
            Role: types.RoleUser,
            Content: []types.ContentPart{
                types.TextContent{Text: "What's in this image?"},
                types.FileContent{
                    URL:       "https://example.com/image.jpg",
                    MediaType: "image/jpeg",
                },
            },
        },
    },
})
```

For OpenAI Responses image/file parts, set the provider option `imageDetail` to forward the API `detail` field:

```go
types.FileContent{
    URL:       "https://example.com/chart.png",
    MediaType: "image/png",
    ProviderOptions: map[string]interface{}{
        "openai": map[string]interface{}{
            "imageDetail": "high",
        },
    },
}
```

### Function Calling

Define tools that the model can call:

```go
weatherTool := types.Tool{
    Name:        "get_weather",
    Description: "Get current weather for a location",
    Parameters: map[string]interface{}{
        "type": "object",
        "properties": map[string]interface{}{
            "location": map[string]string{
                "type":        "string",
                "description": "City name",
            },
            "unit": map[string]interface{}{
                "type": "string",
                "enum": []string{"celsius", "fahrenheit"},
            },
        },
        "required": []string{"location"},
    },
}

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    Prompt: "What's the weather in San Francisco?",
    Tools:  []types.Tool{weatherTool},
    StopWhen: []ai.StopCondition{ai.IsStepCount(5)},
})

if result.ToolCalls != nil {
    for _, call := range result.ToolCalls {
        fmt.Printf("Tool: %s, Args: %v\n", call.ToolName, call.Arguments)
    }
}
```

Responses API tool calls preserve OpenAI provider metadata, including `function_call.namespace`. Tool calls with no input serialize `{}` rather than `null`, and assistant messages serialize `content: null` only when tool calls are present and no text content is available. Non-tool assistant turns are not sent as null-content messages.

Function tools can be grouped into OpenAI Responses namespaces by setting `providerOptions.openai.namespace` on the tool definition. Go serializes the namespace wrapper exactly as the TypeScript provider does and rejects conflicting namespace descriptions. When a reasoning effort is set for Responses models and no summary is provided, Go defaults `reasoning.summary` to `"detailed"`, matching the OpenAI provider default.

For Responses API calls, use `ProviderOptions["openai"]["allowedTools"]` to
restrict callable function tools while keeping the full `tools` array in the
request. This preserves prompt caching behavior across different allowlists and
overrides request-level `ToolChoice`, matching the TypeScript SDK.

```go
result, err := model.DoGenerate(ctx, &provider.GenerateOptions{
    Prompt: prompt,
    Tools:  tools,
    ProviderOptions: map[string]interface{}{
        "openai": map[string]interface{}{
            "allowedTools": map[string]interface{}{
                "toolNames": []string{"weather"},
                "mode":      "auto", // optional: "auto" or "required"
            },
        },
    },
})
```

### Image Generation Options

Use `OpenAIImageModelOptions` under the `"openai"` provider key for typed image options. The struct maps camelCase Go JSON names to the OpenAI wire fields used by the TypeScript SDK.

```go
result, err := ai.GenerateImage(ctx, ai.GenerateImageOptions{
    Model:  imageModel,
    Prompt: "A studio product photo of a matte ceramic cup",
    ProviderOptions: map[string]interface{}{
        "openai": openai.OpenAIImageModelOptions{
            Size:              "1024x1024",
            Quality:           "high",
            OutputFormat:      "png",
            OutputCompression: ptr(80),
            Background:        "transparent",
            Moderation:        "auto",
        },
    },
})
```

### Reasoning Models (o1/o3)

o1 and o3 models use extended thinking for complex problems:

```go
// o1 uses internal reasoning tokens (not visible in response)
model, err := provider.LanguageModel("o1")

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    Prompt: "Solve this complex math problem: Find the derivative of f(x) = x^3 * sin(x)",
})

fmt.Println(result.Text)
// Output includes detailed step-by-step reasoning
```

Note: o1 models do not support:
- System messages
- Streaming
- Temperature settings
- Function calling (use o1-mini for some tool support)

### Streaming Responses

Stream text generation for real-time output:

```go
stream, err := ai.StreamText(ctx, ai.StreamTextOptions{
    Model:  model,
    Prompt: "Write a long story about AI",
})
if err != nil {
    log.Fatal(err)
}
defer stream.Close()

for chunk := range stream.Chunks() {
    fmt.Print(chunk.Text)
}

if stream.Err() != nil {
    log.Fatal(stream.Err())
}
```

### Response Format

Control output format:

```go
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    Prompt: "Generate a JSON object",
    ResponseFormat: &provider.ResponseFormat{
        Type: "json_object",
    },
})
```

## Examples

### Basic Text Generation

```go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

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

func main() {
    ctx := context.Background()
    provider := openai.New(openai.Config{
        APIKey: os.Getenv("OPENAI_API_KEY"),
    })

    model, err := provider.LanguageModel("gpt-4o")
    if err != nil {
        log.Fatal(err)
    }

    result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
        Model:  model,
        Prompt: "Explain quantum computing in simple terms",
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result.Text)
    fmt.Printf("Tokens used: %d\n", result.Usage.GetTotalTokens())
}
```

### Chat Conversation

```go
messages := []types.Message{
    {
        Role: types.RoleUser,
        Content: []types.ContentPart{
            types.TextContent{Text: "How do I reverse a string in Go?"},
        },
    },
}

result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    System: "You are a helpful coding assistant",
    Messages: messages,
})
if err != nil {
    log.Fatal(err)
}

fmt.Println(result.Text)

// Continue conversation
messages = append(messages,
    types.Message{
        Role: types.RoleAssistant,
        Content: []types.ContentPart{
            types.TextContent{Text: result.Text},
        },
    },
    types.Message{
        Role: types.RoleUser,
        Content: []types.ContentPart{
            types.TextContent{Text: "Can you make it more efficient?"},
        },
    },
)

result, err = ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    System: "You are a helpful coding assistant",
    Messages: messages,
})
```

### Image Generation with DALL-E

```go
imageModel, err := provider.ImageModel("dall-e-3")
if err != nil {
    log.Fatal(err)
}

result, err := ai.GenerateImage(ctx, ai.GenerateImageOptions{
    Model:   imageModel,
    Prompt:  "A futuristic city with flying cars",
    Size:    "1024x1024",
    Quality: "hd",
    Style:   "vivid",
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Image bytes: %d\n", len(result.Images[0].Data))
```

`Quality` accepts `"standard"`/`"hd"` (DALL-E) or `"low"`/`"medium"`/`"high"`/`"xhigh"`/`"max"`/`"auto"` (GPT Image models) — `"xhigh"` and `"max"` are new this cycle.

### Text Embeddings

```go
embeddingModel, err := provider.EmbeddingModel("text-embedding-3-large")
if err != nil {
    log.Fatal(err)
}

texts := []string{
    "The quick brown fox",
    "jumps over the lazy dog",
}

result, err := ai.EmbedMany(ctx, ai.EmbedManyOptions{
    Model:  embeddingModel,
    Inputs: texts,
})
if err != nil {
    log.Fatal(err)
}

for i, embedding := range result.Embeddings {
    fmt.Printf("Text %d: %d dimensions\n", i, len(embedding))
}
```

### Speech Synthesis

```go
ttsModel, err := provider.SpeechModel("tts-1-hd")
if err != nil {
    log.Fatal(err)
}

result, err := ai.GenerateSpeech(ctx, ai.GenerateSpeechOptions{
    Model:        ttsModel,
    Text:         "Hello, welcome to Go-AI SDK",
    Voice:        "alloy",
    OutputFormat: "mp3",
})
if err != nil {
    log.Fatal(err)
}

// Save audio file
os.WriteFile("output.mp3", result.Audio.Data, 0644)
```

### Transcription with Whisper

```go
transcriptionModel, err := provider.TranscriptionModel("whisper-1")
if err != nil {
    log.Fatal(err)
}

audioData, err := os.ReadFile("audio.mp3")
if err != nil {
    log.Fatal(err)
}

result, err := ai.Transcribe(ctx, ai.TranscribeOptions{
    Model:    transcriptionModel,
    Audio:    audioData,
    MimeType: "audio/mpeg",
    ProviderOptions: map[string]interface{}{
        "openai": map[string]interface{}{
            "language": "en",
        },
    },
})
if err != nil {
    log.Fatal(err)
}

fmt.Println(result.Text)
```

## Advanced Configuration

### Custom HTTP Client

```go
import "net/http"

provider := openai.New(openai.Config{
    APIKey: os.Getenv("OPENAI_API_KEY"),
    HTTPClient: &http.Client{
        Timeout: time.Second * 120,
        Transport: &http.Transport{
            MaxIdleConns:       10,
            IdleConnTimeout:    90 * time.Second,
            DisableCompression: false,
        },
    },
})
```

### Custom Base URL (for proxies)

```go
provider := openai.New(openai.Config{
    APIKey:  os.Getenv("OPENAI_API_KEY"),
    BaseURL: "https://your-proxy.com/v1",
})
```

### Organization and Project IDs

```go
provider := openai.New(openai.Config{
    APIKey:       os.Getenv("OPENAI_API_KEY"),
    Organization: "org-xxxxx",
    Project:      "proj-xxxxx",
})
```

## Error Handling

### Common Errors

```go
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model,
    Prompt: prompt,
})
if err != nil {
    var providerErr *providererrors.ProviderError
    if errors.As(err, &providerErr) {
        switch providerErr.ErrorCode {
        case "invalid_api_key":
            log.Fatal("Invalid API key")
        case "model_not_found":
            log.Fatal("Model not found")
        case "context_length_exceeded":
            log.Fatal("Prompt too long")
        case "rate_limit_exceeded":
            log.Println("Rate limited, retrying...")
            time.Sleep(time.Second * 5)
        case "insufficient_quota":
            log.Fatal("Insufficient quota")
        default:
            log.Printf("OpenAI error: %s - %s", providerErr.ErrorCode, providerErr.Message)
        }
    }
    log.Fatal(err)
}
```

### Rate Limit Handling

```go
import "github.com/digitallysavvy/go-ai/pkg/ai"

func generateWithBackoff(ctx context.Context, model provider.LanguageModel, prompt string) (*ai.GenerateTextResult, error) {
    maxRetries := 3
    baseDelay := time.Second

    for i := 0; i < maxRetries; i++ {
        result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
            Model:  model,
            Prompt: prompt,
        })
        if err == nil {
            return result, nil
        }

        var providerErr *providererrors.ProviderError
        if errors.As(err, &providerErr) && providerErr.StatusCode == 429 {
            delay := baseDelay * time.Duration(math.Pow(2, float64(i)))
            log.Printf("Rate limited, waiting %v", delay)
            time.Sleep(delay)
            continue
        }

        return nil, err
    }

    return nil, fmt.Errorf("max retries exceeded")
}
```

## Best Practices

1. **Model Selection**
   - Use `gpt-4o` for general tasks with vision support
   - Use `gpt-4o-mini` for cost-effective, fast responses
   - Use `o1` for complex reasoning and math problems
   - Use `gpt-3.5-turbo` for simple, high-volume tasks

2. **Cost Optimization**
   - Monitor token usage with `result.Usage`
   - Use shorter system messages
   - Implement response caching for repeated queries
   - Use `max_tokens` to limit response length

3. **Performance**
   - Use streaming for long responses
   - Implement connection pooling for high-volume apps
   - Batch embedding requests (up to 2048 inputs)
   - Use `gpt-4o-mini` for latency-sensitive applications

4. **Error Handling**
   - Always implement retry logic for rate limits
   - Handle context length errors gracefully
   - Log API errors for debugging
   - Implement fallback models

5. **Security**
   - Never expose API keys in client code
   - Use environment variables for credentials
   - Implement rate limiting on your API
   - Validate user inputs before sending

## Rate Limits & Pricing

### Rate Limits (Tier 3)

| Model | RPM | TPM | RPD |
|-------|-----|-----|-----|
| gpt-4o | 5,000 | 800,000 | 10,000 |
| gpt-4o-mini | 10,000 | 2,000,000 | 10,000 |
| o1 | 500 | 100,000 | 5,000 |
| gpt-3.5-turbo | 10,000 | 2,000,000 | 10,000 |

RPM = Requests per minute, TPM = Tokens per minute, RPD = Requests per day

### Cost Estimation

```go
func estimateCost(promptTokens, completionTokens int, model string) float64 {
    prices := map[string][2]float64{
        "gpt-4o":         {2.50 / 1_000_000, 10.00 / 1_000_000},
        "gpt-4o-mini":    {0.15 / 1_000_000, 0.60 / 1_000_000},
        "o1":             {15.00 / 1_000_000, 60.00 / 1_000_000},
        "gpt-3.5-turbo":  {0.50 / 1_000_000, 1.50 / 1_000_000},
    }

    price, ok := prices[model]
    if !ok {
        return 0
    }

    return float64(promptTokens)*price[0] + float64(completionTokens)*price[1]
}
```

## Realtime

`Provider.RealtimeModel(modelID)` / `ExperimentalRealtimeModel` route known
Live model IDs (currently `"gpt-live-1"`) to the OpenAI Live WebSocket and
everything else (e.g. `"gpt-realtime"`) to the classic Realtime API:

```go
import providerapi "github.com/digitallysavvy/go-ai/pkg/provider"

model, err := provider.RealtimeModel("gpt-realtime")
if err != nil {
    log.Fatal(err)
}

secret, err := provider.GetRealtimeToken(ctx, providerapi.RealtimeFactoryGetTokenOptions{
    Model: "gpt-realtime",
})
if err != nil {
    log.Fatal(err)
}

wsConfigProvider, ok := model.(providerapi.RealtimeWebSocketConfigProvider)
if !ok {
    log.Fatal("model does not support client-side WebSocket config")
}
ws := wsConfigProvider.GetWebSocketConfig(secret.Token, secret.URL)
```

> **Fixed this cycle:** the WebSocket handshake's `Authorization` bearer
> token is now extracted case-insensitively with any whitespace after the
> scheme (`/^bearer\s+(.+)$/i`, TS parity). Previously `BEARER <key>` or a
> tab after `Bearer` were not recognized and the connection silently
> dropped the API key subprotocol. This also affects
> `gpt-realtime-whisper` realtime transcription.

## Speech Translation (experimental)

`Provider.SpeechTranslationModel(modelID)` (alias `Provider.Translation`)
streams speech-to-speech translation over `/realtime/translations`
(default model `"gpt-realtime-translate"`), for use with
`ai.ExperimentalStreamTranslate`:

```go
translationModel, err := provider.SpeechTranslationModel("gpt-realtime-translate")
if err != nil {
    log.Fatal(err)
}

result, err := ai.ExperimentalStreamTranslate(ctx, ai.StreamTranslateOptions{
    Model:          translationModel,
    Audio:          audioStream,
    TargetLanguage: "es",
})
```

## Workflow Serialization

OpenAI embedding, image, speech, and transcription models can cross a
workflow boundary with `providerutils.SerializeModel` / `DeserializeModel`
(language models could already be serialized). See
[Provider Serialization](https://goaisdk.com/docs/agents/workflow-agent.md#provider-serialization)
for the mechanism; realtime and speech-translation models are not yet
serializable.

## See Also

- [API Reference: GenerateText](https://goaisdk.com/docs/reference/ai/generate-text.md)
- [Core Concepts: Language Models](https://goaisdk.com/docs/foundations/providers-and-models.md)
- [OpenAI API Documentation](https://platform.openai.com/docs/api-reference)
- [Azure OpenAI Provider](https://goaisdk.com/docs/providers/azure.md) - For enterprise deployment

## May 2026 parity updates

### Model IDs

The OpenAI provider includes GPT-5.5 chat model constants and `gpt-image-2` image generation support.

| Capability | Go constant | Model ID |
| --- | --- | --- |
| Chat | `openai.ModelGPT55` | `gpt-5.5` |
| Chat | `openai.ModelGPT552026_04_23` | `gpt-5.5-2026-04-23` |
| Image | `openai.ModelGPTImage2` | `gpt-image-2` |

```go
p := openai.New(openai.Config{
    APIKey: os.Getenv("OPENAI_API_KEY"),
    Headers: map[string]string{
        "OpenAI-Beta": "example",
    },
})

model, err := p.LanguageModel(openai.ModelGPT55)
```

### Responses API metadata

`LanguageModel` and `ResponsesModel` both use OpenAI Responses API behavior. The provider preserves response IDs, headers, provider metadata, encrypted reasoning, function-call namespace values, and generated files in the standard result fields.

```go
model, err := p.ResponsesModel(openai.ModelGPT55)
```

Continue an OpenAI Conversation by passing the conversation ID through provider options. This is distinct from `previousResponseId`; if both are set, the request forwards both fields and returns an unsupported warning matching the TypeScript SDK.

```go
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model: model,
    Messages: messages,
    ProviderOptions: map[string]interface{}{
        "openai": map[string]interface{}{
            "conversation": "conv_123",
        },
    },
})
```

### Image detail and file content

Image detail provider options are forwarded on image parts, including tool-result image content. Prefer `types.FileContent` with tagged `types.FileData` for new multimodal code. `types.ImageContent` remains available for compatibility.

For OpenAI Responses compatibility, string file data with the default `file-` prefix is sent as a `file_id` reference instead of inline base64 data, matching the TypeScript provider. To disable or customize this deprecated compatibility path, set `openai.Config.FileIDPrefixes`.

```go
messages := []types.Message{{
    Role: types.RoleUser,
    Content: []types.ContentPart{
        types.TextContent{Text: "Describe the image."},
        types.FileContent{
            FileData: types.FileData{
                Type:      types.FileDataTypeURL,
                URL:       "https://example.com/chart.png",
                MediaType: "image/png",
            },
            ProviderOptions: map[string]interface{}{
                "openai": map[string]interface{}{"imageDetail": "high"},
            },
        },
    },
}}
```

### Assistant null content behavior

Assistant messages with tool calls serialize `content: null` only when the target OpenAI-compatible API requires it for tool-call assistant turns. Plain assistant messages keep their text content.

## October 2026 parity updates

### Model IDs

GPT-6.1 Sol is available as a Chat Completions and Responses model ID.

| Capability | Go constant | Model ID |
| --- | --- | --- |
| Chat / Responses | `openai.ModelGPT61Sol` | `gpt-6.1-sol` |

```go
model, err := p.LanguageModel(openai.ModelGPT61Sol)
```

### `reasoningSummary` warning on Chat Completions models

`providerOptions.openai.reasoningSummary` is a Responses API option. Passing it to a Chat Completions model (`p.LanguageModel("o3")`) now returns an `unsupported` warning instead of being silently dropped:

```go
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
    Model:  model, // a Chat Completions model, e.g. p.LanguageModel("o3")
    Prompt: "Explain quantum tunnelling.",
    ProviderOptions: map[string]interface{}{
        "openai": map[string]interface{}{"reasoningSummary": "detailed"},
    },
})
// result.Warnings contains {Type: "unsupported", Feature: "reasoningSummary", ...}
```

Use `p.ResponsesModel(...)` instead to enable reasoning summaries.
