Streaming¶
Quando você define "stream": true na sua request, o Nodexa responde com um stream de Server-Sent Events (SSE). Cada evento é entregue conforme os tokens são gerados, permitindo exibir as respostas progressivamente.
Conectando ao Stream¶
Defina stream: true no corpo da request. A resposta terá Content-Type: text/event-stream.
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://your-admin.example.com/v1',
apiKey: 'YOUR_API_KEY',
});
const stream = await client.responses.create({
model: 'YOUR_ASSISTANT_ID',
input: 'Tell me about the history of computing.',
stream: true,
});
for await (const event of stream) {
switch (event.type) {
case 'response.output_text.delta':
process.stdout.write(event.delta);
break;
case 'response.completed':
console.log('\n--- done ---');
console.log('Response ID:', event.response.id);
break;
}
}
from openai import OpenAI
client = OpenAI(
base_url="https://your-admin.example.com/v1",
api_key="YOUR_API_KEY",
)
with client.responses.stream(
model="YOUR_ASSISTANT_ID",
input="Tell me about the history of computing.",
) as stream:
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
print()
const response = await fetch('https://your-admin.example.com/v1/responses', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'YOUR_ASSISTANT_ID',
input: 'Tell me about the history of computing.',
stream: true,
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') break;
try {
const event = JSON.parse(data);
if (event.type === 'response.output_text.delta') {
process.stdout.write(event.delta);
}
} catch {
// ignora erros de parse em linhas vazias
}
}
}
}
Formato SSE no Wire¶
Cada evento é enviado como:
(Note a linha em branco entre os eventos.)
O stream termina com:
Exemplo de stream¶
event: response.created
data: {"type":"response.created","response":{"id":"resp_01234567-89ab-cdef-0123-456789abcdef","status":"in_progress"}}
event: response.output_item.added
data: {"type":"response.output_item.added","item":{"type":"message","role":"assistant","content":[]}}
event: response.content_part.added
data: {"type":"response.content_part.added","part":{"type":"output_text","text":""}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"The"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":" history"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":" of"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":" computing..."}
event: response.output_text.done
data: {"type":"response.output_text.done","text":"The history of computing..."}
event: response.output_item.done
data: {"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"The history of computing..."}]}}
event: response.completed
data: {"type":"response.completed","response":{"id":"resp_01234567-89ab-cdef-0123-456789abcdef","status":"completed","output_text":"The history of computing..."}}
data: [DONE]
Heartbeat¶
Para evitar que conexões ociosas sejam encerradas por proxies, load balancers e CDNs, o Nodexa envia um comentário de heartbeat a cada 15 segundos quando nenhum evento de dados foi emitido. Comentários SSE começam com : e são ignorados pelos parsers SSE padrão.
Por que isso importa
Se um assistant estiver processando uma request complexa (chamadas de tool, recuperação de dados, etc.), pode haver uma pausa de vários segundos antes do primeiro token ser emitido. Sem o heartbeat, alguns intermediários (Nginx, CloudFront, Cloudflare) podem fechar a conexão com um 504 ou timeout similar. O heartbeat de 15 segundos mantém a conexão ativa durante essas pausas.
A maioria das bibliotecas SSE lida com comentários de forma transparente — você não precisa fazer nada especial. Se estiver fazendo parse do stream bruto manualmente, ignore as linhas que começam com :.
Referência de Eventos¶
response.created¶
Emitido imediatamente quando a plataforma começa a processar a request. Use para exibir um indicador de carregamento.
{
"type": "response.created",
"response": {
"id": "resp_01234567-89ab-cdef-0123-456789abcdef",
"status": "in_progress"
}
}
response.status¶
Emitido quando o status de processamento interno muda (por exemplo, roteando para um Agente Especialista, carregando tools, etc.).
response.content_part.added¶
Emitido quando uma nova parte de conteúdo é aberta dentro de um item de saída, logo antes do primeiro response.output_text.delta para essa parte.
response.output_text.delta¶
Emitido para cada token de texto conforme o assistant gera sua resposta. Concatene todos os valores delta para reconstruir a resposta completa.
response.output_text.done¶
Emitido uma vez após todos os eventos response.output_text.delta para uma parte de conteúdo, confirmando o texto completamente montado.
response.reasoning_summary_text.delta¶
Emitido para resumos de modelos de raciocínio. Alguns modelos (por exemplo, a série o da OpenAI) produzem um trace de raciocínio antes da resposta final. Esses deltas contêm os tokens do resumo de raciocínio.
Apenas para modelos de raciocínio
Este evento é emitido apenas para modelos que suportam resumos de raciocínio visíveis. Para modelos padrão, você não verá este evento.
response.function_call_arguments.delta¶
Emitido enquanto o assistant faz streaming dos argumentos JSON para uma function call. Você pode usar esses eventos para mostrar um indicador de "processando" ou "chamando tool".
response.function_call_arguments.done¶
Emitido quando os argumentos de uma function call estão completamente montados. Isso sinaliza que você deve executar a função e enviar uma request de acompanhamento.
{
"type": "response.function_call_arguments.done",
"name": "get_weather",
"call_id": "call_abc123",
"arguments": "{\"location\": \"San Francisco\", \"unit\": \"celsius\"}"
}
| Campo | Tipo | Descrição |
|---|---|---|
name |
string |
O nome da função a invocar |
call_id |
string |
ID único — inclua em seu function_call_output |
arguments |
string |
String de argumentos codificada em JSON |
Confira Function Calling para o fluxo completo.
response.output_item.added¶
Emitido quando o assistant adiciona um novo item ao array de saída. Usado para itens estruturados como notificações de handover e prompts OAuth.
{
"type": "response.output_item.added",
"item": {
"type": "message",
"role": "assistant",
"content": []
}
}
Item de handover:
{
"type": "response.output_item.added",
"item": {
"type": "handover",
"from_specialist": "General Assistant",
"to_specialist": "Billing Specialist",
"reason": "User is asking about invoice details"
}
}
Item OAuth required:
{
"type": "response.output_item.added",
"item": {
"type": "oauth_required",
"plugin_id": "plugin_abc123",
"plugin_name": "Google Calendar",
"provider_id": "google",
"required_scopes": ["https://www.googleapis.com/auth/calendar.readonly"],
"auth_url": "https://your-admin.example.com/oauth/google/authorize?state=abc123"
}
}
response.output_item.done¶
Emitido quando um item de saída está completo (após todos os seus eventos delta).
{
"type": "response.output_item.done",
"item": {
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "The full assembled response text."
}
]
}
}
response.web_search_call.in_progress¶
Emitido quando uma tool de busca na web inicia.
response.web_search_call.searching¶
Emitido quando a query de busca foi submetida e os resultados estão sendo buscados.
{
"type": "response.web_search_call.searching",
"call_id": "ws_call_abc123",
"query": "latest news on AI regulations 2024"
}
response.web_search_call.completed¶
Emitido quando os resultados da busca na web estão disponíveis e o assistant começa a incorporá-los.
response.completed¶
Emitido quando a resposta completa está pronta. O objeto response contém a mesma estrutura que o corpo de uma response sem streaming.
{
"type": "response.completed",
"response": {
"id": "resp_01234567-89ab-cdef-0123-456789abcdef",
"object": "response",
"status": "completed",
"model": "asst_01234567-89ab-cdef-0123-456789abcdef",
"output_text": "The full assembled response text.",
"output": [...],
"created_at": 1700000000
}
}
Salve o ID da resposta
Sempre leia o response.id do evento response.completed. Você vai precisar dele para continuar a conversa com previous_response_id.
response.error¶
Emitido quando ocorre um erro durante o streaming. Após este evento, o stream será fechado.
{
"type": "response.error",
"error": {
"type": "server_error",
"code": "upstream_timeout",
"message": "The LLM provider did not respond in time."
}
}
Confira Erros para todos os códigos de erro.
Lidando com requires_action em Streams¶
Quando o assistant chama uma function tool do lado do cliente, o stream termina com status: "requires_action" em vez de "completed". O campo response.status do evento response.completed será "requires_action".
Depois de executar a função:
- Leia
call_ideargumentsderesponse.function_call_arguments.done - Execute a função localmente
- Envie uma nova request com
previous_response_iddefinido como o ID da resposta atual - Inclua o resultado da tool em
inputcomo um itemfunction_call_output
Confira Function Calling para o fluxo completo com exemplos de código.
Tabela Completa de Eventos SSE¶
Confira Referência — Eventos SSE para uma tabela completa de todos os eventos, seus campos e descrições.