-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Expand file tree
/
Copy pathprompt_evaluation_framework.py
More file actions
105 lines (84 loc) · 3.43 KB
/
Copy pathprompt_evaluation_framework.py
File metadata and controls
105 lines (84 loc) · 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""
Prompt Evaluation Framework
Author: Rehan Malik
Framework for systematic prompt testing and optimization.
Used to evaluate and compare prompt variants before production deployment.
"""
import json
from dataclasses import dataclass, field
from typing import Callable, Optional
@dataclass
class TestCase:
input_text: str
expected_output: str
category: str = "general"
difficulty: str = "medium" # easy, medium, hard
@dataclass
class EvalResult:
test_case: TestCase
actual_output: str
score: float # 0-1
latency_ms: float
token_count: int
passed: bool
class PromptEvaluator:
"""Evaluate prompts against test suites with multiple metrics."""
def __init__(self):
self.test_suites: dict[str, list[TestCase]] = {}
self.results: dict[str, list[EvalResult]] = {}
def add_test_suite(self, name: str, cases: list[TestCase]):
self.test_suites[name] = cases
def evaluate(self, prompt_name: str, prompt_fn: Callable,
suite_name: str, scorer: Callable) -> dict:
"""Run evaluation of a prompt against a test suite."""
cases = self.test_suites.get(suite_name, [])
results = []
for case in cases:
output = prompt_fn(case.input_text)
score = scorer(case.expected_output, output)
result = EvalResult(
test_case=case,
actual_output=output,
score=score,
latency_ms=0, # would be measured in production
token_count=len(output.split()),
passed=score >= 0.8
)
results.append(result)
self.results[prompt_name] = results
return self._summarize(prompt_name, results)
def _summarize(self, name: str, results: list[EvalResult]) -> dict:
scores = [r.score for r in results]
return {
"prompt": name,
"total_cases": len(results),
"passed": sum(1 for r in results if r.passed),
"failed": sum(1 for r in results if not r.passed),
"avg_score": round(sum(scores) / len(scores), 4) if scores else 0,
"min_score": round(min(scores), 4) if scores else 0,
"max_score": round(max(scores), 4) if scores else 0,
}
def compare(self, prompt_names: list[str]) -> str:
"""Compare multiple prompts side by side."""
lines = ["\nPrompt Comparison:", "-" * 50]
for name in prompt_names:
if name in self.results:
summary = self._summarize(name, self.results[name])
lines.append(
f" {name}: avg={summary['avg_score']:.3f} "
f"pass={summary['passed']}/{summary['total_cases']}"
)
return "\n".join(lines)
if __name__ == "__main__":
evaluator = PromptEvaluator()
evaluator.add_test_suite("classification", [
TestCase("My payment failed", "billing_issue", "billing"),
TestCase("How do I export data?", "feature_question", "support"),
TestCase("Your app is amazing!", "feedback_positive", "feedback"),
])
def simple_prompt(text):
return "billing_issue" if "payment" in text else "feature_question"
def exact_match_scorer(expected, actual):
return 1.0 if expected.strip() == actual.strip() else 0.0
result = evaluator.evaluate("v1_simple", simple_prompt, "classification", exact_match_scorer)
print(json.dumps(result, indent=2))