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 keyPOLLYTOOL_ANTHROPICKEY- Anthropic API keyPOLLYTOOL_GEMINIKEY- Google Gemini API keyPOLLYTOOL_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:
- Accept
--schemaflag and output a JSON Schema describing the tool - Accept
--execute <json-args>and process the JSON arguments - Return results as plain text to stdout
- 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:
| Field | Type | Default | Description |
|---|---|---|---|
allowNetwork | bool | false | Allow outbound network access |
denyDNS | bool | false | Block DNS resolution. Only effective when allowNetwork is true. |
writablePaths | string[] | [] | Directories where writes are allowed (supports ~) |
readPaths | string[] | [] | Paths exempted from the credential deny list (supports ~) |
allowEnv | string[] | all | If set, only these env vars are passed through |
denyWrite | bool | false | Deny 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
MultiPassprovider 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