Skip to content

chat

A unified, multi-provider AI chat client for Go — Anthropic Claude, a locally installed claude CLI, OpenAI (and any OpenAI-compatible endpoint), and Google Gemini — behind one small interface, as a light, dependency-inverted library.

chat is the same client behind gtb ai, extracted so any project can embed a production chat client — with a ReAct tool-calling loop, streaming, cross-provider fallback, token accounting, and encrypted conversation persistence — without pulling in the go-tool-base framework, and without linking every vendor AI SDK.

import "gitlab.com/phpboyscout/go/chat"

The light-footprint promise

The core module graph is deliberately tiny. go.mod declares cockroachdb/errors, invopop/jsonschema, google/uuid, and spf13/afero — and no vendor AI SDK, no web framework, no go-tool-base. A depfootprint_test.go guard fails the build if any forbidden dependency (a vendor SDK, Viper/Cobra, Charm, OpenTelemetry, the AWS SDK, or go-tool-base) ever enters the graph.

Each vendor SDK lives in its own module, activated by a blank import, so a Claude-only tool never compiles the OpenAI or Gemini SDKs:

Provider Module (blank-import to activate) Underlying SDK
claude-local gitlab.com/phpboyscout/go/chat (in the core) none — the local claude CLI
claude gitlab.com/phpboyscout/go/chat-anthropic anthropics/anthropic-sdk-go
openai, openai-compatible gitlab.com/phpboyscout/go/chat-openai openai/openai-go (+ tiktoken)
gemini gitlab.com/phpboyscout/go/chat-gemini google.golang.org/genai

The SDK-free claude-local provider ships in the core as a zero-weight default.

The library leans on the standard library at every seam:

  • *slog.Logger for logging (nil ⇒ a discard logger),
  • *http.Client for provider transport (nil ⇒ a plain bounded stdlib client),
  • a KeychainLookup func for credential resolution (nil ⇒ the keychain step is skipped),
  • afero.Fs for conversation persistence.

See Dependency inversion for why.

Who it is for

  • CLI and service authors who want an agentic chat client — tool calling, structured output, streaming — without adopting a framework.
  • Any non-framework Go project that needs to talk to an LLM behind a stable, provider-neutral interface.
  • Cost-conscious builds that want exactly one vendor SDK linked, not four.

Quick start

Blank-import one provider module to activate it, then construct a client from package-owned Settings:

import (
    "context"

    "gitlab.com/phpboyscout/go/chat"
    _ "gitlab.com/phpboyscout/go/chat-anthropic" // activate the Claude provider
)

func summarise(ctx context.Context, apiKey, changelog string) (string, error) {
    client, err := chat.New(ctx, chat.Settings{
        Config: chat.Config{Provider: chat.ProviderClaude, Token: apiKey},
    })
    if err != nil {
        return "", err
    }

    return client.Chat(ctx, "Summarise this changelog in one line:\n"+changelog)
}

With no Token set, the client resolves a credential from config references or the well-known ANTHROPIC_API_KEY env var — see Choose & configure a provider.

Where to go next

The documentation follows the Diátaxis framework:

Using go-tool-base? The framework ships a thin adapter that maps its Props/Viper config (the ai.*, ai.fallback.*, and provider api sections) into the chat.Settings this module owns. That adapter lives in go-tool-base, not here; this module is config-system-agnostic.