Guides

Sentiment, intent, and summarization

AdminUpdated Sep 19, 2026

Sentiment, intent, and summarization

The lightweight classifiers Chatly runs on your conversations — what fires automatically, what you have to wire up, and what they actually return.

Beyond the copilot and autonomous agent, Chatly runs a set of small classifiers over your conversations. They use your workspace's BYOK LLM key, are priced by the same cost meter, and feed the contact mood chart, conversation tags, auto-summaries and workflow branching.

These are deliberately small calls — a classification asks for at most 8 output tokens.

Warning — Three of the four are opt-in, not automatic

Only sentiment runs on its own. Auto-tagging must be enabled per workspace, intent classification is a workflow action you place on a canvas, and summarization fires on conversation close or as a workflow step. Nothing here starts spending on a fresh workspace by itself.

Sentiment

A per-message classifier. It runs automatically on every inbound contact message — not on agent replies, bot replies or system messages.

What the classifier returns

Name

Type

Description

label

enum

positive | neutral | negative | angry

raw

string

The model's unparsed answer, lowercased and trimmed.

model

string

The model id that produced it.

confidence

number

A heuristic, not a model probability: 0.9 when the answer matched a label by exact prefix, 0.4 when it did not and we fell back to the last label.

The label is mapped to a score in [-1, 1] and cached in contact_message_sentiment, keyed uniquely by message id so a re-delivered message never burns a second classification. That cache backs the contact mood trend:

GET /v1/contacts/{id}/mood — Bearer token

The most recent cached label is also what a workflow condition on conversation.sentiment reads — there is no sentiment column on conversations, and this is where the signal actually lives.

Sentiment classification is best-effort throughout: if the AI service is unreachable or the workspace has no provider key, the classification is skipped and message ingestion continues. There is no rule-based fallback — with no key, nothing is recorded.

Intent

Intent classification is a workflow action (ai_classify, shown as "AI classify intent"), not something that runs on every message. Drop it on a workflow and it classifies the latest customer message, then stores the label on the contact at attributes._intent so later steps and conditions can branch on it.

The label set is fixed so downstream branch conditions stay stable across providers:

Parameters

Name

Type

Description

sales

label

Pre-sales question.

support

label

Needs help with something they already have.

refund

label

Asking for money back.

billing

label

Invoice, plan or payment question.

feedback

label

Praise, complaint or product suggestion.

other

label

None of the above. Also what an unrecognised answer degrades to.

Provider precedence for the action is workspace OpenAI BYOK → workspace Anthropic BYOK → the internal AI service → a hard error. There is no platform key.

Info — Custom labels go through the generic classifier

The fixed set above belongs to the ai_classify action. If you need your own vocabulary, use AI classify (generic) (ai_classify_text) instead — the underlying classifier takes a caller-supplied labels array (2–32 labels) plus free-text instructions. There is no "add a label with example phrases" screen, and no few-shot example store.

Summarization

Two surfaces, both server-side. There is no "Summarize" button in the conversation header.

On close

When a conversation transitions to closed, the worker pulls the full transcript, summarizes it, and writes the result to conversations.auto_summary with a auto_summary_generated_at timestamp, then republishes a conversation.updated envelope so the inbox refreshes.

Two gates worth knowing:

  • Conversations with fewer than 3 messages are skipped — there is not enough signal to justify the round trip.

  • The transcript is capped at 7,800 bytes before it is sent, because the classify endpoint validates text at 8,000.

It is best-effort: any failure logs and short-circuits without throwing, so a close is never blocked.

Off the same transcript, the worker also asks whether the thread is a good seed for a new KB article. Above a confidence of 0.7 it inserts a row into kb_article_drafts for admin review at /knowledge-base/drafts; anything below is dropped, because false positives in a drafts inbox cost more than a missed suggestion.

As a workflow step

Two actions on the workflow canvas:

  • AI summarize conversation (ai_summarize) — stores the summary on conversation.autoSummary, same column as the on-close path.

  • AI summarize (generic) (ai_summarize_text) — summarizes any string in the chosen style, and hands the result to later steps as {{steps.<id>.summary}}.

Style is bullet, paragraph (default) or one_line, with maxSentences from 1 to 20 on the paragraph style.

Example output:

Customer Jamie reports their May invoice was wrong — charged $79
instead of $59 because they were grandfathered on the old Starter
price. Agent verified the overcharge, issued a $20 refund, and
confirmed the plan flag is now correct. Next: monitor the Jun 12
billing cycle.

Auto-tagging

Opt-in per workspace, and it applies rather than suggests.

When workspaces.settings.autoTagging.enabled is true, a new conversation's first message plus the workspace's configured taxonomy go to the classifier, and the top label is written straight onto conversations.tags. It fires at most once per new conversation.

workspaces.settings.autoTagging

Name

Type

Description

enabled

bool

Off unless the workspace turns it on. Off means no tokens are spent here at all. Default: false.

taxonomy

string[]

The label vocabulary handed to the classifier. Your tag names, verbatim.

There is no suggest-then-accept mode, no per-tag confidence floor and no cap on tags per conversation — one tag is applied per conversation, on creation.

Info — A keyword alternative, no LLM required

The Auto-tag by keyword workflow template scans inbound messages for billing / pricing / refund keywords and tags the conversation. No model call, no spend, and deterministic — often the right first move before reaching for the classifier.

Where the classifiers live

The classifier endpoints are served by the internal AI service, reached over AI_INTERNAL_URL from the worker and the API. They are not part of the public API surface — there is no /v1/ai/classify/* on api.chatlychat.com, and the internal service is not exposed to the internet.

What you can reach from your own code is the data the classifiers produce:

GET /v1/contacts/{id}/mood — Bearer token

GET /v1/conversations/{id} — Bearer token

The conversation read returns tags, so anything auto-tagging applied is visible over the API. The auto-summary is a different story: it is written to conversations.auto_summary and readable from a workflow condition on conversation.auto_summary, but the conversation DTO does not include it, so there is no way to fetch a summary over the public API today.

To classify arbitrary text on a schedule or in response to your own events, build a workflow with the AI classify / summarize actions and trigger it — that path is metered, quota-gated and audited, which a raw passthrough endpoint would not be.

Cost

Every classifier call is priced against the per-model rate table and recorded on the workspace's BYOK spend meter — see cost metering for the rates and the meters.

The calls are small by construction: a classification caps output at 8 tokens, a language detect at a handful. Summarization is the expensive one, because the input is a whole transcript up to 7,800 bytes.

Your real numbers are on GET /v1/ai/quota and in Billing → Usage this month.

Troubleshooting

Warning — The mood chart is empty

Nothing has been classified. Either the workspace has no AI provider key on Workspace → AI providers (/settings/ai), or the AI service is unreachable — sentiment caching is best-effort and swallows both, so an empty chart is the only symptom. The worker logs each skip.

Warning — A conversation closed with no summary

Check the message count: threads under three messages are skipped deliberately. After that, check the AI provider key — the summarize path degrades silently so the close itself is never blocked.

Info — The intent step reports unclassified and writes nothing

That is deliberate, and it is a different outcome from other. When the provider answers with something outside the fixed vocabulary, the step returns { intent: "", confidence: 0, unclassified: true } and leaves the contact attribute alone — stamping other would overwrite a previously good classification with a guess and hide the failure. The reason is logged at warn with the raw label.

Was this page helpful?
Sentiment, intent, and summarization