A large part of the industry serves OpenAI's wire format. One adapter reaches all of it — including local runtimes that need no credential at all.
Reference#
There is no positional API key. Everything is an option, because a compatible host may need no credential.
| Option | Signature | Default | Description |
|---|---|---|---|
WithBaseURL | func WithBaseURL(url string) Option | — | The host to talk to. Required — New panics without it, because there is no sensible default for "some OpenAI-shaped endpoint". |
WithAPIKey | func WithAPIKey(key string) Option | — | The credential. Optional: omit it for Ollama, LM Studio and llama.cpp, which authenticate nothing. |
WithName | func WithName(name string) Option | openai-compatible | The name that appears in Response.Provider, in errors, and in hook events. Set it, or every host in your fleet reports the same anonymous label. |
WithHTTPClient | func WithHTTPClient(hc *http.Client) Option | http.DefaultClient | Supplies the *http.Client used for every request. |
WithHeader | func WithHeader(key, value string) Option | — | Adds a fixed header — OpenRouter’s HTTP-Referer and X-Title, for instance. |
WithMaxTokensField | func WithMaxTokensField(field string) Option | max_tokens | Chooses which wire field carries Request.MaxTokens. |
Caveats
Newpanics withoutWithBaseURL. There is no sensible default for "some OpenAI-shaped endpoint", and a panic at construction beats a confusing failure on the first request. It is the only adapter constructor that can fail.- Set
WithName. It defaults toopenai-compatible, so without it every host in your fleet reports the same label inResponse.Provider, in errors and in hook events — and your metrics cannot tell Groq from DeepSeek. - Omit
WithAPIKeyfor local runtimes. Ollama, LM Studio and llama.cpp authenticate nothing; passing an empty string still sends a header. - These hosts implement OpenAI's format, not necessarily its features. Tool calling, streaming and multimodal support vary by host and by model.
- It shares one implementation with
provider/openai, so every caveat there applies here too.
openai versus openaicompat#
They share one implementation. The complete list of differences:
| openai | openaicompat | |
|---|---|---|
| Output-cap field | max_completion_tokens | max_tokens |
| Provider name in responses, errors and hooks | openai | openai-compatible, or whatever WithName says |
| Base URL | defaults to OpenAI's host | required — New panics without it |
| API key | positional argument | optional — omit it for Ollama, LM Studio, llama.cpp |
| Extra headers | WithOrganization, WithProject | generic WithHeader |
Everything else — request mapping, response parsing, streaming, error classification — is byte-identical.
Verified endpoints#
| Host | Base URL | Key? | Notes |
|---|---|---|---|
| xAI (Grok) | https://api.x.ai/v1 | Required | |
| DeepSeek | https://api.deepseek.com/v1 | Required | |
| Mistral | https://api.mistral.ai/v1 | Required | |
| Groq | https://api.groq.com/openai/v1 | Required | |
| Together | https://api.together.xyz/v1 | Required | |
| Fireworks | https://api.fireworks.ai/inference/v1 | Required | |
| OpenRouter | https://openrouter.ai/api/v1 | Required | Brokers 300+ models on its own, and supplies DisplayName and ContextWindow on model listing. |
| Perplexity | https://api.perplexity.ai | Required | |
| Cerebras | https://api.cerebras.ai/v1 | Required | |
| DeepInfra | https://api.deepinfra.com/v1/openai | Required | |
| Qwen / DashScope | https://dashscope.aliyuncs.com/compatible-mode/v1 | Required | |
| Moonshot (Kimi) | https://api.moonshot.cn/v1 | Required | |
| Z.ai (GLM) | https://open.bigmodel.cn/api/paas/v4 | Required | |
| Nvidia NIM | https://integrate.api.nvidia.com/v1 | Required | |
| Ollama | http://localhost:11434/v1 | None | Local. Needs no credential — omit WithAPIKey entirely. |
| vLLM | http://localhost:8000/v1 | None | Self-hosted. Returns content as an array of blocks on some builds, which skyl handles. |
| LM Studio | http://localhost:1234/v1 | None | Local. |
| llama.cpp | http://localhost:8080/v1 | None | Local. |
Usage#
A hosted provider
p := openaicompat.New(
openaicompat.WithBaseURL("https://api.groq.com/openai/v1"),
openaicompat.WithAPIKey(os.Getenv("GROQ_API_KEY")),
openaicompat.WithName("groq"),
)p := openaicompat.New(
openaicompat.WithBaseURL("https://api.groq.com/openai/v1"),
openaicompat.WithAPIKey(os.Getenv("GROQ_API_KEY")),
openaicompat.WithName("groq"),
)A local runtime, with no credential
p := openaicompat.New(
openaicompat.WithBaseURL("http://localhost:11434/v1"),
openaicompat.WithName("ollama"),
// no WithAPIKey — no Authorization header is sent at all
)p := openaicompat.New(
openaicompat.WithBaseURL("http://localhost:11434/v1"),
openaicompat.WithName("ollama"),
// no WithAPIKey — no Authorization header is sent at all
)A helper that cannot forget WithName
func compat(name, baseURL, key string) skyl.Provider {
opts := []openaicompat.Option{
openaicompat.WithBaseURL(baseURL),
openaicompat.WithName(name), // positional in our signature, so unforgettable
}
if key != "" {
opts = append(opts, openaicompat.WithAPIKey(key))
}
return openaicompat.New(opts...)
}func compat(name, baseURL, key string) skyl.Provider {
opts := []openaicompat.Option{
openaicompat.WithBaseURL(baseURL),
openaicompat.WithName(name), // positional in our signature, so unforgettable
}
if key != "" {
opts = append(opts, openaicompat.WithAPIKey(key))
}
return openaicompat.New(opts...)
}OpenRouter's attribution headers
p := openaicompat.New(
openaicompat.WithBaseURL("https://openrouter.ai/api/v1"),
openaicompat.WithAPIKey(key),
openaicompat.WithName("openrouter"),
openaicompat.WithHeader("HTTP-Referer", "https://example.com"),
openaicompat.WithHeader("X-Title", "My App"),
)p := openaicompat.New(
openaicompat.WithBaseURL("https://openrouter.ai/api/v1"),
openaicompat.WithAPIKey(key),
openaicompat.WithName("openrouter"),
openaicompat.WithHeader("HTTP-Referer", "https://example.com"),
openaicompat.WithHeader("X-Title", "My App"),
)Troubleshooting#
A panic at startup
WithBaseURL is required. This is the only adapter that panics on construction.
All my hosts report the same provider name
You did not set WithName. It defaults to openai-compatible.
Assistant text is empty on vLLM
Some builds return content as an array of blocks rather than a bare string.
The adapter handles both — if you still see empty text, check Response.Raw for
a block type other than text, which is discarded.
Tool calling does not work on this host
The host implements OpenAI's format without implementing tool calling, or the model does not support it. skyl surfaces the host's own error rather than inventing one.