- Docs
- Integrations
- AI
AI
Generate text, stream tokens, and create embeddings with Anthropic, OpenAI, Gemini, or Ollama behind one interface.
ai.AI is one facade (Generate, Stream, Embed) with typed messages, tools, and usage accounting, so the provider is a config value, not an import. Pick anthropic, openai, or gemini by which models you want, and ollama for self-hosted inference. Switching later is a config change.
Unlike most services, ai has no zero-infra default. config.Default() selects anthropic and Validate requires an API key (or a base_url plus key), so tests that touch ai must supply a key or point at a fake endpoint.
Quick start
Section titled “Quick start”AI_ADAPTER=anthropicAI_API_KEY=sk-...AI_MODEL=claude-opus-4AI_TIMEOUT=30sAI_ADAPTER=openaiAI_API_KEY=sk-...AI_MODEL=gpt-5AI_TIMEOUT=30sAI_BASE_URL=https://proxy.internal/v1base_url is optional (a proxy or self-hosted gateway). It must be a valid https URL with scheme and host. timeout bounds each request, and a negative value is rejected. See the ai config reference.
package main
import ( "context" "fmt"
aianthropic "github.com/zenta-dev/zever/adapters/ai/anthropic" "github.com/zenta-dev/zever/config" "github.com/zenta-dev/zever/container" "github.com/zenta-dev/zever/core/ai")
func main() { aianthropic.Register() // container wires nothing c := container.New(config.Default()) defer c.Close(context.Background())
backend, err := c.AI() // fails on a missing key, bad base_url, or unknown adapter if err != nil { fmt.Println("resolve error:", err) return } out, err := backend.Generate(context.Background(), "", []ai.Message{ {Role: ai.RoleUser, Content: "Say hi in five words."}, }, ai.GenerateOptions{}) if err != nil { fmt.Println("generate error:", err) return } fmt.Println("text:", out.Content) fmt.Println("tokens:", out.Usage.PromptTokens+out.Usage.CompletionTokens)}Expected output (text varies by model):
text: Hello, glad you are here.tokens: 24For streaming, call Stream and range over the returned channel until the producer closes it. The channel is bounded, so a stalled consumer cannot leak a goroutine on an unbounded buffer.
Choose an adapter
Section titled “Choose an adapter”| Adapter | Package | Notes |
|---|---|---|
anthropic |
adapters/ai/anthropic |
Default selection. Needs api_key. |
openai |
adapters/ai/openai |
Needs api_key. |
gemini |
adapters/ai/gemini |
Needs api_key. |
ollama |
adapters/ai/ollama |
Self-hosted. base_url bridges to its Addr. |
ollama.New takes ollama.Options with an Addr field. You only write ai: config: with adapter: ollama, the container passes base_url through as Addr and carries over model, timeout, allow_insecure, allow_private, and insecure_skip_verify. Non-loopback endpoints must use https. Outbound requests dial only globally routable addresses and never follow redirects; a local Ollama sidecar (loopback Addr) needs allow_private: true, which logs a line at startup. TLS is always verified unless insecure_skip_verify: true, which logs a warning at startup — allow_insecure only permits the http scheme and never disables verification.
Run tool-calling agents and RAG
Section titled “Run tool-calling agents and RAG”Two batteries build on ai without a provider dependency. Neither has a config entry. Resolve c.Agent() and c.RAG() (which build and cache the loop and engine over the shared backend), or call New with the resolved AI() and VectorStore().
core/agent runs a bounded tool-calling loop: generate, dispatch tool calls, append results, repeat until the model answers or the step bound hits. Unknown tools fail closed, and handler errors go back to the model as tool turns so it can self-correct.
loop, err := agent.New(backend, agent.Options{ Model: "gpt-5", Tools: []agent.Tool{{ Name: "lookup_order", Description: "Look up an order by ID.", Parameters: map[string]any{"type": "object"}, Handler: func(ctx context.Context, args map[string]any) (string, error) { return "order 42", nil }, }},})core/rag composes embeddings with a vectorstore: ingest and embed chunks, then retrieve context and generate a grounded answer.
engine, err := rag.New(backend, store, rag.Options{Model: "gpt-5"})Give coding agents the compiler over MCP
Section titled “Give coding agents the compiler over MCP”tools/zever-mcp is a stdio Model Context Protocol server that exposes the .zen compiler. It implements initialize, tools/list, and tools/call over newline-delimited JSON-RPC 2.0, with seven tools. The first six are read-only; zever_generate writes, gated by dry-run preview plus human elicitation approval:
| Tool | Purpose |
|---|---|
zever_compile |
Compile .zen sources and return the merged OpenAPI document. |
zever_schema |
Return a JSON overview of modules, entities, services, messages, and enums. |
zever_explain |
Describe a module, entity, message, service, or operation by dotted path. |
zever_check |
Validate .zen sources (parse and resolve, no codegen); returns ok or the file:line:col diagnostics. |
zever_routes |
List the HTTP routes the schema declares as JSON (method, path, Module.Service.RPC target). |
zever_doctor |
Resolve every battery and report OK/FAIL rows. |
zever_generate |
Scaffold via dry-run plan, then apply on elicited approval. |
It also serves static reference resources and prompt templates for scaffolding, triage, and diagnostics.
go build -o zever-mcp ./tools/zever-mcp{ "mcpServers": { "zever": { "command": "/path/to/zever-mcp" } } }Production checklist
Section titled “Production checklist”- Keep keys in file config or env (
AI_API_KEY), never in code. Display config throughconfig.RedactedServices.APIKeynever appears in logs or error values. - Set
timeoutexplicitly per deploy. Zero falls back to the adapter default, which may exceed your handler deadline. - Pin
modelso a provider-side default change does not move you to a model with different pricing. - Unsupported features fail closed with
ErrNotSupportedat the call site instead of silently dropping a tool call or stream flag.
Troubleshooting
Section titled “Troubleshooting”AI() fails with an invalid-options error at startup
The key is missing or base_url failed validation. Check AI_API_KEY, and that AI_BASE_URL (if used) includes the https scheme and a host.
Generate returns ErrNotSupported for a call that worked on another adapter
The provider has no native shape for that feature (tool-call form, streaming mode). Gate the feature per adapter or switch adapters. The facade never downgrades silently.
Token usage looks wrong after switching providers
Usage accounting follows each provider’s response shape. Compare Usage per adapter in a test before relying on totals for billing.