REST API¶
prism serve exposes an OpenAI-compatible API at http://127.0.0.1:5272/v1 (fixed port, configurable with --port).
curl -s http://127.0.0.1:5272/v1/chat/completions -H 'Content-Type: application/json' -d '{
"model": "phi-4-mini",
"messages": [{"role": "user", "content": "Write hello world in Rust."}],
"max_tokens": 100
}'
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:5272/v1", api_key="anything") # the real key if --api-key is set
resp = client.chat.completions.create(
model="phi-4-mini",
messages=[{"role": "user", "content": "Count from 1 to 5."}],
stream=True,
)
for chunk in resp:
print(chunk.choices[0].delta.content or "", end="")
curl -sN http://127.0.0.1:5272/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model": "phi-4-mini", "stream": true, "messages": [{"role": "user", "content": "Hi"}]}'
Endpoints¶
| Method and path | Purpose |
|---|---|
GET /v1/models |
ONNX and Ollama models. Extra fields: size_mb, owned_by (engine), device (where the model will run: CPU or CUDA (GPU), following --device and the hardware), and for ONNX models exported_for (what the model files were built for; a generic-cpu variant still runs on CUDA under --device auto). |
POST /v1/chat/completions |
Chat completion, streaming or not |
POST /v1/embeddings |
Embeddings, Ollama models only (see Embeddings) |
POST /v1/completions |
Legacy text completion. ONNX models get the raw prompt (no chat template). Ollama models get it as one user message. |
GET /health (also /v1/health, /v1/status) |
Status, active_model, active_device (cuda/cpu/null when nothing is loaded) and GPU telemetry. No auth required. |
Trailing slashes are accepted.
Request fields¶
| Field | Notes |
|---|---|
model |
Required. Same resolution rules as the CLI. ollama: names, and installed Ollama names, route to Ollama. |
messages |
Required, non-empty list. The chat template is chosen per model (Models). |
stream |
true for server-sent events |
stream_options.include_usage |
With stream, add a last chunk that carries usage (and, for ONNX models, telemetry); see Responses. Ollama models only when the daemon reports counts |
stop |
A string or a list of up to 4 strings. Generation ends at the first match, which is not included in the text; finish_reason is stop |
max_tokens / max_completion_tokens |
Default 512, must be ≥ 1. For ONNX models whose genai_config.json states a context_length, it is capped to the room left after the prompt (finish_reason is then length); a prompt that fills the window is a 400 context_length_exceeded |
tools |
OpenAI function tools ([{"type": "function", "function": {"name", "description", "parameters"}}]); see Tool calling |
tool_choice |
"none" hides the tools for this request. Any other value is treated as "auto": Prism cannot force a call |
temperature |
Default 0.1; 0 means greedy decoding |
top_p |
Default 0.9 |
Message content may be a string or a list of parts; text parts are used and others (images, …) are dropped. Other OpenAI fields (n, response_format, …) are accepted and ignored.
Responses¶
Non-streaming responses follow the OpenAI shape. finish_reason is stop (end of sequence) or length (hit max_tokens),
and usage uses the model tokenizer. Ollama responses carry the finish_reason and usage the daemon reports (none if it reports none). ONNX responses add a Prism extension:
"telemetry": { "ttft_sec": 0.45, "decode_tok_per_sec": 85.2, "device": "cuda" }
Streaming sends chat.completion.chunk events: a first chunk with delta: {"role": "assistant"}, content deltas, a final chunk
with the finish_reason, then data: [DONE]. With stream_options: {"include_usage": true} one more chunk with an empty choices list
comes after the finish_reason chunk and before [DONE], carrying usage (prompt_tokens, completion_tokens, total_tokens, from the model
tokenizer) and the same telemetry block as non-streaming responses; without the option the stream is unchanged. If generation fails after streaming has begun, an event
data: {"error": {...}} is sent before [DONE]. If the client disconnects, generation stops.
Tool calling¶
Send tools as you would to OpenAI. When the model calls one, the reply carries message.tool_calls (id, type: "function", function.name, and
function.arguments as a JSON string), content holds any text that came before the call (or is null), and finish_reason is tool_calls.
Send the result back as a {"role": "tool", "tool_call_id": ..., "content": ...} message after the assistant message that made the call.
- ONNX models need the model's own chat template to take tools, and Prism renders that template only with the optional
jinjaextra (pip install "prism-local[jinja]", see Models). Without both, a request withtoolsgets400 tools_not_supportedinstead of a silent non-answer. The model writes its calls as text in its own convention (<tool_call>…,<|tool_call|>…,[TOOL_CALLS]…,<|python_tag|>…, or a bare JSON object); Prism recognises them in the output. Those markers are special tokens that ONNX Runtime GenAI decodes to nothing, so the engine puts them back. - Streaming with
toolson an ONNX model is buffered: the calls can only be recognised in the finished text, so the reply arrives at the end, as a content chunk and/or onetool_callsdelta, then thefinish_reasonchunk. Withouttoolsstreaming is token by token as before. - Ollama models get
toolspassed to the daemon, which parses the calls itself (use a model that supports tools, such asllama3.1orqwen2.5). - Whether a call is right depends on the model; small models are unreliable. Prism guarantees the shape of the reply, not the choice of tool.
Embeddings¶
POST /v1/embeddings follows OpenAI's shape and is served by Ollama, because ONNX Runtime GenAI does not produce embeddings. Use an Ollama embedding model:
prism pull ollama:nomic-embed-text
curl -s http://127.0.0.1:5272/v1/embeddings -H 'Content-Type: application/json' \
-d '{"model": "ollama:nomic-embed-text", "input": ["first text", "second text"]}'
| Field | Notes |
|---|---|
model |
An Ollama model (ollama: prefix or an installed name). An ONNX model gets 400 embeddings_not_supported |
input |
A string, or a list of up to 256 non-empty strings. Token arrays are not supported |
encoding_format |
float (default) or base64 (little-endian float32; the official OpenAI Python client asks for it) |
dimensions |
Not supported (400 dimensions_not_supported) |
The reply is {"object": "list", "data": [{"object": "embedding", "index", "embedding"}], "model", "usage": {"prompt_tokens", "total_tokens"}}. A missing or
unreachable daemon is 502 backend_unavailable; a model Ollama does not have is 404 model_not_found.
Errors¶
All errors are JSON: {"error": {"message", "type", "param", "code"}}.
| Status | code |
Meaning |
|---|---|---|
| 400 | (none) | Invalid JSON, missing/invalid messages, model, or a numeric field |
| 400 | embeddings_not_supported / dimensions_not_supported |
/v1/embeddings with an ONNX model / with dimensions |
| 400 | tools_not_supported |
tools sent to an ONNX model whose chat template cannot take them, or without jinja2 installed |
| 400 | ambiguous_model |
The name matches several models; the message lists them |
| 400 | context_length_exceeded |
The prompt fills the model's context window (ONNX models with a known context_length) |
| 401 | invalid_api_key |
Missing or wrong bearer token |
| 403 | host_not_allowed |
Non-loopback Host header on a loopback bind |
| 404 | model_not_found / not_found |
Unknown model / unknown route |
| 413 | body_too_large |
Body over 10 MB |
| 503 | server_busy |
The model stayed busy with other requests longer than --queue-timeout; carries Retry-After: 30 |
| 500 | model_load_failed |
The ONNX model could not be loaded, for example --device cuda with a broken CUDA setup |
| 502 | backend_unavailable |
Ollama is unreachable or failed |
Concurrency¶
One ONNX model is resident at a time, and generation is serialized behind a lock: concurrent requests queue, for at most --queue-timeout seconds (default 300), after which they get 503. Requesting a
different ONNX model unloads the current one and loads the new one, which takes seconds. Ollama requests are not serialized by
Prism.
Authentication and CORS¶
See Security. With --api-key, every endpoint except /health requires Authorization: Bearer <key>.