DeepSeek Multi-Turn Reasoning: Managing reasoning_content

Master multi-turn reasoning with DeepSeek. The critical distinction between optional passback for chat turns and mandatory passback for tool-call loops. Avoid 400 errors and maintain reasoning coherence across conversations.

June 11, 2026
DeepSeekMulti-TurnReasoningTool CallsPrompt Engineering

Multi-turn reasoning is where DeepSeek's thinking mode most sharply differs from other models. The reasoning_content from each turn doesn't just sit there — you must actively decide whether to pass it back to the API in subsequent turns. Get this decision wrong and you'll either get 400 errors or degraded reasoning quality. This page covers the rules, the patterns, and the edge cases.

The Two Passback Rules

Rule 1: Non-Tool-Call Turns — Optional Passback

Between two user messages where the model did NOT perform a tool call, the intermediate assistant's reasoning_content does not need to be passed back. If you do pass it, it will be ignored by the API.

Turn 1: User asks question → Model reasons → Model answers (no tool call)
Turn 2: reasoning_content from Turn 1 is OPTIONAL — pass it or don't, either works

Rule 2: Tool-Call Turns — Mandatory Passback

If the model performed a tool call, the reasoning_content MUST be passed back to the API in ALL subsequent turns of that interaction. Failing to do so returns a 400 error.

Turn 1a: User asks → Model reasons → Model calls tool (e.g., get_weather)
Turn 1b: reasoning_content MUST be in the message → Tool result returned
Turn 1c: reasoning_content from 1a MUST be in the message → Model reasons more
Turn 2:  reasoning_content from all Turn 1 sub-turns MUST be passed back

Implementation Patterns

Non-Tool-Call Multi-Turn (Simple)

messages = [{"role": "user", "content": "What is 9.11 vs 9.8?"}]

# Turn 1
response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}}
)

# Append assistant message (reasoning_content is included but will be ignored)
messages.append(response.choices[0].message)

# Turn 2 — new question, independent context
messages.append({"role": "user", "content": "How many R's in 'strawberry'?"})

response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}}
)

Tool-Call Multi-Turn (Mandatory Passback)

messages = [{"role": "user", "content": "What's the weather in Tokyo tomorrow?"}]

# Sub-turn 1: Model reasons and requests a tool call
response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    tools=tools,
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}}
)

message = response.choices[0].message

# CRITICAL: Append the FULL message object (includes reasoning_content + tool_calls)
messages.append(message)

# Execute the tool and append result
tool_result = execute_tool(message.tool_calls[0])
messages.append({
    "role": "tool",
    "tool_call_id": message.tool_calls[0].id,
    "content": tool_result
})

# Sub-turn 2: Model reasons more with tool result → final answer
response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    tools=tools,
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}}
)

# For Turn 2 (new user message), ALL previous reasoning_content must be in context
messages.append(response.choices[0].message)
messages.append({"role": "user", "content": "What about Osaka?"})

The Simplest Pattern: Append the Full Message

The safest approach is to always append the full response.choices[0].message object. It contains content, reasoning_content, and tool_calls (if any). This handles both cases correctly:

# This single line handles both Rule 1 and Rule 2:
messages.append(response.choices[0].message)

# Equivalent to:
messages.append({
    "role": "assistant",
    "content": response.choices[0].message.content,
    "reasoning_content": response.choices[0].message.reasoning_content,
    "tool_calls": response.choices[0].message.tool_calls,  # may be None
})

Common Errors

Error 1: 400 — Missing reasoning_content in Tool Loop

HTTP 400: "reasoning_content is required for tool call turns"

Cause: You appended a message without reasoning_content after a tool call.
Fix: Always use messages.append(response.choices[0].message) — never manually construct the message dict.

Error 2: Degraded Multi-Turn Quality

Symptom: Model loses context or reasoning quality drops in later turns.

Cause: You're stripping reasoning_content between turns.
Fix: Always pass back reasoning_content for tool-call turns. For chat turns, passing or not passing both work — default to passing for simplicity.

Error 3: Streaming Confusion

# WRONG: Forgetting to include reasoning_content when constructing streamed messages
reasoning = ""
answer = ""
for chunk in stream:
    if chunk.choices[0].delta.reasoning_content:
        reasoning += chunk.choices[0].delta.reasoning_content
    else:
        answer += chunk.choices[0].delta.content

messages.append({
    "role": "assistant",
    "content": answer,
    "reasoning_content": reasoning  # DON'T FORGET THIS
})

Reasoning Coherence Across Turns

DeepSeek's reasoning doesn't automatically carry forward between turns like Claude's system prompt does. Each turn is an independent reasoning event. To maintain reasoning coherence:

  1. For chat conversations: Let each turn reason independently. The conversation history provides context.

  2. For tool-call chains: The mandatory passback ensures the model "remembers" its reasoning — this is why the rule exists. The model reads its own previous reasoning and continues the chain.

  3. For complex multi-step analysis: Explicitly ask the model to reference its previous reasoning:

    "Based on your previous analysis that [reference earlier reasoning],
    now consider [new information]. Continue your chain of thought."
    

Note:

Pro Move: When using DeepSeek with LangChain or similar frameworks, verify that the framework correctly handles reasoning_content in tool-call loops. Many frameworks built for OpenAI/Anthropic drop non-standard fields. Test with a simple tool-call chain before deploying.

Note:

Streaming gotcha: In streaming mode, you must manually accumulate reasoning_content from chunks and construct the full message object before appending to the messages list. The response.choices[0].message shortcut only works with non-streaming requests.

  • Thinking Mode Guide — Start here if you're new to thinking mode. Covers enabling, reading, and streaming patterns.
  • Tool Calls with Thinking — Deep dive into the mandatory reasoning_content passback rule for tool-call loops.