How We Keep AI Insights From Hallucinating — Detectors First, LLM Second
Our AI product manager writes a daily brief about your product. Here is the architecture that stops it inventing numbers, covering deterministic detectors, validation gates on model output, honest handling of missing data, and one model call per day.
Short answer: the model never calculates anything. Ordinary code detects problems and computes every number. The model receives those findings and writes them up. Then code checks the model's output: any session ID it mentions that was not in the input is removed, and any recommendation containing a number that was not in the input is rejected. Where data is missing, the product shows nothing instead of an estimate.
Table of Contents
- The Problem We Were Avoiding
- Layer 1: Deterministic Detectors
- Layer 2: The Model Narrates
- Layer 3: Gates on the Output
- Layer 4: Honesty About Missing Data
- Layer 5: Checking Whether Fixes Worked
- One Model Call Per Day
- What This Design Gives Up
- Key Takeaways
The Problem We Were Avoiding
The tempting design for an AI analytics feature is to hand a language model a large export of data and ask what is going on. It produces fluent, confident, plausible analysis. Some of it will be wrong, and nothing on the screen distinguishes the wrong parts.
That is unacceptable for a feature whose output people act on. A fabricated "checkout conversion fell 23%" can send a team to fix something that is not broken. We wrote about how to spot this from the outside in why AI analytics insights are often wrong. This article is the inside view of Pulse.
Layer 1: Deterministic Detectors
Findings are produced by code, not by a model. Each detector is a function over session and event data with fixed rules. They cover:
- Friction hotspots: elements where frustration signals cluster.
- Dead-click expectations: elements people click expecting a response and get none.
- Error spikes: JavaScript errors rising above their recent level.
- KPI anomalies: a key metric moving outside its normal range.
- Performance regressions: pages becoming measurably slower.
Each finding carries:
- Real counts
- The IDs of the sessions that triggered it
- An explanation of why it was detected, generated by the code
- A skeleton recommendation
Related findings are linked into chains, so an error spike and a funnel drop on the same page are presented together. The overall health score is arithmetic.
Everything in this layer is testable. The same data produces the same findings every time.
Layer 2: The Model Narrates
Only after the detectors have run does a model get involved. It receives the findings as structured data and is asked to write a readable brief: a headline, a short narrative, and clearer wording for each recommendation.
That is the job language models are good at. It is also a job where a mistake is a matter of phrasing, not of fact, provided the next layer does its work.
The assistant for ad hoc questions follows a related principle. It is a tool-calling assistant: it chooses a specific query to run for a question (funnels, errors, geography, sessions) and reports what came back, instead of reasoning over a giant paste of tables (Copilot docs).
Layer 3: Gates on the Output
Telling a model "do not make things up" is a request, not a control. So the output is validated by code before anyone sees it.
Session IDs
The brief can reference sessions to watch. The validator holds the set of session IDs that were present in the input. Any ID in the output that is not in that set is dropped. The model cannot send a user to a session that does not exist.
Numbers
Recommendations rewritten by the model are checked for digits. If a recommendation contains a number that does not appear in the context the model was given, the whole rewritten recommendation is rejected and the code-generated version is used instead.
This is deliberately blunt. It will occasionally throw away a harmless sentence. That is an acceptable cost for a guarantee that no figure on the page came from the model's imagination.
// The idea, simplified
function numbersAreGrounded(text, context) {
const mentioned = text.match(/\d[\d.,]*/g) || [];
return mentioned.every((n) => context.includes(n));
}Structure
Output must match an expected block format. Anything else is discarded.
Layer 4: Honesty About Missing Data
A subtler form of fabrication is the estimate presented as a fact. We set rules for where the product must show nothing.
- Revenue. Monetary impact is shown only when it can be calculated from real purchase events. With no purchase data, the figure is absent. There is no assumed average order value.
- Impact. The brief states how many sessions were affected and what share of traffic that is. It does not state a predicted percentage uplift from fixing the problem, because nobody can know that in advance.
- Uplift from proactive chat. In the chat product, the measured lift stays blank until the control group is large enough to mean something. The docs say it directly: a fabricated lift number is worse than no number.
- Not enough data. Detectors have minimum sample sizes. Below them, there is no finding.
Empty states are less impressive in a demo. They are the reason the non-empty ones can be believed.
Layer 5: Checking Whether Fixes Worked
Most analytics tools stop at the suggestion. When a finding is marked as fixed, Pulse records the state of the metric at that moment. On later runs it re-checks, and attaches a verdict: verified, improved, or inconclusive, with the before and after values.
If a resolved problem comes back, the finding reopens and is marked as a recurrence.
Dismissed findings stay silent. Findings are matched by a fingerprint so the same problem is not reported as new each day.
This closes a loop that is usually left open, and it does so with the same deterministic detectors that raised the finding.
One Model Call Per Day
Cost and consistency pushed us to the same design.
- The daily brief is generated on the first request of the day and stored. Everyone who opens it afterwards sees the same text.
- That single call also improves the wording of the recommendations, so there is no second call.
- The weekly digest is generated only when someone opens it.
- A written proposal for a fix is generated on demand and cached with the finding.
- Past briefs are kept as snapshots and never regenerated.
So opening the page repeatedly costs nothing and never changes the answer. A brief that reworded itself on every refresh would undermine trust even if every version were accurate.
What This Design Gives Up
Being direct about the limits:
- It only finds what a detector looks for. A novel kind of problem with no detector goes unnoticed. A model reading raw data might, occasionally, spot it.
- Explanations are conservative. The brief says what changed and where. It offers hypotheses about why with caution.
- The number gate is strict and sometimes discards good prose.
- Detectors need maintenance and tuning as products vary.
We accept all of these. A tool that is sometimes silent is useful. A tool that is sometimes wrong, with no way to tell when, is not.
Key Takeaways
- Code computes; the model writes.
- Model output is validated: unknown session IDs and ungrounded numbers are removed.
- Missing data produces an empty state, not an estimate.
- Fixes are verified against the metric that raised the finding.
Conclusion
Trustworthy AI in analytics is mostly ordinary engineering: clear rules, checks on output, and a willingness to show nothing. The model is the last and smallest part.
Frequently Asked Questions
Related articles
Why AI Analytics Insights Are Often Wrong — How to Tell Grounded From Guessed
10 min readHow an AI Product Manager Finds UX Problems Automatically (Without Hallucinating)
10 min readAI Session Summaries — How to Get Insights From Session Recordings Without Watching Them All
9 min readStay in the loop
Get the latest insights on product analytics and user behavior delivered to your inbox.



