Iris

Iris is a Go SDK and CLI for building AI-powered applications. It provides a unified interface for working with large language models (LLMs), making it easy to integrate AI capabilities into your Go projects.
Why Iris?
Building AI applications often requires:
- Managing multiple LLM provider APIs with different interfaces
- Handling streaming responses, retries, and error normalization
- Securely storing and managing API keys
- Building reusable chat and tool-driven workflows
Iris solves these problems by providing:
- Unified SDK: A consistent Go API across providers (OpenAI, Anthropic, Google Gemini, xAI Grok, Z.ai GLM, Perplexity, Ollama)
- Fluent Builder Pattern: Intuitive, chainable API for constructing requests
- Built-in Streaming: First-class support for streaming responses with proper channel handling
- Secure Key Management: Encrypted local storage for API keys
- CLI Tool: Quickly test models and manage projects from the command line
Features
SDK Features
- Fluent chat builder with
System(), User(), Assistant(), Temperature(), MaxTokens(), and Tools()
- Non-streaming and streaming response modes
- Tool/function calling support
- Tool middleware stack for logging, timeout, rate limiting, cache, validation, retry, and circuit breaking
- Structured Output with
ResponseJSON() and ResponseJSONSchema() for type-safe responses
- Conversation Management with built-in
Conversation type supporting streaming
- Batch API for async processing at 50% cost savings (OpenAI)
- Testing Utilities with
MockProvider and RecordingProvider
- Responses API support for GPT-5+ models with reasoning, built-in tools (web search, code interpreter), and response chaining
- Automatic retry with exponential backoff
- Telemetry hooks for observability
- Configurable non-fatal warning routing with
core.WithWarningHandler(...)
- Normalized error types across providers
CLI Features
iris chat - Send chat completions from the terminal
iris keys - Securely manage API keys with AES-256-GCM encryption and Argon2id key derivation
iris init - Scaffold new Iris projects
Installation
SDK
go get github.com/petal-labs/iris
CLI
go install github.com/petal-labs/iris/cli/cmd/iris@v0.1.0
Quick Start
Using the SDK
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
// Create a provider
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
// Create a client
client := core.NewClient(provider)
// Send a chat request
resp, err := client.Chat("gpt-4o").
System("You are a helpful assistant.").
User("What is the capital of France?").
Temperature(0.7).
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
fmt.Printf("Tokens used: %d\n", resp.Usage.TotalTokens)
}
Using Anthropic Claude
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/anthropic"
)
func main() {
// Create an Anthropic provider
provider := anthropic.New(os.Getenv("ANTHROPIC_API_KEY"))
// Create a client
client := core.NewClient(provider)
// Send a chat request
resp, err := client.Chat("claude-sonnet-4-5").
System("You are a helpful assistant.").
User("What is the capital of France?").
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
}
Using Google Gemini
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/gemini"
)
func main() {
// Create a Gemini provider
provider := gemini.New(os.Getenv("GEMINI_API_KEY"))
// Create a client
client := core.NewClient(provider)
// Send a chat request
resp, err := client.Chat("gemini-2.5-flash").
System("You are a helpful assistant.").
User("What is the capital of France?").
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
}
Gemini models with thinking/reasoning support:
// Use reasoning with Gemini 2.5 models (budget-based)
resp, err := client.Chat("gemini-2.5-pro").
User("Solve this complex problem step by step").
ReasoningEffort(core.ReasoningEffortHigh).
GetResponse(ctx)
// Access reasoning if available
if resp.Reasoning != nil && resp.Reasoning.Output != "" {
fmt.Println("Thinking:", resp.Reasoning.Output)
}
Using xAI Grok
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/xai"
)
func main() {
// Create an xAI provider
provider := xai.New(os.Getenv("XAI_API_KEY"))
// Create a client
client := core.NewClient(provider)
// Send a chat request using Grok 4
resp, err := client.Chat(xai.ModelGrok4).
System("You are a helpful assistant.").
User("What is the capital of France?").
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
}
xAI Grok models with reasoning support:
// Use reasoning with grok-3-mini (only model that exposes reasoning_content)
resp, err := client.Chat(xai.ModelGrok3Mini).
User("Solve this step by step: If I have 5 apples and give away half...").
ReasoningEffort(core.ReasoningEffortHigh).
GetResponse(ctx)
// Access reasoning if available (grok-3-mini only)
if resp.Reasoning != nil && len(resp.Reasoning.Summary) > 0 {
fmt.Println("Thinking:", resp.Reasoning.Summary[0])
}
Using Z.ai GLM
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/zai"
)
func main() {
// Create a Z.ai provider
provider := zai.New(os.Getenv("ZAI_API_KEY"))
// Create a client
client := core.NewClient(provider)
// Send a chat request using GLM-4.7
resp, err := client.Chat(zai.ModelGLM47).
System("You are a helpful assistant.").
User("What is the capital of France?").
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
}
Z.ai GLM models with thinking support:
// Use thinking mode with GLM-4.7 (enabled by default)
resp, err := client.Chat(zai.ModelGLM47).
User("Solve this step by step: What is 15% of 240?").
ReasoningEffort(core.ReasoningEffortHigh).
GetResponse(ctx)
// Access reasoning if available
if resp.Reasoning != nil && len(resp.Reasoning.Summary) > 0 {
fmt.Println("Thinking:", resp.Reasoning.Summary[0])
}
Using Perplexity Search
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/perplexity"
)
func main() {
// Create a Perplexity provider
provider := perplexity.New(os.Getenv("PERPLEXITY_API_KEY"))
// Create a client
client := core.NewClient(provider)
// Send a search-grounded chat request
resp, err := client.Chat(perplexity.ModelSonar).
System("You are a helpful assistant.").
User("What are the latest developments in AI?").
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
}
Using Ollama
package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/ollama"
)
func main() {
// Create a local Ollama provider (no API key needed)
provider := ollama.New()
// Or connect to a remote Ollama instance:
// provider := ollama.New(ollama.WithBaseURL("http://remote-host:11434"))
// Or use Ollama Cloud:
// provider := ollama.New(
// ollama.WithCloud(),
// ollama.WithAPIKey(os.Getenv("OLLAMA_API_KEY")),
// )
// Create a client
client := core.NewClient(provider)
// Send a chat request - use any model you have pulled
resp, err := client.Chat("llama3.2").
System("You are a helpful assistant.").
User("What is the capital of France?").
GetResponse(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
fmt.Println(resp.Output)
}
Ollama models with thinking support:
// Use thinking with models like qwen3
resp, err := client.Chat("qwen3").
User("Solve this step by step: What is 15% of 240?").
ReasoningEffort(core.ReasoningEffortHigh).
GetResponse(ctx)
// Access reasoning if available
if resp.Reasoning != nil && len(resp.Reasoning.Summary) > 0 {
fmt.Println("Thinking:", resp.Reasoning.Summary[0])
}
Streaming Responses
stream, err := client.Chat("gpt-4o").
User("Write a short poem about Go.").
Stream(context.Background())
if err != nil {
log.Fatal(err)
}
// Print chunks as they arrive
for chunk := range stream.Ch {
fmt.Print(chunk.Delta)
}
fmt.Println()
// Or use DrainStream to collect everything
resp, err := core.DrainStream(ctx, stream)
Warning Hooks
Route non-fatal SDK warnings (for example, mismatched tool result IDs) into your application logger:
client := core.NewClient(provider,
core.WithWarningHandler(func(msg string) {
log.Printf("iris warning: %s", msg)
}),
)
// Define a tool
weatherTool := mytools.NewWeatherTool()
resp, err := client.Chat("gpt-4o").
User("What's the weather in San Francisco?").
Tools(weatherTool).
GetResponse(ctx)
if len(resp.ToolCalls) > 0 {
// Handle tool calls
for _, call := range resp.ToolCalls {
fmt.Printf("Tool: %s, Args: %s\n", call.Name, call.Arguments)
}
}
Wrap tools with middleware before passing them to Tools(...) or invoking them directly:
logger := log.New(os.Stdout, "tool ", 0)
weatherTool := mytools.NewWeatherTool()
wrappedTool := tools.ApplyMiddleware(
weatherTool,
tools.WithBasicValidation(),
tools.WithTimeout(5*time.Second),
tools.WithLogging(logger),
)
resp, err := client.Chat("gpt-4o").
User("What's the weather in San Francisco?").
Tools(wrappedTool).
GetResponse(ctx)
Use tools.WithValidation(...) with a custom schema validator when you want JSON-schema enforcement. Tool schemas are propagated automatically through ToolContext.
Structured Output
Constrain model output to valid JSON or a specific JSON Schema:
// JSON mode - model outputs valid JSON
resp, err := client.Chat("gpt-4o").
User("List 3 programming languages with their year created").
ResponseJSON().
GetResponse(ctx)
// Parse the JSON response
var languages []struct {
Name string `json:"name"`
Year int `json:"year"`
}
json.Unmarshal([]byte(resp.Output), &languages)
For strict schema enforcement:
schema := &core.JSONSchemaDefinition{
Name: "person",
Strict: true,
Schema: json.RawMessage(`{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}`),
}
resp, err := client.Chat("gpt-4o").
User("Extract: John is 30 years old").
ResponseJSONSchema(schema).
GetResponse(ctx)
// Output is guaranteed to match the schema
Conversation Management
The Conversation type manages message history automatically:
// Create a conversation with a system prompt
conv := core.NewConversation(client, "gpt-4o",
core.WithSystemMessage("You are a helpful assistant."),
)
// Send messages - history is managed automatically
resp1, _ := conv.Send("What is Go?")
resp2, _ := conv.Send("What are its main features?") // Remembers context
// Streaming responses
stream, _ := conv.Stream("Tell me more about concurrency")
for chunk := range stream.Ch {
fmt.Print(chunk.Delta)
}
Batch API
Submit requests for async processing at 50% cost savings:
// Check if provider supports batch
bp, ok := core.AsBatchProvider(provider)
if !ok {
log.Fatal("Provider does not support batch API")
}
// Create batch requests
requests := []core.BatchRequest{
{CustomID: "req-1", Request: core.ChatRequest{Model: "gpt-4o", Messages: msgs1}},
{CustomID: "req-2", Request: core.ChatRequest{Model: "gpt-4o", Messages: msgs2}},
}
// Submit batch
batchID, _ := bp.CreateBatch(ctx, requests)
// Wait for completion (with polling)
waiter := core.NewBatchWaiter(bp).
WithPollInterval(30 * time.Second).
WithMaxWait(24 * time.Hour)
results, _ := waiter.WaitAndCollect(ctx, batchID)
for _, result := range results {
if result.IsSuccess() {
fmt.Printf("%s: %s\n", result.CustomID, result.Response.Output)
}
}
Testing Utilities
The testing package provides utilities for deterministic tests:
import "github.com/petal-labs/iris/testing"
// MockProvider for predictable responses
mock := testing.NewMockProvider().
WithResponse("Hello!").
WithResponse("Follow-up response")
client := core.NewClient(mock)
resp, _ := client.Chat("any-model").User("Hi").GetResponse(ctx)
// resp.Output == "Hello!"
// RecordingProvider for capturing interactions
recorder := testing.NewRecordingProvider(realProvider)
client := core.NewClient(recorder)
// ... run your code ...
// Inspect recorded calls
for _, call := range recorder.Calls() {
fmt.Printf("Model: %s, Messages: %d\n", call.Request.Model, len(call.Request.Messages))
}
Image Generation
Generate images using OpenAI's image models:
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
// Generate an image
resp, err := provider.GenerateImage(ctx, &core.ImageGenerateRequest{
Model: openai.ModelGPTImage1,
Prompt: "A serene mountain landscape at sunset",
Size: core.ImageSize1024x1024,
Quality: core.ImageQualityHigh,
})
// Save the image
data, _ := resp.Data[0].GetBytes()
os.WriteFile("landscape.png", data, 0644)
Streaming Partial Images
stream, _ := provider.StreamImage(ctx, &core.ImageGenerateRequest{
Model: openai.ModelGPTImage1,
Prompt: "A futuristic cityscape",
PartialImages: 3,
})
for chunk := range stream.Ch {
// Process partial image
fmt.Printf("Partial %d received\n", chunk.PartialImageIndex)
}
final := <-stream.Final
// Save final image
Editing Images
imageData, _ := os.ReadFile("input.png")
resp, _ := provider.EditImage(ctx, &core.ImageEditRequest{
Model: openai.ModelGPTImage1,
Prompt: "Add a rainbow in the sky",
Images: []core.ImageInput{
{Data: imageData},
},
InputFidelity: core.ImageInputFidelityHigh,
})
Supported Image Models
| Model |
Description |
gpt-image-1.5 |
Latest GPT Image model |
gpt-image-1 |
Standard GPT Image |
gpt-image-1-mini |
Fast, cost-effective |
dall-e-3 |
High quality (deprecated May 2026) |
dall-e-2 |
Lower cost, inpainting (deprecated May 2026) |
Using the Responses API (GPT-5)
GPT-5 models automatically use OpenAI's Responses API, which provides advanced features like reasoning, built-in tools, and response chaining.
// GPT-5 uses the Responses API automatically
resp, err := client.Chat("gpt-5").
Instructions("You are a helpful research assistant.").
User("What are the latest developments in quantum computing?").
ReasoningEffort(core.ReasoningEffortHigh).
WebSearch().
GetResponse(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Output)
// Access reasoning summary if available
if resp.Reasoning != nil {
for _, summary := range resp.Reasoning.Summary {
fmt.Println("Reasoning:", summary)
}
}
// Response chaining - continue from a previous response
followUp, err := client.Chat("gpt-5").
ContinueFrom(resp.ID).
User("Can you elaborate on the most promising approach?").
GetResponse(ctx)
Using the CLI
# Set up your API key (stored encrypted)
iris keys set openai
iris keys set anthropic
iris keys set gemini
iris keys set xai
iris keys set zai
iris keys set ollama # Only needed for Ollama Cloud
# Chat with OpenAI
iris chat --provider openai --model gpt-4o --prompt "Hello, world!"
# Chat with Anthropic Claude
iris chat --provider anthropic --model claude-sonnet-4-5 --prompt "Hello, world!"
# Chat with Google Gemini
iris chat --provider gemini --model gemini-2.5-flash --prompt "Hello, world!"
# Chat with xAI Grok
iris chat --provider xai --model grok-4 --prompt "Hello, world!"
# Chat with Z.ai GLM
iris chat --provider zai --model glm-4.7-flash --prompt "Hello, world!"
# Chat with local Ollama (no API key needed)
iris chat --provider ollama --model llama3.2 --prompt "Hello, world!"
# Chat with GPT-5 (uses Responses API automatically)
iris chat --provider openai --model gpt-5 --prompt "Explain quantum entanglement"
# Stream responses
iris chat --provider openai --model gpt-4o --prompt "Tell me a story" --stream
iris chat --provider anthropic --model claude-sonnet-4-5 --prompt "Tell me a story" --stream
# Get JSON output
iris chat --provider openai --model gpt-4o --prompt "Hello" --json
# Initialize a new project
iris init myproject
Project Structure
iris/
├── core/ # Core SDK types and client
├── providers/ # LLM provider implementations
│ ├── internal/ # Shared provider internals (normalize, toolcalls, etc.)
│ ├── openai/ # OpenAI provider (includes Batch API)
│ ├── anthropic/ # Anthropic Claude provider
│ ├── gemini/ # Google Gemini provider
│ ├── xai/ # xAI Grok provider
│ ├── zai/ # Z.ai GLM provider
│ ├── perplexity/ # Perplexity Search provider
│ └── ollama/ # Ollama provider (local and cloud)
├── tools/ # Tool/function calling framework + middleware
├── testing/ # Test utilities (MockProvider, RecordingProvider)
├── cli/ # Command-line interface
│ ├── cmd/iris/ # CLI entry point
│ ├── commands/ # CLI commands
│ ├── config/ # Configuration loading
│ └── keystore/ # Encrypted key storage
└── tests/
└── integration/ # Provider integration + conformance harness
Configuration
Iris looks for configuration at ~/.iris/config.yaml:
default_provider: openai
default_model: gpt-5 # or gpt-4o for older models
providers:
openai:
api_key_env: OPENAI_API_KEY
anthropic:
api_key_env: ANTHROPIC_API_KEY
gemini:
api_key_env: GEMINI_API_KEY
xai:
api_key_env: XAI_API_KEY
zai:
api_key_env: ZAI_API_KEY
ollama:
# For local Ollama, no API key needed
# For Ollama Cloud, set api_key_env: OLLAMA_API_KEY
# Custom base URL: base_url: http://localhost:11434
Security
Setting Up Keystore Encryption
For production use, set a master encryption key for the keystore:
# Generate a strong random key
export IRIS_KEYSTORE_KEY=$(openssl rand -base64 32)
# Add to your shell profile for persistence
echo 'export IRIS_KEYSTORE_KEY="your-key-here"' >> ~/.bashrc
When IRIS_KEYSTORE_KEY is set, Iris uses the V2 keystore format with:
- Argon2id key derivation (OWASP recommended parameters)
- AES-256-GCM authenticated encryption
- Per-file random salt and nonce
Without IRIS_KEYSTORE_KEY, Iris falls back to V1 mode which derives keys from machine-specific data. This is convenient for development but less secure for production.
API Key Protection
Iris uses a Secret type that prevents accidental logging of API keys:
secret := core.NewSecret(os.Getenv("OPENAI_API_KEY"))
fmt.Println(secret) // Prints: [REDACTED]
apiKey := secret.Expose() // Access actual value when needed
See docs/SECURITY.md for comprehensive security documentation.
Supported Providers
| Provider |
Status |
Features |
| OpenAI |
Supported |
Chat, Streaming, Tools, Batch API, Structured Output, Responses API (GPT-5+) |
| Anthropic |
Supported |
Chat, Streaming, Tools |
| Google Gemini |
Supported |
Chat, Streaming, Tools, Reasoning, Structured Output |
| xAI Grok |
Supported |
Chat, Streaming, Tools, Reasoning |
| Z.ai GLM |
Supported |
Chat, Streaming, Tools, Thinking |
| Perplexity |
Supported |
Chat, Streaming, Tools, Web Search |
| Ollama |
Supported |
Chat, Streaming, Tools, Thinking |
xAI Grok Models
| Model ID |
Features |
grok-3 |
Chat, Streaming, Tools, Reasoning |
grok-3-mini |
Chat, Streaming, Tools, Reasoning (exposes reasoning_content) |
grok-4 |
Chat, Streaming, Tools, Reasoning (latest) |
grok-4-fast-non-reasoning |
Chat, Streaming, Tools |
grok-4-fast-reasoning |
Chat, Streaming, Tools, Reasoning |
grok-code-fast |
Chat, Streaming, Tools (code-optimized) |
grok-4-1-fast-non-reasoning |
Chat, Streaming, Tools (default for CLI) |
grok-4-1-fast-reasoning |
Chat, Streaming, Tools, Reasoning |
Z.ai GLM Models
| Model ID |
Features |
glm-4.7 |
Chat, Streaming, Tools, Thinking (latest flagship) |
glm-4.7-flash |
Chat, Streaming, Tools (default for CLI) |
glm-4.7-flashx |
Chat, Streaming, Tools |
glm-4.6 |
Chat, Streaming, Tools, Thinking |
glm-4.6v |
Chat, Streaming, Tools, Thinking, Vision |
glm-4.6v-flash |
Chat, Streaming, Tools, Vision |
glm-4.6v-flashx |
Chat, Streaming, Tools, Vision |
glm-4.5 |
Chat, Streaming, Tools, Thinking |
glm-4.5v |
Chat, Streaming, Tools, Thinking, Vision |
glm-4.5-x |
Chat, Streaming, Tools |
glm-4.5-air |
Chat, Streaming, Tools |
glm-4.5-airx |
Chat, Streaming, Tools |
glm-4.5-flash |
Chat, Streaming, Tools |
glm-4-32b-0414-128k |
Chat, Streaming, Tools (128K context) |
Perplexity Models
| Model ID |
Features |
sonar |
Chat, Streaming, Tools, Web Search (lightweight) |
sonar-pro |
Chat, Streaming, Tools, Web Search (advanced) |
sonar-reasoning-pro |
Chat, Streaming, Tools, Web Search, Reasoning |
sonar-deep-research |
Chat, Streaming, Web Search, Reasoning (research) |
Gemini Models
| Model ID |
Features |
gemini-3-pro-preview |
Chat, Streaming, Tools, Reasoning (thinkingLevel) |
gemini-3-flash-preview |
Chat, Streaming, Tools, Reasoning (thinkingLevel) |
gemini-2.5-pro |
Chat, Streaming, Tools, Reasoning (thinkingBudget) |
gemini-2.5-flash |
Chat, Streaming, Tools, Reasoning (thinkingBudget) |
gemini-2.5-flash-lite |
Chat, Streaming, Tools, Reasoning (thinkingBudget) |
Ollama Models
Ollama supports any model you have pulled locally. Use ollama pull <model> to download models.
| Model ID |
Features |
llama3.2 |
Chat, Streaming, Tools |
llama3.2:70b |
Chat, Streaming, Tools |
mistral |
Chat, Streaming, Tools |
mixtral |
Chat, Streaming, Tools |
qwen3 |
Chat, Streaming, Tools, Thinking |
gemma3 |
Chat, Streaming |
deepseek-coder |
Chat, Streaming |
codellama |
Chat, Streaming |
See https://ollama.com/library for all available models.
Development
Prerequisites
- Go 1.24 or later
- Make (optional, for using Makefile commands)
Getting Started
# Clone the repository
git clone https://github.com/petal-labs/iris.git
cd iris
# Install git hooks (recommended - prevents formatting issues)
make install-hooks
# or: ./scripts/setup-hooks.sh
Makefile Commands
make build # Build all packages
make test # Run all tests
make test-v # Run tests with verbose output
make test-cover # Run tests with coverage
make lint # Check formatting and run go vet
make fmt # Auto-fix formatting issues
make vet # Run go vet
make install-hooks # Install git pre-commit hooks
make build-cli # Build CLI to bin/iris (with version info)
make install-cli # Install CLI locally (with version info)
make test-integration # Run integration tests
make help # Show all available commands
Building the CLI
The CLI is built with version information injected at build time:
# Build with version info
make build-cli
# Check version
./bin/iris version
# Output: iris v0.3.0 (abc1234) built 2026-01-30T12:00:00Z
# JSON output
./bin/iris version --json
Building (without Make)
# Build everything (SDK + examples)
go build ./...
# Run tests
go test ./...
# Check formatting
gofmt -l .
# Fix formatting
gofmt -w .
# Build CLI with version injection
VERSION=$(git describe --tags --always --dirty)
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
go build -ldflags "-X github.com/petal-labs/iris/cli/commands.Version=$VERSION \
-X github.com/petal-labs/iris/cli/commands.Commit=$COMMIT \
-X github.com/petal-labs/iris/cli/commands.BuildDate=$DATE" \
-o bin/iris ./cli/cmd/iris
Running Tests
# Run unit tests
go test ./...
# Run with verbose output
go test -v ./...
# Run with coverage
go test -cover ./...
Integration Tests
Integration tests require API keys and make real API calls:
# Set required environment variables
export OPENAI_API_KEY=your-key
export ANTHROPIC_API_KEY=your-key # optional
export GEMINI_API_KEY=your-key # optional
export XAI_API_KEY=your-key # optional
export ZAI_API_KEY=your-key # optional
export HF_TOKEN=your-token # optional
# Run integration tests
go test -tags=integration ./tests/integration/...
CI Behavior: In CI environments, integration tests fail loudly if required secrets are missing (instead of silently skipping). Set IRIS_SKIP_INTEGRATION=1 to explicitly skip integration tests in CI.
Provider chat conformance scenarios are centralized in tests/integration/chat_conformance_test.go and reused by provider-specific integration files.
Git Hooks
The repository includes a pre-commit hook that automatically checks:
gofmt - Ensures all Go files are properly formatted
go vet - Catches common mistakes
Install the hooks after cloning:
make install-hooks
This prevents CI failures due to formatting issues.
Module Structure
Iris uses a Go workspace with two modules:
iris/
├── go.mod # Main SDK module (github.com/petal-labs/iris)
├── go.work # Workspace file for local development
└── examples/
└── go.mod # Examples module (github.com/petal-labs/iris/examples)
The workspace allows you to develop on both modules simultaneously. When you run go build ./... or go test ./... from the root, it builds/tests both modules.
Importing the SDK:
import (
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
"github.com/petal-labs/iris/providers/anthropic"
"github.com/petal-labs/iris/providers/gemini"
"github.com/petal-labs/iris/providers/xai"
"github.com/petal-labs/iris/providers/zai"
"github.com/petal-labs/iris/providers/perplexity"
"github.com/petal-labs/iris/providers/ollama"
"github.com/petal-labs/iris/tools"
"github.com/petal-labs/iris/testing" // Test utilities
)
Running Examples
Examples are in a separate module but can be run from the project root thanks to the Go workspace:
# Run from project root
go run ./examples/chat/basic
go run ./examples/chat/streaming
go run ./examples/chat/responses-api
go run ./examples/chat/ollama-basic
go run ./examples/chat/huggingface-basic
go run ./examples/chat/xai-basic
go run ./examples/chat/zai-basic
go run ./examples/tools/weather
# Or from the examples directory
cd examples
go run ./chat/basic
go run ./chat/responses-api
go run ./chat/ollama-basic
See examples/README.md for detailed documentation on each example.
Provider Registry
Providers self-register via init() functions, making it easy to add new providers:
// In providers/myprovider/register.go
func init() {
providers.Register("myprovider", func(apiKey string) core.Provider {
return New(apiKey)
})
}
List registered providers:
import "github.com/petal-labs/iris/providers"
fmt.Println(providers.List()) // [anthropic gemini huggingface ollama openai perplexity xai zai]
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome. See CONTRIBUTING.md for setup, test tiers, and PR expectations.