Skip to content

fix: a thinking model is handed its earlier reasoning on tool-calling requests - #512

Merged
Yiannis128 merged 2 commits into
masterfrom
feat/reasoning-replay
Nov 10, 2026
Merged

Yiannis128 merged 2 commits into
masterfrom
feat/reasoning-replay

Conversation

@Yiannis128

@Yiannis128 Yiannis128 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Why

OpenAiClient read reasoning_content / reasoning off the stream but wrote an assistant message back as content + tool_calls only. DeepSeek's thinking-mode guide says that on a request carrying tools, the reasoning_content of all previous turns must be passed back. The official API answers 400 without it. Without tools it is ignored.

Through Ollama Cloud nothing errored. Ollama's compat layer reads earlier thinking from reasoning and drops unknown fields, so DeepSeek V4 Pro ran each of ~1,300 tool-calling steps without the reasoning of the steps before.

What

  • OpenAiClient.reasoningFieldOf(model, tools:) names the field an assistant message's reasoning goes back in. It is told whether the request carries tools, and null sends nothing. The tools-only rule is DeepSeek's, so the provider answers it rather than the encoder.
  • ModelInfo.interleavedField keeps models.dev's interleaved.field (and the catalog cache writes it back). OpenAiCompatibleProvider.replayedReasoningField reads it: DeepSeek → reasoning_content. Z.ai and other described hosts get it the same way. OpenRouter's structured reasoning_details is excluded.
  • OllamaProvider and OllamaCloudProvider share one mixin, OllamaCompat, which answers reasoning beside tools. models.dev has no interleaved on Ollama Cloud's deepseek-v4-pro, and reasoning_content would be dropped there anyway.
  • OpenAI's models name no field, and OpenAiPlatformClient takes none.
  • The projection already keeps reasoning for every reply, live and reloaded. A test now pins that an earlier turn's reasoning survives the rebuild.
  • "Reaching a provider" in lib/data/builtin/llm/AGENTS.md says all of this.

Tests

  • openai_client_test: replayed in the named field beside tools; not sent for an unnamed model, without tools, or by a client with no field (OpenAI's included).
  • providers_test: the catalog gate, the cache round trip, the reasoning_details exclusion, Ollama's field, and none for OpenAI.
  • projection_test: an earlier turn's thinking is rebuilt.

… requests

DeepSeek's thinking mode needs every earlier turn's reasoning_content back on
a request carrying tools, and answers 400 without it. The OpenAI-wire client
read reasoning from the stream but never wrote it back. The field is named per
model by models.dev's interleaved.field, and Ollama (local and Cloud), which
reads it as 'reasoning' and drops the other name, declares it on its class.
OpenAI is sent none.
@Yiannis128
Yiannis128 merged commit 42297ec into master Nov 10, 2026
2 checks passed
@Yiannis128
Yiannis128 deleted the feat/reasoning-replay branch November 10, 2026 16:07
Yiannis128 added a commit that referenced this pull request Nov 10, 2026
Merge master (#512, #515, #516) and read the nudge's grantability from
refuseFolder, the work-folders check add_directory answers with.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant