البث المباشر (SSE)

يرسل البث المباشر الاستجابة أولًا بأول أثناء توليدها، عبر أحداث مرسلة من الخادم (Server-Sent Events). يرى المستخدم النص يظهر فورًا بدل انتظار النهاية : هذا ما يجعل واجهة الدردشة ممتعة.

التفعيل#

أضيفوا "stream": true. لتلقي عدّ الرموز أيضًا، أضيفوا "stream_options": {"include_usage": true}.

from openai import OpenAI

client = OpenAI(api_key="zir_ai_...", base_url="https://api.cloudzir.com/v1")

flux = client.chat.completions.create(
    model="mina-low",
    messages=[{"role": "user", "content": "Explique le DNS en 5 phrases."}],
    stream=True,
    stream_options={"include_usage": True},
)

complet = []
for chunk in flux:
    if chunk.choices and chunk.choices[0].delta.content:
        morceau = chunk.choices[0].delta.content
        complet.append(morceau)
        print(morceau, end="", flush=True)
    if chunk.usage:                      # dernier événement
        print(f"\n\n{chunk.usage.total_tokens} jetons")

خيار -N في curl ضروري : بدونه، يُخزَّن الإخراج مؤقتًا ولن تروا شيئًا قبل النهاية.

صيغة التدفق (واجهة OpenAI)#

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"mina-low",
       "choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"mina-low",
       "choices":[{"index":0,"delta":{"content":"Le "},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"mina-low",
       "choices":[{"index":0,"delta":{"content":"DNS"},"finish_reason":null}]}

: keep-alive

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"mina-low",
       "choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"mina-low",
       "choices":[],"usage":{"prompt_tokens":18,"completion_tokens":96,"total_tokens":114}}

data: [DONE]

نقاط يجب تذكّرها :

  • الأسطر التي تبدأ بـ: هي تعليقات حفاظ على الاتصال (كل 15 ثانية أثناء انتظار محرك). يتجاهلها أي عميل SSE متوافق.
  • لا تظهر القطعة الحاملة لـusage إلا إن طلبتم include_usage ؛ مصفوفة choices فيها فارغة.
  • data: [DONE] تُنهي التدفق دائمًا.

استدعاء الأدوات في البث المباشر#

تصل المعاملات على شكل أجزاء يجب دمجها :

appels = {}
for chunk in flux:
    for choix in chunk.choices or []:
        for tc in choix.delta.tool_calls or []:
            entree = appels.setdefault(tc.index, {"id": "", "name": "", "args": ""})
            if tc.id:
                entree["id"] = tc.id
            if tc.function and tc.function.name:
                entree["name"] = tc.function.name
            if tc.function and tc.function.arguments:
                entree["args"] += tc.function.arguments   # à concaténer !

# À la fin seulement, les arguments forment un JSON valide
import json
for appel in appels.values():
    arguments = json.loads(appel["args"])

الأخطاء أثناء التدفق#

يكون رمز HTTP بالفعل 200 عند بدء التدفق : يصل عطل يقع لاحقًا كأنه كائن error داخل التدفق، متبوعًا بـ[DONE]. تحققوا إذًا من وجود error في كل حدث :

data: {"error":{"message":"Erreur interne.","type":"api_error","param":null,"code":"api_error"}}
data: [DONE]

إعادة الإرسال إلى متصفح#

لا تعرّضوا مفتاحكم أبدًا في جانب العميل. مرروا التدفق عبر خادمكم :

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import httpx, json

app = FastAPI()

@app.post("/chat")
async def chat(payload: dict):
    async def relais():
        async with httpx.AsyncClient(timeout=None) as http:
            async with http.stream(
                "POST",
                "https://api.cloudzir.com/v1/chat/completions",
                headers={"Authorization": f"Bearer {CLE}"},
                json={**payload, "stream": True},
            ) as amont:
                async for ligne in amont.aiter_lines():
                    if ligne:
                        yield f"{ligne}\n\n"

    return StreamingResponse(relais(), media_type="text/event-stream",
                             headers={"Cache-Control": "no-cache",
                                      "X-Accel-Buffering": "no"})
مخازن مؤقتة وسيطة

قد يخزّن وسيط عكسي (nginx) أو شبكة توصيل محتوى (CDN) التدفق مؤقتًا ويلغي كل فائدة البث المباشر. أضيفوا الترويسة X-Accel-Buffering: no وعطّلوا proxy_buffering على المسار المعني.

الفوترة#

يُفوتَر الطلب المقطوع في الطريق عن الجزء المُولَّد. إن لم يُعِد المزوّد حقل usage، تُقدّره CloudZIR وتُشير إليه كتقدير في Usage.

صيغة Anthropic#

يستخدم البث المباشر من جهة /anthropic الأحداث المُسمّاة (message_start، content_block_delta، ...) : انظروا استخدام API الخاصة بـAnthropic.

الإعدادات

استجابة API