Querying
Ask questions with POST /query. The response is an answer grounded strictly in the tenant's data, plus the source chunks it used.
Basic query
bash
curl -s localhost:8000/query -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"question": "What does the safety manual say about work at height?"}'The response includes the answer and a sources array of cited chunks with their retrieval scores.
Optional fields
| Field | Purpose |
|---|---|
history | Prior turns, for multi-turn follow-ups (see below) |
collection | Restrict retrieval to a single collection |
source_types | Restrict retrieval to certain source types |
Citations
Every answer carries the chunks it was built from, each with a relevance score, so answers are auditable back to their source. The built-in chat UI renders these under each answer.
Multi-turn follow-ups
Pass previous turns as history and Evo.ai condenses the follow-up plus the history into a standalone question before retrieving — so "what about at night?" resolves against the earlier subject rather than retrieving on its own.
The relevance gate
Before any LLM call, Evo.ai checks the best retrieval score against RELEVANCE_THRESHOLD (default 0.55). If nothing clears the bar — off-topic questions, general knowledge, prompt-injection attempts — the request is refused without calling the model, and the response carries gated: true with the configured gated answer.
This keeps the assistant honest: it answers from the workspace's records or says they don't cover the question. See Configuration for tuning the threshold and the wording.
The chat UI
A ready-to-use chat interface is served at /ui. It talks to /query, supports multi-turn conversations, and shows the source chunks (with scores) behind each answer. It's the quickest way to try retrieval against a tenant's data without writing a client.
Structured analytics answers
For evo.ehs tenants, aggregate questions ("how many permits are open?", "average time from reported to closed?") are answered by a validated read-only SQL query rather than chunk retrieval, and appear in sources tagged source_type: "analytics". See Connectors for how that layer works and its guardrails.
The views resolve people to their display names, so an answer reads "assigned to Kara Errands" rather than leaking a raw address. Each view that stores an assignee also exposes assigned_to_email for matching and filtering — the name is what gets rendered, the address is what gets compared.
When an answer says the records don't cover it
Two different things produce that response, and they are worth telling apart when a question you expected to work is refused:
- The relevance gate fired — nothing retrieved cleared the threshold, and
gated: truecomes back. - The data isn't indexed. Anything a view or connector doesn't expose is invisible to the assistant, even when it exists in the source system.
The second is the more common surprise, and it is a coverage question rather than a tuning one — the fix is exposing the column or the source, not lowering the threshold.
When no model is configured
If a tenant has no LLM configuration of its own and isn't permitted to use the server default, /query answers with unconfigured: true and an explanation that an administrator needs to add an API key. This is deliberate: see DEFAULT_LLM_ALLOWED_TENANTS in Configuration for why a shared deployment should fail closed rather than silently bill the operator's provider account.