البث
اضبط stream على true فيصل الرد كأحداث يرسلها الخادم (server-sent events) أثناء كتابة النموذج له: الاستدلال أولاً، ثم الإجابة، ثم جزء أخير فيه سبب الانتهاء والاستخدام. استخدمه كلما كان شخص ينتظر النص، وفي الردود الطويلة.
POST https://api.shannon-ai.com/v1/chat/completions
تعطيك OpenAI SDKs الأجزاء واحداً واحداً. اقرأ كل جزء بحسب ما يحمله: قطعة استدلال، أو قطعة من الإجابة، أو استدعاء أداة، أو نهاية الرد.
from openai import OpenAI, APIError
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1",
)
stream = client.chat.completions.create(
model="shannon-3",
messages=[{"role": "user", "content": "Explain server-sent events in three sentences."}],
stream=True,
)
answer, reasoning, tool_calls = [], [], []
finish_reason = usage = None
try:
for chunk in stream:
if chunk.usage:
usage = chunk.usage
if not chunk.choices:
continue
choice = chunk.choices[0]
delta = choice.delta
thought = getattr(delta, "reasoning_content", None)
if thought:
reasoning.append(thought)
if delta.content:
answer.append(delta.content)
print(delta.content, end="", flush=True)
for call in delta.tool_calls or []:
tool_calls.append((call.id, call.function.name, call.function.arguments))
if choice.finish_reason:
finish_reason = choice.finish_reason
except APIError as error:
# A failure after the stream started arrives as an error chunk.
print(f"\nstream failed: {error}")
print(f"\n{finish_reason} {usage}") import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com/v1",
});
const stream = await client.chat.completions.create({
model: "shannon-3",
messages: [{ role: "user", content: "Explain server-sent events in three sentences." }],
stream: true,
});
let answer = "";
let reasoning = "";
const toolCalls = [];
let finishReason = null;
let usage = null;
try {
for await (const chunk of stream) {
if (chunk.usage) usage = chunk.usage;
const choice = chunk.choices?.[0];
if (!choice) continue;
const delta = choice.delta;
if (delta.reasoning_content) reasoning += delta.reasoning_content;
if (delta.content) {
answer += delta.content;
process.stdout.write(delta.content);
}
for (const call of delta.tool_calls ?? []) {
toolCalls.push({ id: call.id, name: call.function.name, arguments: call.function.arguments });
}
if (choice.finish_reason) finishReason = choice.finish_reason;
}
} catch (error) {
// A failure after the stream started arrives as an error chunk.
console.error("\nstream failed:", error.message);
}
console.log("\n", finishReason, usage); curl -N https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "shannon-3",
"messages": [{"role": "user", "content": "Explain server-sent events in three sentences."}],
"stream": true
}' كيف يُؤطَّر البث
- للرد الحالة
200والترويستانcontent-type: text/event-streamوcache-control: no-cache. - كل حدث سطر واحد يبدأ بـ
data:ويتبعه سطر فارغ. والنص بعدdata:هو جزء JSON واحد. ولا توجد أسطرevent:أوid:. - السطر الذي يبدأ بنقطتين رأسيتين، مثل
: ping، تعليق يُبقي الاتصال مفتوحاً. تخطَّه. - ينتهي البث بـ
data: [DONE]. والبث الذي يتوقف بدونه غير مكتمل.
الأجزاء بالترتيب
كل جزء كائن chat.completion.chunk:
{
"id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"object": "chat.completion.chunk",
"created": 1791625200,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {
"content": "Server-sent"
},
"finish_reason": null
}
]
} تحمل كل أجزاء البث الواحد قيم id وcreated وmodel نفسها. وchoices يحتوي دائماً على إدخال واحد. ويحمل delta فيه ما هو جديد، وتبقى finish_reason بقيمة null حتى الجزء الأخير.
| المفتاح في delta | النوع | الوصف | يرسله |
|---|---|---|---|
role | string | دائماً assistant. ويصل مرة واحدة، في بداية البث. | النماذج المفتوحة الأوزان المستضافة، وshannon-2-lite، وshannon-2-pro |
reasoning_content | string | قطعة من استدلال النموذج. اربط القطع بالترتيب. | النماذج المفتوحة الأوزان المستضافة، وعائلة Shannon 3، وshannon-1.6-pro، وshannon-coder-1 |
content | string | قطعة من الإجابة. اربط القطع بالترتيب. | كل النماذج |
tool_calls | array | استدعاء أداة كامل واحد. انظر دلتا استدعاء الأداة. | كل النماذج |
يأتي الاستدلال أولاً، ثم الإجابة، ثم أي استدعاءات أدوات. اقرأ الجزء حسب المفاتيح في delta الخاص به، لا حسب موضعه في البث: فقد يحمل delta الحقل role مع أول قطعة نص، وآخر جزء يكون فارغاً.
بث كامل كما ينتقل. ولإبقاء الأسطر قصيرة، حُذفت id وobject وcreated وmodel من كل جزء هنا.
data: {"choices":[{"index":0,"delta":{"reasoning_content":"Three sentences:"},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{"reasoning_content":" what it is, how it is framed, why it is used."},"finish_reason":null}]}
: ping
data: {"choices":[{"index":0,"delta":{"content":"Server-sent"},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{"content":" events let a server push text to a client over one HTTP reply."},"finish_reason":null}]}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":1190,"completion_tokens":84,"total_tokens":1274}}
data: [DONE] دلتا الاستدلال
النموذج الذي يستدل يرسل استدلاله في delta.reasoning_content قبل الإجابة. أبقِ النصين منفصلين: اعرض الاستدلال كجزء مستقل قابل للطي، أو احذفه.
لا ترسل النماذج المفتوحة الأوزان المستضافة استدلالاً عندما تكون reasoning_effort هي off، ولا ترسله عندما يحتوي الطلب على response_format.
يستدل shannon-2-lite وshannon-2-pro قبل أن يجيبا أيضاً. وعلى نقطة النهاية هذه يرسلان أسطر تعليق : thinking أثناء ذلك، ثم الإجابة.
reasoning_content ليس في النماذج المصنَّفة (typed models) في OpenAI SDKs. في Python اقرأه بـ getattr(delta, "reasoning_content", None)؛ وفي JavaScript يعمل delta.reasoning_content كما هو.
دلتا استدعاء الأداة
يصل استدعاء الأداة كاملاً: جزء واحد لكل استدعاء، يحمل delta فيه الاستدعاء مع السلسلة arguments كاملة. ويرقّم index استدعاءات الرد بدءاً من 0. والكود الذي يجمع arguments قطعة قطعة يعمل دون تغيير، لأنه يجمع قطعة واحدة.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} بعد الاستدعاءات، يحمل الجزء الأخير finish_reason بقيمة tool_calls.
عندما يحتوي الطلب على tools، يرسل shannon-1.6-lite وshannon-1.6-pro وshannon-coder-1 نص الإجابة كدلتا واحدة في نهاية الرد.
الجزء الأخير
آخر جزء قبل data: [DONE] فيه delta فارغ وfinish_reason وusage الطلب، كلها في الجزء نفسه. ويوجد في كل بث؛ ولا تحتاج إلى stream_options.
{
"id": "chatcmpl-5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"object": "chat.completion.chunk",
"created": 1791625200,
"model": "shannon-3",
"choices": [
{
"index": 0,
"delta": {},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 1190,
"completion_tokens": 84,
"total_tokens": 1274
}
} في النماذج المفتوحة الأوزان المستضافة يحتوي usage أيضاً على prompt_tokens_details.cached_tokens وcompletion_tokens_details.reasoning_tokens.
بعد طلب يتضمن web_search: true ووجد بحثه شيئاً، يحمل الجزء الأخير أيضاً sources، ويحمل الجزء الذي قبله علامات الاستشهاد بصيغة delta.annotations. بحث ويب مدمج
| finish_reason | الوصف | يرسله |
|---|---|---|
stop | أنهى النموذج إجابته، أو ظهرت سلسلة stop. | كل النماذج |
tool_calls | يحتوي الرد على استدعاء أداة واحد أو أكثر. | كل النماذج |
length | قُطع الرد عند حد المخرجات. | عائلة Shannon 3، وshannon-1.6-lite، وshannon-1.6-pro، وshannon-coder-1 |
حقول usage موصوفة مع نقطة النهاية. Chat Completions
أسطر keep-alive
بينما يعمل النموذج وليس لديه ما يرسله بعد، يحمل البث أسطر تعليق. ولا بيانات فيها. ويتخطاها محلل SSE تلقائياً؛ أما الكود الذي يقرأ الأسطر الخام فعليه تخطي كل سطر يبدأ بنقطتين رأسيتين.
| السطر | يرسله | متى |
|---|---|---|
: ping | عائلة Shannon 3، وshannon-1.6-lite، وshannon-1.6-pro، وshannon-coder-1 | كل 15 ثانية. |
: keepalive | النماذج المفتوحة الأوزان المستضافة | كل 15 ثانية أثناء عمل النموذج. |
: thinking | shannon-2-lite، shannon-2-pro | أثناء استدلال النموذج. |
الأخطاء داخل البث
بعد أن يبدأ البث تكون حالته 200، ولذلك يصل الفشل داخل البث: جزء فيه عضو error في مكان choices.
data: {"error":{"message":"The model backend failed to answer. Please retry.","type":"api_error","code":null,"param":null}}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":1190,"completion_tokens":12,"total_tokens":1202}}
data: [DONE] بعد جزء الخطأ يتبع مع ذلك الجزء الأخير، مع finish_reason وusage، ثم data: [DONE]. افحص كل جزء بحثاً عن error. ودون هذا الفحص يبدو الرد الذي فشل في منتصفه كأنه مكتمل.
| النوع | الرمز | الرسالة | متى |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | فشل النموذج، أو لم يكتب شيئاً. أعد إرسال الطلب. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | النموذج مشغول في هذه اللحظة. انتظر بضع ثوانٍ وأعد إرسال الطلب. | |
invalid_request_error | context_length_exceeded | المحادثة أطول من نافذة سياق النموذج. اختصرها قبل أن ترسلها مجدداً. ويتفاوت نص الرسالة. |
ترسل النماذج المفتوحة الأوزان المستضافة العضو error مع type وmessage فقط.
يُطلق OpenAI Python SDK وJavaScript SDK جزء الخطأ كخطأ API أثناء التكرار، فضع الحلقة داخل معالجة الأخطاء المعتادة لديك.
لرموز الحالة ونص الخطأ وما ينبغي إعادة محاولته صفحتها الخاصة. معالجة الأخطاء
إغلاق الاتصال
إغلاق الاتصال يوقف التسليم، لا الطلب. يكتب النموذج الرد حتى نهايته ويُحاسَب الطلب كأنك قرأته كله. وللدفع أقل، اطلب أقل: اضبط max_tokens أقل في نموذج يطبّقه.
قد ينتهي البث مبكراً أيضاً في طريقه إليك، مثلاً عند انقطاع الشبكة أو أثناء تحديث خادم. عندئذٍ يتوقف دون الجزء الأخير ودون data: [DONE]. اعتبر ذلك الرد غير مكتمل وأعد إرسال الطلب.
قراءة البث بدون SDK
القارئ الصحيح يفعل خمسة أشياء: يرسل الأخطاء التي تصل قبل البث إلى معالجة الأخطاء لديه، ويقسّم النص إلى أسطر، ويتخطى أسطر التعليق، ويتوقف عند data: [DONE]، ويفحص كل جزء بحثاً عن error.
import json
import requests
response = requests.post(
"https://api.shannon-ai.com/v1/chat/completions",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"model": "shannon-3",
"messages": [{"role": "user", "content": "Explain server-sent events in three sentences."}],
"stream": True,
},
stream=True,
)
# 1. Errors before the stream are plain JSON with their own status.
if response.status_code != 200:
raise RuntimeError(f"{response.status_code}: {response.text}")
answer, done = [], False
for raw in response.iter_lines(): # 2. one line at a time
line = raw.decode("utf-8")
if not line.startswith("data: "): # 3. empty lines and ": ping" comments
continue
data = line[len("data: "):]
if data == "[DONE]": # 4. the end of the stream
done = True
break
chunk = json.loads(data)
if "error" in chunk: # 5. a failure inside the stream
raise RuntimeError(chunk["error"]["message"])
delta = chunk["choices"][0]["delta"]
if delta.get("content"):
answer.append(delta["content"])
if chunk.get("usage"):
print(chunk["choices"][0]["finish_reason"], chunk["usage"])
if not done:
raise RuntimeError("the stream ended before [DONE]")
print("".join(answer)) const response = await fetch("https://api.shannon-ai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "shannon-3",
messages: [{ role: "user", content: "Explain server-sent events in three sentences." }],
stream: true,
}),
});
// 1. Errors before the stream are plain JSON with their own status.
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const decoder = new TextDecoder();
let buffer = "";
let answer = "";
let done = false;
read: for await (const bytes of response.body) {
buffer += decoder.decode(bytes, { stream: true });
const lines = buffer.split("\n"); // 2. one line at a time
buffer = lines.pop(); // keep the unfinished line
for (const line of lines) {
if (!line.startsWith("data: ")) continue; // 3. empty lines and ": ping" comments
const data = line.slice(6);
if (data === "[DONE]") { // 4. the end of the stream
done = true;
break read;
}
const chunk = JSON.parse(data);
if (chunk.error) throw new Error(chunk.error.message); // 5. a failure inside the stream
const choice = chunk.choices[0];
if (choice.delta.content) answer += choice.delta.content;
if (chunk.usage) console.log(choice.finish_reason, chunk.usage);
}
}
if (!done) throw new Error("the stream ended before [DONE]");
console.log(answer); يستخدم مثال Python الحزمة requests. ويعمل مثال JavaScript على Node.js 18 أو أحدث.
البث في الصيغ الأخرى
تبث نقطتا نهاية Responses وMessages أيضاً. وتحمل إطاراتهما سطر event: فيه اسم الحدث وسطر data: فيه JSON الخاص به. وتسرد كل صفحة أحداثها بالترتيب.
| في البث | Chat Completions | Responses | Messages |
|---|---|---|---|
| الإطارات | أسطر data: | أسطر event: وdata: | أسطر event: وdata: |
| يصل الاستدلال في | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| يصل نص الإجابة في | delta.content | response.output_text.delta | text_delta |
| يصل الاستخدام في | الجزء الأخير | response.completed | message_delta |
| ينتهي البث بـ | الجزء الأخير، ثم data: [DONE] | response.completed | message_stop |