Chat completions
Accepts messages (role + content), model, stream, max_tokens,
temperature (0..2), top_p (0..1), presence_penalty and
frequency_penalty (-2..2), and a signed 64-bit seed.
-
top_kis an Ignis extension, not an OpenAI Chat Completions parameter: 0..20, where 0 selects the 20-candidate sampler cap during stochastic sampling. -
classis an Ignis extension: the request’s lane tag,interactive(default) oragent. The same class can be stated as an@agentsuffix on the model name. -
thinking_budgetis an Ignis extension: the reasoning tokens this request may spend before its close is forced. Absent takes--model-thinking-budget,0means no budget, andreasoning_effort: "max"ignores it and runs with no budget. The budget always leaves 2,048 tokens ofmax_tokensfor the answer. A forced close is reported asthinking_budget_forced_aton the choice (on the finish chunk when streaming; top-level on/v1/responses): the reasoning tokens emitted when it began. The field is absent when the close was not forced. -
A turn that ends inside its reasoning answers. With thinking on, an end of turn the model draws while its reasoning block is still open becomes the block’s
</think>, and the model goes on to answer — usually with the call or the text it was writing in its reasoning, now on the content channel. Such a turn used to come back as reasoning only, with no content and no tool call. The same holds on/v1/responses. The request log’signis.request.doneline marks it withreasoning_redirected_at, the output index of that</think>. With thinking off, orignore_eos, the end of turn ends the turn as it always has. -
A loop in the reasoning is taken back (
--model-loop-guard,rewindby default, in the table above).usage.completion_tokens_details.rewound_tokens(usage.output_tokens_details.rewound_tokenson/v1/responses) counts the tokens taken back, whichcompletion_tokens(output_tokens) still counts; it is absent when nothing was. On/v1/responses,include: ["reasoning.encrypted_content"]gives each reasoning item anencrypted_contentwith the reasoning as the model saw it, and a reasoning input item that carries one is rendered from it in place of its text. -
Values outside the ranges above are rejected with
invalid_sampling_parameter; they are never silently clamped. -
Absent sampling fields take the Qwen3.8 model card’s values for the request’s mode, each field on its own (spec server/12):
mode temperaturetop_ptop_kpresence_penaltyfrequency_penaltythinking 1.0 0.95 20 0 0 enable_thinking: false0.7 0.80 20 1.5 0 An absent
seedis a fresh one per request, so two identical requests are two independent samples; a sentseedis kept. Sampling runs on the GPU, and its RNG is counter-based, keyed by the seed, the sequence’s own position and the draw’s purpose (prefill or decode): what a request generates depends on its own seed alone, never on which requests shared its decode round, and no RNG state travels with a sequence that is moved off the device and back. Greedy istemperature: 0: the fields such a request does not send are neutral, and because the leaf’s greedy branch does not read stochastic filters or penalties, a non-neutraltop_p,top_k,presence_penaltyorfrequency_penaltysent with it is rejected rather than ignored. -
A request that sends no
max_tokens(normax_completion_tokens, normax_output_tokenson/v1/responses) generates at most--model-default-max-tokenstokens, its reasoning included. The flag is 38,912 unless you set it (IGNIS_MODEL_DEFAULT_MAX_TOKENS, makeDEFAULT_MAX_TOKENS). Such a request ends withfinish_reason: "length"when it gets there (incompletewithmax_output_tokenson/v1/responses). Before, it could generate to the end of the context. Sendmax_tokensfor more, or start the server with--model-default-max-tokens 0for the old behaviour. -
max_completion_tokensismax_tokensunder OpenAI’s current name — the one the current SDKs send. Both with different values is a 400. -
stop(a string, or 1 to 4 non-empty strings) ends the answer before the first sequence to appear in its content, withfinish_reason: "stop". The sequence is never emitted, streaming included; it is never matched in the reasoning, nor inside a tool call. -
tool_choicetakes"auto"(the default),"none","required", and a named function ({"type": "function", "function": {"name": "..."}}, or{"type": "function", "name": "..."}on the responses API). The last two force the call’s opening —<tool_call>and the function tag, with the name when one is named — and the model writes the arguments. With thinking on (the default) the opening is forced right after the reasoning block closes, so the model still thinks first; withenable_thinking: falseit is the first thing generated. After a</think>the model wrote itself, the first forced token is the opening’s second, its joiner (the\nafter<tool_call>): the token before it was drawn unseen, and may itself have opened the call. The call comes back as any other call does, withfinish_reason: "tool_calls". Naming a function that is not intools,"required"with no tools, and amax_tokensshorter than the opening are a 400; so is a tokenizer that cannot force the opening (tool_choice_unsupported— with thinking on it needs</think>and<tool_call>to be single tokens, andenable_thinking: falseis the way out). With thinking on the reasoning spends the samemax_tokens: a block that never closes endslengthwith no call, which the thinking budget’s answer reserve prevents. -
Non-streaming returns
choices[].message.contentplususage; streaming emitschat.completion.chunkSSE frames (token deltas, a finalfinish_reasonchunk, then[DONE]).usage.prompt_tokens_details.cached_tokensis the prompt the request resumed from retained state instead of prefilling (0 when none). -
Every other OpenAI field is accepted as inert when its value asks for what the server already does (
n: 1,store: false,logprobs: false,response_format: {"type": "text"},metadata,user, …) and refused with a 400unsupported_valuenaming it inparamotherwise. The full table is in the API reference at/v1/docs/; a field outside it is ignored.
