OpenAI-Compatible
Zero-transform passthrough for OpenAI-compatible providers
Many hosted inference providers expose the same wire format as OpenAI's Chat Completions API. VernLLM includes a named adapter for each supported provider, but they're all thin aliases around the same OpenAI-compatible adapter: fromOpenAICompatible().
Use fromOpenAI for OpenAI itself
For OpenAI, use the fromOpenAI alias rather than passing an OpenAI SDK instance straight to
client. A raw client covers the basic non-streaming, text-only path structurally, but skips the
multimodal ContentBlock[] translation and createStream wiring this adapter provides, and newer
openai SDK majors have widened their content-part types in ways that no longer typecheck against
LLMClient unwrapped. See Migration Notes for details.
The openai SDK retries on its own too
The official openai client retries transient failures, including 429s, internally by default
(maxRetries: 2), before VernLLM's own retry loop ever sees them. Pass maxRetries: 0 to the
client constructor if you want VernLLM to be the sole retry authority. Other OpenAI-compatible
clients may have different retry defaults (or none) since fromOpenAICompatible forwards to
whatever chat.completions.create implementation you supply, without guaranteeing this behavior.
To be sure check your client's own docs. See SDK-internal
retries.
The adapter preserves the OpenAI-compatible request shape and only normalizes fields VernLLM adds on top of the standard Chat Completions format. In particular, multimodal ContentBlock[] input is translated into OpenAI's native content array format, while plain string messages pass through unchanged.
The named adapters exist purely for readability and discoverability. They also leave room to introduce provider-specific behavior in the future without requiring a breaking API change.
Named providers
| Adapter | Provider |
|---|---|
fromOpenAI | OpenAI |
fromOpenAICompatible | Generic fallback: any OpenAI-wire-compatible provider not listed |
fromGroq | Groq |
fromMistral | Mistral |
fromDeepSeek | DeepSeek |
fromCerebras | Cerebras |
fromTogether | Together AI |
fromFireworks | Fireworks AI |
fromOllama | Ollama (via its /v1/chat/completions endpoint) |
fromOpenRouter | OpenRouter |
fromPerplexity | Perplexity |
fromDeepInfra | DeepInfra |
fromNovita | Novita |
fromHyperbolic | Hyperbolic |
fromMoonshot | Moonshot (Kimi) |
fromZhipu | Zhipu (GLM) |
fromLMStudio | LM Studio |
fromVLLM | vLLM |
fromXAI | xAI (Grok) |
fromNvidiaNIM | NVIDIA NIM |
fromVercelAIGateway | Vercel AI Gateway |
fromCloudflareWorkersAI | Cloudflare Workers AI |
fromNebius | Nebius AI Studio |
fromSambaNova | SambaNova Cloud |
fromBaseten | Baseten |
fromFeatherless | Featherless AI |
fromFriendli | Friendli AI |
fromSiliconFlow | SiliconFlow |
fromParasail | Parasail |
fromStepFun | StepFun |
fromMiniMax | MiniMax |
fromLambdaLabs | Lambda Labs Inference API |
fromSnowflakeCortex | Snowflake Cortex |
fromAnyscale | Anyscale Endpoints |
fromLepton | Lepton AI |
fromInferenceNet | Inference.net |
fromInfermatic | Infermatic |
fromAtlasCloud | AtlasCloud |
from01AI | 01.AI (Yi models) |
Don't see your provider? If it advertises itself as OpenAI-compatible or a drop-in replacement for the OpenAI API, it will typically work with fromOpenAICompatible() without any additional integration.
Adding a named adapter is just a convenience alias around the same implementation. If your provider isn't listed, feel free to open a PR, it's usually a one-line addition.
import OpenAI from 'openai';
import { VernLLM, fromOpenAICompatible } from 'vern-llm';
const llm = new VernLLM({
client: fromOpenAICompatible(
new OpenAI({
apiKey: process.env.SOME_PROVIDER_API_KEY,
baseURL: 'https://api.some-provider.example/v1',
}),
),
model: 'some-model',
});Tool calling
tools, tool_choice, and tool_calls already match the OpenAI Chat Completions shape, so this
adapter passes them through unchanged. The one normalization it applies is stripping
is_error from outgoing tool messages, since that field has no OpenAI-compatible equivalent
(fromAnthropic and fromBedrock honor it natively, mapping it to each provider's own
error-result field). See Tool Calling for the full
reference.
Streaming
fromOpenAICompatible implements createStream by calling the same endpoint as create with
stream: true. No extra setup is needed beyond the client already passed to the adapter:
const { chunks, finalResult } = await llm.call({
userContent: 'Write a short poem about the ocean.',
stream: true,
});
for await (const chunk of chunks) {
if (chunk.type === 'text-delta') process.stdout.write(chunk.delta);
}A final usage chunk requires the provider to support stream_options: { include_usage: true }.
fromOpenAICompatible and its named aliases send this by default, supportsStreamUsage defaults
to true. If your provider rejects unrecognized fields on a streaming request, pass
{ supportsStreamUsage: false } as the second argument to omit stream_options entirely:
const llm = new VernLLM({
client: fromOpenAICompatible(client, { supportsStreamUsage: false }),
model: 'some-model',
});fromOpenAICompatible's second argument also accepts reasoningEffortTokens, an override for the
token counts budgetTokens buckets into when reasoningEffort isn't set directly. See the
Call Params reference for the full table and
how it works across every adapter.
See Streaming for the full stream: true contract.
Multimodal input
OpenAI-compatible providers with vision-capable models can receive images through VernLLM's
provider-independent ContentBlock[] format:
const result = await llm.call({
userContent: [
{
type: 'text',
text: 'What is in this image?',
},
{
type: 'image',
mimeType: 'image/png',
data: imageBase64,
},
],
});Image data must be raw base64-encoded bytes without a data: URL prefix. The OpenAI-compatible
adapter converts image blocks into the provider's expected image_url data URL format internally.
Supported image types are:
image/pngimage/jpegimage/gifimage/webp
Plain string userContent remains unchanged for text-only requests.