Skip to content

LLM reasoning / thinking

Schema: spec.models[].reasoning on kind: Agent (agent.schema.yaml). Protocol plugin: OpenAI-compatible Chat Completions (OpenAILLMProvider).

Reasoning models (OpenAI o-series / GPT-5, Gemini 2.5, DeepSeek-R1, gpt-oss, Qwen3, …) spend hidden thinking tokens before the visible answer. Those tokens must not land in working memory as if they were the assistant reply. This page is the agent-facing surface for that.


Spec vs contract vs catalog

Spec (spec.models[]) is the static overset: sampling, reasoning, and extra (passthrough for vendor fields we have not named). Contract (LLMProvider.chat_completion) is the per-call overset: stream, stream_options, tool_choice, extra_body, extra_headers, extra_query. Catalog (llm-model-catalog.md) is per-model default / min / max / enum / supported — same shape as context-window tables. Unknown models are not deny-by-default; known models omit fields the backend does not advertise (so Azure does not 400 on nested reasoning).

What the APIs actually expose

These are the knobs vendors document. Named spec fields are the portable overset; anything else goes in spec.models[].extra / extra_body.

Knob OpenAI Chat Completions OpenAI Responses LiteLLM / OpenRouter Ollama Anthropic Gemini
Effort / depth reasoning_effort: none, minimal, low, medium, high, xhigh, max reasoning.effort plus reasoning.mode (standard / pro, GPT-5.6) reasoning.effort (alias reasoning_effort) think: true/false or low/medium/high adaptive thinking; effort mapped by LiteLLM thinkingConfig.thinkingBudget
Thinking-token budget billed inside max_completion_tokens (not a separate field) max_output_tokens reasoning.max_tokens not a token budget thinking.budget_tokens thinkingBudget (0 = off)
Hide CoT from the message hosted models already omit raw CoT opaque reasoning items; optional encrypted_content reasoning.exclude: true (OpenRouter convention) message.thinking is separate; Chat Completions exclude is incomplete thinking blocks includeThoughts
Answer token cap max_completion_tokens on reasoning models (max_tokens is rejected) max_output_tokens either num_predict max_tokens includes thinking maxOutputTokens

What “not surfaced” used to mean

Earlier drafts left three vendor knobs off the spec because they were Responses-only or unofficial on Chat Completions, and we had not live-tested them:

Field Why it looked optional Now
reasoning.mode (standard / pro) GPT-5.6 Responses; may 400 on api.openai.com Chat Completions Named spec field. Catalog-gated. Sent as reasoning.mode.
reasoning.include (e.g. reasoning.encrypted_content) Responses multi-turn encrypted CoT Named spec field. Catalog-gated. Sent as top-level include.
reasoning.think Ollama / vLLM / gpt-oss; LiteLLM’s OpenAI wrapper rejects top-level think Named spec field. Catalog-gated. Ollama keeps think; native vLLM is flattened to chat_template_kwargs.enable_thinking in the JSON body.

Live coverage: Outshift LiteLLM (LITELLM_URL) and GLS vLLM (OUTSHIFT_GLS_API_BASE, default https://vllm.outshift-gls.cisco.com/v1). Tests skip on missing credentials or DNS rather than fail CI.

Per-model defaults, maxima, and whether a field is supported: llm-model-catalog.md. Sampling overset (temperature, top_p, min_p, …): llm-sampling.schema.yaml.


Spec surface (spec.models[])

models:
  - id: main
    model: gpt-5
    kind: openai
    temperature: 0.7
    max_tokens: 2000          # answer budget (see mapping below)
    reasoning:
      effort: low             # none | disable | minimal | low | medium | high | xhigh | max
      budget_tokens: 1024     # optional thinking-token cap
      exclude: true           # default; omit CoT from the assistant message
      mode: standard          # optional; Responses / LiteLLM
      think: true             # optional; Ollama / vLLM
      include:                # optional; Responses-style extras
        - reasoning.encrypted_content

reasoning_effort: low remains a deprecated alias for reasoning.effort. If both are set, the nested object wins.

Field Default Effect
reasoning.effort omitted Sent as Chat Completions reasoning_effort. none / disable turn thinking off where the backend allows it (GPT-5.1+, Ollama via LiteLLM). gpt-oss cannot fully disable thinking — use a low effort and exclude.
reasoning.budget_tokens omitted Sent as reasoning.max_tokens (OpenRouter / Anthropic-style). Independent of max_tokens. 0 is “no thinking budget” on Gemini-via-LiteLLM.
reasoning.exclude true MAS strips reasoning / reasoning_content / thinking fields and <think> / <thinking> blocks from the assistant message (and from streamed content deltas). When the field is present in the spec, the plugin also sends reasoning.exclude for proxies that implement the OpenRouter convention. Official OpenAI Chat Completions may reject that extension, so it is not sent unless the spec set exclude explicitly.
reasoning.mode omitted Sent as reasoning.mode (standard / pro). Catalog-gated.
reasoning.think omitted Ollama: top-level think. Native vLLM: flattened chat_template_kwargs.enable_thinking (string values also set thinking_level). Catalog-gated. LiteLLM OpenAI-compatible HTTP often rejects both; the live think test skips with that error instead of retrying without think.
reasoning.include omitted Sent as include (e.g. reasoning.encrypted_content).

Token fields

When effort is set and is not none/disable and the catalog model is not vLLM/Ollama, the OpenAI plugin sends max_completion_tokens (the o-series / GPT-5 Chat Completions field) and omits max_tokens, which those models reject. vLLM and Ollama keep max_tokens. The value is still spec.models[].max_tokens — that is the answer budget. Hidden reasoning is billed inside it on OpenAI; budget_tokens is the extra cap for backends that expose a separate thinking budget. An explicit max_completion_tokens wins over this mapping; the request never carries both fields, and carries neither when no limit is configured. Defaults, ceilings, and on_truncation: agent.md — Output-token limits.

reasoning_effort is sent as a top-level Chat Completions field. Nested reasoning is only used for budget_tokens / exclude / mode — effort is not duplicated inside it (strict OpenAI/Azure Chat Completions 400 on a nested reasoning object that they do not expect).


Why cache replay is unrelated

Offline CI replays a stored assistant message after sanitization. Configure reasoning on the live protocol plugin, then record.


Example

Keep thinking cheap and never show CoT:

spec:
  models:
    - model: gpt-5-mini
      kind: openai
      max_tokens: 1500
      reasoning:
        effort: low
        exclude: true

Cap Gemini-style thinking tokens through an OpenAI-compatible proxy:

spec:
  models:
    - model: gemini-2.5-pro
      kind: openai
      max_tokens: 2048
      reasoning:
        budget_tokens: 512
        exclude: true

Overlays

Agent overlays may patch spec.models[] (deep-merge by id, then model, then main). Connection fields (api_base, api_key_env) stay infra-only.

kind: Overlay
spec:
  target:
    kind: Agent
  patch:
    models:
      - id: main
        reasoning:
          effort: low
          budget_tokens: 512
          exclude: true
          think: true

Deprecated patch.llm.reasoning still merges onto spec.llm when models[] has no reasoning block.