Help us shape the future of Go! Take the 2026 Go Developer Survey

openai

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package openai implements port.LLMProvider over the OpenAI Responses API (POST /v1/responses) using github.com/openai/openai-go/v3. It names the PROTOCOL, not a single vendor: this one adapter serves the composition registry's "openai", "openrouter", AND (issue #262) the intent-driven ToolHive LLM gateway registry entries — each is the same Responses-API wire protocol with a different base URL + credential, so registering one more OpenAI-compatible endpoint here never touches the OpenAI/Anthropic SDK boundary.

The harness owns its own conversation state (the brief's "strategy B"): every request is stateless (Store:false, no previous_response_id) and resends the full input item slice, with reasoning items carried forward verbatim and reasoning.encrypted_content requested via Include so reasoning survives across turns. Tools are sent as function tools with their JSON schemas. The two-layer system prompt is rendered into Instructions.

The streaming SSE events are translated into provider-neutral port.Chunk values by the pure translate function, which is exercised directly from recorded fixtures in tests; no network is required to test the translation.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CacheDialect

type CacheDialect string

CacheDialect selects which provider-side prompt-cache wire dialect buildParams (method) emits. The OpenAI Responses API already caches implicitly (a breakpoint on the most recent user/tool message, matched against the 50 most recent breakpoints, longest match wins) — this Option only controls the vendor-shaped hints layered on top: prompt_cache_key (routing observability) and prompt_cache_retention (an OpenAI-only, model-gated retention extension).

It does NOT control the ASK for a cache. The request-root cache_control field this type once carried for OpenRouter is retired in favour of the protocol-native prompt_cache_breakpoint, which rides the separate WithPromptCacheBreakpoints Option precisely so an endpoint whose identity the dialect cannot classify still gets the ask.

const (
	// CacheDialectNone emits no cache hints at all — the SAFE DEFAULT, and the
	// byte-identical wire without cache hints. The zero value.
	CacheDialectNone CacheDialect = ""
	// CacheDialectOpenAI emits prompt_cache_key, and — on an allow-listed
	// model (retentionFor) — prompt_cache_retention.
	CacheDialectOpenAI CacheDialect = "openai"
	// CacheDialectOpenRouter emits prompt_cache_key and nothing else. It once
	// also emitted an OpenRouter-private request-root cache_control field;
	// that is retired in favour of the protocol-native breakpoint, so the
	// two OpenAI-shaped dialects now differ ONLY in retention. It must still
	// NEVER emit prompt_cache_retention — an OpenAI-only field a non-OpenAI
	// upstream would reject.
	CacheDialectOpenRouter CacheDialect = "openrouter"
)

type OpenRouterProviderPreferences

type OpenRouterProviderPreferences struct {
	Order          []string
	AllowFallbacks *bool
}

OpenRouterProviderPreferences is the OpenRouter DOWNSTREAM-provider routing object (issue #480) stamped onto the request body's `provider` key. It is the adapter-local, provider-private mirror of OpenRouter's ProviderPreferences schema — v1 carries only Order + AllowFallbacks. It is NOT a port.LLMRequest field: the request stays provider-neutral and the knob is minted per model at adapter construction (the remint discipline).

Order lists downstream provider slugs (lowercase-kebab, e.g. "anthropic", "google-vertex", "deepinfra/turbo") tried in order; setting it disables OpenRouter's default price load-balancing. AllowFallbacks is a POINTER so "absent" (OpenRouter default true) is distinguishable from an explicit false (pin hard to Order, no fallback). Base-slug matching applies: "google-vertex" matches all its regions/variants (service tiers excepted).

type Option

type Option func(*config)

Option configures a Provider.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key used to authenticate requests.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the API host so OpenAI-compatible endpoints (vLLM, LiteLLM, a local proxy, ...) can be targeted. The SDK appends "/responses".

func WithCacheDialect

func WithCacheDialect(d CacheDialect) Option

WithCacheDialect selects the cache-hint dialect buildParams (method) emits. Composition gates this on (providerID, resolvedBaseURL) — NEVER providerID alone: an operator can point the "openai" id at a non-canonical compatible endpoint (vLLM/LiteLLM via --openai-base-url) that would 400 on prompt_cache_retention or an unrecognised field, so the dialect for a non-canonical base URL must degrade to CacheDialectNone in composition, not here. An unrecognised token (this adapter has no validation at Option- application time) degrades to CacheDialectNone in buildParams — fail-soft, mirroring reasoningEffortFor's omit-on-unknown arm.

func WithCacheKeySalt added in v0.2.0

func WithCacheKeySalt(salt string) Option

WithCacheKeySalt folds a per-process random value into the prompt_cache_key prefix hash. Composition mints one value per app.Build and passes it to every openai-adapter entry.

Why: the unsalted derivation has no installation-specific input, so two unrelated installations sharing harness version, soul, agent def and tool inventory emit an IDENTICAL key. That lets a recipient correlate sessions across principals and credentials, and confirm guessed configuration — and routing metadata frequently outlives prompt bodies in a provider's retention tiers, so the key outlives the content it fingerprints.

"" (the Option unset) reproduces the unsalted derivation byte-for-byte, so a consumer passing no Option is unaffected. The salt is never sent as itself, never logged, and never persisted: a fresh value per process costs one sticky-routing lane change per restart, which the derivation already treats as fail-soft for an anchor change.

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient sets the final *http.Client the SDK issues requests through (e.g. a redirect-refusing or policy-guarded client). It is deliberately applied after arbitrary WithRequestOption values, so a late generic SDK option cannot replace and bypass a security transport. nil is ignored (SDK default). NOTE for callers: do NOT set Client.Timeout here — a streaming turn runs for minutes; establishment/idle bounds live in llmresilience, not the transport's blanket deadline.

func WithMaxRetries

func WithMaxRetries(retries int) Option

WithMaxRetries configures the openai-go retry loop. Composition uses zero when llmresilience is the sole retry owner or when a dynamic credential source must not be called multiple times inside one outer attempt.

func WithOpenRouterMetadata

func WithOpenRouterMetadata(enabled bool) Option

WithOpenRouterMetadata arms the `X-OpenRouter-Metadata: enabled` request header, which makes OpenRouter return the openrouter_metadata block (naming the routed downstream provider) on the terminal streaming event. It is a SEPARATE Option from WithOpenRouterProviderPreferences so the routing echo works even with no order configured. Gated to the openrouter registry entry in composition. The adapter never logs; the value reaches the loop as ChunkProviderRoute.

func WithOpenRouterProviderPreferences

func WithOpenRouterProviderPreferences(resolve func(model string) *OpenRouterProviderPreferences) Option

WithOpenRouterProviderPreferences sets a resolve-at-request closure keyed on the request's model id, returning the downstream-provider routing object for that model (nil = send nothing). The per-model closure shape exists because the registry entry is shared across models while the config is per-model. It is gated to the openrouter registry entry in COMPOSITION — every other entry passes no Option, so the `provider` key can never leak to a non-OpenRouter endpoint. The body key is injected via option.WithJSONSet at Stream time, so buildParams and the byte-stable prompt-cache prefix are untouched.

func WithPromptCacheBreakpoints added in v0.2.0

func WithPromptCacheBreakpoints(on bool) Option

WithPromptCacheBreakpoints arms the protocol-native explicit prompt-cache breakpoint. Composition passes !cfg.PromptCacheDisabled.

It is a SEPARATE Option from WithCacheDialect on purpose. The dialect gates vendor-shaped hints on endpoint identity; the breakpoint is part of the Responses protocol and is exactly what an unrecognised endpoint needs, so tying it to the dialect would leave CacheDialectNone endpoints — the ToolHive gateway among them — with no way to ask for a cache, which is the bug this breakpoint exists to fix.

Default false, so a consumer passing no Option keeps the breakpoint-free wire.

func WithProviderCapabilities

func WithProviderCapabilities(caps port.ProviderCapabilities) Option

WithProviderCapabilities sets the per-SESSION input-capability intersection (the catalog ∩ adapter value composition computes via modelCapability) the request builder consults when projecting a tool result's typed Parts. It is an adapter-CONSTRUCTION Option, not a port.LLMRequest field — the per- session engine factory re-mints the adapter (alongside reasoning effort) when the session's resolved (provider, model) carries a DIFFERENT intersection than the operator-default model the shared provider was built with; the default path (same model) reuses the shared provider byte-for-byte. When unset, the builder degrades to the adapter's own static Capabilities(), and a tool result with no Parts always takes the legacy single-string function_call_output regardless. A deliberately text-only (zero-value) caps is distinct from unset (nil).

func WithReasoningEffort

func WithReasoningEffort(effort string) Option

WithReasoningEffort sets the reasoning-effort token stamped on every request's reasoning.effort field. The value is a NEUTRAL composition token, ALREADY CLAMPED for OpenAI (xhigh/max are clamped to high in composition, with a diagnostic, because this adapter has no port.Diagnostics). Empty (and "auto") OMITS the field — the provider default applies. It is an adapter-CONSTRUCTION Option, not a port.LLMRequest field, so the provider stays neutral; the per-session engine factory re-mints the adapter when the session's effort differs from the operator default (the same factory discipline as the per-call model override).

func WithRequestOption

func WithRequestOption(opts ...option.RequestOption) Option

WithRequestOption threads an arbitrary openai-go request option through to the client (e.g. option.WithHeader, option.WithMaxRetries). Multiple are applied in order, after the API key and base URL.

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider is a port.LLMProvider backed by the OpenAI Responses API. Construct it with New.

func New

func New(opts ...Option) *Provider

New constructs a Provider. At minimum supply WithAPIKey; add WithBaseURL for compatible endpoints.

func (*Provider) Capabilities

func (*Provider) Capabilities() port.ProviderCapabilities

Capabilities reports the provider's multimodal input support — the ADAPTER TRANSMIT authority composition's modelCapability intersects with the catalog (catalog ∩ adapter). It is STATIC (the OpenAI Responses input-message content union supports text + image + file but has NO audio member, openai-go v3.37.0, so Audio is false; Image is true; EmbeddedContext is true because inline text flattens into the input_text content part). It does NOT reflect the per- session intersection — that lives on p.caps (set via WithProviderCapabilities) and is consulted ONLY by the request builder's tool-result projection; the port method stays the static transmit authority so modelCapability's AND stays honest.

func (*Provider) Stream

func (p *Provider) Stream(ctx context.Context, req port.LLMRequest) (iter.Seq2[port.Chunk, error], error)

Stream issues a streaming Responses request and yields provider-neutral chunks. The returned iterator translates each SSE event via translate; it stops (abandoning the underlying stream) when ctx is cancelled, and surfaces a terminal transport error as the iterator's error. The outer error is reserved for a failure to construct the request parameters.

A rejection of the replayed encrypted reasoning is REPAIRED once, before any chunk has gone out: see withoutEncryptedReasoning. Everything else — an ordinary 4xx, a post-commit failure, a cancellation — stays terminal.

Jump to

Keyboard shortcuts

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