Streams the "ask" process with real-time state updates and content generation
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
The POST /stream/ask endpoint allows you to ask a question and receive step-by-step progress updates as the AI processes your request — including SQL generation, execution, and summary creation.
This is the streaming version of /ask, ideal for frontend apps (e.g., chatbots) that want to show real-time feedback to users.
What It Does
The endpoint returns a Server-Sent Events (SSE) stream that includes:
- System messages about each processing step (e.g., SQL generation start, execution complete).
- Final outputs like the generated SQL and natural language summary.
- Intermediate explanations, such as rephrased questions and reasoning paths.
Basic Usage
{
"projectId": 1,
"question": "List the top 5 states with the most customers"
}This simplified stream shows the key phases of a /stream/ask request — from understanding and generating SQL, to executing it, and finally producing a streamed summary.
// Stream begins
data: { "type": "message_start" }
// SQL Generation Stages
data: { "type": "state", "data": { "state": "sql_generation_start" }}
data: { "type": "state", "data": { "state": "sql_generation_understanding" }}
data: { "type": "state", "data": { "state": "sql_generation_searching" }}
data: { "type": "state", "data": { "state": "sql_generation_planning" }}
data: { "type": "state", "data": { "state": "sql_generation_generating" }}
data: { "type": "state", "data": { "state": "sql_generation_success", "sql": "SELECT ... LIMIT 5" }}
// SQL Execution
data: { "type": "state", "data": { "state": "sql_execution_start" }}
data: { "type": "state", "data": { "state": "sql_execution_end" }}
// Summary Generation (streamed content block)
data: { "type": "content_block_start", "content_block": { "type": "text", "name": "summary_generation" }}
data: { "type": "content_block_delta", "delta": { "text": "Here" }}
data: { "type": "content_block_delta", "delta": { "text": " are" }}
data: { "type": "content_block_delta", "delta": { "text": " the first 5 customers from the dataset." }}
data: { "type": "content_block_stop" }
// Stream ends
data: { "type": "message_stop" }
This example omits several detailed properties (such as question, rephrasedQuestion, intentReasoning, traceId, and timestamp) for clarity. Refer to the full schema for a complete breakdown of all available fields.
State Lifecycle
graph TD message_start["message_start"] sql_start["sql_generation_start"] sql_understanding["sql_generation_understanding"] sql_searching["sql_generation_searching"] sql_planning["sql_generation_planning"] sql_generating["sql_generation_generating"] sql_correcting["sql_generation_correcting"] sql_success["sql_generation_success"] sql_failed["sql_generation_failed"] sql_stopped["sql_generation_stopped"] sql_exec_start["sql_execution_start"] sql_exec_end["sql_execution_end"] summary_start["content_block_start: summary_generation"] summary_streaming["content_block_delta (text)"] summary_stop["content_block_stop"] error_event["type: error"] message_stop["message_stop"] %% Normal flow message_start -->|"Trigger SQL generation"| sql_start sql_start --> sql_understanding --> sql_searching --> sql_planning --> sql_generating sql_generating -->|"Auto-correct (if needed)"| sql_correcting sql_correcting --> sql_success sql_generating --> sql_success sql_success -->|"Run SQL"| sql_exec_start --> sql_exec_end sql_exec_end -->|"Start LLM summary"| summary_start summary_start --> summary_streaming --> summary_stop --> message_stop %% Error flows sql_generating -->|"Too many corrections failed"| sql_failed -->|"Emit error event"| error_event --> message_stop sql_generating -->|"User canceled"| sql_stopped --> message_stop sql_exec_start -->|"SQL execution error"| error_event --> message_stop summary_start -->|"LLM failed to summarize"| error_event --> message_stop
User Guide Controls
Before answering, Wren classifies what a question is asking for — you can see the step as intent_classification in the response's usage.llmCalls. Most questions are about the data and become SQL. Some are not: "Do I need to know SQL?", "How do I get started?" Wren recognises those as product help and answers them from a built-in user guide describing Wren itself.
If you have embedded Wren in your own product that is usually the wrong answer — your users have never heard of Wren, and the guide describes our product rather than yours. Two per-request fields change it:
allowUserGuide(boolean, defaulttrue) — setfalseand product help is removed from classification altogether. The question is classified as something else and the built-in guide is never consulted.userGuidePrompt(string) — supply your own guide and Wren uses it in place of ours, both when classifying the question and when answering it. Ignored whenallowUserGuideisfalse.
Both default to today's behavior, so omitting them leaves the request unchanged. Both are per-request: re-send them on every turn of a thread. See User Guide Controls for the shared behaviour, including what happens to a question your guide does not cover.
Switch the built-in guide off:
{
"projectId": 1,
"question": "How do I get started?",
"allowUserGuide": false
}Or answer from your own guide instead — same question, your content:
{
"projectId": 1,
"question": "How do I get started?",
"userGuidePrompt": "# Northwind Insights - Help\n\n## Getting started\nOpen **Explore** in the left sidebar and pick a dataset. Type your question in\nthe box at the top; results appear as a table you can switch to a chart.\n\n## Saving a report\nClick **Save** in the top-right of any answer to add it to *My Reports*.\nSaved reports refresh every morning at 06:00 UTC.\n\n## Sharing a report\nOpen a saved report and choose **Share > Copy link**. Links work only for\nteammates in your workspace; external sharing must be enabled by an admin."
}The answer streams as an explanation content block.
Frames below are real but abridged — the stream opens with
message_startand three SQL-generationstateframes that are not shown, and the token-by-tokentext_deltaframes are collapsed. The complete streamed text follows each block, with the literal\nsequences rendered as line breaks.
data: {"type":"content_block_start","content_block":{"type":"text","name":"explanation"},"timestamp":1788000655451}
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"To"},"timestamp":1788000655452}
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":" get"},"timestamp":1788000655454}
// ... 145 more text_delta frames ...
data: {"type":"content_block_stop","timestamp":1788000655539}
data: {"type":"message_stop","data":{"threadId":"8a3f3c70-2c86-45fd-80d3-f242036bf1b9","duration":4125,"usage":{"taskType":"MISLEADING_QUERY","models":["gpt-4.1-2025-04-14","gpt-4.1-mini-2025-04-14"],"inputTokens":7442,"outputTokens":189,"totalTokens":7631,"totalCost":0.00346,"llmCalls":[{"step":"intent_classification","model":"gpt-4.1-2025-04-14","provider":"openai","inputTokens":4529,"outputTokens":42,"totalTokens":4571},{"step":"misleading_assistance","model":"gpt-4.1-mini-2025-04-14","provider":"openai","inputTokens":2913,"outputTokens":147,"totalTokens":3060}]}},"timestamp":1788000655541}To get started with analyzing your e-commerce data, follow these steps:
- Understand your data schema: Review tables like orders, products, customers, payments, and reviews.
- Define your goal: What insights do you want? For example, customer satisfaction, sales trends, or product performance.
- Explore key tables: Start with orders and order_items to see sales data, then join with products and customers for details.
- Analyze reviews for customer feedback and payments for revenue insights.
- Ask specific questions like:
- Which products have the highest sales?
- What is the average customer review score by product category?
- How do payment methods affect sales?
Would you like help formulating specific analysis questions?
data: {"type":"content_block_start","content_block":{"type":"text","name":"explanation"},"timestamp":1788000667019}
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"To"},"timestamp":1788000667020}
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":" get"},"timestamp":1788000667022}
// ... 87 more text_delta frames ...
data: {"type":"content_block_stop","timestamp":1788000667069}
data: {"type":"message_stop","data":{"threadId":"b5a0b454-ee98-4ddb-95d6-d362305a311d","duration":3085,"usage":{"taskType":"USER_GUIDE","models":["gpt-4.1-2025-04-14","gpt-4.1-mini-2025-04-14"],"inputTokens":5096,"outputTokens":132,"totalTokens":5228,"totalCost":0.0099792,"llmCalls":[{"step":"intent_classification","model":"gpt-4.1-2025-04-14","provider":"openai","inputTokens":4659,"outputTokens":43,"totalTokens":4702},{"step":"user_guide_assistance","model":"gpt-4.1-mini-2025-04-14","provider":"openai","inputTokens":437,"outputTokens":89,"totalTokens":526}]}},"timestamp":1788000667071}To get started with Northwind Insights, follow these steps:
- Open Explore in the left sidebar.
- Pick a dataset that you want to analyze.
- Type your question in the box at the top.
- View the results, which will appear as a table by default.
- You can switch the table view to a chart if you prefer a visual representation.
This will help you begin exploring your data easily.
The terminal message_stop frame also carries usage; its taskType reads MISLEADING_QUERY with the guide off and USER_GUIDE otherwise. The v2 /stream/ask reports no usage.
Event Types
During a /stream/ask request, the API streams a sequence of events using Server-Sent Events (SSE). Each event provides insight into the system’s current state or output.
message_start
- Purpose: Indicates the start of a new streaming response.
- Payload: Contains the timestamp when the process began.
{
"type": "message_start",
"timestamp": 1751014954139
}state
- Purpose: Describes what stage the system is in during processing.
State Lifecycle Overview
state | Description |
|---|---|
sql_generation_start | The system has begun processing the user’s question. |
sql_generation_understanding | The AI is interpreting the question and trying to identify its intent. |
sql_generation_searching | The AI is searching relevant tables and metadata to answer the question. |
sql_generation_planning | A plan for how to generate SQL is being formed, including table joins or filters. |
sql_generation_generating | The SQL is being generated. |
sql_generation_correcting | A generated SQL failed to execute and is being corrected. (Auto-retry phase) |
sql_generation_success | SQL was successfully generated. The sql field will be included. |
sql_generation_failed | SQL generation failed. Followed by an error event. |
sql_generation_stopped | SQL generation was manually canceled or interrupted (e.g. client disconnected). |
sql_generation_finished | Final state for SQL generation (used internally; usually followed by success or failed). |
sql_execution_start | SQL execution has started. |
sql_execution_end | SQL execution has completed (successfully or not). |
data Field Reference ("type": "state")
data Field Reference ("type": "state")🔹 sql_generation_start
| Field | Type | Description |
|---|---|---|
state | string | "sql_generation_start" |
question | string | The original user input. |
threadId | string | Unique thread identifier. |
language | string | Language used for summarization (e.g., "English"). |
🔹 SQL Generation In-Progress States
(e.g., sql_generation_understanding, searching, planning, generating, correcting)
| Field | Type | Description |
|---|---|---|
state | string | Current generation state, such as "sql_generation_searching", "sql_generation_planning", etc. |
pollCount | number | Number of polling attempts so far. |
rephrasedQuestion | string | null | Reformulated version of the user query. |
intentReasoning | string | null | AI’s interpretation of what the user is asking. |
sqlGenerationReasoning | string | null | Step-by-step reasoning of SQL generation. Appears during generating. |
retrievedTables | string[] | null | List of tables determined to be relevant. |
invalidSql | string | null | SQL that failed during correction attempts (optional). |
traceId | string | For backend debugging. |
🔹 sql_generation_success
| Field | Type | Description |
|---|---|---|
state | "sql_generation_success" | Marks the completion of SQL generation. |
sql | string | The generated SQL query. |
dialectSql | string | undefined | Database-specific SQL (if requested). |
🔹 sql_execution_start
| Field | Type | Description |
|---|---|---|
state | "sql_execution_start" | SQL execution has begun. |
sql | string | The SQL query being executed. |
🔹 sql_execution_end
| Field | Type | Description |
|---|---|---|
state | "sql_execution_end" | SQL execution completed. No additional fields. |
Example
data: {
"type": "state",
"data": {
"state": "sql_generation_start",
"question": "list 5 customers",
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484",
"language": "English"
},
"timestamp": 1751014954142
}
data: {
"type": "state",
"data": {
"state": "sql_generation_understanding",
"pollCount": 1,
"rephrasedQuestion": null,
"intentReasoning": null,
"sqlGenerationReasoning": null,
"retrievedTables": null,
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014954165
}
data: {
"type": "state",
"data": {
"state": "sql_generation_searching",
"pollCount": 4,
"rephrasedQuestion": "List 5 customers from the olist_customers_dataset table.",
"intentReasoning": "User wants to retrieve specific customer data, likely using SQL query.",
"sqlGenerationReasoning": null,
"retrievedTables": null,
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014957183
}
data: {
"type": "state",
"data": {
"state": "sql_generation_planning",
"pollCount": 6,
"rephrasedQuestion": "List 5 customers from the olist_customers_dataset table.",
"intentReasoning": "User wants to retrieve specific customer data, likely using SQL query.",
"sqlGenerationReasoning": null,
"retrievedTables": ["olist_customers_dataset"],
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014959232
}
data: {
"type": "state",
"data": {
"state": "sql_generation_generating",
"pollCount": 9,
"rephrasedQuestion": "List 5 customers from the olist_customers_dataset table.",
"intentReasoning": "User wants to retrieve specific customer data, likely using SQL query.",
"sqlGenerationReasoning": "1. **Identify the table involved**: The question asks for customer data, so the relevant table is `olist_customers_dataset`.\n\n2. **Determine the number of records needed**: The user requests 5 customers, so we need to select 5 entries from the table.",
"retrievedTables": ["olist_customers_dataset"],
"invalidSql": null,
"traceId": "f218b1f7-4623-4a56-8b66-18d544797b20"
},
"timestamp": 1751014962254
}
data: {
"type": "state",
"data": {
"state": "sql_generation_success",
"sql": "SELECT \"o\".\"customer_id\", \"o\".\"customer_zip_code_prefix\", \"o\".\"customer_city\", \"o\".\"customer_state\" FROM \"olist_customers_dataset\" AS \"o\" LIMIT 5"
},
"timestamp": 1751014963263
}
data: {
"type": "state",
"data": {
"state": "sql_execution_start",
"sql": "SELECT \"o\".\"customer_id\", \"o\".\"customer_zip_code_prefix\", \"o\".\"customer_city\", \"o\".\"customer_state\" FROM \"olist_customers_dataset\" AS \"o\" LIMIT 5"
},
"timestamp": 1751014963263
}
data: {
"type": "state",
"data": {
"state": "sql_execution_end"
},
"timestamp": 1751014963339
}
content_block_start
Purpose: Signals the beginning of a content block (e.g., summary generation).Payload: Describes the block (usually text).
{
"type": "content_block_start",
"content_block": {
"type": "text",
"name": "summary_generation"
}
}content_block_delta
Purpose: Streams the actual content in parts (e.g., summary sentence by sentence).Payload: Includestext_deltafor partial content.
{
"type": "content_block_delta",
"delta": {
"type": "text_delta",
"text": "São Paulo (SP) leads with"
}
}content_block_stop
Purpose: Indicates the end of a streamed content block.
{
"type": "content_block_stop"
}message_stop
Purpose: Marks the end of the entire stream. Includes processingdurationandthreadId.
{
"type": "message_stop",
"data": {
"threadId": "0625991d-1bba-407d-8ad4-dd0210172484",
"duration": 11096
}
}