Skip to main content
Version: 0.12

Chat Completions API

The chat completions endpoint generates responses from a conversation. It's OpenAI-compatible, so if you've used the OpenAI API before, this works the same way.

POST /v1/chat/completions

Basic Request

curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "in-memory::meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
"messages": [
{"role": "user", "content": "What is the capital of France?"}
]
}'

Response:

{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1700000000,
"model": "meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The capital of France is Paris."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 8,
"total_tokens": 20
}
}

Request Parameters

ParameterTypeDefaultDescription
modelstringrequiredModel identifier (e.g., in-memory::meta-llama/Llama-3.2-3B-Instruct)
messagesarrayrequiredConversation messages (see below)
streambooleanfalseEnable token-by-token streaming
temperaturefloatmodel defaultSampling temperature. Lower = more deterministic, higher = more creative. 0 = greedy.
top_kintegermodel defaultKeep only the k most-likely tokens before sampling. 0 disables.
top_pfloatmodel defaultNucleus sampling: keep the smallest set of tokens whose probabilities sum to p. 1.0 disables.
min_pfloatmodel defaultKeep tokens whose probability is at least min_p × the top token's probability. 0 disables.
repetition_penaltyfloatmodel defaultPenalize tokens already in the context. 1.0 = no penalty; higher discourages repetition.
presence_penaltyfloat0OpenAI-style penalty applied once to any token that has appeared.
frequency_penaltyfloat0OpenAI-style penalty scaled by how often a token has appeared.
seedintegerrandomSeed for the sampler. The same seed + params + prompt yields a byte-identical stream.
max_tokensintegermodel defaultMaximum tokens to generate
response_formatobjectnullConstrain output format (see Structured Generation)
enable_thinkingbooleannullToggle the extended-thinking span on models that support it (Qwen 3.5/3.6, SmolLM3). Ignored — with a warning — on other models. See Thinking / Reasoning.

The sampling parameters above (temperature through seed) are all optional — when you omit them, each model falls back to its own recommended defaults. See Sampling below. You rarely need to set them.

Message Roles

Each message in the messages array has a role and content:

RolePurpose
systemSets the AI's behavior, personality, or constraints. Placed first.
userThe human's message.
assistantThe AI's previous response. Used for multi-turn context.

System Prompts

System prompts define how the model behaves. They're essential for game NPCs, assistants, or any specialized behavior:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="not-needed")

response = client.chat.completions.create(
model="in-memory::meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
messages=[
{
"role": "system",
"content": "You are a ship AI aboard a deep-space freighter. You speak formally, "
"address the player as Captain, and provide status reports when asked. "
"You are concerned about a recent anomaly in sector 7.",
},
{"role": "user", "content": "Status report."},
],
temperature=0.7,
)

print(response.choices[0].message.content)

Multi-Turn Conversations

Include previous messages to give the model context of the conversation:

response = client.chat.completions.create(
model="in-memory::meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
messages=[
{"role": "system", "content": "You are a helpful tavern keeper named Boris."},
{"role": "user", "content": "What do you have on the menu?"},
{"role": "assistant", "content": "We have roasted boar, mushroom stew, and fresh bread. The stew is my specialty!"},
{"role": "user", "content": "I'll have the stew. Any rumors lately?"},
],
)

print(response.choices[0].message.content)

Streaming

Set "stream": true to receive tokens as they're generated. This is ideal for typewriter-style dialogue UI.

SSE Format: Each token arrives as a Server-Sent Event. The stream ends with data: [DONE].

data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Once"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" upon"},"finish_reason":null}]}
...
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
stream = client.chat.completions.create(
model="in-memory::meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
messages=[
{"role": "system", "content": "You are a narrator for a fantasy RPG."},
{"role": "user", "content": "Describe the entrance to the dungeon."},
],
stream=True,
)

for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
print()

Sampling

Every sampling parameter is optional. Each model comes with recommended defaults tuned for it, so you get good results without setting anything — a bare request like the basic example above already samples the way the model is meant to. Set a parameter only when you want to override the default for a particular request.

Sampling parameters

ParameterWhat it doesOptional / Default
temperatureControls randomness. Lower is more focused and repeatable; higher is more varied and creative. 0 always picks the single most-likely token.Optional — model default
top_kOnly consider the k most-likely next tokens. Lower values keep output on-topic.Optional — model default
top_pNucleus sampling: only consider the smallest set of tokens whose probabilities add up to p.Optional — model default
min_pIgnore tokens less likely than this fraction of the most-likely token. Filters out long-shot tokens.Optional — model default
repetition_penaltyDiscourages repeating words and phrases already in the conversation. 1.0 means no penalty; higher values push harder against repetition.Optional — model default
presence_penaltyDiscourages reusing any token that has already appeared (applied once per token).Optional — 0
frequency_penaltyLike presence_penalty, but the penalty grows the more often a token has appeared.Optional — 0
seedSet it to get reproducible output: the same seed with the same parameters and prompt produces the same result every time.Optional — random
max_tokensCaps the length of the response.Optional — model default

Temperature quick reference

Temperature is the knob you'll reach for most often. For game applications:

Use CaseTemperatureWhy
Factual responses, game rules0.1 - 0.3Consistent, predictable
NPC dialogue, general conversation0.6 - 0.8Natural variation
Creative writing, storytelling0.9 - 1.2More surprising, diverse

Examples

Rely on the model's recommended defaults — pass no sampling parameters at all:

curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "in-memory::meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
"messages": [{"role": "user", "content": "Describe a misty harbor at dawn."}]
}'

Override a couple of parameters for tighter, reproducible output:

curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "in-memory::meta-llama/Llama-3.2-3B-Instruct-Q4_K_M",
"messages": [{"role": "user", "content": "Describe a misty harbor at dawn."}],
"temperature": 0.4,
"top_p": 0.9,
"seed": 42
}'

Finish Reasons

The finish_reason field tells you why generation stopped:

ValueMeaning
stopModel finished naturally (end of response)
lengthHit the max_tokens limit

length means the output was truncated — common with structured/JSON output. When you see it, raise max_tokens and regenerate; for a best-effort salvage of a cut-off JSON response, see repair_json.

Thinking / Reasoning

Some models support an extended "thinking" (chain-of-thought) phase before the final answer. How a model handles this is a per-model capability, and the enable_thinking request field only has a defined meaning for one group:

Model familyBehavior with enable_thinking
Qwen 3.5, Qwen 3.6, SmolLM3Honor the flag: true gates a <think>…</think> reasoning span; false skips it.
Gemma 4Reasons in its own native format, driven by the model/template — not by enable_thinking. Passing true is ignored and can degrade output; pass false.
All other models (Llama, etc.)No extended-thinking mode. enable_thinking: true is ignored.

If you pass enable_thinking: true to a model that doesn't honor the flag (Gemma 4 or any non-thinking model), the engine logs a warning and runs the request anyway — the flag is simply ignored. The safe default is to omit enable_thinking (or set it false) unless you're targeting a model in the first row.

:::warning Gemma 4 known issue Gemma 4's native reasoning can diverge during long agentic / tool-use decoding. See the Gemma-4 agentic decoding divergence known-issue note. :::

Error Handling

Errors return standard HTTP status codes with an OpenAI-compatible error body:

{
"error": {
"message": "Model 'nonexistent-model' not found",
"type": "invalid_request_error",
"param": "model",
"code": null
}
}
StatusMeaning
400Invalid request (bad model name, malformed JSON)
500Server error (model failed to load, inference error)