Serving an OpenAI-compatible endpoint
Run an engine once and talk to it in a chat window, and you have a toy. Serve it over HTTP in the shape everyone already speaks, and you have infrastructure: editors, UIs, agents, and your own scripts can all drive the same local model. This page gets a local endpoint up, then covers streaming, SDKs, security, and warm loading.
Why the OpenAI shape won
The chat-completions request format — a model name, a list of {role, content} messages, a few knobs — became the de facto standard because every tool author wrote a client for it first. Whatever you think of the company, the shape is neutral: it is just JSON over POST. Practical consequence: any local server that mimics it is instantly compatible with an enormous pile of existing software. Your engine choice and your client choice become independent decisions (see engines).
llama-server in one minute
llama-server -m model.gguf -c 8192 --port 8080
# UI at http://127.0.0.1:8080 — API at /v1/chat/completions
An annotated request:
curl http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"model","messages":[{"role":"user","content":"Why run local? One sentence."}],"temperature":0.7,"max_tokens":60}'
model— llama-server serves whatever you launched; the field is often ignored, but send a sane name.messages— the whole conversation, system prompt first; stateless server, you hold the history.max_tokens— cap the reply; protects you from runaway generations.
The response (trimmed) carries the answer plus the token accounting you will want for cost-free debugging:
{
"choices": [
{ "message": { "role": "assistant", "content": "No accounts, no rate limits, and your data never leaves the machine." } }
],
"usage": { "prompt_tokens": 14, "completion_tokens": 15 }
}
usage is how you learn what your prompts actually cost in context — check it before blaming a model for "forgetting" (see tokens).
Streaming
Add "stream": true and the server switches to server-sent events: newline-delimited data: chunks, each carrying a small delta, terminated by a sentinel.
data: {"choices":[{"delta":{"content":"No "}}]}
data: {"choices":[{"delta":{"content":"accounts"}}]}
data: [DONE]
Client-side: accumulate delta.content until the [DONE] line. Streaming does not make generation faster — it makes waiting tolerable, which for chat UX is the same thing (see speed).
Ollama: one service, two APIs
Ollama's native API is slightly richer — per-request options and model lifecycle control:
curl http://127.0.0.1:11434/api/chat -d '{"model":"qwen3:8b","messages":[{"role":"user","content":"hi"}],"stream":false,"keep_alive":-1}'
It also serves the OpenAI-compatible route at /v1/chat/completions, so OpenAI-shaped clients work against Ollama without changes. Use the native API when you want keep_alive or per-request engine options; use /v1 for everything else.
vLLM for GPU servers
vllm serve Qwen/Qwen3-8B --max-model-len 16384 --port 8000
vLLM pulls safetensors weights, loads once, and serves the same OpenAI shape with continuous batching underneath — the right tool once concurrent users appear (see concurrency). It wants NVIDIA VRAM and a real GPU; do not bother with it for a single-user desktop.
Point the official SDKs at localhost
The official OpenAI libraries are just HTTP clients; swap the base URL and the API key field becomes decorative:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="local")
r = client.chat.completions.create(
model="qwen3-8b",
messages=[{"role": "user", "content": "Why run local?"}],
)
print(r.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "http://127.0.0.1:11434/v1", apiKey: "ollama" });
const r = await client.chat.completions.create({
model: "qwen3:8b",
messages: [{ role: "user", content: "Why run local?" }],
});
Any string works for the key unless the server has one configured. This base-URL swap is the whole trick behind every "works with local models" tool.
What this unlocks
- Editors and chat UIs — see UIs & integrations.
- Your own apps: a local model behind your own backend, zero per-token cost.
- Scripted pipelines with guaranteed schema — see structured output.
- Agent loops that call tools and iterate — see agents.
- This forum's games corner: the chess and janggi agents play by hitting a local chat-completions endpoint exactly like the curl above.
Lock the door
A model server is a free LLM for anyone who can reach the port. Defaults are safe — llama-server and Ollama bind 127.0.0.1 — so keep it that way on laptops. To expose on a LAN: set an API key (--api-key on llama-server and vLLM, and auth in any UI in front), and prefer a reverse proxy for TLS. Never bind 0.0.0.0 unauthenticated: anyone on the network can burn your GPU, fill your context, and probe your setup.
Keep it warm
Cold load — weights from disk into VRAM — costs seconds to tens of seconds of TTFT. llama-server and vLLM load at startup and stay resident, so only the first request pays. Ollama unloads after keep_alive (default ~5 minutes): send "keep_alive": -1 to hold the model indefinitely, or 0 to free VRAM immediately after a burst. If your setup feels "slow at first, fine after," that is a cold load, not a slow model — and under load, a load storm is a real failure mode (see concurrency).
Related: Concurrency · UIs & integrations · Engines · Structured output