სტრიმინგი
დააყენეთ stream მნიშვნელობაზე true და პასუხი server-sent events-ის სახით მოვა, სანამ მოდელი წერს: ჯერ მსჯელობა, შემდეგ პასუხი, შემდეგ ბოლო ნაწილი დასრულების მიზეზითა და usage-ით. გამოიყენეთ ეს ყოველთვის, როცა ადამიანი ტექსტს ელოდება, და გრძელი პასუხებისთვის.
POST https://api.shannon-ai.com/v1/chat/completions
OpenAI SDK-ები ნაწილებს სათითაოდ გაძლევენ. თითოეული ნაწილი იმის მიხედვით წაიკითხეთ, რასაც ის შეიცავს: მსჯელობის ნაწილს, პასუხის ნაწილს, ინსტრუმენტის გამოძახებას ან პასუხის დასასრულს.
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. მოდის ერთხელ, სტრიმის დასაწყისში. | Hosted open-weight მოდელები, shannon-2-lite, shannon-2-pro |
reasoning_content | string | მოდელის მსჯელობის ნაწილი. ნაწილები თანმიმდევრობით გააერთიანეთ. | Hosted open-weight მოდელები, Shannon 3-ის ოჯახი, shannon-1.6-pro, shannon-coder-1 |
content | string | პასუხის ნაწილი. ნაწილები თანმიმდევრობით გააერთიანეთ. | ყველა მოდელი |
tool_calls | array | ერთი სრული ინსტრუმენტის გამოძახება. იხილეთ Tool call deltas. | ყველა მოდელი |
ჯერ მსჯელობა მოდის, შემდეგ პასუხი, შემდეგ ინსტრუმენტების გამოძახებები. ნაწილი წაიკითხეთ მისი 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-ში აგზავნის პასუხამდე. ორი ტექსტი ერთმანეთისგან განაცალკევეთ: მსჯელობა აჩვენეთ ცალკე, ჩაკეცვად ნაწილად, ან გამოტოვეთ.
hosted open-weight მოდელები მსჯელობას არ აგზავნიან, როცა reasoning_effort არის off, და არც მაშინ, როცა მოთხოვნას აქვს response_format.
shannon-2-lite და shannon-2-pro პასუხამდე ასევე მსჯელობენ. ამ endpoint-ზე, სანამ ისინი მსჯელობენ, კომენტარის ხაზებს : thinking აგზავნიან, შემდეგ კი პასუხს.
reasoning_content OpenAI SDK-ების ტიპიზებულ მოდელებში არ არის. Python-ში წაიკითხეთ getattr(delta, "reasoning_content", None)-ით; JavaScript-ში delta.reasoning_content უცვლელად მუშაობს.
Tool call დელტები
ინსტრუმენტის გამოძახება მოდის მთლიანად: თითო გამოძახებაზე ერთი ნაწილი, რომლის 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
}
} hosted open-weight მოდელებზე 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-ის ველები endpoint-თან ერთად არის აღწერილი. Chat Completions
Keep-alive ხაზები
სანამ მოდელი მუშაობს და გასაგზავნი ჯერ არაფერი აქვს, სტრიმი კომენტარის ხაზებს შეიცავს. მონაცემები მათში არ არის. SSE პარსერი მათ თავად გამოტოვებს; კოდმა, რომელიც ნედლ ხაზებს კითხულობს, უნდა გამოტოვოს ყოველი ხაზი, რომელიც ორწერტილით იწყება.
| ხაზი | ვინ აგზავნის | როდის |
|---|---|---|
: ping | Shannon 3-ის ოჯახი, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | ყოველ 15 წამში. |
: keepalive | Hosted open-weight მოდელები | ყოველ 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 | საუბარი მოდელის კონტექსტის ფანჯარაზე გრძელია. შეამოკლეთ, სანამ ხელახლა გააგზავნით. შეტყობინების ტექსტი იცვლება. |
hosted open-weight მოდელები error წევრს მხოლოდ type-ითა და message-ით აგზავნიან.
OpenAI Python და 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 endpoint-ებიც სტრიმინგს უჭერენ მხარს. მათი ფრეიმები შეიცავს ხაზს 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 |
| Usage მოდის | ბოლო ნაწილი | response.completed | message_delta |
| სტრიმი მთავრდება | ბოლო ნაწილი, შემდეგ data: [DONE] | response.completed | message_stop |