Truyền phát
Đặt stream là true và phản hồi đến dưới dạng server-sent events trong khi model viết: suy luận trước, rồi câu trả lời, rồi một chunk cuối với lý do kết thúc và usage. Hãy dùng nó bất cứ khi nào có người đang chờ văn bản, và cho các phản hồi dài.
POST https://api.shannon-ai.com/v1/chat/completions
Các SDK của OpenAI đưa cho bạn từng chunk một. Hãy đọc mỗi chunk theo những gì nó mang: một đoạn suy luận, một đoạn câu trả lời, một lần gọi tool, hoặc phần kết thúc của phản hồi.
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
}' Cách một stream được đóng khung
- Phản hồi có trạng thái
200và các headercontent-type: text/event-streamvàcache-control: no-cache. - Mỗi sự kiện là một dòng bắt đầu bằng
data:và theo sau là một dòng trống. Văn bản saudata:là một chunk JSON. Không có dòngevent:hayid:. - Một dòng bắt đầu bằng dấu hai chấm, như
: ping, là chú thích giữ kết nối mở. Hãy bỏ qua nó. - Stream kết thúc bằng
data: [DONE]. Stream dừng mà không có nó là chưa hoàn chỉnh.
Các chunk theo thứ tự
Mỗi chunk là một đối tượng 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
}
]
} Mọi chunk của một stream mang cùng id, created và model. choices luôn chứa một mục. delta của nó chứa phần mới, và finish_reason là null cho đến chunk cuối cùng.
| Key trong delta | Loại | Mô tả | Được gửi bởi |
|---|---|---|---|
role | string | Luôn là assistant. Nó đến một lần, ở đầu stream. | Các model open-weight hosted, shannon-2-lite, shannon-2-pro |
reasoning_content | string | Một đoạn suy luận của model. Hãy ghép các đoạn theo thứ tự. | Các model open-weight hosted, dòng Shannon 3, shannon-1.6-pro, shannon-coder-1 |
content | string | Một đoạn của câu trả lời. Hãy ghép các đoạn theo thứ tự. | Mọi model |
tool_calls | array | Một lần gọi tool hoàn chỉnh. Xem Tool call deltas. | Mọi model |
Suy luận đến trước, rồi câu trả lời, rồi các lần gọi tool nếu có. Hãy đọc chunk theo các key trong delta của nó, không theo vị trí trong stream: một delta có thể chứa role cùng với đoạn văn bản đầu tiên, và chunk cuối cùng thì rỗng.
Một stream hoàn chỉnh như khi truyền đi. Để giữ các dòng ngắn, id, object, created và model được bỏ khỏi mỗi chunk ở đây.
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] Các delta suy luận
Model có suy luận gửi phần suy luận của nó trong delta.reasoning_content trước câu trả lời. Hãy giữ hai văn bản tách biệt: hiển thị phần suy luận như một phần riêng có thể thu gọn, hoặc bỏ nó đi.
Các model open-weight hosted không gửi suy luận khi reasoning_effort là off, và cũng không gửi khi yêu cầu có response_format.
shannon-2-lite và shannon-2-pro cũng suy luận trước khi trả lời. Trên endpoint này, chúng gửi các dòng chú thích : thinking trong khi suy luận, rồi đến câu trả lời.
reasoning_content không có trong các model có kiểu của SDK OpenAI. Trong Python hãy đọc nó bằng getattr(delta, "reasoning_content", None); trong JavaScript, delta.reasoning_content dùng được nguyên trạng.
Các delta gọi tool
Một lần gọi tool đến nguyên vẹn: một chunk cho mỗi lần gọi, với delta chứa lần gọi cùng chuỗi arguments hoàn chỉnh. index đánh số các lần gọi của phản hồi từ 0. Mã ghép arguments từng đoạn vẫn hoạt động nguyên trạng, vì nó chỉ ghép một đoạn.
{
"tool_calls": [
{
"index": 0,
"id": "call_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Paris\"}"
}
}
]
} Sau các lần gọi, chunk cuối cùng có finish_reason là tool_calls.
Khi yêu cầu có tools, shannon-1.6-lite, shannon-1.6-pro và shannon-coder-1 gửi văn bản câu trả lời thành một delta ở cuối phản hồi.
Chunk cuối cùng
Chunk cuối cùng trước data: [DONE] có delta rỗng, finish_reason và usage của yêu cầu, tất cả trong cùng một chunk. Stream nào cũng có nó; bạn không cần 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
}
} Trên các model open-weight hosted, usage cũng có prompt_tokens_details.cached_tokens và completion_tokens_details.reasoning_tokens.
Sau một yêu cầu gửi web_search: true mà lượt tìm kiếm tìm thấy kết quả, chunk cuối còn mang sources, và một chunk ngay trước nó mang các dấu trích dẫn dưới dạng delta.annotations. Tìm kiếm web tích hợp
| finish_reason | Mô tả | Được gửi bởi |
|---|---|---|
stop | Model đã hoàn tất câu trả lời, hoặc một chuỗi stop đã xuất hiện. | Mọi model |
tool_calls | Phản hồi chứa một hoặc nhiều lần gọi tool. | Mọi model |
length | Phản hồi bị cắt ở giới hạn đầu ra. | Dòng Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
Các trường của usage được mô tả cùng với endpoint. Chat Completions
Các dòng keep-alive
Trong khi model làm việc và chưa có gì để gửi, stream mang các dòng chú thích. Chúng không chứa dữ liệu. Trình phân tích SSE tự bỏ qua chúng; mã đọc các dòng thô phải bỏ qua mọi dòng bắt đầu bằng dấu hai chấm.
| Dòng | Được gửi bởi | Khi nào |
|---|---|---|
: ping | Dòng Shannon 3, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | Mỗi 15 giây. |
: keepalive | Các model open-weight hosted | Mỗi 15 giây trong khi model làm việc. |
: thinking | shannon-2-lite, shannon-2-pro | Trong khi model suy luận. |
Lỗi bên trong stream
Khi một stream đã bắt đầu, trạng thái của nó là 200, nên lỗi đến bên trong stream: một chunk có thành viên error ở chỗ lẽ ra là 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] Sau chunk lỗi, chunk cuối cùng, với finish_reason và usage, và data: [DONE] vẫn theo sau. Hãy kiểm tra error trong mọi chunk. Nếu không kiểm tra, một phản hồi thất bại giữa chừng sẽ trông như đã hoàn tất.
| Loại | Mã | Thông báo | Khi nào |
|---|---|---|---|
api_error | The model backend failed to answer. Please retry. | Model thất bại, hoặc không viết gì. Hãy gửi lại yêu cầu. | |
rate_limit_error | Shannon routes are temporarily busy. Please retry. | Model đang bận vào lúc này. Hãy chờ vài giây rồi gửi lại yêu cầu. | |
invalid_request_error | context_length_exceeded | Cuộc hội thoại dài hơn cửa sổ ngữ cảnh của model. Hãy rút ngắn nó trước khi gửi lại. Nội dung thông báo có thể khác nhau. |
Các model open-weight hosted gửi thành viên error chỉ với type và message.
Các SDK Python và JavaScript của OpenAI nâng một chunk lỗi thành lỗi API trong khi bạn lặp, nên hãy bọc vòng lặp bằng cách xử lý lỗi thông thường của bạn.
Mã trạng thái, nội dung lỗi và việc nên thử lại có trang riêng. Xử lý lỗi
Đóng kết nối
Đóng kết nối dừng việc gửi, không dừng yêu cầu. Model viết phản hồi đến hết và yêu cầu bị tính phí như thể bạn đã đọc toàn bộ. Để trả ít hơn, hãy yêu cầu ít hơn: đặt max_tokens thấp hơn trên model áp dụng nó.
Một stream cũng có thể kết thúc sớm trên đường đến bạn, ví dụ khi đứt mạng hoặc khi một server đang được cập nhật. Khi đó nó dừng mà không có chunk cuối và không có data: [DONE]. Hãy coi phản hồi đó là chưa hoàn chỉnh và gửi lại yêu cầu.
Đọc stream mà không dùng SDK
Một trình đọc đúng làm năm việc: chuyển các lỗi đến trước stream sang cách xử lý lỗi của nó, tách nội dung thành từng dòng, bỏ qua các dòng chú thích, dừng ở data: [DONE], và kiểm tra error trong mỗi chunk.
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); Ví dụ Python dùng gói requests. Ví dụ JavaScript chạy trên Node.js 18 trở lên.
Stream của các định dạng khác
Các endpoint Responses và Messages cũng streaming. Các frame của chúng mang một dòng event: với tên sự kiện và một dòng data: với JSON của nó. Mỗi trang liệt kê các sự kiện của mình theo thứ tự.
| Trong stream | Chat Completions | Responses | Messages |
|---|---|---|---|
| Frame | Các dòng data: | Các dòng event: và data: | Các dòng event: và data: |
| Suy luận đến trong | delta.reasoning_content | response.reasoning_summary_text.delta | thinking_delta |
| Văn bản câu trả lời đến trong | delta.content | response.output_text.delta | text_delta |
| Usage đến trong | Chunk cuối cùng | response.completed | message_delta |
| Stream kết thúc bằng | Chunk cuối cùng, rồi data: [DONE] | response.completed | message_stop |