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:
import (
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
)
Configuration
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:
chatModel, err := provider.ChatModel("gpt-4o")
completionModel, err := provider.CompletionModel("gpt-3.5-turbo-instruct")
Get API Key
- Sign up at platform.openai.com
- Navigate to API Keys section
- Create new secret key
- Set environment variable:
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.
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:
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 patterns 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:
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:
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:
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.
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.
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:
// 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:
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:
result, err := ai.GenerateText(ctx, ai.GenerateTextOptions{
Model: model,
Prompt: "Generate a JSON object",
ResponseFormat: &provider.ResponseFormat{
Type: "json_object",
},
})
Examples
Basic Text Generation
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
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
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
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
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
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
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)
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
BaseURL: "https://your-proxy.com/v1",
})
Organization and Project IDs
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
Organization: "org-xxxxx",
Project: "proj-xxxxx",
})
Error Handling
Common Errors
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
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
-
Model Selection
- Use
gpt-4ofor general tasks with vision support - Use
gpt-4o-minifor cost-effective, fast responses - Use
o1for complex reasoning and math problems - Use
gpt-3.5-turbofor simple, high-volume tasks
- Use
-
Cost Optimization
- Monitor token usage with
result.Usage - Use shorter system messages
- Implement response caching for repeated queries
- Use
max_tokensto limit response length
- Monitor token usage with
-
Performance
- Use streaming for long responses
- Implement connection pooling for high-volume apps
- Batch embedding requests (up to 2048 inputs)
- Use
gpt-4o-minifor latency-sensitive applications
-
Error Handling
- Always implement retry logic for rate limits
- Handle context length errors gracefully
- Log API errors for debugging
- Implement fallback models
-
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
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:
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
Authorizationbearer token is now extracted case-insensitively with any whitespace after the scheme (/^bearer\s+(.+)$/i, TS parity). PreviouslyBEARER <key>or a tab afterBearerwere not recognized and the connection silently dropped the API key subprotocol. This also affectsgpt-realtime-whisperrealtime 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:
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
for the mechanism; realtime and speech-translation models are not yet
serializable.
See Also
- API Reference: GenerateText
- Core Concepts: Language Models
- OpenAI API Documentation
- Azure OpenAI Provider - 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 |
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.
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.
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.
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 |
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:
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.