- 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>
2.6 KiB
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
- DeepEval - LLM evaluation framework
- pytest-evals - pytest plugin for LLM evals
- LLM Testing Guide 2025
- Testing LLM Applications - Langfuse
Implementation Priority
- Add fuzzy number matching helper (quick win)
- Evaluate DeepEval for complex output testing
- Consider LLM-as-judge for semantic correctness