Curated answers and CTAs are the two kinds of "deterministic" behavior the widget supports — they bypass the LLM in favor of exact text or structured calls-to-action. Use them where you can't tolerate the model paraphrasing or going off-script.
Curated answers are for questions where the response has to be exact:
From /app/agents/{id}/curated:
question_pattern column) — one or more trigger keywords. Use commas to split a single field into independent OR-tokens (pricing, refund, web design). The matcher splits on commas, lowercases each token, and short-circuits the LLM if ANY token appears as a substring of the visitor's message. Case-insensitive. Add variants (price, pricing, how much) so misspellings still hit.kb_published = true the answer renders at /kb/{workspace.slug}/{slug} and the bot can recommend it mid-chat via the send_kb_article tool.conditions JSON) — optional matching constraints (e.g. page_url_prefix, visitor_lang) layered on top of the question pattern.
Before the RAG pipeline runs, the message goes through
CuratedAnswerMatcher. If any trigger matches, the curated
text is returned and we never call retrieval or the LLM. That makes
curated answers fast — usually under 100ms end-to-end — and
cheap (no inference cost).
The streaming behavior matches the LLM's: tokens stream out one at a time over a small interval so the visitor sees the same typing animation. They have no way to tell a curated answer from a generated one.
A CTA is a card the visitor can click — a button, a link, or both — rendered inline in the chat panel. Each CTA has:
| Field | Purpose |
|---|---|
title | One-line headline. |
description | Optional supporting text. |
buttons | 1 or 2 buttons. Each has a label and an action (URL, send_message, lead_capture, dismiss). |
conditions | When to show. Same shape as behavior-rule conditions. |
/app/agents/{id}/ctas is the management page. The form is
a structured builder — you don't need to write JSON. Save creates or
updates a behavior rule with kind=cta. Disable to keep the
rule but stop showing it.
The Playground (/app/agents/{id}/playground) is your
sandbox. It runs the same pipeline as the live widget but with
is_playground=true on the conversation, so it doesn't count
against your monthly quota and stays out of the analytics report.
Type a trigger phrase and confirm the curated answer fires. Type something close-but-not-quite and confirm it doesn't (otherwise your triggers are too loose).