Every workflow in this repo was deleted and nothing replaced them, so the sole producer of ghcr.io/hanzoai/agent no longer existed anywhere and there was no CI at all. That is silent staleness: no failing run, just an artifact that quietly stops being rebuilt. .github/workflows now holds sync.yml and nothing else, copied verbatim from universe infra/ci/sync.yml. It replaces a mirror-nudge file that POSTed /api/v1/repos/.../mirror-sync and described git.hanzo.ai as a downstream pull mirror; the forge is canonical and refs are pushed to it. Without it a .hanzo/workflows file has nothing to trigger it. deploy.yml restores docker-publish.yml. Two things could not come across as-is: - Its body was one `uses: hanzoai/.github/.github/workflows/docker-build.yml@main`. A private cross-repo reusable workflow does not resolve on the forge, so the amd64 build/push is vendored inline, as hanzoai/bot already does. - It built `context: control-plane`, and no control-plane directory exists on main; the orchestrator was hoisted to the root module in v0.1.1 and the directory survives only on the unmerged consolidate/held-work branch. Restoring that context restores a build that cannot run. It builds Dockerfile.sdk — the one Dockerfile on main whose context exists, previously referenced by nothing despite sdk-* tags being live on the image — and tags it sdk-* so the two Dockerfiles cannot collide in one tag namespace. The tag rule is shell rather than docker/metadata-action, so it is two readable lines with no action semantics to guess at, and it was tested standalone: v* -> :sdk-X.Y.Z + :sdk-latest, main -> :sdk-sha-<sha7> + :sdk-latest. ci.yml replaces tests.yml and test.yml, which checked the same things twice, the second with `|| true` on most steps so it could not fail. It gates what is verifiable on main today — gofmt/build/vet/test for the Go module, and the locked install plus `import agents` for Python, which is what Dockerfile.sdk's builder stage and HEALTHCHECK do. It deliberately does not run ruff, mypy or pytest: `make sync` cannot resolve (requires-python >=3.9, a transitive numpy needs >=3.10), ruff reports 2337 errors, and pytest INTERNALERRORs at collection because a test module sys.exit(1)s at import. tests.yml ran `make sync` first in all five jobs, so none of them could have gone green. Those four are written down in LLM.md as work rather than shipped as a permanently red check, which gets read as broken CI instead of broken code. gofmt over eight files so that gate is real instead of omitted; build, vet and test re-run green afterwards. Alignment whitespace only. Not restored, with reasons in LLM.md: docs.yml (mkdocs gh-deploy is GitHub Pages, never published anything, and Pages is not how we serve sites), publish.yml (pointed at the PyPI package hanzoai/python-sdk owns; deliberately removed earlier), issues.yml (upstream's stale-issue bot). LLM.md documents the chain, records the four broken Python checks, and states the open question this repo actually has: nothing in hanzoai/universe references ghcr.io/hanzoai/agent, so the image has no consumer and no App CR. .gitignore ignored .hanzo/ under an "# IDE" heading, which silently dropped both new workflows from the commit — the same silent-staleness shape this is meant to remove, one level down. Narrowed to .hanzo/* with .hanzo/workflows/ re-included. Co-authored-by: Hanzo Dev <[email protected]>
Go SDK AI Package
This package provides AI/LLM capabilities for the Hanzo Agents Go SDK, supporting both OpenAI and OpenRouter APIs with structured output support.
Features
- ✅ OpenAI & OpenRouter Support: Works with both OpenAI API and OpenRouter for multi-model routing
- ✅ Structured Outputs: JSON schema validation with Go struct support
- ✅ Streaming: Support for streaming responses
- ✅ Type-Safe: Automatic conversion from Go structs to JSON schemas
- ✅ Functional Options: Clean, idiomatic Go API with functional options pattern
- ✅ Automatic Configuration: Reads from environment variables by default
Quick Start
Basic Usage
import (
"context"
"github.com/hanzoai/agents/sdk/go/agent"
"github.com/hanzoai/agents/sdk/go/ai"
)
// Create agent with AI configured
aiConfig := ai.DefaultConfig() // Reads from env vars
agent, err := agent.New(agent.Config{
NodeID: "my-agent",
Version: "1.0.0",
Hanzo AgentsURL: "http://localhost:8080",
AIConfig: aiConfig,
})
// Make a simple AI call
response, err := agent.AI(ctx, "What is 2+2?")
fmt.Println(response.Text())
Structured Outputs
// Define your response schema
type WeatherResponse struct {
Location string `json:"location" description:"City name"`
Temperature float64 `json:"temperature" description:"Temperature in Celsius"`
Conditions string `json:"conditions" description:"Weather conditions"`
}
// Call AI with schema
response, err := agent.AI(ctx, "What's the weather in Paris?",
ai.WithSystem("You are a weather assistant"),
ai.WithSchema(WeatherResponse{}))
// Parse response into struct
var weather WeatherResponse
response.Into(&weather)
fmt.Printf("%s: %.1f°C, %s\n", weather.Location, weather.Temperature, weather.Conditions)
Streaming Responses
chunks, errs := agent.AIStream(ctx, "Tell me a story")
for chunk := range chunks {
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
}
if err := <-errs; err != nil {
log.Fatal(err)
}
Configuration
Environment Variables
The SDK automatically reads from environment variables:
# For OpenAI (default)
export OPENAI_API_KEY="sk-..."
export AI_MODEL="gpt-4o" # Optional, defaults to gpt-4o
# For OpenRouter
export OPENROUTER_API_KEY="sk-..."
export AI_MODEL="openai/gpt-4o" # Use OpenRouter model format
# Custom base URL
export AI_BASE_URL="https://api.openai.com/v1"
Manual Configuration
aiConfig := &ai.Config{
APIKey: "sk-...",
BaseURL: "https://api.openai.com/v1",
Model: "gpt-4o",
Temperature: 0.7,
MaxTokens: 4096,
}
OpenRouter Configuration
aiConfig := &ai.Config{
APIKey: os.Getenv("OPENROUTER_API_KEY"),
BaseURL: "https://openrouter.ai/api/v1",
Model: "anthropic/claude-3.5-sonnet", // OpenRouter format: provider/model
SiteURL: "https://myapp.com", // For OpenRouter rankings
SiteName: "My AI App",
}
API Reference
AI Client
ai.NewClient(config *Config) (*Client, error)
Creates a new AI client with the given configuration.
client.Complete(ctx context.Context, prompt string, opts ...Option) (*Response, error)
Makes a chat completion request.
client.StreamComplete(ctx context.Context, prompt string, opts ...Option) (<-chan StreamChunk, <-chan error)
Makes a streaming chat completion request.
Agent Methods
agent.AI(ctx context.Context, prompt string, opts ...Option) (*Response, error)
Makes an AI call using the agent's configured AI client.
agent.AIStream(ctx context.Context, prompt string, opts ...Option) (<-chan StreamChunk, <-chan error)
Makes a streaming AI call.
Options
Functional options for customizing AI requests:
ai.WithSystem(content string)- Add a system promptai.WithModel(model string)- Override the default modelai.WithTemperature(temp float64)- Set temperature (0.0-2.0)ai.WithMaxTokens(tokens int)- Set max tokensai.WithStream()- Enable streamingai.WithJSONMode()- Enable JSON object modeai.WithSchema(schema interface{})- Enable structured outputs with schema
Response Methods
response.Text()- Get the text contentresponse.JSON(dest interface{})- Parse response as JSONresponse.Into(dest interface{})- Alias for JSON()
Structured Output Schema
Define schemas using Go structs with JSON tags:
type MyResponse struct {
// Required field
Name string `json:"name" description:"The person's name"`
// Optional field (use omitempty)
Age int `json:"age,omitempty" description:"The person's age"`
// Array
Hobbies []string `json:"hobbies" description:"List of hobbies"`
// Nested object
Address struct {
Street string `json:"street"`
City string `json:"city"`
} `json:"address"`
}
The SDK automatically converts Go structs to JSON schemas compatible with OpenAI's structured output format.
Using AI in Reasoners
You can use AI within your reasoners to create intelligent workflows:
agent.RegisterReasoner("smart_reasoner", func(ctx context.Context, input map[string]any) (any, error) {
question := input["question"].(string)
// Call AI within the reasoner
response, err := agent.AI(ctx, question,
ai.WithSystem("You are a helpful assistant"),
ai.WithTemperature(0.7))
if err != nil {
return nil, err
}
return map[string]any{
"answer": response.Text(),
"model": response.Model,
}, nil
})
Comparison with Python SDK
The Go SDK provides similar functionality to the Python SDK's agent.ai() method:
| Feature | Python SDK | Go SDK |
|---|---|---|
| Simple text calls | agent.ai("prompt") |
agent.AI(ctx, "prompt") |
| System prompts | system="..." kwarg |
ai.WithSystem("...") option |
| Structured outputs | schema=Model kwarg |
ai.WithSchema(Model{}) option |
| Streaming | stream=True kwarg |
agent.AIStream() method |
| Model override | model="..." kwarg |
ai.WithModel("...") option |
| Temperature | temperature=0.7 kwarg |
ai.WithTemperature(0.7) option |
Error Handling
response, err := agent.AI(ctx, "prompt")
if err != nil {
// Handle API errors
log.Printf("AI call failed: %v", err)
return
}
// Parse structured response
var result MyStruct
if err := response.Into(&result); err != nil {
// Handle parsing errors
log.Printf("Failed to parse response: %v", err)
return
}
Performance Considerations
- Connection Pooling: The HTTP client uses connection pooling for efficient requests
- Context Cancellation: Always use contexts with timeouts for AI calls
- Streaming: Use streaming for long responses to improve perceived latency
- Model Selection: Choose appropriate models for your use case (faster models = lower latency)
Examples
See the examples/ai-agent directory for complete examples including:
- Simple text responses
- Structured outputs
- Sentiment analysis
- Agent reasoners with AI
- Streaming responses
- OpenRouter usage
License
Proprietary - Authorized users only