Chronos uses a pluggable provider interface for LLM backends. All providers implement the same interface, so you can swap OpenAI for Anthropic, Ollama, or any OpenAI-compatible endpoint without changing your agent code.

Provider Interface

type Provider interface {
    Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error)
    StreamChat(ctx context.Context, req *ChatRequest) (<-chan *ChatResponse, error)
    Name() string
    Model() string
}
Method Description
Chat Sends a request and returns a complete response
StreamChat Returns a channel of partial responses; channel closes when complete
Name Human-readable provider identifier
Model Default model ID for this provider

Provider Table

Provider Constructor Notes
OpenAI model.NewOpenAI(apiKey) GPT-4, GPT-4o, GPT-3.5-turbo, o1, o3
Anthropic model.NewAnthropic(apiKey) Claude models
Gemini model.NewGemini(apiKey) Google Gemini
Mistral model.NewMistral(apiKey) Mistral models
Ollama model.NewOllama(host, model) Local models (e.g., http://localhost:11434, llama3.2)
Azure model.NewAzureOpenAI(endpoint, key, deployment) Azure OpenAI
Compatible model.NewOpenAICompatible(name, url, key, model) Any OpenAI-compatible API

Convenience Constructors

For providers that expose an OpenAI-compatible API, Chronos provides convenience constructors:

Constructor Base URL Use Case
model.NewTogether(apiKey, modelID) api.together.xyz Together AI
model.NewGroq(apiKey, modelID) api.groq.com Groq
model.NewDeepSeek(apiKey, modelID) api.deepseek.com DeepSeek
model.NewOpenRouter(apiKey, modelID) openrouter.ai OpenRouter (multi-model)
model.NewFireworks(apiKey, modelID) api.fireworks.ai Fireworks AI
model.NewPerplexity(apiKey, modelID) api.perplexity.ai Perplexity
model.NewAnyscale(apiKey, modelID) api.endpoints.anyscale.com Anyscale Endpoints

Example:

provider := model.NewGroq(os.Getenv("GROQ_API_KEY"), "llama-3.1-70b-versatile")

ProviderConfig

For full configuration, use ProviderConfig with the WithConfig constructor:

cfg := model.ProviderConfig{
    APIKey:        os.Getenv("OPENAI_API_KEY"),
    BaseURL:       "https://api.openai.com/v1",
    Model:         "gpt-4o",
    MaxRetries:    3,
    TimeoutSec:    60,
    OrgID:         "org-xxx",
    ContextWindow: 128000,
}

provider := model.NewOpenAIWithConfig(cfg)
Field Type Description
APIKey string Authentication key
BaseURL string API base URL (optional for most providers)
Model string Model identifier
MaxRetries int Retry count on transient failures
TimeoutSec int Request timeout in seconds
OrgID string Organization ID (OpenAI)
ContextWindow int Override default context window size

ChatRequest

Input to a chat completion:

Field Type Description
Model string Override provider default
Messages []Message Conversation messages
MaxTokens int Maximum tokens to generate
Temperature float64 Sampling temperature (0-2)
TopP float64 Nucleus sampling
Stream bool Enable streaming
Tools []ToolDefinition Function definitions for tool calling
Stop []string Stop sequences
ResponseFormat string "json_object" for JSON mode

ChatResponse

Output of a chat completion:

Field Type Description
ID string Response ID
Content string Generated text
Role string Usually "assistant"
Usage Usage Token counts
ToolCalls []ToolCall Requested tool invocations
StopReason StopReason Why generation stopped
Delta bool True when streaming partial response

Usage

type Usage struct {
    PromptTokens     int
    CompletionTokens int
}

StopReason Constants

Constant Value Meaning
StopReasonEnd "end" Natural completion
StopReasonMaxTokens "max_tokens" Hit token limit
StopReasonToolCall "tool_call" Model requested tool execution
StopReasonFilter "content_filter" Content filter triggered

Streaming

Use StreamChat for token-by-token streaming. The returned channel receives partial ChatResponse values with Delta: true; the final response may include usage and StopReason.

ch, err := provider.StreamChat(ctx, &model.ChatRequest{
    Messages: messages,
    Stream:   true,
})
if err != nil {
    log.Fatal(err)
}

for resp := range ch {
    if resp.Content != "" {
        fmt.Print(resp.Content)
    }
}
fmt.Println()

Embeddings Providers

For RAG and vector search, use an EmbeddingsProvider:

type EmbeddingsProvider interface {
    Embed(ctx context.Context, req *EmbeddingRequest) (*EmbeddingResponse, error)
}
Constructor Description
model.NewOpenAIEmbeddings(apiKey) OpenAI text-embedding-3-small
model.NewOpenAIEmbeddingsWithConfig(cfg) With full config
model.NewOllamaEmbeddings(baseURL, modelID) Local embeddings via Ollama
model.NewCachedEmbeddings(inner) In-memory cache wrapper

Example:

embedder := model.NewOpenAIEmbeddings(os.Getenv("OPENAI_API_KEY"))
cached := model.NewCachedEmbeddings(embedder)

FallbackProvider

FallbackProvider tries multiple providers in order. If the primary fails, it automatically falls back to the next. Useful for primary-cloud to cheaper-model or cloud to local-Ollama failover.

primary := model.NewOpenAI(openAIKey)
fallback := model.NewOllama("http://localhost:11434", "llama3.2")

provider, err := model.NewFallbackProvider(primary, fallback)
if err != nil {
    log.Fatal(err)
}

// Optional: log when fallback occurs
provider.OnFallback = func(index int, name string, err error) {
    log.Printf("Provider %s failed, trying next: %v", name, err)
}

resp, err := provider.Chat(ctx, req)

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/spawn08/chronos/engine/model"
    "github.com/spawn08/chronos/sdk/agent"
)

func main() {
    ctx := context.Background()

    provider := model.NewOpenAI(os.Getenv("OPENAI_API_KEY"))

    a, err := agent.New("demo", "Demo Agent").
        WithModel(provider).
        WithSystemPrompt("You are a concise assistant.").
        Build()
    if err != nil {
        log.Fatal(err)
    }

    resp, err := a.Chat(ctx, "Say hello in one sentence.")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(resp.Content)
    fmt.Printf("Tokens: %d prompt, %d completion\n",
        resp.Usage.PromptTokens, resp.Usage.CompletionTokens)
}