LLM orchestration · Evaluation · Live
Smart Returns Resolver
The model classifies. The table decides. When the two disagree about whether a human is needed, the table wins — and the evaluation records every time it did.
Built
2026
Stack
- Next.js 16
- TypeScript
- Zod
- gpt-4o-mini
01 — The problem
Entitlement is a policy question, not a language question.
A customer writes in to say a car seat arrived with a dented box. A model can read that sentence far better than a regular expression can. What it must not do is decide what the customer is owed.
Refunds, replacements and safety escalations are commercial policy. They are written down, they change without warning, and they have to be auditable afterwards — none of which describes a model’s judgement. So the model reads, and a hand-written 36-cell table decides: six issue types against six product categories, each cell naming the entitlement, the SLA, whether it always escalates, and whether it carries a stop-use warning.
- Rubric score
- 88.5%
- 69 of 78 binary dimensions across 15 cases
- Policy cells
- 36
- 6 issue types × 6 product categories
- Pipeline failures
- 0
- every run schema-valid, no errors
- Median latency
- 9.2s
- three sequential model calls
The classifier votes. The orchestrator does not read the vote.
The classification schema includes a needs_human field, and the model fills it in on every request. The escalation gate never looks at it. Escalation happens when the matched policy cell says it always escalates, or when no cell matched at all, or when the model’s own confidence falls below 0.7 — three deterministic conditions, none of which is the model’s opinion about whether a human is needed.
On the committed evaluation that disagreement showed up four times in fifteen cases, in both directions. Three times the model asked for a human and the engine resolved the case anyway. Once, on a dented car-seat box, the model rated it standard urgency and said no human was needed — and the table escalated it regardless, because damaged goods in the infant-safety-critical category are always escalated and carry a stop-use warning. That single case is the reason the design exists.
Where the engine and the model disagreed
Four of fifteen cases. The direction matters as much as the count.
Source evals/results/eval-2026-04-29T16-54-36-081Z.json
Table
| Case | Model asked for a human | Engine escalated | Why |
|---|---|---|---|
| E1 | yes | no | model wanted a human, the engine resolved it |
| E5 | yes | no | model wanted a human, the engine resolved it |
| A1 | yes | no | model wanted a human, the engine resolved it |
| S2 | no | yes | model was content, the table escalated |
Where it scores worst is where it should.
Fifteen cases across four tiers, scored on a binary rubric — 78 dimensions in total, 69 passed. The tier it does best on is the one where refusing is the only right answer. The tier it does worst on is the adversarial set, which is the point of having one.
Rubric pass rate by tier
Adversarial cases are deliberately the hardest; must-refuse cases are the ones with a safety floor.
Source evals/results/eval-2026-04-29T16-54-36-081Z.json
Table
| Tier | Passed | Total | Rate |
|---|---|---|---|
| Easy | 30 | 33 | 90.9% |
| Adversarial | 16 | 20 | 80.0% |
| Safety | 16 | 18 | 88.9% |
| Must refuse | 7 | 7 | 100.0% |
End-to-end latency per case
Three sequential model calls. Twelve of fifteen cases finish under fifteen seconds.
Source evals/results/eval-2026-04-29T16-54-36-081Z.json
Table
| Case | Tier | Latency | Dimensions passed |
|---|---|---|---|
| E1 | easy | 12.0s | 7/7 |
| E2 | easy | 9.0s | 7/7 |
| E3 | easy | 17.8s | 7/7 |
| E4 | easy | 10.7s | 3/6 |
| E5 | easy | 21.1s | 6/6 |
| A1 | adversarial | 21.0s | 6/6 |
| A2 | adversarial | 9.2s | 2/4 |
| A3 | adversarial | 9.7s | 3/3 |
| A4 | adversarial | 7.8s | 3/3 |
| A5 | adversarial | 9.2s | 2/4 |
| S1 | safety | 13.0s | 7/7 |
| S2 | safety | 8.7s | 5/6 |
| S3 | safety | 9.0s | 4/5 |
| R1 | must_refuse | 6.8s | 3/3 |
| R2 | must_refuse | 8.5s | 4/4 |
What it deliberately does not do
-
It does not let the model decide entitlement. The table is declared so that a missing cell is a compile error rather than a runtime surprise. If the issue type or the product category is unknown, no cell matches and the case goes to a human by construction.
-
It does not treat a code-switched message as bilingual. A message mixing English and Arabic is classified as mixed and then answered in English, because the responder branches only on a clean Arabic classification. That is a real gap and the repository documents it.
-
Arabic quality is measured, and it is not good. A separate judge scored three Arabic cases on dialect, register, naturalness and clarity: 7 of 12. Published rather than dropped.
What it gets wrong
There is no test suite and no CI — no vitest, no jest, not a single assertion running anywhere. The 36 policy cells, the one component the whole design depends on, have no unit coverage at all. The evaluation harness is the only automated check, and the repository says so in its own README.