API contract · Chat

Chat completions without the hidden assumptions.

POST /v1/chat/completions is the primary interoperability surface. The JSON shape follows OpenAI conventions, while AFM exposes extra local controls and runtime measurements where they are useful.

A complete structured request

curl http://127.0.0.1:9999/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -H 'X-Request-ID: agent-plan-42' \
  -d '{
    "model": "mlx-community/Qwen3.5-4B-4bit",
    "messages": [
      {"role": "system", "content": "Answer as a precise local assistant."},
      {"role": "user", "content": "Return two deployment risks as JSON."}
    ],
    "temperature": 0.2,
    "max_tokens": 220,
    "seed": 7,
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "risk_list",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "risks": {"type": "array", "items": {"type": "string"}}
          },
          "required": ["risks"],
          "additionalProperties": false
        }
      }
    }
  }'

Accepted request fields

FieldTypeStatusActual behavior
modelstringOptionalRequested model identifier. An MLX single-model server normalizes aliases and serves the active model.
messagesMessage[]RequiredRoles: system, developer, user, assistant, or tool. Content may be text or multimodal parts.
temperaturenumberCompatibleSampling temperature; request value overrides the server default.
top_pnumberCompatibleNucleus sampling threshold; request value overrides the server default.
top_kintegerAFM extensionRestrict sampling to the highest-probability tokens.
min_pnumberAFM extensionRemove candidates below a probability floor relative to the leading token.
max_tokensintegerCompatiblePrimary output-token limit.
max_completion_tokensintegerCompatible aliasUsed only when max_tokens is absent.
repetition_penaltynumberAFM extensionPrimary repetition penalty.
repeat_penaltynumberAccepted aliasUsed only when repetition_penalty is absent.
presence_penaltynumberCompatibleApplied by the MLX generation path.
frequency_penaltynumberParsed onlyAccepted by the decoder but currently ignored.
seedintegerCompatibleReproducibility control, subject to backend and hardware determinism.
stopstring[]CompatibleMerged with CLI --stop values and deduplicated.
streambooleanCompatibleRequests SSE when the server was launched with streaming enabled.
stream_options.include_usagebooleanCompatibleAFM defaults this to true; false suppresses the separate final usage chunk.
logprobsbooleanCompatibleReturn token log probabilities.
top_logprobsintegerCompatibleBounded by --max-logprobs; the default server ceiling is 20.
toolsTool[]CompatibleFunction tools with optional JSON Schema parameters and strict flag.
tool_choicestring | objectCompatibleauto, none, required, or one named function.
parallel_tool_callsbooleanCompatiblefalse limits the assistant turn to at most one emitted tool call.
response_formatobjectCompatibletext, json_object, or json_schema; request value overrides --guided-json.
chat_template_kwargsobjectAFM extensionValues supplied to the model's Jinja chat template, such as enable_thinking.
reasoning_effortstringAFM extensionDeepSeek control: low, high, or max; normalized into chat_template_kwargs.
userstringAcceptedDecoded for client compatibility; it does not select a local user or security boundary.

Precedence and normalization

InputsWinner or merge rule
max_tokens vs max_completion_tokensmax_tokens wins. The second field is an accepted fallback.
repetition_penalty vs repeat_penaltyrepetition_penalty wins.
Request sampling vs CLI defaultsA supplied request value wins; omitted fields inherit the server configuration or backend default.
Request response_format vs --guided-jsonThe request format wins. The server default is used only when the request omits the field.
Request stop vs CLI --stopThe sequences are merged in CLI-then-request order and deduplicated.
reasoning_effort and chat_template_kwargsThe top-level effort is inserted into the template object. A model encoder's explicit enable_thinking=false remains authoritative.

Message contract

Roles may be system, developer, user, assistant, or tool. A developer role is mapped to system behavior by AFM's compatibility layer. content may be a string, null for a tool-call assistant message, or an array of parts:

  • {"type":"text","text":"…"}
  • {"type":"image_url","image_url":{"url":"data:image/png;base64,…","detail":"high"}}
  • {"type":"input_audio","input_audio":{"data":"…","format":"wav","language":"en-US"}}

Multi-turn tool conversations send assistant tool_calls back verbatim, followed by one or more tool messages whose tool_call_id matches the call.

Non-streaming response

{
  "id": "chatcmpl-9a12bc34",
  "object": "chat.completion",
  "created": 1786128300,
  "model": "mlx-community/Qwen3.5-4B-4bit",
  "system_fingerprint": "afm_mlx__mlx-community__Qwen3.5-4B-4bit",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "{\"risks\":[\"...\",\"...\"]}"
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 41,
    "completion_tokens": 22,
    "total_tokens": 63,
    "completion_tokens_per_second": 38.4,
    "peak_memory_gib": 3.71
  }
}

choices[0].message.content is always present, including as JSON null when the assistant returns tool calls. Reasoning models can add reasoning_content. Tool turns add tool_calls and finish with finish_reason: "tool_calls".

Usage and AFM response extensions

FieldMeaning
usage.prompt_tokens_details.cached_tokensPrompt tokens served from the prefix cache, when available.
usage.prompt_time / completion_time / total_timeRequest timing in seconds.
usage.prompt_tokens_per_second / completion_tokens_per_secondMeasured phase throughput.
usage.peak_memory_gibRequest-scoped MLX peak memory estimate.
timingsllama.cpp-style prompt and prediction counts and milliseconds.
afm_profile / afm_profile_extendedOptional MLX GPU, memory, and bandwidth measurements requested with X-AFM-Profile.

Backend boundaries

The Foundation backend accepts the shared request shape but has a narrower generation-control surface. MLX is the path for top_k, min_p, logprobs, model-native tools, grammar enforcement, cache statistics, and GPU profiling. Accepted JSON is therefore not the same thing as identical effect across backends.