Documentation
¶
Overview ¶
Package openai implements ports.IOAdapter against any OpenAI-compatible Chat Completions endpoint (OpenAI itself, Azure OpenAI, Ollama, vLLM, LM Studio, Groq, and others that speak the same wire format).
It is the client-adapter half of api/llm's declare → register → handle pattern: api/llm.Call declares a system prompt plus typed input/output codecs, protocol-agnostically; CallAdapter supplies the OpenAI wire format and implements ports.IOAdapter, so an LLM completion becomes a normal ports.IOPort step in a pipeline — indistinguishable in shape from an HTTP call, a SQL query, or a cache lookup.
Zero external SDK dependency: plain net/http + encoding/json, matching adapters/nethttp/adapters/chi's stdlib-only precedent. Any OpenAI-compatible provider works by pointing CallAdapterOptions.BaseURL at a different host.
Usage ¶
handle, err := domain.Summarize.PluginLLMPattern(domain.SummarizePattern)
domain.Summarize.Bind(ctx, openai.CallAdapter(http.DefaultClient, handle, openai.CallAdapterOptions{
Model: "gpt-4o-mini",
APIKey: os.Getenv("OPENAI_API_KEY"),
MaxRetries: 1,
}))
// Plain-Go consumption style — no forge/gstream:
summary, err := domain.Summarize.Call(ctx, article)
Structured outputs + local re-validation ¶
Every completion request sets response_format to {"type":"json_schema","json_schema":{"schema":...,"strict":true}} using llm.CallHandle.ResponseSchema — OpenAI's structured-outputs guarantee that the raw completion conforms to the given JSON Schema. The adapter then ALSO decodes the completion through llm.CallHandle.DecodeResponse, which runs every [codex.Codec.Refine] constraint on the response codec — cross-field invariants, custom formats, and other checks a JSON Schema alone cannot express.
Retry on invalid completion ¶
CallAdapterOptions.MaxRetries bounds a re-prompt loop: when DecodeResponse fails (the provider's structured-outputs guarantee did not hold, or a Refine constraint rejected an otherwise schema-valid value), the adapter appends the invalid assistant response plus a new user message describing the validation error, then re-sends the full conversation. With MaxRetries: 0 (the default) the first decode failure is returned as-is (a plain llm.ResponseDecodeError); once retries are exhausted, RetriesExhaustedError is returned instead.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CallAdapter ¶
func CallAdapter[Req, Resp any]( client *http.Client, handle *llm.CallHandle[Req, Resp], opts CallAdapterOptions, ) ports.IOAdapter[Req, Resp]
CallAdapter returns a ports.IOAdapter[Req,Resp] that fulfills the port's Connect/Call by completing a Chat Completions request against an OpenAI-compatible endpoint. Use with ports.IOPort.Bind:
handle, _ := domain.Summarize.PluginLLMPattern(domain.SummarizePattern)
domain.Summarize.Bind(ctx, openai.CallAdapter(httpClient, handle, openai.CallAdapterOptions{
Model: "gpt-4o-mini", APIKey: os.Getenv("OPENAI_API_KEY"),
}))
Every request is validated at the API level via llm.CallHandle.ResponseSchema (OpenAI-style "strict structured outputs") AND re-validated locally through llm.CallHandle.DecodeResponse — belt-and-suspenders: the JSON Schema constrains the shape at generation time, [codex.Codec.Refine] catches what a bare schema cannot express (cross-field invariants, custom constraints). CallAdapterOptions.MaxRetries bounds a re-prompt loop for the latter case.
Types ¶
type CallAdapterOptions ¶
type CallAdapterOptions struct {
// BaseURL defaults to "https://api.openai.com/v1". Point at any
// OpenAI-compatible endpoint (Azure OpenAI, Ollama, vLLM, LM Studio, ...).
BaseURL string
// Model is the model identifier sent on every request (e.g. "gpt-4o-mini").
Model string
// APIKey is sent as `Authorization: Bearer <APIKey>`. Use CredentialFunc
// instead for per-request/rotating credentials.
APIKey string
// CredentialFunc, if set, is called per request and takes priority over
// APIKey — mirrors [nethttp.CallOptions.CredentialFunc]'s role, adapted
// to a single bearer-token string (OpenAI's wire format has no
// structured security-requirement negotiation to pass through).
CredentialFunc func(ctx context.Context) (string, error)
// Temperature, MaxTokens: optional, nil/0 = provider default.
Temperature *float64
MaxTokens *int
// MaxRetries bounds the re-prompt-on-invalid-completion loop (default 0
// = no retry; the first codec-validation failure is returned as-is).
// On failure, the adapter appends the invalid assistant response plus a
// new user message describing the validation error, then re-sends the
// full conversation — up to MaxRetries additional attempts.
MaxRetries int
Observer stats.Observer
}
CallAdapterOptions configures CallAdapter.
type NoChoicesError ¶
type NoChoicesError struct {
// Name is the [llm.Call]'s name, as passed to [llm.NewCall] — lets
// callers disambiguate which declared Call failed when multiple Calls
// share the same Model.
Name string
// Model is the model identifier the request was made against.
Model string
}
NoChoicesError is returned by CallAdapter when the provider's response contains zero completion choices — a malformed or empty API response.
func (NoChoicesError) Error ¶
func (e NoChoicesError) Error() string
func (NoChoicesError) LogValue ¶
func (e NoChoicesError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type RequestBuildError ¶
type RequestBuildError struct {
// Name is the [llm.Call]'s name, as passed to [llm.NewCall] — lets
// callers disambiguate which declared Call failed when multiple Calls
// share the same Model.
Name string
// Err is the underlying error from [http.NewRequestWithContext].
Err error
}
RequestBuildError is returned by CallAdapter when constructing the outgoing *http.Request fails (e.g. malformed BaseURL or context already cancelled). Mirrors [nethttp.RequestBuildError].
func (RequestBuildError) Error ¶
func (e RequestBuildError) Error() string
func (RequestBuildError) LogValue ¶
func (e RequestBuildError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type RequestError ¶
type RequestError struct {
// Name is the [llm.Call]'s name, as passed to [llm.NewCall] — lets
// callers disambiguate which declared Call failed when multiple Calls
// share the same Model.
Name string
// Model is the model identifier the request was made against.
Model string
// Err is the underlying transport error from [http.Client.Do].
Err error
}
RequestError is returned by CallAdapter when executing the HTTP call fails (network error, DNS failure, TLS error, or context cancellation). Mirrors [nethttp.RequestError].
func (RequestError) Error ¶
func (e RequestError) Error() string
func (RequestError) LogValue ¶
func (e RequestError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type ResponseBodyError ¶
type ResponseBodyError struct {
// Name is the [llm.Call]'s name, as passed to [llm.NewCall] — lets
// callers disambiguate which declared Call failed when multiple Calls
// share the same Model.
Name string
// Err is the underlying error from reading the response body.
Err error
}
ResponseBodyError is returned by CallAdapter when reading the HTTP response body fails after a successful connection. Mirrors [nethttp.ResponseBodyError].
func (ResponseBodyError) Error ¶
func (e ResponseBodyError) Error() string
func (ResponseBodyError) LogValue ¶
func (e ResponseBodyError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type RetriesExhaustedError ¶
type RetriesExhaustedError struct {
// Name is the [llm.Call]'s name, as passed to [llm.NewCall] — lets
// callers disambiguate which declared Call failed when multiple Calls
// share the same Model.
Name string
// Model is the model identifier the request was made against.
Model string
// Attempts is the total number of completion attempts made (1 + MaxRetries).
Attempts int
// LastErr is the last [llm.ResponseDecodeError] encountered.
LastErr error
}
RetriesExhaustedError is returned by CallAdapter when CallAdapterOptions.MaxRetries re-prompt attempts are exhausted without ever producing a codec-valid completion.
func (RetriesExhaustedError) Error ¶
func (e RetriesExhaustedError) Error() string
func (RetriesExhaustedError) LogValue ¶
func (e RetriesExhaustedError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.
type UnexpectedStatusError ¶
type UnexpectedStatusError struct {
// Name is the [llm.Call]'s name, as passed to [llm.NewCall] — lets
// callers disambiguate which declared Call failed when multiple Calls
// share the same Model.
Name string
// Model is the model identifier the request was made against.
Model string
// StatusCode is the HTTP response status code returned by the provider.
StatusCode int
// Body is the raw response body returned by the provider (may be empty).
Body string
}
UnexpectedStatusError is returned by CallAdapter when the provider responds with a non-2xx HTTP status code. Mirrors [nethttp.UnexpectedStatusError].
func (UnexpectedStatusError) Error ¶
func (e UnexpectedStatusError) Error() string
func (UnexpectedStatusError) LogValue ¶
func (e UnexpectedStatusError) LogValue() slog.Value
LogValue implements slog.LogValuer for structured logging.