Některé endpointy mohou posílat výsledek průběžně, jak vzniká, místo najednou:
odpověď chatu, když požadavek nastaví "stream": true, a
běh workflow. Všechny streamy používají stejnou obálku,
popsanou zde; co obálka nese, závisí na endpointu. Tato stránka popisuje obálku a stream
chatu; události běhu workflow jsou popsány u
endpointu workflow.
Jak se stream doručuje
Streamovaná odpověď je běžná odpověď HTTP, jejíž tělo přichází po částech:
- Stav
200,Content-Type: text/event-stream; charset=utf-8, chunked transfer encoding (bezContent-Length). - Tělo je posloupnost chunků. Každý chunk je jeden objekt JSON a objekty
jsou zapsány přímo jeden za druhým, bez oddělovače: bez nového řádku,
bez prefixu
data:.
{"timestamp": 1759406400810, "data": "Unit tests", "error": null}{"timestamp": 1759406400834, "data": " catch", "error": null}{"timestamp": 1759406400851, "data": " regressions", "error": null}
Navzdory content type tělo není ve formátu Server-Sent Events, takže ho
EventSource ani klientské knihovny SSE přečíst nedokážou (a EventSource stejně
neumí poslat POST s tělem). Čtěte tělo jako proud bajtů a na objekty JSON si ho
rozdělte sami, viz Čtení streamu. Čtení ze sítě může skončit
uprostřed objektu nebo obsahovat několik objektů, proto vždy používejte buffer.
Chyby zjištěné před zahájením streamu (autentizace, oprávnění, neplatný požadavek,
neznámý model, ...) se vracejí jako běžná chybová odpověď s vlastním stavem HTTP
a tělem {"detail": "..."}, viz Chyby. Než začnete tělo číst
jako stream, zkontrolujte stavový kód.
Chunky
| Pole | Typ | Popis |
|---|---|---|
timestamp |
časové razítko | Kdy chunk vznikl (milisekundy od epochy). |
data |
řetězec, objekt nebo null | Obsah. Jeho tvar závisí na endpointu a u chatu na runner_version. V chybovém chunku null. |
error |
Chyba streamu nebo null | Nastaveno, když požadavek selhal po zahájení streamu; jinak null. |
Keepalive. Když zhruba sekundu nic nevzniklo (model přemýšlí, běží nástroj),
server pošle chunk s data nastaveným na prázdný řetězec "". Tyto chunky
ignorujte; jen brání tomu, aby spojení nebo proxy vypršely. Keepalive se posílá
v každém formátu streamu, takže data může být "" i ve streamech, jejichž obsahem
jsou jinak objekty.
Chyby ve streamu
Jakmile stream začal, stav HTTP už je 200. Selhání po tomto okamžiku se pošle jako
poslední chunk s nastaveným error a stream skončí.
{"timestamp": 1759406402117, "data": null, "error": {"status": 422, "text": "Validation error", "detail": "not enough user credit"}}
| Pole | Typ | Popis |
|---|---|---|
status |
celé číslo | Stav HTTP, který by chyba měla jako běžná odpověď, například 422 nebo 500. |
text |
řetězec | Krátký název třídy chyby: Validation error, Forbidden, Not found, Unauthorized, Too many requests, Lock error, Internal server error; Internal Server Error u neočekávaných selhání. |
detail |
řetězec | Chybová zpráva, stejný text, jaký nese v detail běžná chybová odpověď. |
Chyby chatu, které mohou přijít tímto způsobem, jsou v tabulce chyb chatu označeny během; nejčastější z nich je vyčerpání kreditů uprostřed odpovědi. Odpověď chatu přerušená chybou se v konverzaci uchová až do místa, kde se zastavila.
Konec streamu
Stream končí, když skončí tělo odpovědi HTTP. Žádný závěrečný chunk „konec“ neexistuje:
- tělo skončilo po chunku s nastaveným
error: požadavek selhal; - tělo skončilo jinak: požadavek se dokončil.
Ve streamu událostí chatu označuje událost done okamžik,
kdy model dokončil odpověď, ale za konec streamu považujte konec těla.
Pokud váš klient zavře spojení předčasně, server přestane odpověď chatu generovat. Dosud vygenerovaná část se v konverzaci uchová a volání modelu, která už proběhla, se účtují. (Běh workflow po odpojení na serveru pokračuje.)
Formáty streamu chatu
Pole požadavku chatu runner_version určuje, co obsahuje data ve streamu chatu:
runner_version |
data |
Použijte, když |
|---|---|---|
"1" (výchozí) |
Řetězec: další část textu odpovědi. | Potřebujete jen text odpovědi. |
"2" |
Objekt události: text, uvažování, činnost nástrojů, stav. | Chcete zobrazovat průběh, uvažování nebo činnost nástrojů odděleně od textu. |
Oba formáty vznikají ze stejného generování a uložená odpověď je stejná; liší se jen to, co se posílá po síti.
Model bez schopnosti stream (viz Modely a nástroje)
lze přesto streamovat: text jeho odpovědi přijde vcelku (jeden textový fragment nebo
jedna událost text), až je hotový, a uloží se jako každá jiná odpověď.
Textový stream (verze 1)
Každé data je fragment odpovědi v Markdownu. Pro celou odpověď fragmenty spojte
v pořadí; keepalive chunky ("") nic nepřidávají.
Volání nástrojů jsou součástí textu, přesně jako v content nestreamované odpovědi:
každé volání nástroje je blok, který začíná značkou
"\n\n### calling AI tool ...\n", pokračuje textem průběhu, který nástroj hlásí,
a končí značkou "\n***\n". Viz Volání nástrojů v odpovědi.
Uvažování, stavové informace a vnitřní kroky sub-asistentů se v tomto formátu
neposílají.
{"timestamp": 1759406400120, "data": "", "error": null}{"timestamp": 1759406400810, "data": "Let me check", "error": null}{"timestamp": 1759406400833, "data": " that.", "error": null}{"timestamp": 1759406401002, "data": "\n\n### calling AI tool ...\n", "error": null}{"timestamp": 1759406401004, "data": "### Scraping URL: https://example.com/rates\n", "error": null}{"timestamp": 1759406402950, "data": "\n***\n", "error": null}{"timestamp": 1759406403410, "data": "The current rate is 24.35 CZK per euro.", "error": null}
Stream událostí (verze 2)
Každé data (kromě keepalive) je objekt události. Všechna pole jsou vždy přítomna;
ta, která se k typu události nevztahují, jsou prázdná ("", null nebo {}).
| Pole | Typ | Popis |
|---|---|---|
type |
řetězec | Typ události, viz Typy událostí. |
delta |
řetězec | Text, který nesou události text, reasoning a tool_call_output; jinak "". |
phase |
řetězec nebo null | Fáze události status, viz Fáze stavu. |
call_id |
řetězec nebo null | Id volání nástroje, ke kterému událost patří (události nástrojů, nested, některé události status). |
tool_name |
řetězec nebo null | Technický název nástroje, stejné id, jaké uvádí Modely a nástroje a na jaké odkazují asistenti. |
tool_display_name |
řetězec nebo null | Název nástroje čitelný pro člověka. |
tool_icon |
řetězec nebo null | Identifikátor ikony, kterou aplikace AYETO pro nástroj používá; může být prázdný. |
tool_status |
řetězec nebo null | Výsledek v tool_call_end: ok nebo error. |
data |
objekt | Doplňující hodnoty některých událostí (stav model_selected, done). Jinak {}. |
chunk |
událost nebo null | Vnitřní událost události nested. |
Celý chunk streamu událostí vypadá takto:
{"timestamp": 1759406400810, "data": {"type": "text", "delta": "Unit tests", "phase": null, "call_id": null, "tool_name": null, "tool_display_name": null, "tool_icon": null, "tool_status": null, "data": {}, "chunk": null}, "error": null}
Typy událostí
type |
Význam | Použitá pole |
|---|---|---|
text |
Další část viditelného textu odpovědi (Markdown). | delta |
reasoning |
Další část uvažování modelu nebo shrnutí jeho přemýšlení, u modelů, které ho zpřístupňují. Není součástí textu odpovědi. | delta |
status |
Požadavek vstoupil do nové fáze. | phase a podle fáze call_id, tool_name, tool_display_name, tool_icon, data |
tool_call_start |
Nástroj se spustil. | call_id, tool_name, tool_display_name, tool_icon |
tool_call_output |
Text průběhu hlášený běžícím nástrojem (Markdown), například vyhledávací dotazy nebo odkaz na vygenerovaný soubor. | call_id, delta |
tool_call_end |
Nástroj skončil. | call_id, tool_status |
nested |
Událost sub-asistenta, kterého asistent zavolal jako nástroj. call_id je id tohoto volání nástroje, chunk je vlastní událost sub-asistenta (libovolného typu, včetně dalšího nested pro hlubší úrovně). |
call_id, chunk |
done |
Model dokončil odpověď. data.stop_reason je důvod od poskytovatele, například stop, end_turn nebo completed, případně null. |
data |
Fáze stavu
phase |
Význam |
|---|---|
context_building |
Požadavek se připravuje: instrukce, nástroje, historie. Obvykle první událost. |
rag_query |
Prohledává se znalostní báze asistenta. |
relevant_history |
Vybírá se relevantní část historie (relevant_history v požadavku). |
model_selected |
Uvádí model, který odpovídá: data.model (id modelu), data.model_name (zobrazovaný název) a, když asistent používá automatický model, data.auto_model (id automatického modelu, který ho vybral). |
model_call |
Modelu byl odeslán požadavek a čeká se na jeho první tokeny. Posílá se znovu po každém kole volání nástrojů. |
context_compacted |
Historie konverzace byla zkrácena, aby se vešla do kontextového okna modelu. |
tool_call_pending |
Model píše volání nástroje; call_id a tool_name ho identifikují. Nástroj se spustí událostí tool_call_start. |
silent_tool |
Běží nástroj, který nemá viditelný výstup; call_id, tool_name, tool_display_name a tool_icon ho identifikují. Žádné události tool_call_* pro něj nenásledují. |
Mohou přibýt nové typy událostí, fáze a klíče data. Hodnoty, které neznáte,
ignorujte.
Pořadí událostí
Typická odpověď, která používá jeden nástroj, vytvoří (bez keepalive, jedno data
na řádek):
{"type": "status", "phase": "context_building", ...}
{"type": "status", "phase": "model_selected", "data": {"model": "gpt-5-mini", "model_name": "GPT-5 mini"}, ...}
{"type": "status", "phase": "model_call", ...}
{"type": "text", "delta": "Let me check", ...}
{"type": "text", "delta": " that.", ...}
{"type": "status", "phase": "tool_call_pending", "call_id": "call_9f2c", "tool_name": "WebScraperFunctionTool", ...}
{"type": "tool_call_start", "call_id": "call_9f2c", "tool_name": "WebScraperFunctionTool", "tool_display_name": "Web Scraper", "tool_icon": "ayeto", ...}
{"type": "tool_call_output", "call_id": "call_9f2c", "delta": "### Scraping URL: https://example.com/rates\n", ...}
{"type": "tool_call_end", "call_id": "call_9f2c", "tool_status": "ok", ...}
{"type": "status", "phase": "model_call", ...}
{"type": "text", "delta": "The current rate is 24.35 CZK per euro.", ...}
{"type": "done", "data": {"stop_reason": "stop"}, ...}
Sestavení textu odpovědi
Chcete-li získat stejný text, jaký obsahuje textový stream nebo uložená zpráva, připojte pro každou událost nejvyšší úrovně:
| Událost | Připojit |
|---|---|
text |
delta |
tool_call_start |
"\n\n### calling AI tool ...\n" |
tool_call_output |
delta |
tool_call_end |
"\n***\n" |
| jakákoli jiná | nic |
Vygenerované soubory (například obrázky) se objevují jako odkazy v Markdownu
v tool_call_output. Po skončení streamu si uloženou odpověď včetně jejích příloh
můžete přečíst přes API Konverzace.
Čtení streamu
Příklady posílají zprávu chatu s runner_version "2", vypisují text odpovědi, jak
přichází, a zastaví se na chybovém chunku. Oba rozdělují tělo na objekty JSON pomocí
malého bufferu, takže fungují bez ohledu na to, jak síť data rozdělí.
Python
import codecs
import json
import os
import uuid
import requests
def iter_chunks(response):
"""Yield the JSON chunks of a streamed response."""
decoder = json.JSONDecoder()
text = codecs.getincrementaldecoder("utf-8")()
buffer = ""
for raw in response.iter_content(chunk_size=None):
buffer += text.decode(raw)
while True:
buffer = buffer.lstrip()
if not buffer:
break
try:
chunk, end = decoder.raw_decode(buffer)
except json.JSONDecodeError:
break # incomplete object, wait for more data
buffer = buffer[end:]
yield chunk
response = requests.post(
"https://ayeto.ai/api/v3/chat",
headers={"uni-api-key": os.environ["AYETO_API_KEY"]},
json={
"conversation_id": str(uuid.uuid4()),
"model": "gpt-5-mini",
"message": "Explain recursion with a short example.",
"stream": True,
"runner_version": "2",
},
stream=True,
timeout=(10, 600),
)
if response.status_code != 200: # failed before the stream started
raise RuntimeError(f"{response.status_code}: {response.json()['detail']}")
for chunk in iter_chunks(response):
if chunk["error"]:
error = chunk["error"]
raise RuntimeError(f"{error['status']}: {error['detail']}")
event = chunk["data"]
if not isinstance(event, dict):
continue # keepalive
if event["type"] == "text":
print(event["delta"], end="", flush=True)
elif event["type"] == "tool_call_start":
print(f"\n[{event['tool_display_name']}]", flush=True)
elif event["type"] == "status" and event["phase"] == "model_selected":
print(f"[answering: {event['data']['model']}]", flush=True)
print()
S runner_version "1" je chunk["data"] řetězec: vypište nebo připojte ho tak, jak je.
JavaScript
// Yields the JSON chunks of a streamed response.
async function* readChunks(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let pos = 0;
let depth = 0;
let inString = false;
let escaped = false;
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
for (; pos < buffer.length; pos++) {
const c = buffer[pos];
if (inString) {
if (escaped) escaped = false;
else if (c === "\\") escaped = true;
else if (c === '"') inString = false;
} else if (c === '"') {
inString = true;
} else if (c === "{") {
depth++;
} else if (c === "}" && --depth === 0) {
yield JSON.parse(buffer.slice(0, pos + 1));
buffer = buffer.slice(pos + 1);
pos = -1; // continue at the start of the remaining buffer
}
}
}
}
const response = await fetch("https://ayeto.ai/api/v3/chat", {
method: "POST",
headers: {
"uni-api-key": process.env.AYETO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
conversation_id: crypto.randomUUID(),
model: "gpt-5-mini",
message: "Explain recursion with a short example.",
stream: true,
runner_version: "2",
}),
});
if (!response.ok) {
// failed before the stream started
const body = await response.json();
throw new Error(`${response.status}: ${JSON.stringify(body.detail)}`);
}
let answer = "";
for await (const chunk of readChunks(response)) {
if (chunk.error) {
throw new Error(`${chunk.error.status}: ${chunk.error.detail}`);
}
const event = chunk.data;
if (typeof event !== "object" || event === null) continue; // keepalive
if (event.type === "text") {
answer += event.delta;
process.stdout.write(event.delta);
}
}
Stejné funkce čtou i stream běhu workflow; liší se jen obsah data, viz
Streamování běhu.