Skip to content
← Back to work

Service Inquiry Triage & Reply Assistant

Python · Claude Messages API · CRM stub

The problem

A dealership service department gets a constant stream of inbound messages — email, web forms, SMS, Facebook — and most are repetitive: "how much is a service", "can I book Saturday", "do you have this part". A service advisor spends real time reading each one, working out what it is, and typing a reply. Meanwhile a genuinely urgent message (grinding brakes, overheating) sits in the same queue as a question about opening hours. The cost is slow first response and no triage of safety-critical messages.

The approach

One Claude call does four jobs per message:

  • Classifies intent — booking / quote / parts / complaint / other
  • Scores urgency — high for safety issues or upset customers
  • Extracts the customer name and vehicle when present
  • Drafts a warm, on-brand reply for an advisor to approve and send

It then logs structured data to a CRM and flags complaints and safety issues for a human — automate the volume, keep a person on the high-stakes edge.

Architecture

inbound message ↓ orchestrator (system prompt + XML-tagged message + few-shot examples) ↓ Claude → schema-enforced JSON ↓ { intent, urgency, customer, vehicle, summary, draft_reply, needs_human } ├──→ CRM stub (columns map 1:1 to HubSpot / VinSolutions) └──→ human-review flag → advisor approves / edits / sends

The CRM is a thin, swappable seam: one function writes the row, so moving from the CSV stub to a real CRM API is a one-function change — the Claude logic never changes.

The engineered prompt

The system prompt fixes the role, the dealership voice, and an explicit escalation rule. Inputs are wrapped in XML tags so a customer can't hijack the instructions, and output is constrained to a JSON schema:

# system prompt (excerpt)
You are the service front-desk assistant for Northgate Auto...
Classify intent, score urgency, extract details, draft a reply.

# escalation rule — the key to reliability
Set "needs_human" to true ONLY if:
  - the intent is "complaint";
  - the message describes a safety concern; or
  - it is a billing/refund dispute.
Otherwise false. Routine bookings, quotes, and parts
enquiries do NOT need a human.

Sample runs

"while driving home my temperature gauge went all the way to red and there was steam from the hood. is it safe to drive to you tomorrow??"
intent: booking · urgency: highNEEDS HUMAN
Draft: "...An engine that hot with steam is a safety concern — please don't drive it. The safest option is to have it towed in. Call us on (02) 555-0192 and we'll prioritise it..."
"Good morning, I'd like to book my Ford Ranger for its 90,000 km service this Saturday. Also roughly how much will that cost?"
intent: booking · urgency: mediumAUTO-DRAFT
Draft: "...We'd be glad to book your Ranger in. We have Saturday morning slots — the 90k service price varies by model, so we'll confirm an exact figure before your visit..."

Measuring & improving it (evals)

"Analyse outputs and continuously improve them" is in the job description, so the demo ships with a labelled eval harness. It caught a real bug: the original prompt told the model to "flag anything you're unsure about", which over-escalated routine quotes. I replaced it with an explicit decision rule and measured the fix:

Version                              needs_human   exact-match
Before (vague "when in doubt, flag")    91% (10/11)   10/11
After (explicit rule)                  100% (11/11)   11/11

Intent and urgency accuracy stayed at 100% — the change was surgical, and escalation is now consistent run-to-run.

Cost & model choice

Defaults to Claude Haiku 4.5 ($1 / $5 per 1M tokens) because triage is high-volume, low-complexity classification — cents per hundreds of messages. One env var switches to Opus 4.8 for the rare hard case. Cheap model for the common case, strong model for the hard one.

← Back to work