OpenAI-compat hosts
Groq, Mistral, DeepSeek, Perplexity, Cohere, Ollama and the other Chat Completions hosts, plus createProviderRegistry.
El contenido de las páginas de documentación está en inglés. La navegación, la búsqueda y la interfaz siguen el idioma elegido.
Every factory below speaks the OpenAI Chat Completions wire (surface: 'chat_completions'). Import from @deuz-sdk/core/providers (or the dedicated azure / bedrock subpaths).
Factories at a glance
| Factory | Provider id | Default base URL | Notes |
|---|---|---|---|
createGroq | groq | https://api.groq.com/openai/v1 | LPU cloud |
createMistral | mistral | https://api.mistral.ai/v1 | La Plateforme |
createDeepSeek | deepseek | https://api.deepseek.com/v1 | V3 / R1 / V4 — see the V4 note |
createTogether | together | https://api.together.xyz/v1 | Open models |
createOpenRouter | openrouter | https://openrouter.ai/api/v1 | Multi-provider router |
createCerebras | cerebras | https://api.cerebras.ai/v1 | Wafer-scale |
createFireworks | fireworks | https://api.fireworks.ai/inference/v1 | Open models |
createMoonshot / createKimi | moonshot | https://api.moonshot.ai/v1 | Kimi K2 family (createKimi is an alias) |
createQwen | qwen | https://dashscope.aliyuncs.com/compatible-mode/v1 | DashScope compatible-mode |
createGLM | glm | https://open.bigmodel.cn/api/paas/v4 | Zhipu BigModel |
createMiniMax | minimax | https://api.minimax.io/v1 | M2 family |
createPerplexity | perplexity | https://api.perplexity.ai | Sonar — search-grounded. No /v1, and no client tool calling |
createCohere | cohere | https://api.cohere.ai/compatibility/v1 | Command via the OpenAI compatibility endpoint |
createDeepInfra | deepinfra | https://api.deepinfra.com/v1/openai | Open models (org/Model slugs) |
createNvidia | nvidia | https://integrate.api.nvidia.com/v1 | NVIDIA NIM |
createSambaNova | sambanova | https://api.sambanova.ai/v1 | RDU inference |
createHyperbolic | hyperbolic | https://api.hyperbolic.xyz/v1 | Open models |
createOllama | ollama | http://localhost:11434/v1 | Keyless — see Local models |
createLMStudio | lmstudio | http://localhost:1234/v1 | Keyless — see Local models |
createAzure | azure | deployment-scoped | See Azure OpenAI — api-key + api-version |
createBedrock | bedrock | Mantle …/openai/v1 | See Amazon Bedrock — Bearer API key |
Lowercase singletons (mistral, deepseek, kimi, qwen, perplexity, ollama, …) are exported unbound — prefer the create* factories with an explicit apiKey in apps.
Every factory takes the same CompatSettings: apiKey, baseURL, fetch, headers, and (2.0) capabilities — capability overrides applied to every slug that factory mints, for a host whose slugs the registry does not pin.
Which model slugs work
Any slug the host serves. A factory is a two-line descriptor builder: createGroq({...})('whatever') produces { provider: 'groq', modelId: 'whatever', surface: 'chat_completions' } and the string goes to the wire verbatim. There is no allow-list, nothing throws locally, and a slug the host does not have comes back as that host's own 404 → ModelNotFoundError.
What the slug does change is which row of the capability registry the call picks up. These are the slugs pinned in 2.0 — the flagship of each host, chosen so that the numbers that reach the wire (max_tokens, the reasoning switch, the structured-output strategy) are right without configuration:
| Provider id | Pinned slugs | Notes on the row |
|---|---|---|
groq | llama-4-maverick | vision: true, 131k context |
deepseek | deepseek-v3.2, deepseek-v4-flash, deepseek-v4-pro | V4 rows set reasoning: true — see below |
mistral | mistral-large-latest | vision: true, 256k context |
moonshot | kimi-k2 | 131k context |
qwen | qwen3-max | 262k context |
glm | glm-4.6 | 200k context |
minimax | minimax-m2 | 200k context |
perplexity | sonar, sonar-pro, sonar-reasoning-pro | tools: false — see below |
cohere | command-a-03-2025 | 256k context |
deepinfra | meta-llama/Llama-4-Maverick-17B-128E-Instruct, deepseek-ai/DeepSeek-V3.2 | org/Model casing is part of the slug |
nvidia | meta/llama-4-maverick-17b-128e-instruct, nvidia/llama-3.3-nemotron-super-49b-v1.5 | NIM slugs are lower-cased vendor/model |
sambanova | Llama-4-Maverick-17B-128E-Instruct, Meta-Llama-3.3-70B-Instruct | bare CamelCase, no vendor prefix |
hyperbolic | Qwen/Qwen3-235B-A22B-Instruct, moonshotai/Kimi-K2-Instruct | org/Model |
together, openrouter, cerebras, fireworks | none | Router/aggregator catalogs turn over too fast to pin honestly |
ollama, lmstudio | none, deliberately | The slug is whatever you pulled — see Local models |
Every row on the four 2.0 hosts (deepinfra, nvidia, sambanova, hyperbolic) carries a verify slug at publish marker in the source, for exactly the reason stated there: these vendors rename often, a stale key costs nothing (it simply stops matching and you get the fallback), but a wrong key would hand out capabilities the model does not have.
What an unpinned slug gets
Not an error — new models ship constantly and a registry that threw would be a release blocker for every one of them. An unknown slug takes the conservative fallback row and one warning:
const result = await generateText({ model: together('some-new-model'), prompt: 'hi' });
result.warnings; // [{ type: 'unknown-model', message: "Unknown model 'some-new-model' — …" }]| Field | Fallback value | What it costs you |
|---|---|---|
maxOutput | 4096 | This is the one that matters. It becomes the request's max_tokens, so a long answer is truncated. |
contextWindow | 128_000 | Feeds compaction sizing — too small truncates history early, too large summarizes too late. |
tools | false | Reported only. No adapter reads it; tool calling works regardless. |
reasoning | false | effort is dropped rather than sent. A reasoning model behaves as its own default. |
structuredOutput | false | generateObject uses the tool strategy instead of json_schema. |
vision, caching, … | false | Reported only. |
The fix is one line, and it is the same line whether the host is Together, an internal gateway, or Ollama — state what you know at the factory:
const together = createTogether({
apiKey: process.env.TOGETHER_API_KEY!,
capabilities: { maxOutput: 32_000, contextWindow: 131_072, reasoning: true },
});Nothing is validated: an override is a claim about your model, not a request the SDK can check. See capabilities for the per-factory/per-call precedence.
The registry is keyed by slug alone, not by (provider, slug)
getCapabilities looks the model id up in one flat table. Two hosts serving the same string therefore share one row — which is usually exactly right on an aggregator (openrouter('kimi-k2') inherits the pinned Moonshot row instead of the 4096-token fallback), and occasionally is not.
The case to watch is a slug pinned for a first-party provider being served by someone else. together('gpt-5.5') would pick up the OpenAI row and send max_tokens: 128000; a slug pinned on the OpenAI Responses surface (gpt-5.4, o4-mini) carries samplingRestrictions: true, which makes the Chat Completions adapter send max_completion_tokens and silently drop temperature/topP — while the call itself still goes out on the Chat Completions wire, because dispatch reads the descriptor's surface, never the row's.
In practice most aggregators prefix their slugs (openai/gpt-5.5, meta-llama/…), and a prefixed string can never collide. When it does collide and the row is wrong, a per-factory capabilities override fixes it — that merge happens after the row is chosen.
DeepSeek V4 always thinks
deepseek-v4-flash and deepseek-v4-pro reason on every call — there is no switch — and that has two consequences worth knowing before you debug something that is not a bug. Both were confirmed against the live API; the assertions live in test/live/deepseek.live.test.ts.
Reasoning shares the output budget. reasoning_content is billed and counted as output, so a maxOutputTokens sized for the visible reply returns an empty string rather than an error — the thinking pass consumed the allowance. Give it room:
const result = await generateText({
model: deepseek('deepseek-v4-flash'),
prompt: 'Reply with the single word: pong',
maxOutputTokens: 512, // 16 would come back empty
});
// result.usage.reasoningTokens tells you what the thinking costReasoning arrives as reasoning-delta parts, never spliced into textStream — the two channels stay apart.
generateObject does not work on V4. It has two strategies and V4 rejects both: json sends response_format: json_schema, which V4 answers with "This response_format type is unavailable now", and tool forces tool_choice, which it answers with "Thinking mode does not support this tool_choice".
Ordinary (unforced) tool calling is unaffected, so the agentic loop works normally — only the forced-coercion path is closed. Until a json_object strategy exists, ask for the shape in the prompt:
const { text } = await generateText({
model: deepseek('deepseek-v4-flash'),
prompt: 'Return ONLY JSON matching {"capital": string} for the capital of Turkey.',
maxOutputTokens: 512,
});
const parsed = JSON.parse(text);The wire accepts max_tokens up to 65,536, but the registry keeps the default at 8,192: one runaway thinking pass at the ceiling is a real bill. Raise it per call when an answer needs the room.
Perplexity Sonar does no client tool calling
Perplexity does no client tool calling
Sonar's search runs server-side; the API has no tools array, so the registry rows pin tools: false and structuredOutput: false, and a tool loop against sonar* will not call anything. Perplexity's citations array is a top-level response field that the Chat Completions wire has no slot for, so it is not surfaced as a canonical part. Its base URL is also the one exception with no /v1 segment — the wire path is https://api.perplexity.ai/chat/completions.
Examples
import { streamChat } from '@deuz-sdk/core';
import {
createMistral,
createDeepSeek,
createQwen,
createKimi,
createProviderRegistry,
} from '@deuz-sdk/core/providers';
const mistral = createMistral({ apiKey: process.env.MISTRAL_API_KEY! });
const deepseek = createDeepSeek({ apiKey: process.env.DEEPSEEK_API_KEY! });
const qwen = createQwen({ apiKey: process.env.DASHSCOPE_API_KEY! });
const kimi = createKimi({ apiKey: process.env.MOONSHOT_API_KEY! }); // same as createMoonshot
const registry = createProviderRegistry({
mistral,
deepseek,
qwen,
moonshot: kimi,
});
const model = registry.model('mistral:mistral-large-latest');
// or: deepseek:deepseek-v3.2 · qwen:qwen3-max · moonshot:kimi-k2
const result = streamChat({
model,
messages: [{ role: 'user', content: 'ping' }],
});Pinned flagship slugs (tools / context) live in the model registry: e.g. mistral-large-latest, deepseek-v3.2, qwen3-max, kimi-k2, and 2.0's sonar / sonar-pro / sonar-reasoning-pro and command-a-03-2025. Slugs on DeepInfra, NVIDIA NIM, SambaNova and Hyperbolic rename often, so only one or two representative rows are pinned per host — everything else takes the conservative fallback, which capabilities (below) corrects.
createOpenAICompatible()
For any host that speaks the OpenAI wire but has no named factory above — a self-hosted vLLM or llama.cpp, an internal company gateway. Before 1.9 the workaround was to point an unrelated factory somewhere else (createGroq({ baseURL: 'https://llm.internal/v1' })), which then resolved keys, pricing, registry rows and every log line under the wrong provider id.
import { createOpenAICompatible } from '@deuz-sdk/core/providers';
const gateway = createOpenAICompatible({ id: 'vllm', baseURL: 'http://gpu-box:8000/v1' });
const model = gateway('gpt-oss-120b');
// → { provider: 'vllm', modelId: 'gpt-oss-120b', surface: 'chat_completions' }interface OpenAICompatibleSettings extends CompatSettings {
/** Provider id used for key/baseURL lookup, the registry, pricing and every log line. Required. */
id: string;
/** Wire dialect. Default 'chat_completions'. */
surface?: 'chat_completions' | 'responses';
/** Auth header style. Default 'bearer'. */
authHeader?: 'bearer' | 'api-key';
/** Keyless host: substitute a placeholder instead of throwing when no key is found (2.0). */
apiKeyOptional?: boolean;
// plus CompatSettings: apiKey, baseURL, fetch, headers, capabilities
}
function createOpenAICompatible(settings: OpenAICompatibleSettings): Provider;Key and base-URL resolution use the same precedence chain as every named factory — deps.keyProvider > factory apiKey > createClient({ apiKeys: { [id]: … } }) > AuthenticationError. id creates no bypass.
apiKeyOptional (2.0) changes only the last step: when every link came up empty, a placeholder bearer token is sent instead of throwing. A real key from any link still wins. Use it for an unauthenticated server on your own network — Local models covers the whole story, including createOllama / createLMStudio, which set it for you.
Two eager errors instead of a confusing 401 later:
- an empty
id, and surface: 'responses'combined withauthHeader: 'api-key'— the Responses adapter always sendsAuthorization: Bearer. Usesurface: 'chat_completions', or pass the header yourself viaheaders: { 'api-key': … }.
A custom `id` has no default base URL
Core never reads env vars, so there is no OPENAI_BASE_URL-style fallback. Pass baseURL here or via createClient({ baseUrls: { [id]: … } }), otherwise the call fails with an InvalidRequestError — baseURL is effectively required.
capabilities for a host the registry does not know
An unknown slug does not throw; it falls back to a conservative row whose maxOutput is 4096, reasoning is false and structuredOutput is false. Set what you know, per factory (this option is on CompatSettings, so every named factory takes it too) or per call:
const gateway = createOpenAICompatible({
id: 'internal-gateway',
baseURL: 'https://llm.internal/v1',
apiKey: process.env.GATEWAY_KEY!,
capabilities: { maxOutput: 32_000, reasoning: true, structuredOutput: true },
});The per-call capabilities option wins over the factory value. See capabilities for what it can and cannot change.