ស្ទ្រីម
កំណត់ stream ជា true ហើយចម្លើយមកដល់ជា server-sent event ខណៈពេលម៉ូដែលសរសេរវា៖ reasoning មុន បន្ទាប់មកចម្លើយ បន្ទាប់មក chunk ចុងក្រោយដែលមាន finish reason និង usage។ សូមប្រើវាគ្រប់ពេលដែលមនុស្សកំពុងរង់ចាំអត្ថបទ និងសម្រាប់ចម្លើយវែង។
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI SDK ផ្តល់ chunk ឱ្យអ្នកម្តងមួយ។ សូមអានរាល់ chunk តាមអ្វីដែលវាផ្ទុក៖ បំណែកនៃ reasoning បំណែកនៃចម្លើយ ការហៅ tool ឬចុងបញ្ចប់នៃចម្លើយ។
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
}' របៀបដែល stream ត្រូវបានចែកជា frame
- ចម្លើយមាន status
200និង headercontent-type: text/event-streamនិងcache-control: no-cache។ - រាល់ event គឺជាបន្ទាត់មួយដែលចាប់ផ្តើមដោយ
data:ហើយតាមពីក្រោយដោយបន្ទាត់ទទេ។ អត្ថបទក្រោយdata:គឺជា JSON chunk មួយ។ គ្មានបន្ទាត់event:ឬid:ទេ។ - បន្ទាត់ដែលចាប់ផ្តើមដោយសញ្ញាចុចពីរ ដូចជា
: pingគឺជា comment ដែលរក្សាការតភ្ជាប់ឱ្យបើក។ សូមរំលងវា។ - stream បញ្ចប់ដោយ
data: [DONE]។ stream ដែលឈប់ដោយគ្មានវាគឺមិនពេញលេញ។
Chunk តាមលំដាប់
រាល់ chunk គឺជា object ប្រភេទ 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
}
]
} រាល់ chunk នៃ stream មួយមាន id, created និង model ដូចគ្នា។ choices តែងតែមានធាតុមួយ។ delta របស់វាមានអ្វីដែលថ្មី ហើយ finish_reason គឺ null រហូតដល់ chunk ចុងក្រោយ។
| Key ក្នុង delta | ប្រភេទ | ការពិពណ៌នា | ផ្ញើដោយ |
|---|---|---|---|
role | string | តែងតែជា assistant។ វាមកដល់មួយដងនៅដើម stream។ | ម៉ូដែល open-weight ដែលបង្ហោះ, shannon-2-lite, shannon-2-pro |
reasoning_content | string | បំណែកមួយនៃ reasoning របស់ម៉ូដែល។ សូមភ្ជាប់បំណែកតាមលំដាប់។ | ម៉ូដែល open-weight ដែលបង្ហោះ ក្រុម Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | បំណែកមួយនៃចម្លើយ។ សូមភ្ជាប់បំណែកតាមលំដាប់។ | គ្រប់ម៉ូដែល |
tool_calls | array | ការហៅ tool ពេញលេញមួយ។ សូមមើល Tool call delta។ | គ្រប់ម៉ូដែល |
Reasoning មកមុន បន្ទាប់មកចម្លើយ បន្ទាប់មកការហៅ tool ណាមួយ។ សូមអាន chunk តាម key ក្នុង delta របស់វា មិនមែនតាមទីតាំងរបស់វាក្នុង stream ទេ៖ delta អាចមាន role រួមជាមួយបំណែកអត្ថបទដំបូង ហើយចុងក្រោយគឺទទេ។
stream ទាំងមូលដូចដែលវាធ្វើដំណើរ។ ដើម្បីរក្សាបន្ទាត់ឱ្យខ្លី id, object, created និង model ត្រូវបានទុកចោលពីរាល់ chunk នៅទីនេះ។
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] Reasoning delta
ម៉ូដែលដែលវែកញែកផ្ញើ reasoning របស់វាក្នុង delta.reasoning_content មុនចម្លើយ។ សូមរក្សាអត្ថបទទាំងពីរដាច់ពីគ្នា៖ បង្ហាញ reasoning ជាផ្នែកដាច់ដោយឡែកដែលអាចបត់បាន ឬទុកវាចោល។
ម៉ូដែល open-weight ដែលបង្ហោះមិនផ្ញើ reasoning នៅពេល reasoning_effort គឺ off ហើយក៏មិនផ្ញើនៅពេល request មាន response_format ដែរ។
shannon-2-lite និង shannon-2-pro ក៏វែកញែកមុនពេលឆ្លើយដែរ។ លើ endpoint នេះពួកវាផ្ញើបន្ទាត់ comment : thinking ខណៈពេលពួកវាវែកញែក ហើយបន្ទាប់មកជាចម្លើយ។
reasoning_content មិនមានក្នុង typed model របស់ OpenAI SDK ទេ។ ក្នុង Python សូមអានវាដោយ getattr(delta, "reasoning_content", None)៖ ក្នុង JavaScript delta.reasoning_content ដំណើរការដូចដែលវាជា។
Tool call delta
ការហៅ tool មកដល់ទាំងមូល៖ មួយ chunk ក្នុងមួយការហៅ ដែល delta របស់វាមានការហៅជាមួយ string arguments ពេញលេញ។ index ដាក់លេខការហៅនៃចម្លើយចាប់ពី 0។ កូដដែលភ្ជាប់ arguments ម្តងមួយបំណែកដំណើរការដដែល ព្រោះវាភ្ជាប់តែមួយបំណែក។
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} បន្ទាប់ពីការហៅ chunk ចុងក្រោយមាន finish_reason គឺ tool_calls។
នៅពេល request មាន tools shannon-1.6-lite, shannon-1.6-pro និង shannon-coder-1 ផ្ញើអត្ថបទចម្លើយជា delta មួយនៅចុងចម្លើយ។
Chunk ចុងក្រោយ
chunk ចុងក្រោយមុន data: [DONE] មាន delta ទទេ finish_reason និង usage នៃ request ទាំងអស់ក្នុង chunk តែមួយ។ រាល់ stream មានវា៖ អ្នកមិនត្រូវការ 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
}
} លើម៉ូដែល open-weight ដែលបង្ហោះ usage ក៏មាន prompt_tokens_details.cached_tokens និង completion_tokens_details.reasoning_tokens ដែរ។
បន្ទាប់ពី request ដែលមាន web_search: true ហើយការស្វែងរករបស់វារកឃើញអ្វីមួយ chunk ចុងក្រោយក៏មាន sources ដែរ ហើយ chunk មួយមុននោះផ្ទុកសញ្ញាដកស្រង់ជា delta.annotations។ ស្វែងរកវេបក្នុងស្រាប់
| finish_reason | ការពិពណ៌នា | ផ្ញើដោយ |
|---|---|---|
stop | ម៉ូដែលបានបញ្ចប់ចម្លើយរបស់វា ឬ string stop បានលេចឡើង។ | គ្រប់ម៉ូដែល |
tool_calls | ចម្លើយមានការហៅ tool មួយ ឬច្រើន។ | គ្រប់ម៉ូដែល |
length | ចម្លើយត្រូវបានកាត់នៅដែនកំណត់ output។ | ក្រុម Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
field នៃ usage ត្រូវបានពិពណ៌នាជាមួយ endpoint។ Chat Completions
បន្ទាត់ keep-alive
ខណៈពេលម៉ូដែលធ្វើការ ហើយមិនទាន់មានអ្វីត្រូវផ្ញើ stream មានបន្ទាត់ comment។ ពួកវាមិនមានទិន្នន័យទេ។ SSE parser រំលងពួកវាដោយខ្លួនឯង៖ កូដដែលអានបន្ទាត់ដើមត្រូវតែរំលងរាល់បន្ទាត់ដែលចាប់ផ្តើមដោយសញ្ញាចុចពីរ។
| បន្ទាត់ | ផ្ញើដោយ | ពេលណា |
|---|---|---|
: ping | ក្រុម Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | រៀងរាល់ 15 វិនាទី។ |
: keepalive | ម៉ូដែល open-weight ដែលបង្ហោះ | រៀងរាល់ 15 វិនាទី ខណៈពេលម៉ូដែលធ្វើការ។ |
: thinking | shannon-2-lite, shannon-2-pro | ខណៈពេលម៉ូដែលវែកញែក។ |
កំហុសក្នុង stream
នៅពេល stream ចាប់ផ្តើម status របស់វាគឺ 200 ដូច្នេះការបរាជ័យមកដល់ក្នុង stream៖ chunk ដែលមាន member 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] បន្ទាប់ពី error chunk chunk ចុងក្រោយ ដែលមាន finish_reason និង usage និង data: [DONE] នៅតែតាមក្រោយ។ សូមពិនិត្យរាល់ chunk រក error។ បើគ្មានការពិនិត្យនោះ ចម្លើយដែលបរាជ័យពាក់កណ្តាលផ្លូវមើលទៅពេញលេញ។
| ប្រភេទ | កូដ | សារ | ពេលណា |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | ម៉ូដែលបរាជ័យ ឬមិនបានសរសេរអ្វីទេ។ សូមផ្ញើ request ម្តងទៀត។ | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | ម៉ូដែលរវល់ក្នុងពេលនេះ។ សូមរង់ចាំពីរបីវិនាទី ហើយផ្ញើ request ម្តងទៀត។ | |
invalid_request_error | context_length_exceeded | ការសន្ទនាវែងជាង context window របស់ម៉ូដែល។ សូមបង្រួមវាមុនពេលអ្នកផ្ញើម្តងទៀត។ អត្ថបទនៃសារប្រែប្រួល។ |
ម៉ូដែល open-weight ដែលបង្ហោះផ្ញើ member error ដែលមានតែ type និង message ប៉ុណ្ណោះ។
OpenAI Python និង JavaScript SDK បោះ error chunk ជា API error ខណៈពេលអ្នក iterate ដូច្នេះសូមរុំ loop ក្នុងការគ្រប់គ្រង error ធម្មតារបស់អ្នក។
កូដ status ខ្លឹមសារ error និងអ្វីដែលត្រូវព្យាយាមម្តងទៀតមានទំព័រផ្ទាល់ខ្លួន។ ការគ្រប់គ្រងកំហុស
ការបិទការតភ្ជាប់
ការបិទការតភ្ជាប់បញ្ឈប់ការបញ្ជូន មិនមែន request ទេ។ ម៉ូដែលសរសេរចម្លើយដល់ចុង ហើយ request ត្រូវបានគិតថ្លៃដូចជាអ្នកបានអានទាំងអស់។ ដើម្បីបង់តិចជាង សូមសុំតិចជាង៖ កំណត់ max_tokens ទាបជាងលើម៉ូដែលដែលអនុវត្តវា។
stream ក៏អាចបញ្ចប់មុនពេលលើផ្លូវមករកអ្នក ឧទាហរណ៍នៅពេលបណ្តាញដាច់ ឬខណៈពេល server កំពុងត្រូវបានធ្វើបច្ចុប្បន្នភាព។ ពេលនោះវាឈប់ដោយគ្មាន chunk ចុងក្រោយ និងគ្មាន data: [DONE]។ សូមចាត់ទុកចម្លើយនោះថាមិនពេញលេញ ហើយផ្ញើ request ម្តងទៀត។
ការអាន stream ដោយគ្មាន SDK
អ្នកអានត្រឹមត្រូវធ្វើប្រាំយ៉ាង៖ វាបញ្ជូន error ដែលមកដល់មុន stream ទៅការគ្រប់គ្រង error របស់វា បំបែកខ្លឹមសារជាបន្ទាត់ រំលងបន្ទាត់ comment ឈប់នៅ data: [DONE] និងពិនិត្យរាល់ chunk រក 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 ឬក្រោយនេះ។
Stream នៃទ្រង់ទ្រាយផ្សេងទៀត
endpoint Responses និង Messages ក៏ stream ដែរ។ frame របស់ពួកវាមានបន្ទាត់ event: ដែលមានឈ្មោះ event និងបន្ទាត់ data: ដែលមាន JSON របស់វា។ ទំព័រនីមួយៗរាយ event របស់វាតាមលំដាប់។
| ក្នុង stream | Chat Completions | Responses | Messages |
|---|---|---|---|
| Frame | បន្ទាត់ data: | បន្ទាត់ event: និង data: | បន្ទាត់ event: និង data: |
| Reasoning មកដល់ក្នុង | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| អត្ថបទចម្លើយមកដល់ក្នុង | delta.content | response.output_text.delta | text_delta |
| Usage មកដល់ក្នុង | chunk ចុងក្រោយ | response.completed | message_delta |
| stream បញ្ចប់ដោយ | chunk ចុងក្រោយ បន្ទាប់មក data: [DONE] | response.completed | message_stop |