LexiEngine native HTTP API
LexiEngine serves one resident text model through
/v1/chat/completions, /v1/completions, and
/v1/responses. GET /v1/models lists its
declared HTTP names. Declaring names does not load additional models,
change the weights, or permit arbitrary client-supplied names.
Server launch options
lexi-engine serve -m /models/model.gguf \
--alias main --alias client-main \
--chat-template-file /models/chat.jinja \
--chat-template-kwargs '{"reasoning_effort":"xhigh","enable_thinking":true}' \
--reasoning-format auto \
--temp 1.0 --top-p 0.95 --top-k 20 --min-p 0 --n-predict 2048
--alias NAME is repeatable. The first name is the
resident model's public ID; additional names are accepted as exact
matches and listed separately by GET /v1/models. Omitting
model from a supported request selects the resident model.
Names that were not declared remain a 400 with
requested model is not loaded. The default name, if no
alias is given, is the model identity normally reported by the loader.
Aliases are not a second model and cannot be used to bypass
authorization.
--chat-template-file PATH overrides the
tokenizer.chat_template embedded in the GGUF. The file must
be valid UTF-8, nonempty, no more than 256 KiB, and syntactically
parseable by the sandboxed template renderer. The server validates it
before listening and refuses a malformed or unreadable file. This option
affects chat prompt rendering and chat input-token counting; it does not
replace the model tokenizer or weights.
--chat-template-kwargs JSON accepts a JSON object of
server-default template variables. Chat request
chat_template_kwargs overrides matching defaults; reserved
message/BOS/EOS keys and invalid value types remain rejected. For
example, a template that consumes reasoning_effort can
receive "xhigh" from these variables. An arbitrary variable
has an effect only when the chosen template actually uses it. There is
no separate mechanism that forces a model to generate hidden
reasoning.
Sampling defaults apply to requests that omit the respective field:
--temp (0–2), --top-p (0–1),
--top-k (0–100000), --min-p (0–1) and
--n-predict (1–131072). Explicit request settings take
precedence, including explicit zero values; request generation limits
and context ceilings are still enforced. Without these switches,
existing server sampling defaults remain unchanged.
Reasoning response format
--reasoning-format auto is the default. For chat output
that opens <think>...</think>, LexiEngine puts
the thought text in choices[0].message.reasoning_content
and the visible answer alone in choices[0].message.content.
Streaming chat emits the corresponding
delta.reasoning_content and delta.content
frames. Delimiters split across generation events are held back so they
do not appear in either field. When generation stops before
</think>, content is empty. The
previously supported thought-channel template also continues to be
separated.
--reasoning-format none disables this output separation
and preserves the raw generated text in content, including
thought delimiters. This is an output-format selection, not a switch to
disable reasoning in the model. To disable the template's thinking mode,
use enable_thinking:false when the template supports
it.
Example nonstreaming response excerpt:
{
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"reasoning_content": "intermediate text",
"content": "final answer"
}
}]
}
The reasoning separation applies to the native chat-completions route; non-chat compatibility endpoints retain their own output contracts. Structured/tool outputs continue to be verified before client-visible structured events are emitted. A template may produce other, currently unsupported reasoning formats; they are not inferred or silently relabeled. None of these options guarantees a particular model capability, performance, or device-backend qualification.
Reasoning level in a request
A chat request may carry reasoning_effort
(none, minimal, low,
medium, high, xhigh). For a model
whose chat template has a thinking switch, any level other than
none turns thinking on and none turns it off.
For a model whose template cannot think, a level other than
none is answered with 501: the engine does not invent
reasoning.
Conversation history
An assistant message in the history may carry the
reasoning_content it was returned with. It is accepted and
left out of the prompt: an earlier turn's thinking is not replayed to
the model. On any other role the field is refused. A tool
message may carry the function's name beside its
tool_call_id; the id is what pairs it with its call.
Tools
When the chat template in use renders tools itself (most current
models' templates do), the request's tools, earlier
assistant tool_calls and tool results are
given to the template in its own terms, so the model sees its trained
format. Calls are read in the JSON form
(<tool_call>{"name":...,"arguments":{...}}</tool_call>)
and in the function form
(<tool_call><function=NAME><parameter=KEY>VALUE</parameter></function></tool_call>);
in the function form a value is text when the tool's schema says the
parameter is a string and is otherwise read as JSON when it is JSON.
With a template that has no tool support, and for a request that forces
one named tool, the engine uses its own tool prompt.
Every call is verified against its tool's schema before it is
returned. Output that only looks like a call is returned as the
assistant's text, unless tool_choice required a call, in
which case it is an error.
Schemas. A tool marked "strict": true
must use a closed object schema with all properties required, and is
refused otherwise. A tool that is not strict may use ordinary JSON
Schema: an object without additionalProperties,
default, a nullable anyOf,
pattern, format, numeric and length bounds,
$ref. What the engine cannot enforce while decoding is
widened to the nearest thing it can, never narrowed, with one exception
that follows common practice: an object that lists properties and says
nothing about others accepts only the listed ones. The model is shown
the schema as the client wrote it.
Waiting for a busy slot
--queue-wait SECONDS (default 0) makes a generation
request wait up to that long for a free slot instead of being answered
429 inference_busy at once. A client that sends two
requests at the same moment to a one-slot server then has both answered,
one after the other.