Skip to main content

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​

  1. Sign up at platform.openai.com
  2. Navigate to API Keys section
  3. Create new secret key
  4. Set environment variable:
export OPENAI_API_KEY=sk-...

Available Models​

GPT-4 Series (Latest Generation)​

Model IDContextInput PriceOutput PriceBest For
gpt-4o128K$2.50/1M$10.00/1MGeneral purpose, multimodal
gpt-4o-mini128K$0.15/1M$0.60/1MFast, cost-effective tasks
gpt-4-turbo128K$10.00/1M$30.00/1MComplex tasks, vision
gpt-48K$30.00/1M$60.00/1MLegacy high-quality

GPT-5 Series (Frontier)​

Model IDContextInput PriceOutput PriceBest For
gpt-5200KTBATBANext-generation reasoning
gpt-5.5200KTBATBAFrontier reasoning and coding
gpt-5.5-2026-04-23200KTBATBAPinned GPT-5.5 release

o-Series (Reasoning Models)​

Model IDContextInput PriceOutput PriceBest For
o1200K$15.00/1M$60.00/1MComplex reasoning, math
o1-mini128K$3.00/1M$12.00/1MFast reasoning tasks
o3200KTBATBAAdvanced reasoning (preview)
o3-mini128KTBATBAEfficient reasoning

GPT-3.5 Series (Legacy)​

Model IDContextInput PriceOutput PriceBest For
gpt-3.5-turbo16K$0.50/1M$1.50/1MSimple, fast tasks

Embedding Models​

Model IDDimensionsPriceBest For
text-embedding-3-large3072$0.13/1MHigh-quality embeddings
text-embedding-3-small1536$0.02/1MCost-effective embeddings
text-embedding-ada-0021536$0.10/1MLegacy embeddings

Image Generation Models​

Model IDQualitySpeedPriceBest For
gpt-image-2ExcellentMediumPer imageLatest image generation
gpt-image-1ExcellentMediumPer imageMultimodal image generation
dall-e-3ExcellentMedium$0.040/imageHigh-quality images
dall-e-2GoodFast$0.020/imageQuick generations

Speech Models​

Model IDTypeQualityPriceBest For
tts-1TTSGood$15/1M charsFast synthesis
tts-1-hdTTSHigh$30/1M charsHigh-quality voices
whisper-1STTExcellent$0.006/minTranscription

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​

  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)​

ModelRPMTPMRPD
gpt-4o5,000800,00010,000
gpt-4o-mini10,0002,000,00010,000
o1500100,0005,000
gpt-3.5-turbo10,0002,000,00010,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 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:

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​

May 2026 parity updates​

Model IDs​

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

CapabilityGo constantModel ID
Chatopenai.ModelGPT55gpt-5.5
Chatopenai.ModelGPT552026_04_23gpt-5.5-2026-04-23
Imageopenai.ModelGPTImage2gpt-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.

CapabilityGo constantModel ID
Chat / Responsesopenai.ModelGPT61Solgpt-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.