Skip to content
ExperienceLearningsProjectsResumeContact
AI InfrastructureEvaluationLLM-as-a-JudgeBenchmarkingMLOps

Testing Non-Deterministic Systems: Building ConvoSets for Production AI - II

7 min read1037 words

1. Introduction: The Semantic Cliff

In our previous post, we built ConvoSets to run deterministic regression testing against execution traces. We successfully verified that our AI agent calls the correct tools, triggers security guardrails, and stays within token and latency budgets. The techniques here, like the rest of the series, apply across both voice and text-based LLM agents.

But what happens when your code assertions all pass, yet the output is still wrong?

Python
# The trace passes perfectly:
assert trace.latency_ms <= 2500           # Pass (1420ms)
assert "sql_query" in trace.tool_calls    # Pass
assert "refund_api" not in trace.tool_calls # Pass
 
# But the agent's actual response to the user:
print(agent.response)
# > "I have successfully looked up your March invoice. It shows a balance of $0.00."
# (The actual database record showed a balance of $4,500.00)
 

This is the Semantic Cliff. Deterministic unit tests can validate how a system computed an answer, but they cannot validate the meaning of the text generated. To cross this line, we have to transition from standard code assertions to probabilistic evaluation frameworks: entering the world of LLM-as-a-Judge.

Key Takeaway: Deterministic tracing proves your agent followed the right path. Semantic evaluation proves the agent told the truth. You need both to ship confidently.


2. Node-Level vs. System-Level Evals

The most common mistake teams make when adopting LLM-as-a-judge frameworks is only testing the final output of the entire system.

If you throw a 10-turn conversation transcript at an LLM evaluator and ask, "Is this a good response?", the judge will give you vague, high-variance scores. To get stable, reproducible judgments, you must decompose your system graph and run node-level benchmarking.

By grading individual nodes independently, you isolate errors. If your final output is bad, you know exactly whether it was caused by a hallucination in the synthesis node, or a failure in the retrieval node.

Key Takeaway: Broad, system-wide grading prompts suffer from high variance. Micro-evals targeted at specific execution nodes yield highly deterministic, actionable judgments.


3. Designing Rigorous Grading Rubrics

LLMs are notoriously susceptible to egocentric bias (preferring responses generated by themselves) and verbosity bias (grading longer, wordier answers higher).

To counter this, your grading rubrics must never ask open-ended qualitative questions like "Rate this on a scale of 1-5." Instead, you must force the judge to act as a binary or multi-categorical classifier with explicit, mutually exclusive criteria.

The Anatomy of an Evaluation Schema

A production-grade rubric requires three distinct properties:

  1. Context/Grounding: The absolute source of truth (e.g., retrieved context or golden reference answers).
  2. Explicit Counter-Examples: Explicitly defining what constitutes a failing grade.
  3. Chain-of-Thought Enforcement: Forcing the judge to extract evidence before outputting a final score.
Yaml
# faithfulness_rubric.yaml
name: Faithfulness Evaluator
target_node: synthesis
grading_criteria:
  score_1: "The response contains facts, dates, or numbers completely unsupported by the retrieved documents."
  score_2: "The response is technically accurate based on the documents, but introduces unverified assumptions."
  score_3: "The response contains ONLY facts explicitly stated in or directly inferred from the retrieved documents."
 

4. Engineering the Judgment Engine

To guarantee that your test runner can parse judgments programmatically, you should enforce structured outputs using libraries like Pydantic or Instructor. The judge must always explain its reasoning before outputting its score.

Here is how to implement a production-grade evaluation node in Python:

Python
from pydantic import BaseModel, Field
from openai import OpenAI
 
class JudgmentSchema(BaseModel):
    quotes_extracted: list[str] = Field(
        description="Direct quotes from the retrieved context that support the agent's statements."
    )
    contradictions_found: list[str] = Field(
        description="Any claims made by the agent that conflict with or extend past the provided context."
    )
    score: int = Field(
        description="The final alignment score: 1 (unfaithful), 2 (partial hallucinations), 3 (fully faithful)."
    )
 
def evaluate_faithfulness(query: str, context: str, response: str) -> JudgmentSchema:
    client = OpenAI()
    
    prompt = f"""
    You are an expert QA Auditor. Analyze the Agent Response against the Verified Context.
    
    User Query: {query}
    Verified Context: {context}
    Agent Response: {response}
    
    Follow these steps:
    1. Extract direct quotes from the context supporting the response.
    2. Identify any logical leaps, unverified claims, or contradictions.
    3. Assign a score based on the rubric.
    """
    
    completion = client.beta.chat.completions.parse(
        model="gpt-4o-mini", # Use a fast, cost-effective model for judging structural alignment
        messages=[{"role": "user", "content": prompt}],
        response_format=JudgmentSchema,
    )
    
    return completion.choices[0].message.parsed
 

5. The Multi-Judge Consensus Model

For high-stakes domains (like legal analysis, medical documentation, or financial compliance), relying on a single LLM call as a judge introduces too much risk. Individual models can still experience alignment drift.

To mitigate this, production infrastructure scales out to a Consensus Panel Architecture.

By mixing open-weight models (like Qwen3) with proprietary models (like Claude or GPT), you eliminate architecture-specific biases. If two out of three judges flag a semantic hallucination, the test block triggers a build failure in your CI pipeline.


6. Closing the Loop: The Production Dashboard

By combining our structural testing metrics from ConvoSets with semantic metrics from our Evaluation Judges, we create a comprehensive, holistic look at agent safety and performance.

Bash
$ make test-all-suites
>> Running 450 Conversational Scenarios...
 
[STRUCTURAL METRICS]
- P90 Latency: 1250ms (Target: <2000ms) -> PASSED
- Token Overhead: 1100 tokens/avg      -> PASSED
- Forbidden Tool Violations: 0          -> PASSED
 
[SEMANTIC METRICS]
- Intent Classification Accuracy: 98.2% -> PASSED
- Context Relevance Score: 2.85 / 3.0   -> PASSED
- Faithfulness / Hallucination Rate:    -> FAILED
 
CRITICAL FAILURE: Build failed on Commit 4f9a2. 
Model 'qwen3' introduced 4.2% higher semantic hallucination rate on the 'Finance Domain' ConvoSet compared to 'gpt-4.5'. Rollback initiated.
 

By making our evaluation framework multi-layered—testing structural execution traces with code and semantic behavior with structured LLM judges—we turn the unpredictable "vibes" of generative AI into a data-driven engineering discipline.