Local requests and responses
- In one line
- The request is rendered through the model's chat template; the stream carries
Text,ThinkingandToolCallblocks with exact usage counts. - You need
- The
provider-localfeature and a loadedLocalModel. - Read this if
- You need to know how a request field reaches the template, or what the stream and the usage numbers mean.
Request translation
UnifiedRequest fields map to the model's chat template and the sampler:
messages:SystemandUsermessages carry theirTextblocks joined with a newline ascontent. AnAssistantmessage adds itsThinkingblocks, concatenated, asreasoning_content, and itsToolCallblocks astool_callsentries{id, type: "function", function: {name, arguments}}. AToolmessage carries thetool_call_id(from the message, else from its firstToolResult), an optionalname, and itsToolResultoutputs joined with a newline ascontent.- Tool call
argumentsstay JSON objects. An object,nullor a string holding an object is accepted; anything else fails the request. toolsreaches the template as{"type": "function", "function": {name, description, parameters}}withparametersfrominput_schema. The template sees thetoolsvariable only when the request declares at least one tool.temperatureandmax_tokensapply per request.top_k,top_pandseedcome fromSamplingConfig.thinkingsets theenable_thinkingandthinkingtemplate variables; see Thinking.- A message with an
ImageBase64block is rejected: the engine is text only. - The prompt is rendered with
add_generation_prompttrue. Template variables and helpers are listed on Local models and formats.
Response stream
- The stream yields
Result<MessageContentBlock, CucaError>items:Text,ThinkingandToolCallblocks. AToolCallid is generated by the engine ascall_plus 16 hexadecimal digits. - How reasoning and tool calls are recognized depends on the model's template; see Reasoning and Tool calls.
- A malformed tool call becomes a
CucaError::Provideritem with the textmalformed tool call: <body>, and the stream continues. - Generation stops at an end-of-generation token, at
max_tokens, or when the context is full. - Dropping the stream stops generation. The work already done stays in the K/V cache and is reusable.
Token usage
- Usage is exact, not estimated:
prompt_tokensequals the prefix-reuse read plus write counts, and amax_tokenscap is met to the token. prompt_cache_usagereportsread_tokens(reused from the previous prompt) andwrite_tokens(prefilled). The engine keeps the longest common prefix of the cached ids and the new prompt, always leaving the last prompt token to prefill.- On the CPU the output of a request that reuses a prefix equals the output of a cold request, bit for bit.
reasoning_tokensis set only when the request enabled thinking. It counts the tokens inside reasoning blocks, markers included.
Read the usage per call with CucaClient::generate_stream_with_response.
Thinking
- Thinking is on or off. A request whose
thinkingis enabled sets the template variablesenable_thinkingandthinkingto true; its effort level is ignored. A disabled or missingthinkingsets both to false.preserve_thinkingis always false. - Reasoning arrives as
Thinkingblocks. If the rendered prompt ends inside an open reasoning block, generation starts in thinking. - A model whose template reads neither variable, such as the DeepSeek-R1 distills, reasons on every request.
LocalModel::capabilities()reports what the model supports, derived from its own chat template and output conventions:reasoning(it writes a reasoning block),can_disable_thinking(the template readsenable_thinking) andtools(it has a tool syntax the output parser understands).