DOCS / PROVIDERS / OLLAMA & VLLM

Self-Hosted: Ollama & vLLM

Ollama and vLLM are both thin configurations of the same openai-compatible adapter factory pointed at a different baseUrl, since both already speak an OpenAI-shaped /v1/chat/completions. Neither has a bespoke adapter file beyond a few lines wiring up endpointUrl.

ts
Authorization: Bearer lattice_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Configuration

OllamavLLM
Base URL env varOLLAMA_BASE_URLVLLM_BASE_URL
API key env varOLLAMA_API_KEY (optional)VLLM_API_KEY (optional)
Endpoint called${OLLAMA_BASE_URL}/v1/chat/completions${VLLM_BASE_URL}/v1/chat/completions

A self-hosted provider is "configured" — and therefore eligible to be referenced by a registry.models[].provider entry — the moment its baseUrl env var is set. The authorization header is only added to outgoing requests when an API key is present, since these deployments commonly run with no auth in front of them at all.

WARNING
If a self-hosted endpoint sits on a network reachable beyond your own infrastructure, set an API key. Lattice will happily call it without one, but that's a statement about the adapter's flexibility, not a recommendation to run it open.

Wiring one into the registry

models.config.ts
type ProviderAdapter = {
  id: ProviderId;
  createChatCompletion(
    request: ChatCompletionRequest,
    ctx: ProviderRequestContext,
  ): Promise<ChatCompletionResponse>;
  streamChatCompletion(
    request: ChatCompletionRequest,
    ctx: ProviderRequestContext,
  ): AsyncGenerator<ChatCompletionChunk, void, void>;
};

type ProviderRequestContext = {
  apiKey?: string;
  providerModel: string;
  signal: AbortSignal;
};

providerModel is whatever name your Ollama or vLLM instance serves that model under locally — it doesn't need to match the registry id a client requests.

Streaming and stop reasons

Because both ride the shared openai-compatible request/response/stream mapping, they behave exactly like the OpenAI adapter: stream_options: { include_usage: true } is set on streaming requests so usage is reported on the terminal chunk, and finish_reason is passed through mostly as-is (length/content_filter are special-cased, everything else normalizes to stop).

See Adding a Provider if your self-hosted stack doesn't speak an OpenAI-compatible API and needs a bespoke adapter instead.

LATTICE — INTERNAL AI INFRASTRUCTURE
GITHUB © 2026