Build a Regression Test for Your AI Support Bot
A support bot says it has escalated a ticket. The message is polite, the explanation sounds reasonable and the conversation ends neatly. But the ticket is still sitting in the original queue.
A test that checks whether the response contains “escalated” would reward exactly the wrong behavior. The thing worth verifying lives in the support system, not just in the assistant’s last sentence.
I’d build a regression suite around that distinction before changing a working prompt or swapping the model underneath it. The goal is a repeatable way to discover that an apparently helpful change has broken something customers depend on.
Start with one concrete policy
Imagine a fictional store whose policy says that a damaged delivery reported within seven days must be referred to a human support queue. The bot may collect details and open the escalation, but it may not promise or issue a refund. This is an invented policy for the example, not advice about what a real store should offer.
A test case needs the initial ticket, the relevant policy, a customer message and the expected result. Fix the clock as part of the fixture so the delivery does not become ineligible when the suite runs next week.
Write the expected result before looking at the bot’s response. Otherwise a plausible answer can pull your interpretation of “correct” toward whatever the model happened to do.
In this case, the final ticket should be in the human support queue with a reason that identifies the damaged delivery. No refund should have been issued. The customer should receive an accurate explanation of what happened without a guarantee the policy does not authorize.
That last requirement needs judgment. The queue and refund requirements do not. Keeping them separate makes the failure easier to diagnose.
Run against an isolated support system
Give the bot a small implementation of the same tool interface it uses in the application. Reads return fixture data. Writes update an isolated in memory store or a test database. The evaluation must not send real customer messages or change production tickets.
Avoid making every tool return success regardless of its arguments. That produces a friendly fictional world where invalid identifiers and missing fields never hurt. Validate inputs and return the same kinds of failures the real integration can produce.
Record every attempted action as well as the final state. A bot could try an unauthorized refund, receive an error and then perform the correct escalation. The final ticket might look fine while the trace reveals a behavior you need to fix.
A closely related approach was already public in the 2024 tau-bench project, which evaluates agents interacting with users, policies and tools. Its environment code includes a comparison between the resulting database state and the state produced by expected actions. You do not need the full benchmark to borrow the principle of checking consequences.
Keep the mechanical checks boring
The deterministic part of the grader should be ordinary application code. Feed it the final state and recorded actions, then assert the properties the policy requires.
Here is a small executable example using Python’s unittest library. The returned object is an illustrative result from the test adapter. In an actual evaluation, your runner would supply the observed result from the bot’s completed trial.
import unittest
def check_escalation(result):
checks = unittest.TestCase()
checks.assertEqual(result["ticket"]["queue"], "human_support")
checks.assertEqual(result["ticket"]["reason"], "damaged_delivery")
checks.assertEqual(result["refunds"], [])
checks.assertNotIn("issue_refund", result["attempted_tools"])
check_escalation({
"ticket": {"queue": "human_support", "reason": "damaged_delivery"},
"refunds": [],
"attempted_tools": ["get_order", "escalate_ticket"],
})
The example uses fixed reason codes because the hypothetical tool contract provides them. If your integration only accepts a free text reason, demanding an exact sentence would be a brittle test. Verify the structured properties you control and judge the remaining explanation separately.
Likewise, do not require a single exact sequence of reads unless the sequence itself is part of the contract. Reading the order twice may be inefficient, but it is a different problem from issuing a refund. The grader should distinguish unnecessary work from a policy violation.
Give language its own rubric
For the customer reply, I’d ask a reviewer whether the bot described the completed escalation accurately and whether it promised an outcome it could not authorize. A concise rubric is easier to apply consistently than a broad question such as “Was this a good answer?”
A second model can help review responses, but its score is another output to inspect. Give it the applicable policy, relevant tool results and the candidate reply. Ask it to identify the passage supporting its judgment. Keep the candidate reply clearly separated as material being evaluated so an instruction inside it does not become part of the grading request.
Calibrate that reviewer against examples a human has already judged. Include a response that politely makes an unauthorized promise and another that uses different wording while correctly explaining the escalation. If the grader cannot distinguish those, automating it mainly makes the wrong judgment faster.
Anthropic’s September 2025 article on writing tools for agents discusses building evaluations and inspecting the resulting behavior to improve tools. That feedback loop is useful here: a failed case can point to an unclear tool description or an awkward response shape as readily as a weak prompt.
Repeat cases and keep the evidence
One successful run is useful evidence, but it is not a reliability guarantee. Repeat important cases with fresh state and record each outcome. Reset the conversation, tool store and fixture clock between trials so an earlier escalation cannot make the next attempt look successful.
Report repeated outcomes honestly. If a hypothetical case succeeds four times and fails once, record those five results rather than selecting the best response. That example is arithmetic, not a benchmark result from a model tested here.
Store enough metadata to reproduce the comparison: the model identifier, prompt version, tool definitions, policy revision and fixture version belong beside the result. Capture latency and usage when the API provides them, while avoiding customer secrets in logs. A quality improvement that doubles tool calls is worth seeing even if it is ultimately acceptable.
Turn a real failure into a lasting test
Once the first scenario works, add cases around the policy boundary. A report outside the allowed window should take the appropriate alternate route. A missing order identifier should lead to a clarifying question. A failed escalation tool should prevent the assistant from claiming the ticket was escalated.
Keep some cases aside while refining the prompt. If every change is tuned against the same visible examples, the suite can become a script the bot has learned to satisfy. Unseen variants help reveal whether the behavior generalizes beyond those sentences.
I would make an unauthorized action a reason to stop a release even if the average score improved. The acceptable release criteria belong to the product, but they should be decided before a tempting result arrives.
The useful outcome is a small collection of conversations that can prove something about the support workflow. A bot that sounds helpful is easy to demo. A bot whose claims agree with the ticket state is something you can start trusting with a real job.