chatopenai

package module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 15 Imported by: 0

README

chat-openai

OpenAI (and OpenAI-compatible) provider for the chat client

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base. Docs: chat.go.phpboyscout.uk


gitlab.com/phpboyscout/go/chat-openai is the OpenAI provider for the gitlab.com/phpboyscout/go/chat multi-provider AI chat client. It registers two providers with the core:

Provider constant Backend
chat.ProviderOpenAI OpenAI's hosted API
chat.ProviderOpenAICompatible Any OpenAI-compatible endpoint (Ollama, vLLM, Groq, …) — requires Config.BaseURL and Config.Model

It is a thin adapter over OpenAI's official Go SDK (openai/openai-go/v3) plus the tiktoken tokenizer (used to chunk long prompts by token count). It links no other vendor AI SDK and not the go-tool-base framework — a depfootprint_test.go guard enforces that.

Install

go get gitlab.com/phpboyscout/go/chat-openai

Usage

Activate the provider with a blank import; the package's init() registers openai and openai-compatible with the core registry, and a status extractor so cross-provider fallback can classify OpenAI HTTP errors.

import (
    "gitlab.com/phpboyscout/go/chat"
    _ "gitlab.com/phpboyscout/go/chat-openai" // activate the OpenAI provider
)

client, err := chat.New(ctx, chat.Settings{
    Config: chat.Config{Provider: chat.ProviderOpenAI, Token: apiKey},
})
if err != nil {
    return err
}
answer, err := client.Chat(ctx, "Summarise this changelog in one line.")
OpenAI-compatible backends

Point at any OpenAI-compatible server by selecting chat.ProviderOpenAICompatible and supplying both a BaseURL and a Model:

client, err := chat.New(ctx, chat.Settings{
    Config: chat.Config{
        Provider: chat.ProviderOpenAICompatible,
        BaseURL:  "https://ollama.example/v1",
        Model:    "llama3.2",
    },
})

Keeping up with OpenAI

Two generators, both run deliberately rather than automatically applied.

internal/genmodels regenerates the per-model capability table. OpenAI's /v1/models reports nothing about what a model accepts — no sampling, no effort, no limits — so the generator establishes it by probing each model with a real request:

OPENAI_API_KEY=... go run ./internal/genmodels > models_generated.go

internal/detectmodels watches for OpenAI shipping something chat.DefaultModelOpenAI does not reflect, and raises an issue in this project when it does. Because the listing carries no capability data, it can only report that a model exists — and it says so, rather than letting silence read as "nothing changed".

OPENAI_API_KEY=... go run ./internal/detectmodels -dry-run

It runs weekly — 03:00 Europe/London on Mondays — from a DETECT_MODELS=true scheduled pipeline. Neither -dry-run nor -update-snapshot touches the forge — only a plain run raises. internal/detectmodels/snapshot.json is the committed baseline, and it moves when a human resolves the issue, not when CI runs.

Credentials

The provider resolves an API key through the core's precedence chain (Config.TokenCredentials.Env var → Credentials.Key literal → the well-known OPENAI_API_KEY fallback). See the core client's credential docs for the full resolution order.

Capabilities

  • ReAct tool-calling loop with automatic tool dispatch (sequential or parallel)
  • Streaming via StreamChat (text deltas, tool-call start/end, completion)
  • Structured output through Ask and JSON-schema response formats
  • Image and PDF media parts (image → image_url, PDF → file part)
  • Conversation Save/Restore snapshots
  • Stateless one-shot calls via Config.Stateless — each call carries only its own turns, so one client can serve a batch of independent documents across goroutines instead of re-sending the accumulated prefix every time
  • Token-usage accounting via client.Usage()

Documentation

Version compatibility

chat-openai requires the core version named in its own go.mod, and is built and tested against exactly that version. Do not compare the two version numbers — each module releases when that module changes, so they move independently.

Install this module and let it bring the core with it:

go get gitlab.com/phpboyscout/go/chat-openai
go mod tidy

See version compatibility for what a mismatch does, and why requiring chat directly at @latest is the way to break it.

Licence

MIT — see LICENSE.

Documentation

Index

Constants

View Source
const EnvOpenAIKey = "OPENAI_API_KEY"

EnvOpenAIKey is the well-known unprefixed environment variable used as the ecosystem fallback when no explicit credential is configured.

Variables

This section is empty.

Functions

func New added in v0.9.0

func New(ctx context.Context, settings chat.Settings, opts ...Option) (chat.ChatClient, error)

New builds an OpenAI (or OpenAI-compatible) client, accepting options this provider owns. Reach for it when you need one; otherwise chat.New with the provider name does the same job through the registry.

It goes through chat.NewWithFactory rather than calling build directly, so it gets every guard chat.New applies: the endpoint is validated before credentials reach it, the logger this package logs through is resolved, provider defaults are applied, unapplicable settings are dropped and reported, and the built client is asserted able to carry what survived.

Calling build directly is what this used to do. An http:// endpoint was accepted with a nil error and the API key then went over the wire in cleartext, and a call with no Logger panicked. See https://gitlab.com/phpboyscout/go/chat-openai/-/issues/2 and spec 0015.

Types

type OpenAI

type OpenAI struct {
	chat.UsageTracker
	// contains filtered or unexported fields
}

func (*OpenAI) Add

func (a *OpenAI) Add(_ context.Context, prompt string, media ...chat.Media) error

func (*OpenAI) AddCached added in v0.5.0

func (a *OpenAI) AddCached(ctx context.Context, prompt string, media ...chat.Media) error

AddCached appends a user turn. OpenAI caches eligible prefixes automatically and reliably — measured caching on every call after the first, covering 88% of a ~4,400-token prefix on gpt-5.4 and 99.7% on gpt-5.6 — so there is no annotation to send and this is Add with a documented intent.

The intent is not wasted: it tells the client a stable prefix exists, which is what prompt_cache_key is derived from.

func (*OpenAI) ApplyPolicyNow added in v0.11.0

func (a *OpenAI) ApplyPolicyNow(ctx context.Context, policy chat.HistoryPolicy) (int, error)

ApplyPolicyNow bounds the retained conversation immediately, rather than waiting for the next request to do it.

It is the same sequence this client runs before every request — describe the turns, ask the policy, rewrite from the result — with two differences. There is no pending turn to hold out of the budget, because nothing is about to be answered. And the system turn is held out as it always is: it lives in the message list on this API but it is configuration, and a command changes what is in a conversation rather than how the client is configured.

A nil policy means the configured one. With none configured there is nothing to apply and it returns zero rather than an error.

func (*OpenAI) Ask

func (a *OpenAI) Ask(ctx context.Context, question string, target any, media ...chat.Media) error

Ask sends a question to the OpenAI chat client and expects a structured response which is unmarshalled into the target interface.

func (*OpenAI) Chat

func (a *OpenAI) Chat(ctx context.Context, prompt string, media ...chat.Media) (string, error)

Chat sends a message and returns the response content. It handles tool calls internally.

func (*OpenAI) History added in v0.9.0

func (a *OpenAI) History() chat.History

History reports the conversation this client will re-send, counting the committed turns and anything buffered by Add and not yet sent.

The system turn is included when there is one: it is re-sent on every call and costs input tokens like any other turn, so a caller watching for growth should see it.

Known is true — this provider owns its transcript, so the count is authoritative.

func (*OpenAI) Model added in v0.6.0

func (o *OpenAI) Model() string

Model implements chat.ModelIdentifier.

func (*OpenAI) Provider added in v0.6.0

func (o *OpenAI) Provider() chat.Provider

Provider implements chat.ModelIdentifier, reporting which of the two registered providers this client was built for.

func (*OpenAI) Restore

func (a *OpenAI) Restore(snapshot *chat.Snapshot) error

Restore replaces the current conversation state with a previously saved snapshot.

func (*OpenAI) Save

func (a *OpenAI) Save() (*chat.Snapshot, error)

Save captures the current OpenAI conversation state as a snapshot.

A stateless client retains no conversation, so its snapshot carries only the system prefix it starts every call from.

func (*OpenAI) SetTools

func (a *OpenAI) SetTools(tools []chat.Tool) error

SetTools configures the tools available to the AI.

func (*OpenAI) StreamChat

func (a *OpenAI) StreamChat(ctx context.Context, prompt string, callback chat.StreamCallback, media ...chat.Media) (string, error)

StreamChat implements StreamingChatClient.

func (*OpenAI) SupportsEffort added in v0.3.0

func (a *OpenAI) SupportsEffort()

SupportsEffort marks OpenAI as able to carry Config.Effort, via reasoning_effort. OpenAI's ladder is a superset of the neutral one, so all five rungs map directly.

func (*OpenAI) SupportsSampling added in v0.3.0

func (a *OpenAI) SupportsSampling()

SupportsSampling marks OpenAI as able to carry Config.Temperature and TopP.

Structural only: gpt-5.4 accepts both, but reasoning models in general are where vendors have been withdrawing sampling controls, so a model that refuses yields chat.ErrModelRejectedParameter at request time.

func (*OpenAI) SupportsStateless added in v0.2.0

func (a *OpenAI) SupportsStateless()

SupportsStateless marks OpenAI as honouring chat.Config.Stateless.

type Option added in v0.9.0

type Option func(*options)

Option configures an OpenAI client at construction.

These are knobs only this provider understands. The core's Config carries what every provider means the same way; anything one provider owns belongs to that provider's own constructor, where the compiler can see it. See https://gitlab.com/phpboyscout/go/chat/-/wikis/specs/0011-chat-provider-conformance D10.

func WithSeed added in v0.9.0

func WithSeed(seed int64) Option

WithSeed pins the sampling seed, asking the backend for reproducible-ish completions. Unset, no seed is sent and the backend samples normally.

It replaces chat.Config.Seed, removed from the core in v0.10.0: it was an OpenAI-only field that every other provider carried and none could read.

Directories

Path Synopsis
internal
detectmodels command
Command detectmodels reports when OpenAI has shipped something the module's default model does not reflect.
Command detectmodels reports when OpenAI has shipped something the module's default model does not reflect.
genmodels command
Command genmodels regenerates the per-model capability table.
Command genmodels regenerates the per-model capability table.
modelfilter
Package modelfilter selects the general-purpose chat models from OpenAI's model listing.
Package modelfilter selects the general-purpose chat models from OpenAI's model listing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL