Files
tatlock/docs/testing-improvements.md
T
jpmschweitzerandClaude Opus 4.6 a3c7fcf8c3 refactor: consolidate project structure and clean up documentation
- Move docs to docs/ (philosophy, roadmap, orchestration scenarios,
  claude integration, testing improvements)
- Strip completed phases from roadmap and claude integration docs
- Move dependencies from requirements*.txt into pyproject.toml
- Move pytest config from pytest.ini into pyproject.toml
- Add Makefile replacing wakeup.sh (setup, run, test, lint, etc.)
- Add CI test gate in Gitea Actions workflow
- Consolidate caches into .cache/ (pytest, mypy, ruff)
- Consolidate build output into build/ (coverage, logs)
- Update Dockerfile for pyproject.toml install
- Update cross-references in README, AGENTS.md, CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 20:44:25 +01:00

2.6 KiB

Testing Improvements for LLM Outputs

Problem

LLM outputs are non-deterministic. Tests checking for exact string matches fail when the LLM writes "thirty-seven" instead of "37".

Proposed Solutions

1. LLM-as-Judge Pattern

Use a smaller/faster model to evaluate semantic correctness:

async def llm_judge(output: str, criteria: str) -> bool:
    """Use LLM to evaluate if output meets criteria."""
    prompt = f"""
    Evaluate if this output is correct:
    Output: {output}
    Criteria: {criteria}
    Answer only YES or NO.
    """
    result = await judge_model.run(prompt)
    return "YES" in result.output.upper()

# Usage in test:
assert await llm_judge(
    response,
    "The answer correctly states that sqrt(144) + 25 = 37"
)

2. Fuzzy/Regex Matching

For numeric answers, accept multiple representations:

import re

def contains_number(text: str, number: int) -> bool:
    """Check if text contains number in any form."""
    patterns = [
        rf'\b{number}\b',  # Digit form
        number_to_words(number),  # Word form
    ]
    return any(re.search(p, text, re.I) for p in patterns)

# Usage:
assert contains_number(response, 37)  # Matches "37" or "thirty-seven"

3. DeepEval Framework

from deepeval.metrics import AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase

def test_calculation():
    test_case = LLMTestCase(
        input="What is sqrt(144) + 25?",
        actual_output=response,
        expected_output="37"
    )
    metric = AnswerRelevancyMetric(threshold=0.7)
    assert metric.measure(test_case)

4. pytest-evals Plugin

Minimal pytest plugin for LLM testing with metrics collection.

pip install pytest-evals

5. Multiple Runs with Threshold

Run flaky tests multiple times and require majority pass:

@pytest.mark.flaky(reruns=3, reruns_delay=1)
def test_llm_response():
    ...

Or custom:

@pytest.mark.parametrize("run", range(3))
def test_llm_response(run):
    ...
    # Aggregate results across runs

Resources

Implementation Priority

  1. Add fuzzy number matching helper (quick win)
  2. Evaluate DeepEval for complex output testing
  3. Consider LLM-as-judge for semantic correctness