classify
Ask an LLM to bucket free-text input into one of a fixed set of cases. The verdict drives outgoing edges by case:.
| Source | internal/agents/workflow/nodes/classify.go |
| Branching | Yes — emits verdict |
| When to use | Input is free text and needs to be routed into a small set of cases. |
Schema
| Field | Type | Required | Notes |
|---|---|---|---|
output_cases | array | ✅ | Enum labels the LLM must pick from. Each becomes a JSON Schema enum value passed to the provider's structured output. |
input | template | ✅ | Text to classify. Use a template expression like {{index .Event.Payload "text"}}. |
provider | string | Provider name. Optional — falls back to the default. | |
fuzzy_match | bool | Allow Levenshtein / substring fallback when the model returns a variant (e.g. "bugs" for bug). | |
retry_on_mismatch | int | Retry count when the LLM returns an unrecognized label. Each retry tightens the system prompt — costs tokens. Keep ≤ 2. |
Output
| Field | Type | What |
|---|---|---|
verdict | string | Matched output_cases label. Edge case: filters route on this. Falls back to "default" after retries fail. |
confidence | float | 0.0–1.0 score from the provider's structured output. 0 when the provider didn't return one. |
reasoning | string | Short explanation. Useful as Slack reply or audit; not a routing input. |
raw | any | Raw provider response — debugging only. |
fuzzy | bool | true when verdict resolved via fuzzy match instead of exact. |
Example
json
{
"id": "triage",
"type": "classify",
"output_cases": ["bug", "feature", "question"],
"input": "{{index .Event.Payload \"text\"}}",
"provider": "claude"
}Reliability stack
Six layers, in order:
- Structured output — provider returns one of
output_casesdirectly. - Normalize — strip whitespace, lowercase, collapse to enum.
- Exact match vs
output_cases. - Fuzzy match (if
fuzzy_match: true) — Levenshtein + substring. - Retry on mismatch — re-prompt with a tightened system message.
- Confidence threshold fallback → emit
"default"so the downstream branch can catch it.
Add a "default" case in your downstream branch to handle the "model gave up" path — otherwise the run dead-ends.
The outgoing edges that route each verdict carry a case: label you can set directly in the canvas editor — see branch ▶ Setting edge cases in the canvas.
Pair with
Common pitfalls
- Forgetting a
"default"edge → run dead-ends when the LLM can't decide. - High
retry_on_mismatch(>2) — burns tokens for diminishing returns. Better to widenoutput_casesor addfuzzy_match.