Pollytool API Documentation

April 20, 2026 · View on GitHub

This document describes how to use the pollytool package as a library to interact with various LLM providers.

Installation

go get github.com/alexschlessinger/pollytool

Environment Variables

Set these for API authentication:

  • POLLYTOOL_OPENAIKEY - OpenAI API key
  • POLLYTOOL_ANTHROPICKEY - Anthropic API key
  • POLLYTOOL_GEMINIKEY - Google Gemini API key
  • POLLYTOOL_OLLAMAKEY - Ollama API key (optional for local)

Quick Start

Simple Example Using Helpers

package main

import (
    "context"
    "fmt"
    
    "github.com/alexschlessinger/pollytool/llm"
)

func main() {
    ctx := context.Background()
    
    // Set environment variable: export POLLYTOOL_OPENAIKEY="your-key"
    
    // Simple completion
    joke, err := llm.QuickComplete(ctx, "openai/gpt-5.4", "Tell me a joke", 500)
    if err != nil {
        panic(err)
    }
    fmt.Println(joke)
    
    // Streaming completion
    fmt.Println("\nWriting a story:")
    err = llm.StreamComplete(ctx, "openai/gpt-5.4", "Write a short story", 500, func(chunk string) {
        fmt.Print(chunk)
    })
    if err != nil {
        panic(err)
    }
}

Helper Functions

The llm/helpers package provides convenience functions to simplify common LLM operations. These functions create a fresh router from environment variables on each call. If your application makes many requests, create one client with GetDefaultClient or NewMultiPass and reuse it explicitly.

One-line Completions

import "github.com/alexschlessinger/pollytool/llm"

// Quick completion - just pass the model and prompt
// API keys are loaded from POLLYTOOL_*KEY environment variables
response, err := llm.QuickComplete(ctx, "openai/gpt-5.4", "Tell me a joke", 1000)

// Or with more tokens for longer output
response, err := llm.QuickComplete(ctx, "anthropic/claude-opus-4-7", "Write a story", 4000)

Streaming with Callback

// Stream completion with real-time output
err := llm.StreamComplete(ctx, "openai/gpt-5.4", "Write a story", 2000, func(chunk string) {
    fmt.Print(chunk) // Print each chunk as it arrives
})

Conversation with History

// Maintain conversation context
history := []messages.ChatMessage{
    {Role: messages.MessageRoleSystem, Content: "You are helpful"},
    {Role: messages.MessageRoleUser, Content: "Hi"},
    {Role: messages.MessageRoleAssistant, Content: "Hello! How can I help?"},
}

response, err := llm.ChatWithHistory(ctx, "openai/gpt-5.4", history, "What did I just say?", 1000)

Structured JSON Output

// Define your result struct
type UserInfo struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email"`
}

// Define schema
schema := &llm.Schema{
    Raw: map[string]any{
        "type": "object",
        "properties": map[string]any{
            "name":  map[string]any{"type": "string"},
            "age":   map[string]any{"type": "integer"},
            "email": map[string]any{"type": "string"},
        },
        "required": []string{"name", "email"},
    },
}

// Get structured response
var user UserInfo
err := llm.StructuredComplete(ctx, "openai/gpt-5.4", 
    "Extract: John Doe, 30, john@example.com", schema, 500, &user)
// user now contains: {Name: "John Doe", Age: 30, Email: "john@example.com"}

Builder Pattern for Complex Requests

client := llm.GetDefaultClient()

// Fluent API for building requests
result, err := llm.NewCompletionBuilder("openai/gpt-5.4").
    WithSystemPrompt("You are a helpful assistant").
    WithUserMessage("Tell me about Go").
    WithTemperature(0.8).
    WithMaxTokens(500).
    Execute(ctx, client)

// Or with streaming
err := llm.NewCompletionBuilder("openai/gpt-5.4").
    WithSystemPrompt("You are a creative writer").
    WithUserMessage("Write a haiku").
    ExecuteStreaming(ctx, client, func(chunk string) {
        fmt.Print(chunk)
    })

Automatic Tool Handling

// Setup tools
weatherTool := &WeatherTool{}
registry := tools.NewToolRegistry([]tools.Tool{weatherTool})

// Execute with automatic tool handling
response, err := llm.NewCompletionBuilder("openai/gpt-5.4").
    WithUserMessage("What's the weather in NYC?").
    WithTools(registry.All()).
    ExecuteWithTools(ctx, client, registry)
// Automatically calls weather tool and returns final response

// Skills are also supported on the builder
result, err := llm.NewCompletionBuilder("openai/gpt-5.4").
    WithSystemPrompt("You are a helpful assistant").
    WithSkills(catalog).
    WithUserMessage("Tell me about Go").
    Execute(ctx, client)

Skills Runtime

import (
    "github.com/alexschlessinger/pollytool/skills"
    "github.com/alexschlessinger/pollytool/tools"
)

// Resolve skill directories (expands ~, deduplicates, validates).
// Falls back to ~/.pollytool/skills when dirs is empty.
dirs, err := skills.ResolveDirs([]string{"~/my-skills", "./local-skills"})

// Or load a ready-to-use catalog in one step:
catalog, err := skills.LoadCatalog([]string{"~/my-skills"})
if err != nil {
    panic(err)
}

// LoadCatalog returns nil when no skills are found.
if catalog == nil {
    // no skills available — proceed without them
}

registry := tools.NewToolRegistry(nil)
skillRuntime, err := tools.NewSkillRuntime(catalog, registry)
if err != nil {
    panic(err)
}

// Set Skills on CompletionRequest for automatic system prompt augmentation.
// Skill prompt injection happens transparently during completion.
req := &llm.CompletionRequest{
    Model:    "openai/gpt-5.4",
    Messages: history,
    Skills:   catalog,
}

// Or use RuntimeSystemPrompt / BuildMessages for manual control:
systemPrompt := catalog.RuntimeSystemPrompt("You are a helpful assistant")

// Activate directly from application code
_, err = skillRuntime.Activate("code-reviewer")
if err != nil {
    panic(err)
}

// Persist and restore active skills across runs
savedSkills := skillRuntime.ActivatedSkills()
err = skillRuntime.Restore(savedSkills)
if err != nil {
    panic(err)
}

_ = systemPrompt
_ = msgs
_ = dirs
_ = registry

Core Interfaces

LLM Interface

The main interface for interacting with language models:

import "github.com/alexschlessinger/pollytool/llm"

type LLM interface {
    ChatCompletionStream(
        ctx context.Context,
        req *CompletionRequest,
        processor StreamProcessor,
    ) <-chan *messages.StreamEvent
}

CompletionRequest

Configuration for LLM requests:

type CompletionRequest struct {
    Model          string                   // Model identifier (e.g., "gpt-5.4", "claude-opus-4-7")
    Messages       []messages.ChatMessage   // Conversation history
    Temperature    float32                  // Sampling temperature (0.0-1.0)
    MaxTokens      int                      // Maximum tokens to generate
    Tools          []tools.Tool             // Available tools for function calling
    ResponseSchema *Schema                  // JSON schema for structured output
    Timeout        time.Duration            // Request timeout
    BaseURL        string                   // Custom API endpoint (for OpenAI-compatible)
    Skills         *skills.Catalog          // Optional skill catalog for system prompt augmentation
}

Message Types

ChatMessage

type ChatMessage struct {
    Role       MessageRole              // "system", "user", "assistant", or "tool"
    Content    string                   // Text content
    ToolCalls  []ChatMessageToolCall    // Tool/function calls from assistant
    ToolCallID string                   // ID for tool response messages
}

type ChatMessageToolCall struct {
    ID        string   // Unique identifier for this tool call
    Name      string   // Name of the tool to call
    Arguments string   // JSON string of arguments
}

Message Roles

const (
    MessageRoleSystem    MessageRole = "system"
    MessageRoleUser      MessageRole = "user"
    MessageRoleAssistant MessageRole = "assistant"
    MessageRoleTool      MessageRole = "tool"
)

Streaming Events

The streaming API returns events through a channel:

type StreamEvent struct {
    Type    EventType
    Content string           // For EventTypeContent
    Message *ChatMessage     // For EventTypeComplete
    Error   error           // For EventTypeError
}

type EventType string

const (
    EventTypeContent  EventType = "content"   // Streaming text chunk
    EventTypeToolCall EventType = "tool_call" // Tool call in progress
    EventTypeComplete EventType = "complete"  // Final message with all content
    EventTypeError    EventType = "error"     // Error occurred
)

Provider Support

Model Identifiers

Use the format provider/model:

  • OpenAI: openai/gpt-5.4, openai/gpt-5.4-mini
  • Anthropic: anthropic/claude-opus-4-7, anthropic/claude-sonnet-4-6
  • Gemini: gemini/gemini-3.1-pro-preview, gemini/gemini-3.1-flash-lite-preview
  • Ollama: ollama/gpt-oss

MultiPass Provider

The recommended way to use multiple providers:

import (
    "github.com/alexschlessinger/pollytool/llm"
    "github.com/alexschlessinger/pollytool/messages"
)

// Create MultiPass with API keys
apiKeys := map[string]string{
    "openai":    os.Getenv("POLLYTOOL_OPENAIKEY"),
    "anthropic": os.Getenv("POLLYTOOL_ANTHROPICKEY"),
    "gemini":    os.Getenv("POLLYTOOL_GEMINIKEY"),
    "ollama":    os.Getenv("POLLYTOOL_OLLAMAKEY"),
}

multipass := llm.NewMultiPass(apiKeys)
// or use 
// multipass:= llm.GetDefaultClient()

// MultiPass is a stateless router. It does not memoize provider clients internally.

// Create request
req := &llm.CompletionRequest{
    Model: "anthropic/claude-opus-4-7",
    Messages: []messages.ChatMessage{
        {
            Role:    messages.MessageRoleSystem,
            Content: "You are a helpful assistant.",
        },
        {
            Role:    messages.MessageRoleUser,
            Content: "Hello, how are you?",
        },
    },
    Temperature: 0.7,
    MaxTokens:   1000,
    Timeout:     30 * time.Second,
}

// Stream response
processor := messages.NewStreamProcessor()
eventChan := multipass.ChatCompletionStream(ctx, req, processor)

for event := range eventChan {
    switch event.Type {
    case messages.EventTypeContent:
        fmt.Print(event.Content)
    case messages.EventTypeComplete:
        fmt.Printf("\nComplete: %+v\n", event.Message)
    case messages.EventTypeError:
        fmt.Printf("Error: %v\n", event.Error)
    }
}

Direct Provider Usage

You can also use providers directly:

// OpenAI
client := llm.NewOpenAIClient(apiKey, "")

// Anthropic  
client := llm.NewAnthropicClient(apiKey)

// Gemini
client := llm.NewGeminiClient(apiKey)

// Ollama (local)
client := llm.NewOllamaClient(apiKey) // apiKey can be empty for local

Tool Integration

Defining Tools

Tools allow LLMs to call functions. Implement the Tool interface:

import (
    "github.com/alexschlessinger/pollytool/tools"
    "github.com/modelcontextprotocol/go-sdk/jsonschema"
)

type Tool interface {
    GetSchema() *jsonschema.Schema
    Execute(ctx context.Context, args map[string]any) (string, error)
}

Example Tool Implementation

type WeatherTool struct{}

func (w *WeatherTool) GetSchema() *jsonschema.Schema {
    return &jsonschema.Schema{
        Title:       "get_weather",
        Description: "Get the current weather for a location",
        Type:        "object",
        Properties: map[string]*jsonschema.Schema{
            "location": {
                Type:        "string",
                Description: "The city and state, e.g. San Francisco, CA",
            },
        },
        Required: []string{"location"},
    }
}

func (w *WeatherTool) Execute(ctx context.Context, args map[string]any) (string, error) {
    location, ok := args["location"].(string)
    if !ok {
        return "", fmt.Errorf("location is required")
    }
    
    // Implementation here
    return fmt.Sprintf("The weather in %s is sunny and 72°F", location), nil
}

Using Tools with LLM

// Create tool registry
registry := tools.NewToolRegistry([]tools.Tool{
    &WeatherTool{},
})

// Include tools in request
req := &llm.CompletionRequest{
    Model: "openai/gpt-5.4",
    Messages: []messages.ChatMessage{
        {
            Role:    messages.MessageRoleUser,
            Content: "What's the weather in San Francisco?",
        },
    },
    Tools: registry.All(),
}

// Process response with tool calls
eventChan := client.ChatCompletionStream(ctx, req, processor)

for event := range eventChan {
    if event.Type == messages.EventTypeComplete && len(event.Message.ToolCalls) > 0 {
        // Execute tool calls
        for _, toolCall := range event.Message.ToolCalls {
            var args map[string]any
            json.Unmarshal([]byte(toolCall.Arguments), &args)
            
            tool, _ := registry.Get(toolCall.Name)
            result, err := tool.Execute(ctx, args)
            
            // Add tool result to conversation
            messages = append(messages, messages.ChatMessage{
                Role:       messages.MessageRoleTool,
                Content:    result,
                ToolCallID: toolCall.ID,
            })
        }
        
        // Continue conversation with tool results
        req.Messages = messages
        eventChan = client.ChatCompletionStream(ctx, req, processor)
    }
}

Shell Tool Integration

Pollytool includes a ShellTool that allows you to wrap shell scripts as LLM tools. The scripts must implement a simple protocol with --schema and --execute flags.

Example: Weather Script Tool

Create a weather.sh script:

#!/bin/bash

# weather.sh - A simple weather tool for LLMs

if [ "\$1" = "--schema" ]; then
    cat <<EOF
{
  "title": "get_weather",
  "description": "Get current weather for a location",
  "type": "object",
  "properties": {
    "location": {
      "type": "string",
      "description": "City and state, e.g. 'San Francisco, CA'"
    },
    "units": {
      "type": "string",
      "enum": ["celsius", "fahrenheit"],
      "default": "fahrenheit",
      "description": "Temperature units"
    }
  },
  "required": ["location"]
}
EOF
    exit 0
fi

if [ "\$1" = "--execute" ]; then
    # Parse JSON arguments
    LOCATION=$(echo "\$2" | jq -r '.location')
    UNITS=$(echo "\$2" | jq -r '.units // "fahrenheit"')
    
    # In production, call a real weather API
    # For demo, return mock data based on location
    case "$LOCATION" in
        "San Francisco, CA")
            if [ "$UNITS" = "celsius" ]; then
                echo "18°C, foggy with moderate winds"
            else
                echo "65°F, foggy with moderate winds"
            fi
            ;;
        "New York, NY")
            if [ "$UNITS" = "celsius" ]; then
                echo "7°C, partly cloudy"
            else
                echo "45°F, partly cloudy"
            fi
            ;;
        "Miami, FL")
            if [ "$UNITS" = "celsius" ]; then
                echo "28°C, sunny and humid"
            else
                echo "82°F, sunny and humid"
            fi
            ;;
        *)
            if [ "$UNITS" = "celsius" ]; then
                echo "21°C, mild weather"
            else
                echo "70°F, mild weather"
            fi
            ;;
    esac
    exit 0
fi

echo "Usage: \$0 [--schema | --execute <json-args>]"
exit 1

Make it executable:

chmod +x weather.sh

Using Shell Tools in Go

package main

import (
    "context"
    "fmt"
    "os"
    
    "github.com/alexschlessinger/pollytool/llm"
    "github.com/alexschlessinger/pollytool/messages"
    "github.com/alexschlessinger/pollytool/tools"
)

func main() {
    ctx := context.Background()
    
    // Load shell tools
    shellTools, err := tools.LoadShellTools([]string{
        "./weather.sh",
        "./calculator.sh",  // You can load multiple scripts
    })
    if err != nil {
        fmt.Printf("Warning: %v\n", err)
    }
    
    // Create tool registry
    registry := tools.NewToolRegistry(shellTools)
    
    client := llm.GetDefaultClient()
    
    // Create request with tools
    req := &llm.CompletionRequest{
        Model: "openai/gpt-5.4",
        Messages: []messages.ChatMessage{
            {
                Role:    messages.MessageRoleUser,
                Content: "What's the weather in San Francisco and New York?",
            },
        },
        Tools:     registry.All(),
        MaxTokens: 1000,
    }
    
    // Process with automatic tool handling
    processor := messages.NewStreamProcessor()
    eventChan := client.ChatCompletionStream(ctx, req, processor)
    
    for event := range eventChan {
        switch event.Type {
        case messages.EventTypeContent:
            fmt.Print(event.Content)
            
        case messages.EventTypeComplete:
            if len(event.Message.ToolCalls) > 0 {
                // Execute tool calls
                for _, toolCall := range event.Message.ToolCalls {
                    var args map[string]any
                    json.Unmarshal([]byte(toolCall.Arguments), &args)
                    
                    tool, _ := registry.Get(toolCall.Name)
                    result, err := tool.Execute(ctx, args)
                    
                    // Add tool response to conversation
                    req.Messages = append(req.Messages,
                        *event.Message,
                        messages.ChatMessage{
                            Role:       messages.MessageRoleTool,
                            Content:    result,
                            ToolCallID: toolCall.ID,
                        },
                    )
                }
                
                // Continue conversation
                eventChan = client.ChatCompletionStream(ctx, req, processor)
            }
            
        case messages.EventTypeError:
            fmt.Printf("Error: %v\n", event.Error)
        }
    }
}

Script Protocol Requirements

Shell scripts used as tools must:

  1. Accept --schema flag and output a JSON Schema describing the tool
  2. Accept --execute <json-args> and process the JSON arguments
  3. Return results as plain text to stdout
  4. Exit with code 0 on success, non-zero on error

The schema may include "sandbox" at the top level to opt a tool into sandboxing. When sandbox is applied, [sandboxed] is appended to the tool's description in the LLM-facing schema. If no supported sandbox backend is available, Polly exits with an error instead of running unsandboxed. Disable with --nosandbox or POLLYTOOL_NOSANDBOX=true.

Tools that omit "sandbox" or set it to false run without restrictions, even when sandboxing is active.

Sandbox Spec Reference

"sandbox" can be true (use defaults) or an object:

FieldTypeDefaultDescription
allowNetworkboolfalseAllow outbound network access
denyDNSboolfalseBlock DNS resolution. Only effective when allowNetwork is true.
writablePathsstring[][]Directories where writes are allowed (supports ~)
readPathsstring[][]Paths exempted from the credential deny list (supports ~)
allowEnvstring[]allIf set, only these env vars are passed through
denyWriteboolfalseDeny all file writes, including temp. Overrides writablePaths.

Defaults (when "sandbox": true):

  • Writes: denied everywhere except OS temp dir (/tmp)
  • Network: denied
  • Reads: all files accessible except credential paths (see below)
  • Env: all vars passed through, except POLLYTOOL_* which are always stripped

Credential paths denied by default: ~/.ssh, ~/.gnupg, ~/.gpg, ~/.aws, ~/.azure, ~/.config/gcloud, ~/.kube, ~/.docker/config.json, ~/.npmrc, ~/.pypirc, ~/.gem/credentials, ~/.cargo/credentials, ~/.config/gh, ~/.netrc, ~/.git-credentials, ~/.local/share/keyrings, ~/Library/Keychains

POLLYTOOL_* env vars (API keys) are always stripped from sandboxed processes, even without allowEnv. To explicitly pass one through, include it in allowEnv.

Conflict resolution: denyWrite: true silently overrides writablePaths. denyDNS: true has no additional effect when allowNetwork is false.

Examples

Sandbox with defaults:

{
  "title": "my_tool",
  "type": "object",
  "sandbox": true,
  "properties": { ... }
}

Network access and extra write paths:

{
  "title": "api_tool",
  "type": "object",
  "sandbox": { "allowNetwork": true, "writablePaths": ["/tmp/data"] },
  "properties": { ... }
}

Network access without DNS (connect by IP only):

{
  "title": "ip_only_tool",
  "type": "object",
  "sandbox": { "allowNetwork": true, "denyDNS": true },
  "properties": { ... }
}

Deploy tool that needs AWS credentials and specific env vars:

{
  "title": "deploy_tool",
  "type": "object",
  "sandbox": {
    "allowNetwork": true,
    "writablePaths": ["/tmp/deploy"],
    "readPaths": ["~/.aws"],
    "allowEnv": ["AWS_PROFILE", "AWS_REGION", "HOME", "PATH"]
  },
  "properties": { ... }
}

Fully read-only sandbox (no writes anywhere, not even temp):

{
  "title": "readonly_tool",
  "type": "object",
  "sandbox": { "denyWrite": true },
  "properties": { ... }
}

More Complex Example: Database Query Tool

#!/bin/bash
# dbquery.sh - Query database tool

if [ "\$1" = "--schema" ]; then
    cat <<EOF
{
  "title": "query_database",
  "description": "Execute SQL queries on the application database",
  "type": "object",
  "properties": {
    "query": {
      "type": "string",
      "description": "SQL query to execute"
    },
    "database": {
      "type": "string",
      "enum": ["users", "products", "orders"],
      "description": "Target database"
    },
    "limit": {
      "type": "integer",
      "default": 10,
      "description": "Maximum rows to return"
    }
  },
  "required": ["query", "database"]
}
EOF
    exit 0
fi

if [ "\$1" = "--execute" ]; then
    QUERY=$(echo "\$2" | jq -r '.query')
    DATABASE=$(echo "\$2" | jq -r '.database')
    LIMIT=$(echo "\$2" | jq -r '.limit // 10')
    
    # Add LIMIT clause if not present
    if ! echo "$QUERY" | grep -qi "limit"; then
        QUERY="$QUERY LIMIT $LIMIT"
    fi
    
    # Execute query (example using sqlite)
    sqlite3 "/data/${DATABASE}.db" "$QUERY" 2>&1
    exit $?
fi

MCP Server Integration

Pollytool supports Model Context Protocol (MCP) servers, allowing you to use remote tools and resources:

Connecting to MCP Servers

import (
    "github.com/alexschlessinger/pollytool/mcp"
    "github.com/alexschlessinger/pollytool/tools"
)

// Connect to an MCP server
mcpClient, err := mcp.NewClient("stdio", []string{"npx", "-y", "@modelcontextprotocol/server-filesystem"})
if err != nil {
    panic(err)
}
defer mcpClient.Close()

// Get tools from MCP server
mcpTools, err := mcpClient.GetTools()
if err != nil {
    panic(err)
}

// Create tool registry with MCP tools
registry := tools.NewToolRegistry(mcpTools)

Using MCP Tools with LLM

// Create request with MCP tools
req := &llm.CompletionRequest{
    Model: "openai/gpt-5.4",
    Messages: []messages.ChatMessage{
        {
            Role:    messages.MessageRoleUser,
            Content: "List files in the current directory",
        },
    },
    Tools: registry.All(),
}

// Process as usual - MCP tools work just like local tools
eventChan := client.ChatCompletionStream(ctx, req, processor)

Available MCP Servers

Common MCP servers you can connect to:

  • @modelcontextprotocol/server-filesystem - File system operations
  • @modelcontextprotocol/server-github - GitHub API access
  • @modelcontextprotocol/server-gitlab - GitLab API access
  • @modelcontextprotocol/server-postgres - PostgreSQL database access
  • @modelcontextprotocol/server-sqlite - SQLite database access

Combining Local and MCP Tools

// Load local tools
localTools := []tools.Tool{
    &WeatherTool{},
    &CalculatorTool{},
}

// Get MCP tools
mcpClient, _ := mcp.NewClient("stdio", []string{"npx", "-y", "@modelcontextprotocol/server-filesystem"})
mcpTools, _ := mcpClient.GetTools()

// Combine all tools
allTools := append(localTools, mcpTools...)
registry := tools.NewToolRegistry(allTools)

Session Management

For persistent conversations:

import "github.com/alexschlessinger/pollytool/sessions"

// Create file-based session store
store, err := sessions.NewFileSessionStore("~/.pollytool/contexts")

// Get or create session
session := store.Get("my-session-id")

// Add messages
session.AddMessage(messages.ChatMessage{
    Role:    messages.MessageRoleUser,
    Content: "Hello!",
})

// Get history for requests
history := session.GetHistory()

// Clear session
session.Clear()

// Close session (releases locks)
if fileSession, ok := session.(*sessions.FileSession); ok {
    defer fileSession.Close()
}

Structured Output

Use JSON Schema for structured responses:

schema := &llm.Schema{
    Name: "UserInfo",
    Schema: &jsonschema.Schema{
        Type: "object",
        Properties: map[string]*jsonschema.Schema{
            "name": {
                Type:        "string",
                Description: "User's full name",
            },
            "age": {
                Type:        "integer",
                Description: "User's age",
            },
            "email": {
                Type:        "string",
                Format:      "email",
                Description: "User's email address",
            },
        },
        Required: []string{"name", "email"},
    },
}

req := &llm.CompletionRequest{
    Model: "openai/gpt-5.4",
    Messages: []messages.ChatMessage{
        {
            Role:    messages.MessageRoleUser,
            Content: "Extract user info from: John Doe, 30 years old, john@example.com",
        },
    },
    ResponseSchema: schema,
}

Error Handling

eventChan := client.ChatCompletionStream(ctx, req, processor)

for event := range eventChan {
    switch event.Type {
    case messages.EventTypeError:
        // Handle errors
        if strings.Contains(event.Error.Error(), "rate limit") {
            // Implement backoff
            time.Sleep(time.Second * 5)
        } else if strings.Contains(event.Error.Error(), "context length") {
            // Truncate conversation history
            req.Messages = req.Messages[len(req.Messages)-5:]
        }
    }
}

Advanced Examples

Example with Sessions

package main

import (
    "context"
    "fmt"
    "os"
    "time"
    
    "github.com/alexschlessinger/pollytool/llm"
    "github.com/alexschlessinger/pollytool/messages"
    "github.com/alexschlessinger/pollytool/sessions"
)

func main() {
    ctx := context.Background()
    
    // you can create a specific client if you wish
    client := llm.NewOpenAIClient(os.Getenv("OPENAIKEY"), "")
    
    // Create session
    store, _ := sessions.NewFileSessionStore("")
    session := store.Get("example-session")
    defer func() {
        if fs, ok := session.(*sessions.FileSession); ok {
            fs.Close()
        }
    }()
    
    // Add system prompt if new session
    if len(session.GetHistory()) == 0 {
        session.AddMessage(messages.ChatMessage{
            Role:    messages.MessageRoleSystem,
            Content: "You are a helpful AI assistant.",
        })
    }
    
    // Add user message
    session.AddMessage(messages.ChatMessage{
        Role:    messages.MessageRoleUser,
        Content: "Tell me a joke",
    })
    
    // Create request
    req := &llm.CompletionRequest{
        Model:       "openai/gpt-5.4",
        Messages:    session.GetHistory(),
        Temperature: 0.7,
        MaxTokens:   500,
        Timeout:     30 * time.Second,
    }
    
    // Stream response
    processor := messages.NewStreamProcessor()
    eventChan := client.ChatCompletionStream(ctx, req, processor)
    
    var response messages.ChatMessage
    fmt.Print("Assistant: ")
    
    for event := range eventChan {
        switch event.Type {
        case messages.EventTypeContent:
            fmt.Print(event.Content)
        case messages.EventTypeComplete:
            response = *event.Message
            session.AddMessage(response)
            fmt.Println()
        case messages.EventTypeError:
            fmt.Printf("\nError: %v\n", event.Error)
            return
        }
    }
}

Thread Safety

  • The MultiPass provider is thread-safe
  • Session stores use mutexes for concurrent access
  • Individual sessions should not be accessed concurrently
  • Tool execution should be thread-safe in your implementation