How to Pass Thinking History in Multi-Turn Conversations with OpenAI SDK Compatibility? Tool Call Checklist

2026-08-22 85 0

You must pass back the assistant message's content, reasoning_content, and tool_calls verbatim, and ensure the tool_call_id in the tool message strictly pairs with the tool_calls[].id from the previous turn—these four pieces of information are indispensable. Missing any one of them can cause errors or abnormal behavior in the second turn. Using Kimi K3's preserved thinking history as a typical example, this article explains how to assemble the message history, why fields get lost, and how to handle it.

Conclusion First: The Four Field Types That Must Be Included for Multi-Turn Pass-Back

When passing back messages in multi-turn, the messages array must retain four types of information: the assistant message's content, reasoning_content, tool_calls, and the tool_call_id in the subsequent tool message. Each field has its own responsibility, and the symptoms when missing vary:

FieldResponsibilityTypical Symptoms When Missing
contentThe model's final reply textConversation logic breaks, model seems to "lose memory"
reasoning_contentThe model's thinking process (model-specific)Model may repeat reasoning or skip key steps
tool_callsRequest which tool to call and with what parametersModel repeats the same tool call
tool_call_idIdentifies which call this tool result corresponds toRequest judged as incomplete history, error or empty result

Among these, the pairing of tool_call_id and the complete pass-back of the assistant message are protocol-level requirements (based on Kimi K3 official documentation as of August 2026); the symptom column lists common manifestations during multi-turn troubleshooting, which are engineering experience and may vary by model implementation.

Structure of Assistant Messages in OpenAI SDK Compatible Mode: Standard Fields vs. Model-Specific Fields

In the official OpenAI SDK, standard fields for assistant messages typically include role and content, and tool_calls is also a standard field. However, fields like reasoning_content (chain-of-thought) are model-specific extensions not included in OpenAI's protocol, so strongly-typed SDK objects often ignore or "swallow" these fields when parsing responses. This leads to a very common misjudgment: when the model returns, you can see reasoning_content, but when you stuff the response object back into messages and send it again, it's gone.

To avoid this pitfall, the safest approach is: don't rely on the SDK's message objects for history; instead, keep the raw response JSON (or model_dump() dictionary) and append it to messages as a dict. This way, non-standard fields like reasoning_content won't be lost during SDK type conversion.

Why Retaining Thinking History Changes Multi-Turn Behavior: What Kimi K3's Preserved Thinking History Illustrates

According to Moonshot AI's official Kimi API documentation (platform.moonshot.cn, public as of August 2026), the Kimi K3 model released in August 2026 is a MoE multimodal reasoning model with 2.8T total parameters and 104B activated parameters, supporting a 1M token context. The official docs explicitly require the preserved thinking history mode: in multi-turn conversations and tool calls, you must pass back the assistant message including reasoning_content and tool_calls verbatim. Whether you preserve thinking history directly changes multi-turn behavior. These conclusions are based on the docs available at that time; please re-check the latest docs before integration. This means thinking history is no longer just logs but part of the conversation state. If developers stick to old habits and strip out reasoning_content before passing back, the model may not understand the context, leading to repeated reasoning or tool call anomalies.

Two Most Likely Breaks in the Tool Call Chain: tool_calls Not Passed Back Verbatim and tool_call_id Not Paired

Tool calls are the most error-prone part of multi-turn conversations. Failures usually concentrate in two areas:

  1. tool_calls not passed back verbatim: In the second turn, if you simplify the assistant's tool_calls to plain text or discard the arguments, the model won't know what was requested, so it repeats the same call.
  2. tool_call_id not paired: The tool_call_id in the tool message must exactly match the tool_calls[].id from the previous turn. If you made multiple parallel tool calls but only returned one result, the request will be judged as incomplete history.

Multi-turn request field flow diagram

For troubleshooting, we suggest: first dump the outbound request body to confirm whether the fields were actually sent; then compare the IDs in tool_calls with the tool_call_id in tool messages one by one.

Fallback Approach When SDK Strongly-Typed Objects Lose Fields: Dict Messages, extra_body, and Raw Response Archiving

For engineering, we recommend the following fallback approaches:

  • Preserve the raw response JSON: Every time you receive a response, save the full JSON and append it to messages as a dict to avoid losing fields during SDK object conversion.
  • Non-standard fields (including reasoning_content) in the message body should be passed directly via dict-form messages, not via extra_body; extra_body is only for top-level non-standard request parameter pass-through.
  • Snapshot requests at the gateway: Record the outbound request body at the API gateway to reproduce issues. You can also refer to the multi-model API gateway approach.

Below is a simplified example (Python pseudocode):

# 假设 resp 是 SDK 返回的响应对象
assistant_msg = resp.choices[0].message.model_dump(exclude_none=True)
messages.append(assistant_msg)  # 该 dict 自带 role='assistant'
# 并行调用时,每个 tool_calls[].id 都要 append 一条对应的 tool 消息
for tc in assistant_msg.get("tool_calls", []):
    messages.append({"role": "tool", "tool_call_id": tc["id"], "content": result})

Minimal Reproduction Script: Three-Step Comparison (Normal Q&A, Tool Call, Follow-Up)

We recommend preparing a model-agnostic three-step script for self-checks before integrating any new reasoning model:

  1. First turn: normal question – Record all fields in the response, especially reasoning_content.
  2. Second turn: trigger a tool call – Pass back the assistant and tool messages verbatim, observe whether the call is triggered normally.
  3. Third turn: follow-up based on tool result – Confirm whether the model continues to answer based on the result or repeats the tool call.

Below is a minimal runnable skeleton (Python):

import json

def echo_tool_schema():
    # 任意 echo 工具即可
    return {"type": "function", "function": {"name": "echo", "description": "Returns the input", "parameters": {"type": "object", "properties": {"text": {"type": "string"}}, "required": ["text"]}}}

def run_chain(strip):
    messages = [{"role": "user", "content": "你好,请说一句话"}]
    fields_sets = []
    for turn in range(3):
        resp = client.chat.completions.create(model="...", messages=messages, tools=[echo_tool_schema()])
        msg = resp.choices[0].message
        msg_dict = msg.model_dump(exclude_none=True)
        fields_sets.append(set(msg_dict.keys()))
        if strip and "reasoning_content" in msg_dict:
            del msg_dict["reasoning_content"]
        messages.append(msg_dict)
        if msg_dict.get("tool_calls"):
            for tc in msg_dict["tool_calls"]:
                messages.append({"role": "tool", "tool_call_id": tc["id"], "content": "echo"})
        else:
            messages.append({"role": "user", "content": "请继续"})
    return fields_sets

fields_a = run_chain(strip=False)
fields_b = run_chain(strip=True)
print("字段差异:", set.union(*fields_a) - set.union(*fields_b))

Run two chains in parallel for comparison: one with reasoning_content passed back, and one with it stripped, and record the differences.

How to Tell Whether a Field Was Lost by the SDK or Swallowed by an Intermediate Layer

For binary troubleshooting between OpenAI SDK-compatible endpoints, use the SDK's custom HTTP client/event hooks or a local reverse proxy to capture the outbound request body. Decision branches: if the outbound request body doesn't have reasoning_content, it was lost during local SDK serialization; if the request body has it but the response body doesn't, it's a pass-through issue on the server or intermediate layer.

Specifically: use the same OpenAI-compatible code, switch base_url to NexAIX's OpenAI base_url (https://api.nexaix.net/v1),用同一份多轮脚本核对) and check whether reasoning_content and tool_calls are returned as-is, whether the model field in the response corresponds to the actual executing model, and whether a standard 429 is returned under load instead of silently switching models. Treat this as a re-testable acceptance criterion, not just whether the feature "works". For checking the current specs and availability of models like Kimi K3, refer to the NexAIX model page and change log. Additionally, before switching vendors, refer to the evaluation methods in How to Choose an AI Relay Station to avoid some pitfalls.

Multi-Turn Regression Checklist Before Integrating a New Reasoning Model

Before integrating a new model with OpenAI SDK compatibility, run through this checklist:

  • [ ] Archive response field list: Record all fields from the first response as a baseline.
  • [ ] Pass back assistant messages verbatim: content, reasoning_content, tool_calls—none missing.
  • [ ] Pair all tool_call_ids: Each tool call has a corresponding tool message.
  • [ ] Include all parallel call results: When multiple tools are called in parallel, return all results.
  • [ ] History truncation strategy: Thinking history consumes context; consider whether to drop and how.
  • [ ] Context budget monitoring: Thinking history speeds up token consumption; set up monitoring alerts.
  • [ ] Re-run the same script after switching vendors and compare field differences; refer to the risk warning in AI Relay Station Dilution.

Multi-turn pass-back field verification matrix

FAQ

Should reasoning_content be passed back?

Yes. For models that require preserved thinking history (like Kimi K3), reasoning_content is a protocol requirement and must be passed back verbatim. Otherwise, multi-turn behavior may be abnormal.

What happens if the reasoning model's thinking history is not passed back?

It may not error immediately, but the model will lose context, leading to repeated reasoning, repeated tool calls, or abnormal results. It is recommended to preserve it.

What to do when the openai python sdk does not recognize reasoning_content?

The SDK strongly-typed object may ignore this field. The solution is to keep the raw response JSON (or message.model_dump()) and append it to messages as a dict; extra_body only handles top-level non-standard request parameters and won't solve message field loss.

What to do when tool_calls and tool_call_id don't match?

First dump the request body and check whether tool_call_id exactly matches the previous turn's tool_calls.id. For parallel calls, ensure each tool has a corresponding result.

Will thinking history blow up the context?

It consumes tokens. Kimi K3 has a 1M context, but for long conversations, be mindful of budget. You can consider truncating old thinking history, but check whether the model allows it.

Last updated on 2026-08-22 11:07:34

Related Posts

How to Troubleshoot Claude API 400? A Comparison of Four Parameter Intercepti...
How to Choose a Long-Context API: 5 Cost Criteria for 1M Windows
How to Pass Thinking History in Multi-Turn Conversations with OpenAI SDK Comp...
How to Change OpenAI base_url: Three Ways and Error Reference
How to Troubleshoot AI API 429 Errors? A Guide to Classifying Four Causes and...

Comments(0)

No comments yet

Leave a Comment