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

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

HTTP 200-এর পরে স্ট্রিম ব্যর্থ: Python-এ মিড-স্ট্রিম ত্রুটি ধরুন

কেন একটি স্ট্রিম 200 স্ট্যাটাসের পরে ভাঙতে পারে, কীভাবে event: error চিহ্নিত করবেন, এবং একটি Python পুনরায় চেষ্টা যা উত্তর ডুপ্লিকেট না করে আংশিক টেক্সট রাখে।

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

HTTP 200 OK সহ স্ট্রিম যা message_stop-এর পরিবর্তে event: error এবং overloaded_error দিয়ে শেষ হয়
এই পৃষ্ঠায়
  1. একটি 200 আসলে কী প্রতিশ্রুতি দেয়
  2. Python-এ এটি ধরুন
  3. উত্তর দুবার প্রিন্ট না করে পুনরায় চেষ্টা করুন
  4. প্রতিটি তাড়াতাড়ি থামা ত্রুটি নয়
  5. শিপ করার আগে

আপনার চ্যাট UI অর্ধেক উত্তর স্ট্রিম করে, ফ্রিজ হয়ে যায়, এবং লগে দেখা যায় 200 OK। এটি কোনো দ্বন্দ্ব নয়: 200 শুধু বলে স্ট্রিম শুরু হয়েছে, শেষ হয়েছে তা নয়। যদি পরে কিছু ভেঙে যায়, সার্ভার শুধুমাত্র স্ট্রিমের ভিতরে একটি ত্রুটি পাঠাতে পারে, তাই স্ট্যাটাস কোডের চেক তা কখনো দেখে না। সমাধান হলো একটি স্ট্রিমকে কেবল তখনই সফল হিসেবে গণ্য করা যখন এটি তার ফিনিশ সিগন্যাল দিয়ে শেষ হয়, এবং না হলে পরিষ্কার বাফার থেকে পুনরায় চেষ্টা করা।

এই পৃষ্ঠার বাকি অংশ দেখায় ভাঙা স্ট্রিম কেমন দেখায়, দুটি Python হ্যান্ডলার (Anthropic Messages এবং OpenAI SDK), এবং একটি পুনরায় চেষ্টা যা উত্তরের শুরুটা দুবার প্রিন্ট করে না। উভয় হ্যান্ডলার API এন্ডপয়েন্ট হিসেবে CheapTokens ব্যবহার করে: ডকুমেন্টেশন-এ বেস URL আছে, এবং নমুনা মডেল হলো DeepSeek-V4-Flash (আইডি deepseek-ai/DeepSeek-V4-Flash-0731, 380K কনটেক্সট, প্রতি উত্তরে সর্বোচ্চ 32,768 আউটপুট টোকেন, যেকোনো সময়ে একই দাম — দেখুন মডেল পৃষ্ঠা)।

একটি 200 আসলে কী প্রতিশ্রুতি দেয়#

একজন কুরিয়ারের কথা ভাবুন যে দরজায় পার্সেলের জন্য সাইন করে: স্বাক্ষর প্রমাণ করে সে পৌঁছেছে, পার্সেল অক্ষত আছে তা নয়। HTTP একইভাবে কাজ করে। সার্ভার প্রথমে স্ট্যাটাস লাইন এবং হেডার পাঠায়, তারপর বডি টুকরো টুকরো করে আসে। যদি মডেল ব্যাকএন্ড মাঝপথে ওভারলোড হয়ে যায়, স্ট্যাটাস লাইন ইতিমধ্যে চলে গেছে।

Anthropic-এর API ত্রুটি পৃষ্ঠা (১ নভেম্বর ২০২৬-এ যাচাইকৃত) সরাসরি বলে: যখন আপনি SSE-এর মাধ্যমে একটি স্ট্রিমিং রেসপন্স পান, API 200 ফেরত দেওয়ার পরে একটি ত্রুটি ঘটতে পারে, এবং তখন স্ট্যান্ডার্ড ত্রুটি হ্যান্ডলিং প্রযোজ্য হয় না। এর স্ট্রিমিং ডকুমেন্টেশন সেই ইভেন্টের আকৃতি দেয়:

text
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}

overloaded_error সাধারণত নন-স্ট্রিমিং কলে HTTP 529-এর সাথে মিলে যায় (উচ্চ ট্রাফিক থেকে সাময়িক ওভারলোড)। একই পৃষ্ঠা তালিকাভুক্ত করে 500 api_error (এক্সপোনেনশিয়াল ব্যাকঅফ সহ পুনরায় চেষ্টা) এবং 504 timeout_error (দীর্ঘ অনুরোধের জন্য স্ট্রিমিং ব্যবহার করুন)। ওয়্যারে একটি ভাঙা স্ট্রিম এমন দেখায়:

একটি স্ট্রিম যা 200 OK দিয়ে শুরু হয় এবং message_stop-এর পরিবর্তে event: error দিয়ে শেষ হয়
মিড-স্ট্রিম ত্রুটির অ্যানাটমি: কনটেন্ট ইভেন্টগুলো থেমে যায় এবং message_stop যেখানে থাকার কথা সেখানে event: error আসে (আকৃতি Anthropic-এর স্ট্রিমিং ডকুমেন্টেশন থেকে)

ডকুমেন্টেশন আরও সতর্ক করে যে নতুন ইভেন্ট টাইপ যোগ করা হতে পারে, তাই আপনার কোডের অজানা ইভেন্টগুলো সুন্দরভাবে হ্যান্ডেল করা উচিত। এজন্যই নিচের হ্যান্ডলারগুলো প্রতিটি ত্রুটি বাইট চিনতে চেষ্টা করে না। তারা একটি প্রশ্ন করে: স্ট্রিমটি কি তার ফিনিশ সিগন্যাল দিয়ে শেষ হয়েছে?

  • Anthropic Messages: শেষ ইভেন্ট হলো message_stop, এবং message_delta-এ stop_reason বলে কেন এটি শেষ হয়েছে (end_turn, max_tokens, stop_sequence)।
  • OpenAI Chat Completions: শেষ চাঙ্কে একটি অ-খালি finish_reason থাকে (stop, length, …)।

কোনো ফিনিশ সিগন্যাল নেই, বা পথে একটি এক্সসেপশন — উত্তরটি অসম্পূর্ণ, স্ট্যাটাস যা-ই বলুক না কেন।

Python-এ এটি ধরুন#

একটি সাপোর্ট বট নিন যা ব্রাউজারে উত্তর স্ট্রিম করে। লুপটি টেক্সট একটি বাফারে সংগ্রহ করে এবং তিনটি জিনিস রিপোর্ট করে: এখন পর্যন্ত টেক্সট, স্ট্রিমটি শেষ হয়েছে কিনা, এবং ত্রুটি থাকলে তা। আপনি যে SDK ব্যবহার করেন তার ট্যাব বেছে নিন।

একটি হ্যান্ডলার, দুটি SDK

Anthropic SDK

python
import os
import anthropic

client = anthropic.Anthropic(
    base_url='https://api.cheaptokens.dev',  # no /v1 for the Anthropic SDK
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'

def ask_once(prompt, on_text=lambda t: None):
    parts, finished = [], False
    try:
        with client.messages.stream(
            model=MODEL,
            max_tokens=1024,
            messages=[{'role': 'user', 'content': prompt}],
        ) as stream:
            for event in stream:
                if event.type == 'content_block_delta' and event.delta.type == 'text_delta':
                    parts.append(event.delta.text)
                    on_text(event.delta.text)
                elif event.type == 'message_stop':
                    finished = True
    except anthropic.APIError as err:
        return ''.join(parts), False, err
    return ''.join(parts), finished, None

OpenAI SDK

python
import os
import openai

client = openai.OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'

def ask_once(prompt, on_text=lambda t: None):
    parts, finish = [], None
    try:
        stream = client.chat.completions.create(
            model=MODEL,
            max_tokens=1024,
            stream=True,
            messages=[{'role': 'user', 'content': prompt}],
        )
        for chunk in stream:
            if not chunk.choices:
                continue
            choice = chunk.choices[0]
            if choice.delta.content:
                parts.append(choice.delta.content)
                on_text(choice.delta.content)
            if choice.finish_reason:
                finish = choice.finish_reason
    except openai.APIError as err:
        return ''.join(parts), False, err
    return ''.join(parts), finish is not None, None

দুটি বিবরণ আবার দেখার মতো। প্রথমত, উভয় হ্যান্ডলার SDK-এর বেস ত্রুটি ক্লাস ধরে (anthropic.APIError, openai.APIError), তাই একটি নির্দিষ্ট ব্যর্থতার জন্য SDK কোন সাবক্লাস রেইজ করে তা গুরুত্বপূর্ণ নয়। দ্বিতীয়ত, finished ফ্ল্যাগটি এক্সসেপশন থেকে আলাদা: একটি স্ট্রিম শুধু তাড়াতাড়ি শেষও হতে পারে, কোনো এক্সসেপশন ছাড়াই, এবং সেই ক্ষেত্রটিও ব্যর্থতা হিসেবে গণ্য হতে হবে।

openai-python README যোগ করে OpenAI-এর দিকে কী আশা করবেন (১ নভেম্বর ২০২৬-এ যাচাইকৃত): একটি স্ট্রিমে রিড টাইমআউট APITimeoutError রেইজ করে, অন্যান্য HTTPX ব্যর্থতা APIConnectionError রেইজ করে যেখানে __cause__-এ মূল এক্সসেপশন থাকে। যদি স্ট্রিম ব্যর্থ হওয়ার পরিবর্তে থেমে যায়, দেখুন OpenAI Python SDK-তে টাইমআউট এবং পুনরায় চেষ্টা।

উত্তর দুবার প্রিন্ট না করে পুনরায় চেষ্টা করুন#

এখানে একটি বাগ যা শিপ করা সহজ। প্রথম প্রচেষ্টা "The refund policy is…" প্রিন্ট করে, মারা যায়, পুনরায় চেষ্টা শুরু থেকে শুরু করে এবং আবার "The refund policy is…" প্রিন্ট করে — এখন ব্যবহারকারী শুরুটা দুবার দেখে, বা একটি বাক্য তার নিজের পুনরারম্ভের সাথে আটকে থাকে। সাধারণ কারণ হলো একটি শেয়ার্ড বাফার: answer += text প্রচেষ্টার বাইরে থাকে, তাই দ্বিতীয় চেষ্টা প্রথমটির সাথে যোগ করে।

প্রতিকার বিরক্তিকর। একটি প্রচেষ্টা একটি বাফারের মালিক, একটি ব্যর্থ প্রচেষ্টার টেক্সট ফেলে দেওয়া হয় (বা শুধু লগের জন্য রাখা হয়), এবং পুনরায় চেষ্টা শুরু থেকে শুরু হয়:

python
import time

def ask_with_retry(prompt, attempts=3, on_text=lambda t: None, on_restart=lambda: None):
    for attempt in range(attempts):
        text, finished, err = ask_once(prompt, on_text)
        if finished:
            return text
        status = getattr(err, 'status_code', None)
        if status is not None and 400 <= status < 500 and status not in (408, 409, 429):
            raise err  # a 400/401/404 will not fix itself
        print(f'attempt {attempt + 1} failed after {len(text)} chars: {err!r}')
        if attempt == attempts - 1:
            break
        on_restart()  # clear the message box in your UI
        time.sleep(2 ** attempt)  # 1 s, 2 s
    raise RuntimeError('stream failed after all attempts')

on_restart হলো সেই অংশ যা ডুপ্লিকেট থামায়। একটি ওয়েব UI-তে এটি পরবর্তী প্রচেষ্টা স্ট্রিম হওয়ার আগে মেসেজ বাবল খালি করে। একটি সাধারণ টার্মিনালে আপনি আন-প্রিন্ট করতে পারেন না, তাই হয় finished সত্য হওয়ার পরেই প্রিন্ট করুন, অথবা একটি দৃশ্যমান --- retrying --- লাইন প্রিন্ট করুন। শুরু থেকে পুনরায় চেষ্টা করা পুরো প্রম্পট আবার পাঠায়, তাই নতুন প্রচেষ্টার জন্য ইনপুট আবার বিল হয়; বেশিরভাগ চ্যাট উত্তরের জন্য এটি একটি পরিষ্কার উত্তরের জন্য ন্যায্য মূল্য।

ক্রমে সাজান

পুনরায় চেষ্টার ধাপগুলো আপনার কোডের চালানোর ক্রমে রাখুন

  1. ভাঙা স্ট্রিম পড়া বন্ধ করুন এবং এর আংশিক টেক্সট লগের জন্য রাখুন
  2. ত্রুটি পরীক্ষা করুন: 408, 409 এবং 429 ছাড়া একটি 4xx মানে থামুন, পুনরায় চেষ্টা নয়
  3. মেসেজ বক্স পরিষ্কার করুন এবং একটি নতুন বাফার শুরু করুন
  4. এক্সপোনেনশিয়াল ব্যাকঅফ দিয়ে অপেক্ষা করুন (১ সেকেন্ড, তারপর ২ সেকেন্ড)
  5. সম্পূর্ণ অনুরোধ আবার পাঠান এবং শুধু নতুন উত্তর দেখান

Anthropic-এর SDK ইতিমধ্যে ক্ষণস্থায়ী ব্যর্থতা — কানেকশন ত্রুটি, রেট লিমিট, 5xx — এক্সপোনেনশিয়াল ব্যাকঅফ দিয়ে পুনরায় চেষ্টা করে, ডিফল্টভাবে ২ বার, retry-after মানে, এবং max_retries তা পরিবর্তন করে। এটি অনুরোধটি কভার করে। 200-এর পরে মারা যাওয়া একটি স্ট্রিম ভিন্ন গল্প: ডকুমেন্টেশন বলে সেখানে স্ট্যান্ডার্ড ত্রুটি হ্যান্ডলিং প্রযোজ্য নয়, তাই উপরের লুপটি আপনারই রাখতে হবে। শুধু 429-এর জন্য, একটি আলাদা ওয়াকথ্রু আছে: 429 Too Many Requests: ব্যাকঅফ সহ পুনরায় চেষ্টা।

প্রতিটি তাড়াতাড়ি থামা ত্রুটি নয়#

পুনরায় চেষ্টা করার আগে, উত্তরটি কেন শেষ হয়েছে তা পরীক্ষা করুন। যে স্ট্রিম stop_reason: max_tokens (Anthropic) বা finish_reason: length (OpenAI) দিয়ে শেষ হয় তা শেষ হয়েছে — এটি শুধু আপনার সেট করা সীমায় পৌঁছেছে। এটি পুনরায় চেষ্টা করলে একই কাটা টেক্সট এবং একই বিল ফেরত আসে। পরিবর্তে সীমা বাড়ান; কাটা উত্তরের গাইড এটি নিয়ে আলোচনা করে।

একটি তৃতীয় গ্রুপ আছে: নেটওয়ার্ক। পথে নিষ্ক্রিয় কানেকশন বাদ পড়তে পারে, যা একটি কানেকশন ত্রুটি বা কোনো ইভেন্ট ছাড়াই থেমে যাওয়া স্ট্রিম হিসেবে দেখা যায়। এটি মিড-স্ট্রিম ত্রুটির মতোই হ্যান্ডেল করুন, এবং যদি এটি বারবার ঘটে তবে সাধারণ কানেকশন ত্রুটি দেখুন। দীর্ঘ জেনারেশনের জন্য, Anthropic একটি বিশাল নন-স্ট্রিমিং max_tokens-এর পরিবর্তে স্ট্রিমিং সুপারিশ করে, তাই আপনি ইতিমধ্যে সেটির সঠিক দিকে আছেন।

শিপ করার আগে#

প্রতিটি ব্যর্থ প্রচেষ্টার রিকোয়েস্ট আইডি সংরক্ষণ করুন। Anthropic ত্রুটি বডিতে একটি request_id ফিল্ড এবং একটি request-id রেসপন্স হেডার থাকে, এবং Python SDK message._request_id প্রকাশ করে; সেই আইডি দিয়ে একটি ব্যর্থতা উপাখ্যানের পরিবর্তে ট্রেসযোগ্য হয়।

চেকলিস্ট

স্ট্রিমিং ত্রুটি হ্যান্ডলিং, সঠিকভাবে সম্পন্ন

আপনি যদি হ্যান্ডলারটি চালাতে দেখতে চান, এটি আপনার নিজের কী-তে নির্দেশ করুন: ডকুমেন্টেশনে একটি API কী তৈরি করুন, CHEAPTOKENS_API_KEY সেট করুন, এবং ask_with_retry('Explain SSE in two sentences') কল করুন। Python-এ স্ট্রিমিংয়ের মূল বিষয়গুলো জানতে, Python-এ স্ট্রিমিং রেসপন্স দিয়ে শুরু করুন; Anthropic-এর দিকটি CheapTokens সহ Anthropic Python SDK-তে আলোচনা করা হয়েছে।

পুনরায় চেষ্টার প্রবাহ: স্ট্রিম শুরু হয়, ত্রুটি আসে, আংশিক টেক্সট বাদ পড়ে, অনুরোধ আবার পাঠানো হয়
পাঁচ ধাপে পুনরায় চেষ্টার লুপ: শুরু, ত্রুটি ইভেন্ট, আংশিক বাফার বাদ, ব্যাকঅফ, আবার জিজ্ঞাসা