Ir para o conteúdo

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.

curl https://your-admin.example.com/v1/responses \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --no-buffer \
  -d '{
    "model": "YOUR_ASSISTANT_ID",
    "input": "Tell me about the history of computing.",
    "stream": true
  }'
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:

event: <event-type>
data: <json-payload>

(Note a linha em branco entre os eventos.)

O stream termina com:

data: [DONE]

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.

: heartbeat

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.).

{
  "type": "response.status",
  "status": "processing"
}

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.

{
  "type": "response.content_part.added",
  "part": { "type": "output_text", "text": "" }
}

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.

{
  "type": "response.output_text.delta",
  "delta": " computing"
}

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.

{
  "type": "response.output_text.done",
  "text": "The fully assembled response text."
}

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.

{
  "type": "response.reasoning_summary_text.delta",
  "delta": "The user wants to know about..."
}

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".

{
  "type": "response.function_call_arguments.delta",
  "delta": "{\"location\": \"San"
}

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.

{
  "type": "response.web_search_call.in_progress",
  "call_id": "ws_call_abc123"
}

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.

{
  "type": "response.web_search_call.completed",
  "call_id": "ws_call_abc123",
  "results_count": 5
}

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:

  1. Leia call_id e arguments de response.function_call_arguments.done
  2. Execute a função localmente
  3. Envie uma nova request com previous_response_id definido como o ID da resposta atual
  4. Inclua o resultado da tool em input como um item function_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.