Skip to content

Iris Getting Started

This guide walks you through installing Iris, configuring your first provider, and making your first LLM request. By the end, you’ll have a working setup ready for building AI applications.

Before installing Iris, ensure you have:

  • Go 1.21+: Iris uses generics and other modern Go features
  • API Key: At least one provider API key (OpenAI, Anthropic, etc.)
  • Git: For installing from source (optional)

Verify your Go installation:

Terminal window
go version
# go version go1.22.0 darwin/arm64

Add Iris to your Go project:

Terminal window
go get github.com/petal-labs/iris

This installs the core SDK with all provider packages.

The CLI provides a command-line interface for quick experiments and prompt testing:

Terminal window
go install github.com/petal-labs/iris/cli/cmd/iris@latest

Verify the installation:

Terminal window
iris version
# iris v0.13.0

Let’s make a simple chat request with OpenAI. This example demonstrates the core workflow: create a provider, wrap it in a client, and use the fluent builder API.

package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
// Create provider with API key from environment
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
// Wrap in client for retry logic and telemetry
client := core.NewClient(provider)
// Build and execute 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 {
panic(err)
}
fmt.Println(resp.Output)
}

Run your code:

Terminal window
export OPENAI_API_KEY=sk-...
go run main.go
# The capital of France is Paris.

For chat applications and real-time interfaces, streaming delivers tokens as they’re generated:

package main
import (
"context"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
client := core.NewClient(provider)
// Stream returns immediately with channels
stream, err := client.Chat("gpt-4o").
System("You are a helpful assistant.").
User("Write a haiku about programming.").
Stream(context.Background())
if err != nil {
panic(err)
}
// Process chunks as they arrive
for chunk := range stream.Ch {
fmt.Print(chunk.Delta) // Print without newline for streaming effect
}
fmt.Println() // Final newline
// Check for errors after stream completes
if err := <-stream.Err; err != nil {
panic(err)
}
// Optional: get the final aggregated response
final := <-stream.Final
fmt.Printf("\nTotal tokens: %d\n", final.Usage.TotalTokens)
}

Tools let models invoke functions with structured arguments. Here’s a simple example:

package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
client := core.NewClient(provider)
// Define a tool
weatherTool := core.Tool{
Name: "get_weather",
Description: "Get the current weather for a location",
Parameters: core.ToolParameters{
Type: "object",
Properties: map[string]core.Property{
"location": {
Type: "string",
Description: "City name, e.g., 'San Francisco, CA'",
},
"unit": {
Type: "string",
Enum: []string{"celsius", "fahrenheit"},
Description: "Temperature unit",
},
},
Required: []string{"location"},
},
}
// Request with tool
resp, err := client.Chat("gpt-4o").
System("You are a helpful weather assistant.").
User("What's the weather in Tokyo?").
Tools(weatherTool).
GetResponse(context.Background())
if err != nil {
panic(err)
}
// Check if model wants to call a tool
if len(resp.ToolCalls) > 0 {
tc := resp.ToolCalls[0]
fmt.Printf("Tool called: %s\n", tc.Name)
var args map[string]string
json.Unmarshal(tc.Arguments, &args)
fmt.Printf("Arguments: %+v\n", args)
// In a real app, you'd execute the tool and continue the conversation
}
}

See the Tools Guide for complete tool calling patterns including multi-turn conversations with tool results.

Force the model to respond with valid JSON or conform to a specific schema:

package main
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
client := core.NewClient(provider)
// JSON mode - model outputs valid JSON
resp, err := client.Chat("gpt-4o-mini").
System("You respond in JSON format.").
User("List 3 programming languages with name and year created.").
ResponseJSON().
GetResponse(context.Background())
if err != nil {
panic(err)
}
fmt.Println(resp.Output)
// {"languages": [{"name": "Go", "year": 2009}, ...]}
}

For strict schema enforcement, use ResponseJSONSchema():

schema := &core.JSONSchemaDefinition{
Name: "person_info",
Strict: true,
Schema: json.RawMessage(`{
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"],
"additionalProperties": false
}`),
}
resp, err := client.Chat("gpt-4o-mini").
User("Extract: John is 30 years old").
ResponseJSONSchema(schema).
GetResponse(ctx)

See the Structured Output Guide for complete patterns.

Iris provides an encrypted keystore for securely storing API keys locally. This is more secure than environment variables for development machines.

The keystore uses AES-256-GCM encryption with Argon2id key derivation. Set your master password:

Terminal window
export IRIS_KEYSTORE_KEY=your-secure-master-password
Terminal window
# Store keys (prompts for value)
iris keys set openai
iris keys set anthropic
iris keys set gemini
# List stored keys (shows key names, not values)
iris keys list
# openai
# anthropic
# gemini
# Remove a key
iris keys remove gemini

The encrypted keystore is stored at ~/.iris/keys.enc with 0600 permissions (owner read/write only).

package main
import (
"context"
"fmt"
"log"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
// Load key from keystore
keystore, err := core.LoadKeystore()
if err != nil {
log.Fatal(err)
}
apiKey, err := keystore.Get("openai")
if err != nil {
log.Fatal(err)
}
// Use the key
provider := openai.New(apiKey)
client := core.NewClient(provider)
resp, _ := client.Chat("gpt-4o").
User("Hello!").
GetResponse(context.Background())
fmt.Println(resp.Output)
}

For consistent settings across sessions, create a configuration file at ~/.iris/config.yaml:

# Default provider and model
default_provider: openai
default_model: gpt-4o
# Provider-specific settings
providers:
openai:
api_key_env: OPENAI_API_KEY # Or use keystore
default_model: gpt-4o
organization: org-xxx # Optional
anthropic:
api_key_env: ANTHROPIC_API_KEY
default_model: claude-3-5-sonnet-20241022
ollama:
base_url: http://localhost:11434
default_model: llama3.2
# Telemetry settings
telemetry:
enabled: true
log_level: info
# Retry policy
retry:
max_attempts: 3
initial_backoff: 1s
max_backoff: 30s
backoff_multiplier: 2.0
package main
import (
"log"
"github.com/petal-labs/iris/core"
)
func main() {
// Load config from default location (~/.iris/config.yaml)
config, err := core.LoadConfig()
if err != nil {
log.Fatal(err)
}
// Create provider from config
provider, err := config.GetProvider("openai")
if err != nil {
log.Fatal(err)
}
client := core.NewClient(provider)
// Use client...
}

The CLI provides commands for quick testing and experimentation:

Terminal window
# Simple chat
iris chat "Explain quantum entanglement"
# With specific provider/model
iris chat --provider anthropic --model claude-3-opus "Write a poem"
# Streaming output
iris chat --stream "Tell me a story"
# With system prompt
iris chat --system "You are a pirate" "What's the weather like?"
# With temperature
iris chat --temperature 0.9 "Be creative: invent a new word"
# Multi-turn conversation (interactive mode)
iris chat --interactive
Terminal window
# Set a key
iris keys set openai
# List keys
iris keys list
# Remove a key
iris keys remove openai
# Test a key
iris keys test openai
Terminal window
# List available models for a provider
iris models list openai
# Get model details
iris models info gpt-4o

Iris provides typed errors for different failure modes:

package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/petal-labs/iris/core"
"github.com/petal-labs/iris/providers/openai"
)
func main() {
provider := openai.New(os.Getenv("OPENAI_API_KEY"))
client := core.NewClient(provider)
resp, err := client.Chat("gpt-4o").
User("Hello").
GetResponse(context.Background())
if err != nil {
// Check for specific error types
var rateLimitErr *core.RateLimitError
var authErr *core.AuthenticationError
var contextErr *core.ContextLengthError
switch {
case errors.As(err, &rateLimitErr):
fmt.Printf("Rate limited. Retry after: %v\n", rateLimitErr.RetryAfter)
case errors.As(err, &authErr):
fmt.Println("Authentication failed. Check your API key.")
case errors.As(err, &contextErr):
fmt.Printf("Input too long. Max tokens: %d\n", contextErr.MaxTokens)
default:
fmt.Printf("Request failed: %v\n", err)
}
return
}
fmt.Println(resp.Output)
}

Ensure your API key is set correctly:

Terminal window
# Check environment variable
echo $OPENAI_API_KEY
# Or check keystore
iris keys list

Verify the model name is correct for your provider:

Terminal window
# List available models
iris models list openai

Your input is too long for the model. Options:

  1. Use a model with larger context (e.g., gpt-4o-128k)
  2. Truncate or summarize your input
  3. Use streaming with the MaxTokens() builder method

The provider is throttling requests. Options:

  1. Add delays between requests
  2. Use Iris’s built-in retry policy
  3. Upgrade your API tier with the provider

Ensure Ollama is running:

Terminal window
# Start Ollama
ollama serve
# Verify it's accessible
curl http://localhost:11434/api/tags