- 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>
106 lines
2.6 KiB
Markdown
106 lines
2.6 KiB
Markdown
# 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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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.
|
|
|
|
```bash
|
|
pip install pytest-evals
|
|
```
|
|
|
|
### 5. Multiple Runs with Threshold
|
|
|
|
Run flaky tests multiple times and require majority pass:
|
|
|
|
```python
|
|
@pytest.mark.flaky(reruns=3, reruns_delay=1)
|
|
def test_llm_response():
|
|
...
|
|
```
|
|
|
|
Or custom:
|
|
|
|
```python
|
|
@pytest.mark.parametrize("run", range(3))
|
|
def test_llm_response(run):
|
|
...
|
|
# Aggregate results across runs
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [DeepEval](https://github.com/confident-ai/deepeval) - LLM evaluation framework
|
|
- [pytest-evals](https://github.com/AlmogBaku/pytest-evals) - pytest plugin for LLM evals
|
|
- [LLM Testing Guide 2025](https://www.confident-ai.com/blog/llm-testing-in-2024-top-methods-and-strategies)
|
|
- [Testing LLM Applications - Langfuse](https://langfuse.com/blog/2025-10-21-testing-llm-applications)
|
|
|
|
## 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
|