ask_question
Ask a natural-language question about a project's data, or continue a chat. Cassis grounds the question against the published context, asks for what it cannot infer, and generates the SQL.
When essential business information is missing, a definition the context does not hold and the data cannot settle, the tool stops and asks a short targeted question, with choices when it has them. Nothing runs until the question is answered on the same chat.
Each chat is pinned to the published context version current when it started. Follow-ups in the same chat keep that version even if a newer one is published meanwhile. Omit chat_id to start a new chat on the latest published version.
A definition or context question can be answered without SQL. For data queries, what comes back depends on the data source. Connected to an executable warehouse, Cassis runs the SQL and returns the answer, the SQL, and results. On a schema-only source, it generates SQL in that dialect but never runs it, so results is always null.
Parameters
Passed by the client, not typed by the user.
| Name | Type | Notes |
|---|---|---|
project_id | UUID | The Cassis project to query. Required, except with an API key scoped to one project, where it defaults to that project |
question | string | Natural-language question, or the user’s reply to a pending clarification. For follow-ups it can reference earlier messages (“now by product category”) |
chat_id | string | Passed by the client on follow-ups and clarification replies. Omitted to start a new chat |
clarification_id | UUID | The id of the clarification question being answered (clarification.questions[].id), when replying. The card’s own clarification.id is accepted too and answers its first unanswered question. Without it, a reply answers the first unanswered question |
Response
| Field | Notes |
|---|---|
chat_id | Store it to continue the chat. Null when the request fails before a chat exists (missing question, malformed or unknown chat_id) |
status | "answered", "needs_input", "not_answerable", or "error" |
answer | The assistant’s text response. On needs_input it introduces the questions. Null on an early error |
sql | The generated SQL, when available. Null when no SQL was generated, including answers that only explain a definition. status="answered" does not guarantee SQL is present. |
results | Columnar: {columns, rows, total_rows, truncated}, rows as positional lists. columns is in the query’s SELECT order, also on a zero-row result. rows holds the first 100 rows; truncated says when there are more. Cassis fetches at most 10,000 rows per query, so total_rows stops at 10,000 and the answer says when the query returned more. Null when the SQL was not executed, and always null on a schema-only source. Zero rows means the query ran and matched nothing |
objects_used | Qualified names of the context objects the answer used |
domains_explored | Domain paths read during planning, in retrieval order, including revisits. "" denotes the root. Present on answered, needs-input, and not-answerable turns, as an empty list when no domain was opened. A domain was consulted; this does not prove it determined the answer |
clarification | The card of outstanding questions, when status="needs_input". It carries id, question, options[], answer, source, and questions[], one entry per question with its own id, question, options[], and answer (null while unanswered). A card with a single question has an empty questions[]; its own id is then the question’s id |
next_action | When status="needs_input": instructions for the client and replies[], one ready-to-send ask_question call per unanswered question with chat_id and clarification_id already filled in and a placeholder for the user’s reply |
ontology_version | {id, version, git_commit_sha} of the published version the chat is pinned to, set when the chat starts. Null when the project has no published version yet |
error | Populated when status="error" or status="not_answerable" |
Continue a clarification
Use next_action.replies to send one call per unanswered question, with its clarification_id, the same chat_id and the reply as question. Collect answers together before submitting the individual replies. Send unknown for information nobody can supply; Cassis concludes with what the context supports or explains what is missing. submit_feedback does not resume the question.
not_answerable is terminal for that question. The question cannot run against the available context or data, for example because it references an unknown object or the requested calculation cannot be executed. Read answer and error, then refine the question or fix the context. After publishing a fix, start a new chat to use it. A pending clarification returns needs_input; answer it with the same chat_id and its clarification_id as described above.
Example: a straight answer
Example response for “What was revenue in 2025?” on an online shop whose context defines revenue as item price on delivered orders, shipping excluded:
chat_id: "019e3a41-7c2d-7b10-8f4e-2d61c0a9b7e5"
status: "answered"
answer: "Revenue in 2025 was €4.22M: item price on delivered
orders, shipping excluded."
sql: |
SELECT SUM(oi.PRICE) AS revenue_eur
FROM STALLORA.ORDER_ITEMS oi
JOIN STALLORA.ORDERS o ON oi.ORDER_ID = o.ORDER_ID
WHERE o.ORDER_STATUS = 'delivered'
AND YEAR(o.ORDER_PURCHASE_TIMESTAMP) = 2025
results:
columns: ["REVENUE_EUR"]
rows: [[4218563.40]]
total_rows: 1
truncated: false
objects_used: [business_volume, STALLORA.ORDER_ITEMS, STALLORA.ORDERS]
ontology_version:
id: "019e3a40-8b1a-7c10-9b4e-2d61c0a9b7e5"
version: 3
git_commit_sha: null
Example: a clarification, then the answer
“How many active customers do we have?” turns on a term the context does not define, so the first call returns a question rather than an answer:
chat_id: "019e3a45-2f6b-7a31-8c0d-5e7f1b2a9c44"
status: "needs_input"
answer: "One thing to confirm before I run this."
clarification:
id: "019e3a46-9d2c-7f10-b1e3-4a8c6d0f2e71"
question: "What makes a customer active?"
options: ["A delivered order in the last 90 days",
"Any order in the last 12 months"]
answer: null
questions: []
next_action:
instructions: "Cassis paused this chat and is waiting for these replies. ..."
replies:
- tool: "ask_question"
chat_id: "019e3a45-2f6b-7a31-8c0d-5e7f1b2a9c44"
clarification_id: "019e3a46-9d2c-7f10-b1e3-4a8c6d0f2e71"
question: "<your reply: one of the options verbatim, free text,
or \"unknown\" if you cannot supply it>"
Show every outstanding question to the user, or answer from information they already gave and say so. Then send each reply with the same chat_id and the question’s id as clarification_id; the question field carries the reply, one of the options verbatim or free text. When several questions are open, collect the answers together and submit each without asking again. Reply unknown to a question nobody can answer: Cassis then concludes with what the context supports instead of waiting. Never reword the question into a new chat, invent a reply, pick a default silently, or hand-write SQL around a required clarification.
The reply resumes the chat and returns the answer:
chat_id: "019e3a45-2f6b-7a31-8c0d-5e7f1b2a9c44"
clarification_id: "019e3a46-9d2c-7f10-b1e3-4a8c6d0f2e71"
question: "A delivered order in the last 90 days"
The rule applies to this chat only. To make it the shared definition, it goes through curation.
Answering a clarification is not feedback. Send the reply with ask_question on the same chat_id. submit_feedback records a judgment on a previous answer; it does not answer a pending question or resume the conversation.
Example: a follow-up
The client keeps the chat_id from the previous turn, so “Now by product category.” needs no restated scope and answers against the same pinned context version.
Limits (question timeout, row caps) are in Limits and errors.