การแก้ไขปัญหาคู่มือ
400 หลัง tool call: ส่ง reasoning_content กลับไป
ทำไมคำขอที่สองของลูป tool-call จึงส่งคืน 400 บนโมเดลแบบคิด การแก้ไขด้วย Python ที่ต่อท้ายข้อความทั้งหมด และเมื่อคุณข้ามได้

ในหน้านี้
ลูปเอเจนต์ของคุณทำงานได้ในคำขอแรกและล้มเหลวในคำขอที่สองด้วย 400 บนโมเดลแบบคิด สาเหตุหนึ่งที่ dokumentไว้คือฟิลด์ที่หายไป: ข้อความ assistant ที่คุณส่งกลับได้สูญเสีย reasoning_ ไป หากนี่คือสาเหตุ การต่อท้ายข้อความทั้งหมดตามที่ API ส่งกลับมาจะแก้ไขได้
คู่มือโหมดคิดของ DeepSeek (docs ตรวจสอบเมื่อ 8 พฤศจิกายน 2026) ระบุกฎในประโยคเดียว: เมื่อคำขอมีพารามิเตอร์ tools ต้องส่ง reasoning_ กลับมาทั้งหมดในทุกคำขอถัดไป รวมถึงเทิร์นที่ไม่มี tool call มิฉะนั้น API จะส่งคืน 400 หน้าเดียวกันระบุว่าโหมดคิดเปิดอยู่โดยค่าเริ่มต้น ด้วย effort high ดังนั้นลูปที่คุณเขียนสำหรับโมเดลที่ไม่คิดอาจพังได้โดยไม่มีการเปลี่ยนแปลงแม้แต่บรรทัดเดียว
เกิดอะไรขึ้นระหว่างคำขอแรกและคำขอที่สอง#
ในโหมดคิด chain of thought จะกลับมาใน reasoning_ ซึ่งเป็นฟิลด์ระดับเดียวกับ content ลองนึกภาพลูป:
- คุณส่งคำถามและรายการ
tools - โมเดลตอบกลับด้วย
tool_calls(และreasoning_อยู่ข้างๆ)content - คุณรันเครื่องมือและส่งทุกอย่างกลับ: ประวัติ ข้อความ assistant และข้อความ
toolพร้อมผลลัพธ์
400 จะเกิดขึ้นในคำขอที่ขั้นตอนที่ 3 หากข้อความ assistant ในนั้นไม่มี reasoning_

หน้า DeepSeek dokument 400 เฉพาะกรณี tool-call นี้และไม่ได้ยกข้อความ error มาอ้าง ดังนั้นให้ดูที่ HTTP status ไม่ใช่ข้อความเฉพาะ เมื่อคุณตรวจสอบว่าคุณมีปัญหานี้หรือไม่
การแก้ไข: ต่อท้ายข้อความตามที่ส่งกลับมา#
วิธีที่ปลอดภัยที่สุดคือไม่สร้างข้อความ assistant ขึ้นใหม่เลย นำมาจาก response แล้วต่อท้ายทั้งหมด นี่คือลูปที่สมบูรณ์ด้วย OpenAI Python SDK ที่ชี้ไปยัง CheapTokens เครื่องมือสภาพอากาศเป็น stub ดังนั้นคุณสามารถรันได้ทันที
import json, os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
def get_weather(city):
return {"city": city, "temp_c": 7} # stub
messages = [{"role": "user", "content": "Do I need a jacket in Oslo today?"}]
for _ in range(5): # hard step limit
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
msg = resp.choices[0].message
messages.append(msg.model_dump(exclude_none=True)) # the whole message
if not msg.tool_calls:
print(msg.content)
break
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(get_weather(**args)),
})บรรทัดเดียวที่สำคัญคือ messages. SDK เก็บฟิลด์ที่ไม่รู้จักไว้ ดังนั้นอะไรก็ตามที่ API ใส่ไว้ในข้อความ รวมถึง reasoning_ จะเดินทางกลับไปด้วย คำตอบสุดท้ายก็ถูกต่อท้ายเช่นกัน ซึ่งเป็นสิ่งที่กฎกำหนด: หลังจาก tool call แรก ทุกคำขอถัดไปจะพา reasoning ของเทิร์น assistant ก่อนหน้ามาด้วย
กลไกของ tool-call เอง (schema, tool_call_id, การแยกวิเคราะห์อาร์กิวเมนต์) อยู่ใน Function calling in Python with an OpenAI-compatible API base URL, key และ model id อยู่บน หน้า docs ของ CheapTokens; โมเดลที่ใช้ที่นี่อธิบายไว้บน หน้า DeepSeek: context 380K และ output สูงสุด 32,768 โทเค็นต่อคำตอบ ซึ่งเหลือที่ว่างสำหรับการคิดยาวบวกคำตอบ
ตัวการที่พบบ่อย: ข้อความที่สร้างขึ้นใหม่ด้วยมือ#
วิธีที่พบบ่อยในการทำฟิลด์หายคือโค้ดที่ไม่ได้ต่อท้าย response object แต่สร้าง dict ใหม่ ดูไม่เป็นอันตราย: role, content, tool calls เฟรมเวิร์กและ wrapper ก็ทำแบบเดียวกันเมื่อเก็บประวัติในรูปแบบของตัวเอง ฟิลด์เดียวที่พวกเขาไม่เคยรู้จักคือฟิลด์ที่หายไป

เปรียบเทียบ
สร้างใหม่ด้วยมือ vs ต่อท้ายทั้งหมด
สร้างใหม่ด้วยมือ (พัง)
messages.append({
"role": "assistant",
"content": msg.content,
"tool_calls": [c.model_dump() for c in msg.tool_calls],
})ต่อท้ายทั้งหมด (ทำงานได้)
messages.append(msg.model_dump(exclude_none=True))หากคุณใช้เฟรมเวิร์กและไม่สามารถแตะบรรทัดนั้นได้ ให้ตรวจสอบว่ามันเก็บเทิร์น assistant อย่างไร ประวัติที่ผ่าน serializer ซึ่งเก็บเฉพาะ content และ tool_calls มีบั๊กเดียวกัน แค่ต่ำลงหนึ่งชั้น
เมื่อคุณไม่จำเป็นต้องใช้สิ่งนี้#
ไม่ใช่ทุกการเรียกโมเดลแบบคิดที่ต้องระวังเป็นพิเศษ สำหรับแชทหลายเทิร์นที่ไม่มีเครื่องมือ คู่มือของ DeepSeek ระบุว่าไม่จำเป็นต้องส่ง reasoning_ กลับมา; หากคุณส่งไปก็จะถูกละเว้นและ reasoning ก่อนหน้าจะไม่ถูกเพิ่มเข้า context ดังนั้นแชทธรรมดาสามารถทิ้งมันได้และไม่มีอะไรพัง
เลือกกรณีของคุณ
กรณีของคุณคือแบบไหน?
เลือกตัวเลือกเพื่อดูคำตอบ
คำขอมี tools โมเดลกำลังคิด
ต่อท้ายข้อความ assistant ทั้งหมดทุกเทิร์น ทั้งเทิร์นเครื่องมือและคำตอบสุดท้าย
แชทธรรมดา ไม่มีพารามิเตอร์ tools
ไม่ต้องแก้ไข คุณอาจทิ้ง reasoning_ จากประวัติ หรือส่งไปก็ได้; มันถูกละเว้น
เฟรมเวิร์กสร้างประวัติให้คุณ
หาที่ที่มัน serialize ข้อความ assistant และตรวจสอบให้แน่ใจว่า reasoning_ รอดชีวิต หากทำไม่ได้ ให้เรียก API ด้วยลูปของคุณเองสำหรับส่วนเครื่องมือ
ผลข้างเคียงที่ต้องจำไว้: ประวัติที่คุณส่งซ้ำคือ input ในทุกคำขอ ดังนั้น reasoning ยาวในลูปยาวจะสะสม วิธีนับ thinking token อยู่ใน Reasoning effort and thinking tokens และสิ่งที่ต้องทำเมื่อคำขอโตเกินขีดจำกัดของโมเดลอยู่ใน Request too long: 400 vs 413
หากยังส่งคืน 400#
ตรวจสอบตามลำดับนี้:
- พิมพ์
messagesก่อนการเรียกที่ล้มเหลว ทุกข้อความ assistant ที่มาจากโมเดลมีreasoning_หรือไม่ หาก response มีมัน?content - คุณสร้างข้อความใหม่ที่ไหนหรือไม่: helper, คลาสประวัติ,
json.dumpsแล้วกลับมาด้วยรายการคีย์คงที่? - ข้อความ
toolสำหรับทุกtool_call_idมีอยู่หรือไม่ ด้วย id เดียวกัน? - เป็นความยาวจริงหรือไม่ คำขอที่เกินขีดจำกัด context เป็น 400 คนละแบบ อธิบายไว้ในบทความข้างต้น
เช็กด่วน
คุณต่อท้ายอะไรหลังจากโมเดลขอเครื่องมือ?
แสดงคำตอบ
คำตอบ: ข้อความ assistant ทั้งหมดตามที่ส่งกลับ รวม reasoning_content
tool_calls คือสิ่งที่ข้อความเครื่องมือของคุณตอบ และเมื่อมีเครื่องมือในคำขอ reasoning ต้องเดินทางไปพร้อมกับมัน
นั่นคือการแก้ไขทั้งหมด: ต่อท้ายครั้งเดียว ไม่ต้องสร้างใหม่ เพิ่มขีดจำกัดขั้นตอนจากตัวอย่างลงในทุกูลูปที่คุณ deploy เพื่อให้เทิร์นที่ผิดพลาดเสียห้าคำขอ ไม่ใช่ห้าร้อย



