Streaming
Receive an answer as server-sent events, and parse the final metadata line correctly.
Add ?stream=true to POST /v1/agents/{agent_id}/chat (or to the follow-up route) to receive the answer as text/event-stream instead of one JSON body.
What streaming does — and does not — change
The agent still works out the whole answer first: reading the schema, writing and running SQL. Streaming then sends the finished text one sentence at a time, so a chat interface can start drawing words immediately instead of waiting for the full body. It does not make the first byte arrive sooner than a non-streamed answer, so use it for presentation, not for latency.
Wire format
The stream is a series of blank-line-separated lines:
data: {"chunk": "There are **2,400 customers**.", "session_id": "184b72b3-625c-4c10-93ec-621f2e154e00"}
full_metadata: {"response": "There are **2,400 customers**.", "session_id": "184b72b3-…", "chat_id": "1008c506-…", "follow_up": […], "is_answered": true, "debug_info": null, "chart_output": null, …}
data:lines carrychunk— the next piece of the answer text — and thesession_id.- The last line starts with
full_metadata:and carries the complete JSON answer, exactly as the non-streaming response would return it (response,chat_id,debug_info,chart_output, …). - If the request fails after the stream has started, the HTTP status is still
200and the last line isfull_metadata: {"error": "…", "code": "…"}.
Reading the stream
import json
import os
import requests
API_ROOT = "https://futuresmart.ai/database-agent/api"
AGENT_ID = os.environ["DATABASE_AGENT_ID"]
with requests.post(
f"{API_ROOT}/v1/agents/{AGENT_ID}/chat",
params={"stream": "true"},
headers={"X-API-Key": os.environ["DATABASE_AGENT_API_KEY"]},
json={"query": "How many customers are there?"},
stream=True,
timeout=180,
) as r:
r.raise_for_status()
final = None
for line in r.iter_lines(decode_unicode=True):
if line.startswith("data: "):
print(json.loads(line[6:])["chunk"], end=" ", flush=True)
elif line.startswith("full_metadata: "):
final = json.loads(line[len("full_metadata: "):])
print()
if final is None or "error" in final:
raise SystemExit(f"Stream failed: {final}")
print("session:", final["session_id"], "answered:", final["is_answered"])const API_ROOT = "https://futuresmart.ai/database-agent/api";
const AGENT_ID = process.env.DATABASE_AGENT_ID;
const res = await fetch(`${API_ROOT}/v1/agents/${AGENT_ID}/chat?stream=true`, {
method: "POST",
headers: {
"X-API-Key": process.env.DATABASE_AGENT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ query: "How many customers are there?" }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const decoder = new TextDecoder();
let buffer = "";
let final = null;
for await (const bytes of res.body) {
buffer += decoder.decode(bytes, { stream: true });
let newline;
while ((newline = buffer.indexOf("\n")) >= 0) {
const line = buffer.slice(0, newline).trim();
buffer = buffer.slice(newline + 1);
if (line.startsWith("data: ")) process.stdout.write(JSON.parse(line.slice(6)).chunk + " ");
else if (line.startsWith("full_metadata: ")) final = JSON.parse(line.slice("full_metadata: ".length));
}
}
console.log();
if (!final || final.error) throw new Error(`Stream failed: ${JSON.stringify(final)}`);
console.log("session:", final.session_id, "answered:", final.is_answered);curl -sS -N -X POST "https://futuresmart.ai/database-agent/api/v1/agents/$DATABASE_AGENT_ID/chat?stream=true" \
-H "X-API-Key: $DATABASE_AGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "How many customers are there?"}'-N turns off cURL's output buffering so each line prints as it arrives.
Errors before the stream starts
Authentication, permission, validation and quota errors (401, 403, 404, 422, 429) are returned as normal JSON responses with that status code before any event is sent — check the status first, as the samples do. See Errors and limits.