Skip to content

0.83.1: reasoning off picks the lowest accepted level and survives format conversion - #376

Merged
fylorn merged 1 commit into
mainfrom
fix/reasoning-off
Oct 25, 2026
Merged

fylorn merged 1 commit into
mainfrom
fix/reasoning-off

Conversation

@fylorn

@fylorn fylorn commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Fixes two cases where set.reasoning: off left the model reasoning, and bumps the version to 0.83.1.

Refused none made reasoning go up

When an upstream rejected the none written by off (models that accept only minimal/low/medium/high), the resend dropped the effort, so the model reasoned at its default (usually medium), and that was remembered for 7 days.

  • New tw_dialect::think::none_fix(status, error_body) -> Option<EffortFix>: when none_rejection holds, Use(lowest level listed in the error), else Drop. none_rejection is unchanged.
  • hop.rs effort_refused uses it.

off did nothing across a format conversion

Converted hops applied off to the intermediate representation by removing the reasoning config, so an Anthropic or Gemini client routed to an OpenAI Chat/Responses upstream sent no effort at all and the upstream reasoned at its default, while the attempt chain showed sent: null.

  • translate::apply_set takes the target format and reports whether it turned reasoning off. After encoding, translate::reasoning_off writes none on the converted body for Chat (reasoning_effort) and Responses (reasoning.effort), via the new tw_dialect::params::write_reasoning_none, which also fills in the field when the converted body carries no effort (an adaptive Claude request without output_config.effort).
  • Gemini targets: reasoning is marked disabled instead of removed, so the existing encoder rule writes thinkingBudget: 0 on Gemini 2.5 Flash; models that cannot turn thinking off (2.5 Pro, Gemini 3) still get no thinking config, and the conversion now reports reasoning as dropped. Smallest non-zero budgets (2.5 Pro) and Gemini 3 minimal levels are not modelled.
  • Anthropic/Bedrock targets are unchanged (no thinking, no effort).
  • Levels across a conversion keep going through the intermediate representation, so the encoder writes them in the upstream format (including requests with reasoning on and no effort, which a wire-level rewrite would skip).
  • adjust_effort, the effort memory and the resend already read the outbound body, so AttemptView.effort and remembered fixes now reflect the none really sent. Same-format hops and their byte-preserving splice are untouched.

The control-plane protocol stays at 54; no API or TS type changes.

Tests

  • think.rs: none_fix with a list containing minimal → minimal; low/medium/high → low; no list → Drop; not about none → None.
  • params.rs: write_reasoning_none fills in and overwrites the OpenAI fields, leaves other formats alone.
  • tests/effort.rs: refused none resent with low and remembered, Drop without a list; Anthropic client + off → Chat gets reasoning_effort: "none" (also adaptive without an effort, and a refused none there is resent with minimal); → Responses gets reasoning.effort: "none"; Chat client → Anthropic upstream has no thinking; → Gemini 2.5 Flash gets thinkingBudget: 0; a rule level across conversion is written in Chat, Responses and Anthropic format; each attempt-chain effort matches what was sent.

Docs (config.md, config.zh-CN.md) and release-notes/0.83.1.md describe the change.

🤖 Generated with Claude Code

…rmat conversion

- tw-dialect: add think::none_fix. A refused `none` is resent with the
  lowest level the error lists, and without the effort only when it lists
  none; dropping it made the model reason at its default level.
  none_rejection is unchanged.
- tw-dialect: add params::write_reasoning_none, which writes OpenAI's
  `none` even where the body carries no effort yet.
- Converted hops: `set.reasoning: off` now reaches the upstream. OpenAI
  Chat and Responses targets get `none` written on the converted body;
  Gemini targets get reasoning marked as disabled, so the encoder writes
  a zero thinking budget on 2.5 Flash and reports it dropped elsewhere.
  The attempt chain and the effort memory read what was sent.
- Docs for set.reasoning and the resend; version 0.83.1 with release notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn
fylorn merged commit 0a34ac1 into main Oct 25, 2026
7 checks passed
@fylorn
fylorn deleted the fix/reasoning-off branch October 25, 2026 08:40
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant