DeepSeek Tool Calls with Thinking: reasoning_content Management

Combine DeepSeek's tool calls with thinking mode. The mandatory reasoning_content passback in tool loops, strict mode for JSON schema enforcement, and error handling patterns to avoid 400 errors.

June 11, 2026
DeepSeekTool CallsThinking ModeFunction CallingStrict Mode

DeepSeek supports tool calls in thinking mode — a feature introduced in V3.2 that combines structured function calling with chain-of-thought reasoning. The model can reason about which tool to call, interpret tool results, and continue reasoning across multiple tool call sub-turns. But the reasoning_content management in tool loops follows strict rules that will 400-error if you get them wrong.

The Tool Call + Thinking Flow

User: "What's the weather in Tokyo and should I bring an umbrella?"

Model (turn 1.1):
  reasoning: "I need to get tomorrow's date first, then call get_weather."
  tool_call: get_date()

Tool result: "2026-06-13"

Model (turn 1.2):
  reasoning: "Tomorrow is June 13. Now I'll call get_weather for Tokyo."
  tool_call: get_weather(location="Tokyo", date="2026-06-13")

Tool result: "Rainy, 15°C"

Model (turn 1.3):
  reasoning: "It's rainy and cool. The user should definitely bring an umbrella."
  content: "Yes, bring an umbrella! Tomorrow will be rainy with temperatures around 15°C."

The Critical Rule: Mandatory reasoning_content Passback

In tool-call turns, reasoning_content MUST be passed back to the API in all subsequent requests. Failure to do so returns a 400 error.

# CORRECT: Always pass the full message object
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

# This single line handles reasoning_content + content + tool_calls correctly:
messages.append(message)

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

# Continue — reasoning_content from the previous message is automatically passed
response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    tools=tools,
    reasoning_effort="high",
    extra_body={"thinking": {"type": "enabled"}}
)

The Full Tool Call Loop

def run_with_tools(messages, tools, tool_handler):
    """Complete tool call loop with reasoning management."""
    while True:
        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
        messages.append(message)  # Includes reasoning_content, content, tool_calls

        # No tool calls → final answer reached
        if not message.tool_calls:
            return message.content, message.reasoning_content

        # Execute all tool calls
        for tool_call in message.tool_calls:
            result = tool_handler(tool_call.function.name, tool_call.function.arguments)
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

strict Mode (Beta)

Strict mode enforces JSON schema compliance on tool call outputs. Useful when tool outputs must match exact schemas:

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "strict": True,  # Enable strict mode
        "description": "Get weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name"
                }
            },
            "required": ["location"],
            "additionalProperties": False  # Required in strict mode
        }
    }
}]

# Must use beta endpoint for strict mode
client = OpenAI(
    base_url="https://api.deepseek.com/beta",
    api_key="<key>"
)

Supported JSON Schema Types in Strict Mode

TypeSupported Constraints
objectproperties, required, additionalProperties: false
stringpattern (regex), format (email, hostname, ipv4, ipv6, uuid)
number/integerminimum, maximum, const, multipleOf
arrayitems schema
enumValue list
anyOfMultiple schema alternatives
$ref/$defReusable schema modules

Common Errors

Error 1: 400 — Missing reasoning_content

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

Fix: Always use messages.append(response.choices[0].message)
     Never manually construct the assistant message dict in tool-call loops.

Error 2: 400 — strict mode schema validation

HTTP 400: "Schema validation failed"

Fix: Ensure all object properties are in 'required' array.
     Set 'additionalProperties: false' on all objects.
     Check that parameter names match between schema and function definition.

Error 3: Tool calls with non-thinking mode

Tool calls work in both thinking and non-thinking mode.
If you don't need reasoning, omit the thinking parameter for simpler tool loops.
Non-thinking mode doesn't require reasoning_content management.

When to Use Tool Calls with Thinking

ScenarioRecommendation
Complex multi-tool chainsThinking mode — reasoning helps select the right tools
Simple single-tool callsNon-thinking mode — faster, cheaper
Tools with ambiguous resultsThinking mode — model reasons about results
Deterministic tool workflowsNon-thinking mode — no reasoning benefit
Production with strict schemaThinking + strict mode — reliable structured outputs

Note:

Pro Move: For LangChain agent workflows, verify that your framework correctly handles reasoning_content in multi-step tool chains. Many frameworks built for OpenAI drop non-standard fields. Test with a simple 2-step tool chain before deploying complex agents.

Note:

Streaming + Tool Calls: When streaming with tool calls, reasoning_content and tool_calls arrive in separate chunks. You must accumulate the full message (reasoning + content + tool_calls) before appending to the messages list. The non-streaming convenience of messages.append(response.choices[0].message) doesn't work in streaming mode — you must manually construct the full message object.

  • OpenAI-Compatible API — SDK configuration and migration patterns for both OpenAI and Anthropic formats.
  • Multi-Turn Reasoning — The reasoning_content management rules that apply to all multi-turn thinking, including tool-call loops.