VernLLMVernLLM
Adapters

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

AdapterProvider
fromOpenAIOpenAI
fromOpenAICompatibleGeneric fallback: any OpenAI-wire-compatible provider not listed
fromGroqGroq
fromMistralMistral
fromDeepSeekDeepSeek
fromCerebrasCerebras
fromTogetherTogether AI
fromFireworksFireworks AI
fromOllamaOllama (via its /v1/chat/completions endpoint)
fromOpenRouterOpenRouter
fromPerplexityPerplexity
fromDeepInfraDeepInfra
fromNovitaNovita
fromHyperbolicHyperbolic
fromMoonshotMoonshot (Kimi)
fromZhipuZhipu (GLM)
fromLMStudioLM Studio
fromVLLMvLLM
fromXAIxAI (Grok)
fromNvidiaNIMNVIDIA NIM
fromVercelAIGatewayVercel AI Gateway
fromCloudflareWorkersAICloudflare Workers AI
fromNebiusNebius AI Studio
fromSambaNovaSambaNova Cloud
fromBasetenBaseten
fromFeatherlessFeatherless AI
fromFriendliFriendli AI
fromSiliconFlowSiliconFlow
fromParasailParasail
fromStepFunStepFun
fromMiniMaxMiniMax
fromLambdaLabsLambda Labs Inference API
fromSnowflakeCortexSnowflake Cortex
fromAnyscaleAnyscale Endpoints
fromLeptonLepton AI
fromInferenceNetInference.net
fromInfermaticInfermatic
fromAtlasCloudAtlasCloud
from01AI01.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.

openai-compatible-setup.ts
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:

openai-compatible-streaming.ts
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:

openai-compatible-streaming-no-usage.ts
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:

openai-compatible-multimodal-input.ts
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/png
  • image/jpeg
  • image/gif
  • image/webp

Plain string userContent remains unchanged for text-only requests.

On this page