The OpenAI adapter reaches the GPT family. It ships inside the core module, so
it costs you no dependencies — it is written against net/http and
encoding/json.
Reference#
func New(apiKey string, opts ...Option) *Provider
Parameters
apiKey— sent asAuthorization: Bearer.opts— functional options, applied in order.
| Option | Signature | Default | Description |
|---|---|---|---|
WithBaseURL | func WithBaseURL(url string) Option | OpenAI's own host | Overrides the API host — an Azure deployment, a proxy, or the sandbox. |
WithHTTPClient | func WithHTTPClient(hc *http.Client) Option | http.DefaultClient | Supplies the *http.Client used for every request. |
WithOrganization | func WithOrganization(org string) Option | — | Sets the OpenAI-Organization header, for accounts that bill per org. |
WithProject | func WithProject(project string) Option | — | Sets the OpenAI-Project header. |
WithMaxTokensField | func WithMaxTokensField(field string) Option | max_completion_tokens | Chooses which wire field carries Request.MaxTokens. OpenAI renamed max_tokens to max_completion_tokens; a host that has not followed needs the old name. |
Caveats
- It shares one implementation with
openaicompat(internal/oai), so request mapping, response parsing, streaming and error classification are byte-identical. See the comparison. Thinkingis ignored unless bothEnabledandEffortare set. So&Thinking{Enabled: false}does nothing — an explicit "off" has no wire representation here.Effort: maxis sent literally, and OpenAI does not define that value. Expect a 400.ProviderOptionsis a shallow top-level merge. Setting a nested object replaces it wholesale.ToolResult.IsErroris lossy: it prefixes"error: "to the content and drops the flag entirely when the content is empty.- Model listing reports almost nothing — no display name, context window or output cap. The gap is upstream.
StopStopSequenceis never produced. A stop-sequence hit arrives as plainstop, so it becomesStopEndTurn.
Usage#
Constructing
client := skyl.New(openai.New(os.Getenv("OPENAI_API_KEY")))client := skyl.New(openai.New(os.Getenv("OPENAI_API_KEY")))An organisation and project
p := openai.New(key,
openai.WithOrganization(os.Getenv("OPENAI_ORG")),
openai.WithProject(os.Getenv("OPENAI_PROJECT")),
)p := openai.New(key,
openai.WithOrganization(os.Getenv("OPENAI_ORG")),
openai.WithProject(os.Getenv("OPENAI_PROJECT")),
)Azure or a host that has not renamed max_tokens
// OpenAI renamed max_tokens to max_completion_tokens; a host that has not
// followed needs the old name.
p := openai.New(key,
openai.WithBaseURL("https://my-resource.openai.azure.com/openai/v1"),
openai.WithMaxTokensField("max_tokens"),
)// OpenAI renamed max_tokens to max_completion_tokens; a host that has not
// followed needs the old name.
p := openai.New(key,
openai.WithBaseURL("https://my-resource.openai.azure.com/openai/v1"),
openai.WithMaxTokensField("max_tokens"),
)Turning reasoning off, which Thinking cannot do here
req.ProviderOptions = map[string]any{"reasoning_effort": "minimal"}req.ProviderOptions = map[string]any{"reasoning_effort": "minimal"}Against the sandbox
p := openai.New("sandbox-key",
openai.WithBaseURL("http://127.0.0.1:8099/openai/v1"))p := openai.New("sandbox-key",
openai.WithBaseURL("http://127.0.0.1:8099/openai/v1"))Troubleshooting#
Turning reasoning off did not reduce cost
&Thinking{Enabled: false} is ignored by this adapter. Send reasoning_effort
through ProviderOptions.
A 400 mentioning max_tokens
The host expects the older field name. Use
WithMaxTokensField("max_tokens").
My generationConfig-style override lost other fields
The merge is shallow: a nested object you set replaces the whole object. Restate
every sibling key skyl would have set — including messages and tools, which
are the dangerous ones.
Streaming reports zero usage
The host must honour stream_options.include_usage. OpenAI itself does; many
compatible hosts do not.