กลับไปที่บล็อก

การแก้ไขปัญหาคู่มือ

400 หลัง tool call: ส่ง reasoning_content กลับไป

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

โดย CheapTokens Teamเผยแพร่เมื่อ ใช้เวลาอ่าน 5 นาที

หน้าปก: ลูป tool-call ที่ reasoning_content เดินทางจากข้อความ assistant ไปยังคำขอถัดไป
ในหน้านี้
  1. เกิดอะไรขึ้นระหว่างคำขอแรกและคำขอที่สอง
  2. การแก้ไข: ต่อท้ายข้อความตามที่ส่งกลับมา
  3. ตัวการที่พบบ่อย: ข้อความที่สร้างขึ้นใหม่ด้วยมือ
  4. เมื่อคุณไม่จำเป็นต้องใช้สิ่งนี้
  5. หากยังส่งคืน 400

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

คู่มือโหมดคิดของ DeepSeek (docs ตรวจสอบเมื่อ 8 พฤศจิกายน 2026) ระบุกฎในประโยคเดียว: เมื่อคำขอมีพารามิเตอร์ tools ต้องส่ง reasoning_content กลับมาทั้งหมดในทุกคำขอถัดไป รวมถึงเทิร์นที่ไม่มี tool call มิฉะนั้น API จะส่งคืน 400 หน้าเดียวกันระบุว่าโหมดคิดเปิดอยู่โดยค่าเริ่มต้น ด้วย effort high ดังนั้นลูปที่คุณเขียนสำหรับโมเดลที่ไม่คิดอาจพังได้โดยไม่มีการเปลี่ยนแปลงแม้แต่บรรทัดเดียว

เกิดอะไรขึ้นระหว่างคำขอแรกและคำขอที่สอง#

ในโหมดคิด chain of thought จะกลับมาใน reasoning_content ซึ่งเป็นฟิลด์ระดับเดียวกับ content ลองนึกภาพลูป:

  1. คุณส่งคำถามและรายการ tools
  2. โมเดลตอบกลับด้วย tool_calls (และ reasoning_content อยู่ข้างๆ)
  3. คุณรันเครื่องมือและส่งทุกอย่างกลับ: ประวัติ ข้อความ assistant และข้อความ tool พร้อมผลลัพธ์

400 จะเกิดขึ้นในคำขอที่ขั้นตอนที่ 3 หากข้อความ assistant ในนั้นไม่มี reasoning_content

สามขั้นตอนของลูปเครื่องมือ: คำขอ ข้อความ assistant พร้อม reasoning_content คำขอที่สอง
ที่ที่ฟิลด์ต้องเดินทาง: จากข้อความ assistant ไปยังคำขอถัดไป

หน้า DeepSeek dokument 400 เฉพาะกรณี tool-call นี้และไม่ได้ยกข้อความ error มาอ้าง ดังนั้นให้ดูที่ HTTP status ไม่ใช่ข้อความเฉพาะ เมื่อคุณตรวจสอบว่าคุณมีปัญหานี้หรือไม่

การแก้ไข: ต่อท้ายข้อความตามที่ส่งกลับมา#

วิธีที่ปลอดภัยที่สุดคือไม่สร้างข้อความ assistant ขึ้นใหม่เลย นำมาจาก response แล้วต่อท้ายทั้งหมด นี่คือลูปที่สมบูรณ์ด้วย OpenAI Python SDK ที่ชี้ไปยัง CheapTokens เครื่องมือสภาพอากาศเป็น stub ดังนั้นคุณสามารถรันได้ทันที

python
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.append(msg.model_dump(exclude_none=True)) SDK เก็บฟิลด์ที่ไม่รู้จักไว้ ดังนั้นอะไรก็ตามที่ API ใส่ไว้ในข้อความ รวมถึง reasoning_content จะเดินทางกลับไปด้วย คำตอบสุดท้ายก็ถูกต่อท้ายเช่นกัน ซึ่งเป็นสิ่งที่กฎกำหนด: หลังจาก 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 ก็ทำแบบเดียวกันเมื่อเก็บประวัติในรูปแบบของตัวเอง ฟิลด์เดียวที่พวกเขาไม่เคยรู้จักคือฟิลด์ที่หายไป

ซ้าย: ข้อความ assistant ที่สร้างใหม่จาก role, content, tool_calls ขวา: ข้อความตามที่ส่งกลับ พร้อม reasoning_content
ความแตกต่างระหว่างข้อความ assistant สองแบบคือหนึ่งฟิลด์

เปรียบเทียบ

สร้างใหม่ด้วยมือ vs ต่อท้ายทั้งหมด

สร้างใหม่ด้วยมือ (พัง)

python
messages.append({
    "role": "assistant",
    "content": msg.content,
    "tool_calls": [c.model_dump() for c in msg.tool_calls],
})

ต่อท้ายทั้งหมด (ทำงานได้)

python
messages.append(msg.model_dump(exclude_none=True))

หากคุณใช้เฟรมเวิร์กและไม่สามารถแตะบรรทัดนั้นได้ ให้ตรวจสอบว่ามันเก็บเทิร์น assistant อย่างไร ประวัติที่ผ่าน serializer ซึ่งเก็บเฉพาะ content และ tool_calls มีบั๊กเดียวกัน แค่ต่ำลงหนึ่งชั้น

เมื่อคุณไม่จำเป็นต้องใช้สิ่งนี้#

ไม่ใช่ทุกการเรียกโมเดลแบบคิดที่ต้องระวังเป็นพิเศษ สำหรับแชทหลายเทิร์นที่ไม่มีเครื่องมือ คู่มือของ DeepSeek ระบุว่าไม่จำเป็นต้องส่ง reasoning_content กลับมา; หากคุณส่งไปก็จะถูกละเว้นและ reasoning ก่อนหน้าจะไม่ถูกเพิ่มเข้า context ดังนั้นแชทธรรมดาสามารถทิ้งมันได้และไม่มีอะไรพัง

เลือกกรณีของคุณ

กรณีของคุณคือแบบไหน?

คำขอมี tools โมเดลกำลังคิด

ต่อท้ายข้อความ assistant ทั้งหมดทุกเทิร์น ทั้งเทิร์นเครื่องมือและคำตอบสุดท้าย

แชทธรรมดา ไม่มีพารามิเตอร์ tools

ไม่ต้องแก้ไข คุณอาจทิ้ง reasoning_content จากประวัติ หรือส่งไปก็ได้; มันถูกละเว้น

เฟรมเวิร์กสร้างประวัติให้คุณ

หาที่ที่มัน serialize ข้อความ assistant และตรวจสอบให้แน่ใจว่า reasoning_content รอดชีวิต หากทำไม่ได้ ให้เรียก API ด้วยลูปของคุณเองสำหรับส่วนเครื่องมือ

ผลข้างเคียงที่ต้องจำไว้: ประวัติที่คุณส่งซ้ำคือ input ในทุกคำขอ ดังนั้น reasoning ยาวในลูปยาวจะสะสม วิธีนับ thinking token อยู่ใน Reasoning effort and thinking tokens และสิ่งที่ต้องทำเมื่อคำขอโตเกินขีดจำกัดของโมเดลอยู่ใน Request too long: 400 vs 413

หากยังส่งคืน 400#

ตรวจสอบตามลำดับนี้:

  • พิมพ์ messages ก่อนการเรียกที่ล้มเหลว ทุกข้อความ assistant ที่มาจากโมเดลมี reasoning_content หรือไม่ หาก response มีมัน?
  • คุณสร้างข้อความใหม่ที่ไหนหรือไม่: helper, คลาสประวัติ, json.dumps แล้วกลับมาด้วยรายการคีย์คงที่?
  • ข้อความ tool สำหรับทุก tool_call_id มีอยู่หรือไม่ ด้วย id เดียวกัน?
  • เป็นความยาวจริงหรือไม่ คำขอที่เกินขีดจำกัด context เป็น 400 คนละแบบ อธิบายไว้ในบทความข้างต้น

เช็กด่วน

คุณต่อท้ายอะไรหลังจากโมเดลขอเครื่องมือ?

แสดงคำตอบ

คำตอบ: ข้อความ assistant ทั้งหมดตามที่ส่งกลับ รวม reasoning_content

tool_calls คือสิ่งที่ข้อความเครื่องมือของคุณตอบ และเมื่อมีเครื่องมือในคำขอ reasoning ต้องเดินทางไปพร้อมกับมัน

นั่นคือการแก้ไขทั้งหมด: ต่อท้ายครั้งเดียว ไม่ต้องสร้างใหม่ เพิ่มขีดจำกัดขั้นตอนจากตัวอย่างลงในทุกูลูปที่คุณ deploy เพื่อให้เทิร์นที่ผิดพลาดเสียห้าคำขอ ไม่ใช่ห้าร้อย