Reasoning Effort Control
Mojentic supports reasoning effort control, allowing you to enable extended thinking capabilities in supported LLM models.
Overview
The reasoningEffort field on CompletionConfig controls how much computational effort the model spends on reasoning before responding:
'disabled': Explicitly disable reasoning where supported'enabled': Enable reasoning without selecting a named level'xhigh': Select the provider's extra-high named level'low': Quick responses with minimal reasoning'medium': Balanced reasoning effort'high': Extended thinking for complex problems
Usage
import { LlmBroker, OllamaGateway, Message, isOk, CompletionConfig } from 'mojentic';
const gateway = new OllamaGateway();
const broker = new LlmBroker('qwen3:32b', gateway);
const config: CompletionConfig = {
temperature: 0.3,
reasoningEffort: 'high'
};
const messages = [Message.user('Explain quantum entanglement')];
const result = await broker.generate(messages, undefined, config);
if (isOk(result)) {
console.log(result.value);
}Explicit model profiles
Select a profile through the shared CompletionConfig. Profiles are never inferred from model names: an arbitrary alias can select Qwen3.8, while a Qwen-looking name without a profile keeps existing provider behavior.
import { QWEN3_8_REASONING_PROFILE, validateReasoningProfile } from 'mojentic';
const config: CompletionConfig = {
reasoningProfile: QWEN3_8_REASONING_PROFILE,
reasoningEffort: 'xhigh',
};
const result = await new LlmBroker('my-local-alias', gateway)
.generate(messages, undefined, config);
const validation = validateReasoningProfile(QWEN3_8_REASONING_PROFILE, 'low');Qwen3.8 accepts disabled, low, medium, xhigh and rejects enabled and high with ValidationError before HTTP. Omitted effort stays unknown and adds no control. The public ReasoningProfile interface also supports caller-defined name, readonly supportedEfforts, and supportsReasoningHistory metadata. Profiles and capability metadata never enter request bodies. Accepted efforts keep the exact provider mappings below across ordinary, structured, legacy streaming and event streaming requests, with recovery off or on. Broker structured configuration and tool continuations preserve the selected profile.
OpenAI's provider constraints still apply: even a profile permitting every effort cannot enable disabled, enabled, or xhigh. Its existing registry adaptation for omitted effort and low/medium/high remains unchanged.
QWEN3_8_REASONING_PROFILE.supportsReasoningHistory is true. Profiles do not activate retention: set retainReasoning: true explicitly. A selected profile with supportsReasoningHistory: false returns ValidationError (field: retainReasoning) before HTTP when retention is requested. validateReasoningProfile(profile, effort, true) provides the same validation.
Provider-Specific Behavior
Ollama Gateway
Ollama sends think: false for disabled, think: true for enabled, and the exact string for low, medium, high or xhigh. Omission sends no think field. The model's internal reasoning trace is captured in the thinking field of the gateway response.
OpenAI Gateway
For OpenAI reasoning models (o1, o3 series), the reasoningEffort value maps directly to the API's reasoning_effort parameter:
'low'→"low"'medium'→"medium"'high'→"high"
For non-reasoning models, Low/Medium/High are ignored with a warning, preserving the existing model-registry behavior. Omission sends no effort field.
Under the 3.0.0 rule, disabled, enabled and xhigh return Err(ValidationError) with field: 'reasoningEffort' before HTTP dispatch on every model, including recovery-enabled requests. Legacy streams yield one error result; event streams yield one request_failed error with the ValidationError in detail. These controls are never silently omitted or rewritten on OpenAI. The different 3.1 mapping is outside this release.
import { OpenAIGateway } from 'mojentic';
const broker = new LlmBroker('o1-preview', new OpenAIGateway());
const config: CompletionConfig = {
reasoningEffort: 'medium'
};
const result = await broker.generate(messages, undefined, config);oMLX Gateway
oMLX sends top-level enable_thinking: false for disabled and enable_thinking: true for enabled. Named levels use top-level reasoning_effort with the exact string. Boolean controls send no named effort; named controls send no boolean. Omission sends neither field. These mappings apply to ordinary, structured and streaming requests, with recovery on or off. Provider/model support determines whether the request is accepted.
With retainReasoning: true, oMLX also requests chat_template_kwargs: { preserve_thinking: true }. Forced server settings can override this request. Effort and retention are separate controls.
Native history and migration
Retention defaults to off. Enable it for Ollama or oMLX when the selected model accepts native reasoning history:
const config: CompletionConfig = {
reasoningProfile: QWEN3_8_REASONING_PROFILE,
reasoningEffort: 'low',
retainReasoning: true,
};
const history = [Message.user('What is the weather?')];
// supply your LlmTool[] as tools
const result = await broker.generate(history, tools, config);
// generateStream(history, config, tools) uses the same controls.The broker stores completed assistant tool turns and ordered tool results in history, including native reasoning in LlmMessage.thinking. It preserves those completed turns if a later request fails. The final visible answer is returned separately; callers managing outer turns append their final message. With retention off, the broker omits native reasoning from new tool history; existing recovery-specific preservation of completed content/tools is unchanged.
For caller-managed native history, use Message.assistant(visibleContent, toolCalls, nativeThinking) or set thinking on an assistant message. Gateways serialize supplied assistant thinking as Ollama thinking or oMLX reasoning_content independently of automatic retention. Keep it out of visible content and tool results. Native fields on user/tool messages are not serialized. Missing reasoning stays absent.
Incomplete, cancelled or failed streams do not create completed native history. Opt-in oMLX legacy streaming rejects malformed frames and tool calls ended with [DONE] without a successful finish reason. Broker streams still yield visible text only. Recovery admission, semantic progress and timing retain their existing rules; retaining reasoning does not authorize retries of partial output.
Structured requests use the same history, profile and preservation controls. OpenAI rejects retainReasoning: true before dispatch on every request route, including recovery, and never sends reasoning_content. Use omission or false for OpenAI; its existing named-effort adaptation remains unchanged.
Streaming with Reasoning Effort
Reasoning effort works with streaming:
const config: CompletionConfig = {
reasoningEffort: 'high'
};
for await (const result of broker.generateStream(messages, config)) {
if (isOk(result)) {
process.stdout.write(result.value);
}
}When to Use
Use higher reasoning effort for:
- Complex mathematical problems
- Multi-step logical reasoning
- Code generation requiring architectural decisions
- Tasks requiring careful consideration of trade-offs
Use lower reasoning effort (or omit) for:
- Simple factual questions
- Quick classifications
- Routine formatting tasks
- When response speed is critical
Thinking Traces
When using Ollama with reasoning effort enabled, the model's thinking process is captured in the thinking field of GatewayResponse:
// Access thinking traces at the gateway level
const response = await gateway.generate(model, messages, config);
if (isOk(response) && response.value.thinking) {
console.log('Model reasoning:', response.value.thinking);
}