ব্লগে ফিরে যান

সমস্যা সমাধানগাইড

টুল কলের পরে 400: reasoning_content ফেরত পাঠান

থিংকিং মডেলে একটি টুল-কল লুপের দ্বিতীয় রিকোয়েস্ট কেন 400 ফেরত দেয়, পুরো মেসেজ অ্যাপেন্ড করার একটি Python সমাধান, এবং কখন এটি বাদ দিতে পারেন।

CheapTokens Team দ্বারাপ্রকাশিত হয়েছে 9 মিনিট পড়ার সময়

কভার: একটি টুল-কল লুপ যেখানে reasoning_content অ্যাসিস্ট্যান্ট মেসেজ থেকে পরবর্তী রিকোয়েস্টে যায়
এই পৃষ্ঠায়
  1. প্রথম ও দ্বিতীয় রিকোয়েস্টের মাঝে যা ঘটে
  2. সমাধান: মেসেজ যেভাবে ফেরত এসেছে সেভাবেই অ্যাপেন্ড করুন
  3. একটি সাধারণ অপরাধী: হাতে পুনর্নির্মিত মেসেজ
  4. যখন আপনার এটির প্রয়োজন নেই
  5. যদি তবুও 400 ফেরত আসে

আপনার এজেন্ট লুপ প্রথম রিকোয়েস্টে কাজ করে আর দ্বিতীয়টিতে 400 দিয়ে ফেল করে। একটি থিংকিং মডেলে একটি ডকুমেন্টেড কারণ হলো একটি অনুপস্থিত ফিল্ড: আপনি যে অ্যাসিস্ট্যান্ট মেসেজটি ফেরত পাঠান সেটি তার reasoning_content হারিয়ে ফেলেছে। যদি সেটিই কারণ হয়, তবে API যেভাবে ফেরত দিয়েছে ঠিক সেভাবেই পুরো মেসেজটি অ্যাপেন্ড করলে সমস্যা ঠিক হয়ে যায়।

DeepSeek-এর থিংকিং-মোড গাইড (ডকস, ৮ নভেম্বর ২০২৬-এ যাচাইকৃত) নিয়মটি এক বাক্যে বলে: যখন রিকোয়েস্টে tools প্যারামিটার থাকে, তখন reasoning_content পরবর্তী সব রিকোয়েস্টে সম্পূর্ণভাবে ফেরত পাঠাতে হবে, টুল কল ছাড়া টার্নসহ। অন্যথায় API 400 ফেরত দেয়। একই পেজে বলা হয়েছে থিংকিং ডিফল্টভাবে চালু, এফোর্ট high সহ। তাই একটি নন-থিংকিং মডেলের জন্য লেখা লুপ এক লাইন না বদলেও ভেঙে যেতে পারে।

প্রথম ও দ্বিতীয় রিকোয়েস্টের মাঝে যা ঘটে#

থিংকিং মোডে চেইন অফ থট reasoning_content-এ ফেরত আসে, যা content-এর সমান স্তরের একটি ফিল্ড। লুপটি কল্পনা করুন:

  1. আপনি প্রশ্ন এবং tools লিস্ট পাঠান।
  2. মডেল tool_calls দিয়ে উত্তর দেয় (এবং তাদের পাশে reasoning_content)।
  3. আপনি টুল চালান এবং সবকিছু ফেরত পাঠান: হিস্ট্রি, অ্যাসিস্ট্যান্ট মেসেজ, এবং ফলাফলসহ একটি tool মেসেজ।

ধাপ 3-এর রিকোয়েস্টে 400 আসে যদি সেখানে থাকা অ্যাসিস্ট্যান্ট মেসেজে reasoning_content না থাকে।

একটি টুল লুপের তিন ধাপ: রিকোয়েস্ট, reasoning_content সহ অ্যাসিস্ট্যান্ট মেসেজ, দ্বিতীয় রিকোয়েস্ট
ফিল্ডটি যেখানে যেতে হবে: অ্যাসিস্ট্যান্ট মেসেজ থেকে পরবর্তী রিকোয়েস্টে

DeepSeek পেজটি শুধুমাত্র এই টুল-কল ক্ষেত্রেই 400 ডকুমেন্ট করে এবং কোনো এরর টেক্সট উদ্ধৃত করে না, তাই আপনার এই সমস্যা আছে কিনা যাচাই করার সময় নির্দিষ্ট মেসেজ নয়, HTTP স্ট্যাটাসের দিকে তাকান।

সমাধান: মেসেজ যেভাবে ফেরত এসেছে সেভাবেই অ্যাপেন্ড করুন#

সবচেয়ে নিরাপদ পদক্ষেপ হলো অ্যাসিস্ট্যান্ট মেসেজটি একেবারেই পুনর্নির্মাণ না করা। এটি রেসপন্স থেকে নিন এবং সম্পূর্ণভাবে অ্যাপেন্ড করুন। এখানে CheapTokens-এর দিকে নির্দেশিত OpenAI Python SDK দিয়ে একটি সম্পূর্ণ লুপ। ওয়েদার টুলটি একটি স্টাব, তাই আপনি এটি যেমন আছে তেমনই চালাতে পারেন।

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_id, আর্গুমেন্ট পার্সিং) আছে Function calling in Python with an OpenAI-compatible API-তে। বেস URL, কী এবং মডেল আইডি আছে CheapTokens docs page-এ; এখানে ব্যবহৃত মডেলটি বর্ণিত আছে DeepSeek page-এ: 380K কনটেক্সট এবং প্রতি উত্তরে সর্বোচ্চ 32,768 আউটপুট টোকেন, যা দীর্ঘ থিংকিং প্লাস একটি উত্তরের জন্য জায়গা রাখে।

একটি সাধারণ অপরাধী: হাতে পুনর্নির্মিত মেসেজ#

ফিল্ডটি হারানোর একটি সাধারণ উপায় হলো এমন কোড যা রেসপন্স অবজেক্ট অ্যাপেন্ড না করে নতুন ডিক্ট তৈরি করে। এটি নিরীহ দেখায়: role, content, tool calls। ফ্রেমওয়ার্ক এবং র্যাপাররাও একই কাজ করে যখন তারা হিস্ট্রি নিজেদের ফরম্যাটে সংরক্ষণ করে। যে একটি ফিল্ড তাদের কখনো শোনা হয়নি, সেটিই হারিয়ে যায়।

বাম: role, content, tool_calls থেকে পুনর্নির্মিত অ্যাসিস্ট্যান্ট মেসেজ। ডান: যেভাবে ফেরত এসেছে সেই মেসেজ, reasoning_content সহ
দুটি অ্যাসিস্ট্যান্ট মেসেজের মধ্যে পার্থক্য একটি ফিল্ড

তুলনা করুন

হাতে পুনর্নির্মিত বনাম সম্পূর্ণভাবে অ্যাপেন্ড করা

হাতে পুনর্নির্মিত (ভাঙে)

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))

আপনি যদি একটি ফ্রেমওয়ার্ক ব্যবহার করেন এবং সেই লাইনে হাত দিতে না পারেন, তবে এটি কীভাবে অ্যাসিস্ট্যান্ট টার্ন সংরক্ষণ করে তা দেখুন। এমন একটি সিরিয়ালাইজারের মধ্য দিয়ে যাওয়া হিস্ট্রি যা শুধু content এবং tool_calls রাখে, তার একই বাগ আছে, শুধু এক স্তর নিচে।

যখন আপনার এটির প্রয়োজন নেই#

প্রতিটি থিংকিং-মডেল কলের জন্য এই অতিরিক্ত সতর্কতার প্রয়োজন নেই। টুল ছাড়া মাল্টি-টার্ন চ্যাটের জন্য, DeepSeek-এর গাইড বলে reasoning_content ফেরত পাঠাতে হয় না; আপনি যদি তবুও পাঠান, এটি উপেক্ষা করা হয় এবং আগের রিজনিং কনটেক্সটে যোগ হয় না। তাই একটি সাধারণ চ্যাট এটি বাদ দিয়েই চালিয়ে যেতে পারে এবং কিছুই ভাঙে না।

আপনার কেস বেছে নিন

আপনার ক্ষেত্র কোনটি?

রিকোয়েস্টে tools আছে, মডেল থিংক করছে

প্রতি টার্নে পুরো অ্যাসিস্ট্যান্ট মেসেজ অ্যাপেন্ড করুন, টুল টার্ন এবং চূড়ান্ত উত্তর উভয়েই।

সাধারণ চ্যাট, tools প্যারামিটার নেই

কোনো সমাধান দরকার নেই। আপনি হিস্ট্রি থেকে reasoning_content বাদ দিতে পারেন, বা পাঠাতে পারেন; এটি উপেক্ষা করা হয়।

একটি ফ্রেমওয়ার্ক আপনার জন্য হিস্ট্রি তৈরি করে

এটি কোথায় অ্যাসিস্ট্যান্ট মেসেজ সিরিয়ালাইজ করে তা খুঁজুন এবং নিশ্চিত করুন reasoning_content টিকে থাকে। যদি না পারে, টুল অংশের জন্য নিজের লুপ দিয়ে API কল করুন।

মনে রাখার মতো একটি পার্শ্বপ্রতিক্রিয়া: আপনি যে হিস্ট্রি আবার পাঠান তা প্রতিটি রিকোয়েস্টে ইনপুট, তাই দীর্ঘ লুপে দীর্ঘ রিজনিং যোগ হয়। থিংকিং টোকেন কীভাবে গণনা করা হয় তা আছে Reasoning effort and thinking tokens-এ, এবং একটি রিকোয়েস্ট মডেলের সীমা ছাড়িয়ে গেলে কী করতে হবে তা আছে Request too long: 400 vs 413-এ।

যদি তবুও 400 ফেরত আসে#

এই ক্রমে যাচাই করুন:

  • ব্যর্থ কলের ঠিক আগে messages প্রিন্ট করুন। মডেল থেকে আসা প্রতিটি অ্যাসিস্ট্যান্ট মেসেজে reasoning_content আছে কি, যদি রেসপন্সে তা থাকে?
  • আপনি কি কোথাও মেসেজ পুনর্নির্মাণ করেন: একটি হেল্পার, একটি হিস্ট্রি ক্লাস, json.dumps এবং ফিরে একটি নির্দিষ্ট কী লিস্ট দিয়ে?
  • প্রতিটি tool_call_id-এর জন্য tool মেসেজ কি উপস্থিত, একই id সহ?
  • এটি কি সত্যিই দৈর্ঘ্য? কনটেক্সট সীমার বেশি একটি রিকোয়েস্ট ভিন্ন একটি 400, উপরের আর্টিকেলে বর্ণিত।

দ্রুত যাচাই

মডেল টুল চাইলে আপনি কী অ্যাপেন্ড করেন?

উত্তর দেখান

উত্তর: যেভাবে ফেরত এসেছে সেই পুরো অ্যাসিস্ট্যান্ট মেসেজ, reasoning_content সহ

tool_calls হলো যা আপনার টুল মেসেজগুলো উত্তর দেয়, এবং রিকোয়েস্টে টুল থাকলে রিজনিংকে তাদের সাথে যেতে হবে।

এটাই পুরো সমাধান: একটি অ্যাপেন্ড, কোনো পুনর্নির্মাণ নয়। উদাহরণের ধাপ-সীমা আপনার পাঠানো যেকোনো লুপে যোগ করুন, যাতে একটি খারাপ টার্নে পাঁচশো নয়, পাঁচটি রিকোয়েস্ট খরচ হয়।