openai

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 1, 2026 License: MIT Imports: 11 Imported by: 0

README

OpenAI Service Package

A reusable OpenAI service package for Go that provides easy access to OpenAI's API, including:

  • Chat Completions — Text generation with GPT models (including vision with image URLs)
  • Streaming — Real-time streaming responses via a callback handler
  • Audio Transcription — Speech-to-text using Whisper (TranscribeAudio)
  • Audio Translation — Translate audio to English using Whisper (TranslateAudio)

Configuration

Environment variables (OS env > .env.local > .env):

# Required
OPENAI_API_KEY=sk-...

# Optional (defaults shown)
OPENAI_BASE_URL=https://api.openai.com/v1  # Custom endpoint (Azure OpenAI, proxy, etc.)
OPENAI_TIMEOUT=60                          # Request timeout in seconds
OPENAI_MAX_RETRIES=2                       # Number of retry attempts

# Model defaults per capability
OPENAI_TEXT_MODEL=gpt-4o      # Default model for chat/text completions
OPENAI_AUDIO_MODEL=whisper-1  # Default model for audio transcription/translation
Initializing the service
svc := openai.New(openai.Config{
    APIKey:     cfg.OpenAIAPIKey,
    BaseURL:    cfg.OpenAIBaseURL,
    Timeout:    cfg.OpenAITimeout,
    MaxRetries: cfg.OpenAIMaxRetries,
    TextModel:  cfg.OpenAITextModel,
    AudioModel: cfg.OpenAIAudioModel,
})

Usage Examples

1. Basic Chat Completion
resp, err := svc.ChatCompletion(ctx, openai.ChatRequest{
    Messages: []openai.Message{
        {Role: "user", Content: "What is the capital of France?"},
    },
})
if err != nil {
    return err
}
fmt.Printf("Response: %s\n", resp.Content)
fmt.Printf("Tokens used: %d\n", resp.Usage.TotalTokens)
2. Chat with System Prompt
systemPrompt := "You are a helpful assistant that speaks like a pirate."

resp, err := svc.ChatCompletion(ctx, openai.ChatRequest{
    SystemPrompt: &systemPrompt,
    Messages: []openai.Message{
        {Role: "user", Content: "Tell me about treasure hunting."},
    },
})
3. Vision — Analyze Images

Pass image URLs alongside text in a user message (requires a vision-capable model such as gpt-4o):

resp, err := svc.ChatCompletion(ctx, openai.ChatRequest{
    Messages: []openai.Message{
        {
            Role:      "user",
            Content:   "What is in this image?",
            ImageURLs: []string{"https://example.com/photo.jpg"},
        },
    },
    MaxTokens: openai.Int64Ptr(300),
})
4. Per-Request Overrides
model := "gpt-4o-mini"
temperature := 0.9
maxTokens := int64(500)

resp, err := svc.ChatCompletion(ctx, openai.ChatRequest{
    Model:       &model,
    Temperature: &temperature,
    MaxTokens:   &maxTokens,
    Messages: []openai.Message{
        {Role: "user", Content: "Write a creative poem."},
    },
})
5. Multi-Turn Conversation
resp, err := svc.ChatCompletion(ctx, openai.ChatRequest{
    Messages: []openai.Message{
        {Role: "user",      Content: "What is 2+2?"},
        {Role: "assistant", Content: "2+2 equals 4."},
        {Role: "user",      Content: "What about 2+3?"},
    },
})
6. Streaming Responses
handler := func(chunk string, done bool) error {
    if done {
        fmt.Println() // newline at end
        return nil
    }
    fmt.Print(chunk) // print token as it arrives
    return nil
}

resp, err := svc.ChatCompletionStream(ctx, openai.ChatRequest{
    Messages: []openai.Message{
        {Role: "user", Content: "Write a short story about a robot."},
    },
}, handler)
if err != nil {
    return err
}
// resp.Content holds the full assembled response
fmt.Printf("\n%d chunks received\n", ...)
7. Audio Transcription (Whisper)
file, err := os.Open("audio.mp3")
if err != nil {
    return err
}
defer file.Close()

language := "en"
resp, err := svc.TranscribeAudio(ctx, openai.AudioRequest{
    File:     file,
    FileName: "audio.mp3",
    Language: &language, // optional — improves accuracy
})
if err != nil {
    return err
}
fmt.Printf("Transcription: %s\n", resp.Text)
8. Audio Translation to English
file, err := os.Open("german_audio.mp3")
if err != nil {
    return err
}
defer file.Close()

resp, err := svc.TranslateAudio(ctx, openai.AudioRequest{
    File:     file,
    FileName: "german_audio.mp3",
})
if err != nil {
    return err
}
fmt.Printf("English translation: %s\n", resp.Text)
9. Custom Base URL (Azure OpenAI / Proxy)
svc := openai.New(openai.Config{
    APIKey:  "your-azure-key",
    BaseURL: "https://your-resource.openai.azure.com/openai/deployments/your-deployment",
})

Error Handling

resp, err := svc.ChatCompletion(ctx, req)
if err != nil {
    var he *apperr.HTTPError
    if errors.As(err, &he) {
        switch he.Type {
        case "NO_MESSAGES":
            // no messages provided
        case "EMPTY_PROMPT":
            // message content is empty
        case "NO_FILE":
            // audio file missing
        case "OPENAI_API_ERROR":
            // upstream API error — he.Internal has details
        }
    }
}
Error reference
Error var Type HTTP status
ErrInvalidAPIKey INVALID_API_KEY 401
ErrEmptyPrompt EMPTY_PROMPT 400
ErrNoMessages NO_MESSAGES 400
ErrNoFile NO_FILE 400
ErrStreamingFailed STREAMING_FAILED 500
ErrAPIError OPENAI_API_ERROR 502

Supported Models

Text / Chat
  • gpt-4o (default) — multimodal, latest
  • gpt-4o-mini — faster and cheaper
  • gpt-4-turbo
Audio
  • whisper-1 (default) — transcription and translation

For the full list see OpenAI Models.

Helper Utilities

openai.StringPtr("value")    // *string
openai.Int64Ptr(500)         // *int64
openai.Float64Ptr(0.9)       // *float64

Running Tests

Unit tests (no API key required)
go test ./pkg/openai/... -run "^Test[^I]"
Integration tests (require OPENAI_API_KEY)
OPENAI_API_KEY=sk-... go test ./pkg/openai/... -v -run "^TestIntegration"
Audio integration tests (also require an audio file)
OPENAI_API_KEY=sk-... OPENAI_TEST_AUDIO_FILE=./testdata/sample.mp3 \
  go test ./pkg/openai/... -v -run "^TestIntegration_(Transcribe|Translate)"

Dependency Injection

// internal/di/wire.go
func ProvideOpenAIService(cfg *config.Configuration) *openai.Service {
    return openai.New(openai.Config{
        APIKey:     cfg.OpenAIAPIKey,
        BaseURL:    cfg.OpenAIBaseURL,
        Timeout:    cfg.OpenAITimeout,
        MaxRetries: cfg.OpenAIMaxRetries,
        TextModel:  cfg.OpenAITextModel,
        AudioModel: cfg.OpenAIAudioModel,
    })
}

// Application struct field:
OpenAI *openai.Service

// wire.Build():
wire.Build(..., ProvideOpenAIService, wire.Struct(new(Application), "*"))

Run make wire after modifying wire.go.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidAPIKey is returned when the API key is invalid or missing
	ErrInvalidAPIKey = apperr.NewHTTPError(http.StatusUnauthorized, "INVALID_API_KEY", "Invalid or missing OpenAI API key")

	// ErrEmptyPrompt is returned when the prompt is empty
	ErrEmptyPrompt = apperr.NewHTTPError(http.StatusBadRequest, "EMPTY_PROMPT", "Prompt cannot be empty")

	// ErrUnsupportedFormat is returned when the file format is not supported
	ErrUnsupportedFormat = apperr.NewHTTPError(http.StatusBadRequest, "UNSUPPORTED_FORMAT", "Unsupported file format")

	// ErrStreamingFailed is returned when streaming fails
	ErrStreamingFailed = apperr.NewHTTPError(http.StatusInternalServerError, "STREAMING_FAILED", "Streaming response failed")

	// ErrNoMessages is returned when no messages are provided
	ErrNoMessages = apperr.NewHTTPError(http.StatusBadRequest, "NO_MESSAGES", "At least one message is required")

	// ErrNoFile is returned when no file is provided for audio transcription
	ErrNoFile = apperr.NewHTTPError(http.StatusBadRequest, "NO_FILE", "Audio file is required")

	// ErrAPIError is returned when the OpenAI API returns an error
	ErrAPIError = apperr.NewHTTPError(http.StatusBadGateway, "OPENAI_API_ERROR", "OpenAI API error")
)

Custom errors for OpenAI service

Functions

func Float64Ptr

func Float64Ptr(f float64) *float64

Float64Ptr returns a pointer to the float64 value

func Int64Ptr

func Int64Ptr(i int64) *int64

Int64Ptr returns a pointer to the int64 value

func StringPtr

func StringPtr(s string) *string

StringPtr returns a pointer to the string value

Types

type AudioRequest

type AudioRequest struct {
	File     io.Reader `json:"-"`                         // Audio file reader
	FileName string    `json:"file_name"`                 // File name (required for API)
	Language *string   `json:"language,omitempty"`        // Optional language code (ISO-639-1, e.g., "en")
	Prompt   *string   `json:"prompt,omitempty"`          // Optional context to guide transcription
	Format   *string   `json:"response_format,omitempty"` // Optional format (json, text, srt, vtt)
}

AudioRequest represents an audio transcription request

type AudioResponse

type AudioResponse struct {
	Text     string   `json:"text"`               // Transcribed text
	Language string   `json:"language,omitempty"` // Detected language (if available)
	Duration *float64 `json:"duration,omitempty"` // Duration in seconds (if available)
}

AudioResponse represents an audio transcription response

type ChatRequest

type ChatRequest struct {
	Messages     []Message `json:"messages"`                // List of messages
	Model        *string   `json:"model,omitempty"`         // Optional model override
	MaxTokens    *int64    `json:"max_tokens,omitempty"`    // Optional max tokens override
	Temperature  *float64  `json:"temperature,omitempty"`   // Optional temperature override (0.0 to 2.0)
	SystemPrompt *string   `json:"system_prompt,omitempty"` // Optional system prompt
	TopP         *float64  `json:"top_p,omitempty"`         // Optional nucleus sampling
	Stop         []string  `json:"stop,omitempty"`          // Optional stop sequences
}

ChatRequest represents a chat completion request

type ChatResponse

type ChatResponse struct {
	ID           string     `json:"id"`
	Content      string     `json:"content"`
	Model        string     `json:"model"`
	FinishReason string     `json:"finish_reason"`
	Usage        TokenUsage `json:"usage"`
	CreatedAt    int64      `json:"created_at"`
}

ChatResponse represents a chat completion response

type Config

type Config struct {
	APIKey     string
	BaseURL    string
	Timeout    int
	MaxRetries int

	// Model defaults per capability
	TextModel  string // Default model for chat/text completions (e.g. gpt-4o)
	AudioModel string // Default model for audio transcription/translation (e.g. whisper-1)
}

Config holds the configuration for the OpenAI service

type Message

type Message struct {
	Role      string   `json:"role"`       // system, user, or assistant
	Content   string   `json:"content"`    // Text content
	ImageURLs []string `json:"image_urls"` // Optional image URLs for vision models
}

Message represents a chat message with optional image URLs

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service represents the OpenAI service

func New

func New(cfg Config) *Service

New creates a new OpenAI service instance

func (*Service) ChatCompletion

func (s *Service) ChatCompletion(ctx context.Context, req ChatRequest) (*ChatResponse, error)

ChatCompletion performs a chat completion request

func (*Service) ChatCompletionStream

func (s *Service) ChatCompletionStream(ctx context.Context, req ChatRequest, handler StreamHandler) (*ChatResponse, error)

ChatCompletionStream performs a streaming chat completion request The handler is called for each chunk, and a complete ChatResponse is returned at the end

func (*Service) TranscribeAudio

func (s *Service) TranscribeAudio(ctx context.Context, req AudioRequest) (*AudioResponse, error)

TranscribeAudio transcribes audio using the Whisper model

func (*Service) TranslateAudio

func (s *Service) TranslateAudio(ctx context.Context, req AudioRequest) (*AudioResponse, error)

TranslateAudio translates audio to English using the Whisper model

type StreamHandler

type StreamHandler func(chunk string, done bool) error

StreamHandler is a callback function that receives streaming chunks chunk: the text content of the current chunk done: true when streaming is complete

type TokenUsage

type TokenUsage struct {
	PromptTokens     int `json:"prompt_tokens"`
	CompletionTokens int `json:"completion_tokens"`
	TotalTokens      int `json:"total_tokens"`
}

TokenUsage represents token usage statistics

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL