Praus Developers Get API access

Guides

Streaming

Set stream to true to receive the answer as it is written, as newline-delimited JSON events.

#Turn on streaming

Send the same request with "stream": true. The response has the content type application/x-ndjson: one JSON object per line, sent as Praus works.

curl -N https://praus.ai/api/chat/compound-pipeline-sync \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $PRAUS_API_KEY" \
  -d '{
    "message": "Write a two-sentence welcome for new customers.",
    "queryProfile": "ask",
    "searchEnabled": false,
    "stream": true,
    "sessionOnly": true,
    "userMemory": { "memories": [], "customInstructions": "" }
  }'
import json
import os
import requests

response = requests.post(
    "https://praus.ai/api/chat/compound-pipeline-sync",
    headers={"Content-Type": "application/json", "X-Api-Key": os.environ["PRAUS_API_KEY"]},
    json={
        "message": "Write a two-sentence welcome for new customers.",
        "queryProfile": "ask",
        "searchEnabled": False,
        "stream": True,
        "sessionOnly": True,
        "userMemory": {"memories": [], "customInstructions": ""},
    },
    stream=True,
    timeout=120,
)
if not response.ok:
    error = response.json()
    raise RuntimeError(f"{response.status_code} {error.get('code')}: {error.get('message')}")

draft, final = "", None
for line in response.iter_lines(decode_unicode=True):  # yields one complete line at a time
    if not line:
        continue
    event = json.loads(line)
    kind = event.get("type")
    if kind == "token":
        draft += event.get("content", "")
        print(event.get("content", ""), end="", flush=True)
    elif kind == "reset":
        draft = ""  # discard the provisional text
    elif kind == "error":
        raise RuntimeError(f"{event.get('code')}: {event.get('message')}")
    elif kind == "done":
        final = event["result"]["content"]
    # other event types, such as "status", can be shown or ignored

if final is None:
    raise RuntimeError("Stream ended before it finished. Treat the answer as incomplete.")
print("\n\nFinal answer:", final)
// Node.js 18 or later. Run on your server.
const response = await fetch("https://praus.ai/api/chat/compound-pipeline-sync", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PRAUS_API_KEY },
  body: JSON.stringify({
    message: "Write a two-sentence welcome for new customers.",
    queryProfile: "ask",
    searchEnabled: false,
    stream: true,
    sessionOnly: true,
    userMemory: { memories: [], customInstructions: "" },
  }),
});
if (!response.ok) {
  const error = await response.json();
  throw new Error(`${response.status} ${error.code}: ${error.message}`);
}

let buffer = "", draft = "", final = null;

function handle(event) {
  if (event.type === "token") draft += event.content ?? "";
  else if (event.type === "reset") draft = "";
  else if (event.type === "error") throw new Error(`${event.code}: ${event.message}`);
  else if (event.type === "done") final = event.result.content;
  // other event types, such as "status", can be shown or ignored
}

const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  let newline;
  while ((newline = buffer.indexOf("\n")) >= 0) {   // only parse complete lines
    const line = buffer.slice(0, newline).trim();
    buffer = buffer.slice(newline + 1);
    if (line) handle(JSON.parse(line));
  }
}
buffer += decoder.decode();
if (buffer.trim()) handle(JSON.parse(buffer.trim()));

if (final === null) throw new Error("Stream ended before it finished. Treat the answer as incomplete.");
console.log(final);

#What a stream looks like

200 OK · application/x-ndjson
{"type":"status","label":"Working"}
{"type":"token","content":"Hello"}
{"type":"token","content":" there."}
{"type":"done","result":{"content":"Hello there."}}

#Event types

TypeFieldsWhat to do
statuslabelProgress, such as Working. Show it or ignore it.
tokencontentA piece of the answer. Append it to the text you show.
resetNoneClear the provisional answer text you have shown so far.
doneresult.contentThe final answer. Use it as the answer of record.
errorcode, messageThe request failed, even though the HTTP status was 200.
reset and error events
{"type":"reset"}
{"type":"error","code":"SERVICE_UNAVAILABLE","message":"Please retry shortly."}

Other event types may appear. Ignore any type you don’t recognize.

#Rules for a reliable client

  1. Buffer until you have a full line. Network chunks can split a JSON object anywhere. Parse only complete lines.
  2. Append each token.content to the text you are showing.
  3. On reset, clear the provisional text and keep reading.
  4. Treat done.result.content as final. It can differ from the tokens you joined, so replace your draft with it.
  5. Treat error as a failure. The HTTP status was already 200 when the stream began, so check every event.
  6. A stream that ends without done is incomplete. Don’t present it as a finished answer.

#Errors before the stream starts

Problems found before any events are sent, such as a missing key or a request over your limits, return a normal non-streaming error with an HTTP error status and a JSON body. Check the status before you start reading lines. See error codes.