#7537·ironclaw

feat(llm): generic per-request thinking/effort control (provider-native mapping incl. DeepSeek chat_template_kwargs)

Author: serrrfiratCreated Aug 12, 2026Updated Sep 18, 2026
Labelsenhancementscope: llm

Summary

Add a generic thinking/effort control to the LLM request path — a per-request (and per-model default) thinking level that each provider adapter maps to its native parameter. DeepSeek V4 Flash via NEAR AI is the trigger case (0731 checkpoint got verbose), but the control must be provider-agnostic, matching the thinking vocabulary OpenClaw already exposes (off/minimal/low/medium/high/xhigh/adaptive/max).

Trigger

NEAR AI upgraded deepseek-ai/DeepSeek-V4-Flash to the 0731 checkpoint. Users report responses that are often very chatty, especially mid-conversation pieces (each interim model call in the tool loop streams as its own visible message). Proposed test configs need client-side levers that do not exist yet: chat_template_kwargs: {"thinking": false} for normal chats, and reasoning-effort levels for deep agentic work.

Current state (verified in code)

  • No generic control exists. CompletionRequest/ToolCompletionRequest (crates/domains/ironclaw_llm/src/provider.rs:323-341, 575-590) have no thinking/effort field; the loop host never sets temperature/max_tokens either, so generation behavior is server defaults for every provider that lacks a hardcoded shim.
  • Provider-specific, hardcoded, non-user-controlled today:
    • Gemini: thinkingConfig auto-injected at thinkingLevel: HIGH / thinkingBudget: 8192 when the model name matches (gemini_oauth.rs:1646-1655).
    • Anthropic: thinking_for_request auto-enables adaptive/enabled with a fixed 1024 budget (anthropic_thinking.rs), disabled when temperature or tools are set.
    • OpenAI Codex: {"effort": "medium"} hardcoded (responses_reasoning.rs:5-7).
    • DeepSeek/nearai: nothing sent — no chat_template_kwargs, no reasoning_effort (nearai_chat.rs:1111-1127).
  • Reasoning separation works: reasoning_content/reasoning parse into CompletionResponse.reasoning, surfaced as dedicated UI reasoning blocks via safe_reasoning_deltas. Caveat: when content is null/empty the provider substitutes the reasoning text as the answer (nearai_chat.rs:780-793, 852-856, 952-956, 1025-1033), which makes a verbose default user-visible.
  • No thinking/effort picker in the WebUI; FEATURE_PARITY.md pins per-level thinking control as missing for IronClaw (OpenClaw has off/minimal/low/medium/high/xhigh/adaptive/max).

Proposed scope

  1. Add thinking_effort (or equivalent) to CompletionRequest/ToolCompletionRequest: off | minimal | low | medium | high | xhigh | adaptive | max (or the subset each provider supports), defaulting to provider behavior when unset.
  2. Provider adapters map it to native params:
    • nearai / OpenAI-compat (DeepSeek V4, vLLM/SGLang): chat_template_kwargs: {"thinking": false} and reasoning_effort / chat_template_kwargs.thinking levels
    • Gemini: thinkingConfig.thinkingLevel / thinkingBudget
    • Anthropic: thinking: {type: adaptive|enabled, budget_tokens}
    • OpenAI Codex: reasoning.effort (replace hardcoded medium)
  3. Thread a per-model thinkingDefault override through config (FEATURE_PARITY.md already tracks this as missing), plus a per-request override (UI effort picker is net-new).
  4. Revisit the content: null -> reasoning_content fallback gating per provider/model while touching this surface.

Related

  • #3673 — same file: openai_compatible/nearai drops reasoning_content on outgoing requests, breaks DeepSeek v4-pro multi-turn tool calls
  • #3436 — DeepSeek reasoning_content echo requirement (400 in thinking mode)
  • #3327 — surface and persist LLM reasoning content
  • #84 — umbrella: agent advanced features incl. thinking modes