Streaming SSE
Use stream: true para receber text/event-stream. O projeto precisa ter
permissão de streaming. O exemplo usa cURL e jq; -N desativa o buffer de saída.
jq -n --arg model "$NEXVRA_MODEL" \ '{model: $model, messages: [{role: "user", content: "Olá!"}], max_tokens: 128, stream: true, stream_options: {include_usage: true}}' | curl --silent --show-error --fail-with-body -N --connect-timeout 10 --max-time 60 \ "$NEXVRA_BASE_URL/chat/completions" \ -H "Authorization: Bearer $NEXVRA_API_KEY" \ -H 'Content-Type: application/json' --data-binary @-Como consumir
Seção intitulada “Como consumir”- Confira o status HTTP e o
Content-Typeantes de ler o stream. - Leia eventos SSE separados por uma linha em branco. Chunks de rede não correspondem necessariamente a eventos; mantenha um buffer entre leituras.
- Decodifique o JSON do campo
datae concatenechoices[].delta.contentquando presente. Nem todo evento contém texto. - Se houver
error, trate a resposta como falha mesmo com HTTP 200. - Reconheça
data: [DONE]como encerramento do protocolo de sucesso. Uma conexão encerrada sem conclusão válida não prova sucesso.
finish_reason pode indicar length ou content_filter; o fim do protocolo
não significa necessariamente que o texto terminou da forma esperada pelo usuário.
Quando solicitado e conhecido, o uso pode chegar em evento separado com
choices: []. Uso ausente/desconhecido não representa zero tokens.
Falhas depois dos headers
Seção intitulada “Falhas depois dos headers”Após o início do SSE, o servidor não pode mudar o status HTTP já enviado.
Pode emitir um envelope de erro e encerrar sem [DONE]. Cancelamento ou falha
de rede também pode interromper a conexão. Não reinicie automaticamente uma
geração que já entregou texto parcial.
O comando cURL mostra os eventos brutos. Seu exit code, isoladamente, não valida erros enviados dentro de um stream com HTTP 200.
Exemplo Python
Seção intitulada “Exemplo Python”Requer Python 3 e as três variáveis dos primeiros passos.
O exemplo usa a biblioteca padrão, combina linhas data, aceita eventos sem
texto, trata o envelope de erro e falha quando a conexão termina sem [DONE].
O timeout de leitura de 60 segundos não é uma garantia de prazo total do stream.
"""Python 3: consome eventos SSE completos e exige [DONE] para concluir."""import jsonimport osimport sysfrom urllib.error import HTTPErrorfrom urllib.request import HTTPRedirectHandler, Request, build_opener
class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None
body = json.dumps({ "model": os.environ["NEXVRA_MODEL"], "messages": [{"role": "user", "content": "Olá! Responda em uma frase."}], "max_tokens": 128, "stream": True, "stream_options": {"include_usage": True},}).encode()request = Request(os.environ["NEXVRA_BASE_URL"].rstrip("/") + "/chat/completions", data=body, headers={ "Authorization": "Bearer " + os.environ["NEXVRA_API_KEY"], "Content-Type": "application/json",})try: with build_opener(NoRedirect).open(request, timeout=60) as response: if response.headers.get_content_type() != "text/event-stream": raise ValueError("Resposta não é text/event-stream") data_lines = [] done = False for raw_line in response: line = raw_line.decode("utf-8").rstrip("\r\n") if line.startswith("data:"): value = line[5:] data_lines.append(value[1:] if value.startswith(" ") else value) elif line == "" and data_lines: data = "\n".join(data_lines) data_lines = [] if data == "[DONE]": done = True break event = json.loads(data) if "error" in event: raise ValueError("Falha SSE: " + json.dumps(event["error"])) for choice in event.get("choices", []): text = choice.get("delta", {}).get("content") if text: print(text, end="", flush=True) if not done: raise ValueError("Stream encerrado sem [DONE]; resposta incompleta") print()except HTTPError as error: print(f"HTTP {error.code}; request-id={error.headers.get('x-nexvra-request-id', '')}", file=sys.stderr) print(error.read().decode("utf-8", errors="replace"), file=sys.stderr) sys.exit(1)except (ValueError, OSError) as error: print(str(error), file=sys.stderr) sys.exit(1)Execute com python3 stream.py. Se a geração falhar depois de entregar texto,
o texto parcial permanece impresso e o exit code indica falha.