App Evaluation
AppEvaluator evaluates a Persian AI app — any framework — against a suite of Golden expectations. Each filled Golden field switches on its check; there is no separate metric configuration.
Attributes:
| Name | Type | Description |
|---|---|---|
goldens |
list[Golden]
|
The golden expectations to evaluate. |
judge |
Model | Callable | str
|
The judge for judge-based
checks — a parsbench Model, a callable |
metrics |
list[str]
|
Filter/override of which checks run, e.g. ["tools:strict", "contains"]. |
Methods:
| Name | Description |
|---|---|
evaluate |
Runs each golden against the app and scores the traces. |
score_traces |
Scores pre-captured traces instead of running the app. |
Source code in parsbench/appeval/evaluator.py
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | |
evaluate(app, n_runs=1, prefer_concurrency=False, n_workers=4, save_evaluation=False, output_path=None, record=True)
Run each golden against the app and score the resulting traces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Callable
|
The app under evaluation — a plain callable (sync
or async), |
required |
n_runs
|
int
|
Repeated runs per golden (default is 1);
see |
1
|
prefer_concurrency
|
bool
|
Evaluate goldens in parallel over a thread pool (default is False). The app and judge callables must then be thread-safe — most stateful bots are not, which is why this is off by default. |
False
|
n_workers
|
int
|
The number of workers for concurrent processing (default is 4). |
4
|
save_evaluation
|
bool
|
Flag to save the evaluation result (default is False). |
False
|
output_path
|
str
|
The output path to save the evaluation result. |
None
|
record
|
bool
|
Record this run into the local
|
True
|
Returns:
| Name | Type | Description |
|---|---|---|
AppEvaluationResult |
AppEvaluationResult
|
The evaluation result over all goldens. |
Source code in parsbench/appeval/evaluator.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | |
score_traces(traces, save_evaluation=False, output_path=None, record=True)
Score pre-captured traces instead of running the app — run the app yourself (or in production) and evaluate what happened.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traces
|
list[Trace | list[dict]]
|
One trace per golden — a Trace or an OpenAI-format message list. |
required |
save_evaluation
|
bool
|
Flag to save the evaluation result (default is False). |
False
|
output_path
|
str
|
The output path to save the evaluation result. |
None
|
record
|
bool
|
Record this run into the local
|
True
|
Returns:
| Name | Type | Description |
|---|---|---|
AppEvaluationResult |
AppEvaluationResult
|
The evaluation result over all goldens. |
Source code in parsbench/appeval/evaluator.py
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
One expectation for the evaluated app. Every field except input is
optional; each filled field switches on its corresponding check.
Attributes:
| Name | Type | Description |
|---|---|---|
input |
str
|
The user message sent to the app. |
name |
str
|
A readable name for reports. |
output |
str
|
Reference answer; judged by the |
contains |
list[str]
|
Substrings the answer must state — normalized, money- and calendar-equivalent. |
not_contains |
list[str]
|
Substrings the answer must not state. |
format |
type
|
A pydantic-style model class the answer must
validate against (checked via |
tools |
list[ToolCall]
|
Tool calls the app must make; arguments are compared date -> number -> normalized text. |
forbidden_tools |
list[str]
|
Tools the app must not call. |
context |
list[str]
|
Grounding context; judged by the |
refuses |
bool
|
Whether the app must decline the request ( |
max_steps |
int
|
Budget on assistant steps. |
max_latency |
float
|
Budget on wall-clock seconds. |
max_cost |
float
|
Budget on cost. |
check |
Callable
|
Custom |
tags |
list[str]
|
Free-form labels. |
Methods:
| Name | Description |
|---|---|
from_dict |
Builds a Golden from a dict, accepting the short |
Source code in parsbench/appeval/golden.py
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 | |
A multi-turn expectation, consumed by SimulationEvaluator.
Attributes:
| Name | Type | Description |
|---|---|---|
goal |
str
|
What the simulated user wants from the conversation. |
name |
str
|
A readable name for reports. |
scenario |
str
|
Extra situation description for the user simulator. |
expected_outcome |
str
|
What "done well" looks like, for the judge. |
criteria |
list[str]
|
Behaviors judged each as their own check. |
max_turns |
int
|
Turn cap for the conversation. |
Source code in parsbench/appeval/golden.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
What the evaluated app actually did in response to one input.
Attributes:
| Name | Type | Description |
|---|---|---|
messages |
list[Message]
|
The conversation messages, OpenAI-style. |
final_output |
str
|
The final assistant answer. |
latency |
float
|
Wall-clock seconds for the run. |
cost |
float
|
Cost of the run, if known. |
raw |
Any
|
The original framework object, untouched. |
Methods:
| Name | Description |
|---|---|
from_messages |
Builds a Trace from OpenAI-format message dicts. |
Source code in parsbench/appeval/trace.py
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
from_messages(messages, *, final_output=None, raw=None)
classmethod
Build a Trace from OpenAI-format message dicts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
list[dict]
|
OpenAI chat-format messages. |
required |
final_output
|
Any
|
Overrides the derived final answer, but only when it is a non-empty string. |
None
|
raw
|
Any
|
The original framework object to keep around. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Trace |
Trace
|
The parsed trace. |
Source code in parsbench/appeval/trace.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
A single message in a conversation trace.
Attributes:
| Name | Type | Description |
|---|---|---|
role |
str
|
One of "system", "user", "assistant", or "tool". |
content |
str
|
The text content of the message. |
tool_calls |
list[ToolCall]
|
Tool calls made in this message. |
tool_call_id |
str
|
For tool messages, the id of the call this message answers. |
Source code in parsbench/appeval/trace.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
Represents an expected or observed tool call.
Extra keyword arguments become tool arguments, so the short form
ToolCall("search", date="1405-07-05") is equivalent to
ToolCall(name="search", arguments={"date": "1405-07-05"}).
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name of the tool. |
arguments |
dict
|
The arguments the tool was (or should be) called with. |
result |
Any
|
The observed tool result, if any. |
error |
str
|
The observed tool error, if any. |
Source code in parsbench/appeval/trace.py
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 | |
The result of evaluating an app against a suite of goldens.
Attributes:
| Name | Type | Description |
|---|---|---|
golden_results |
list[GoldenEvaluationResult]
|
One result per golden. |
Methods:
| Name | Description |
|---|---|
score |
Mean score, optionally restricted to one check name. |
pass_hat_k |
tau2-style pass^k consistency over repeated runs. |
save |
Writes the result to |
diff |
Prints per-check mean deltas vs a previously saved result. |
to_langfuse |
Pushes per-check scores to a Langfuse instance. |
assert_passed |
Raises AssertionError with the failing checks (pytest-friendly). |
Source code in parsbench/appeval/evaluation_result.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
assert_passed()
Raise AssertionError listing every failing check, for pytest/CI.
Source code in parsbench/appeval/evaluation_result.py
214 215 216 217 218 219 220 221 222 223 | |
diff(path)
Print per-check mean score deltas vs a previously saved result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to a saved |
required |
Source code in parsbench/appeval/evaluation_result.py
194 195 196 197 198 199 200 201 202 203 204 205 206 | |
pass_hat_k(k=None)
tau2-style pass^k over repeated runs: C(c,k)/C(n,k) averaged over goldens, where n = runs done and c = runs passed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
The consistency exponent (default is all runs). |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
float |
float
|
The pass^k score. |
Source code in parsbench/appeval/evaluation_result.py
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
to_langfuse(**kwargs)
Push per-check scores into Langfuse. Returns the created trace id.
Source code in parsbench/appeval/evaluation_result.py
208 209 210 211 212 | |
The evaluation result for one golden: its check results and, when the golden was run more than once, the pass/fail of each repeated run.
Attributes:
| Name | Type | Description |
|---|---|---|
golden_name |
str
|
The label of the evaluated golden. |
check_results |
list[CheckResult]
|
The results of each check. |
run_passes |
list[bool]
|
Pass/fail of each repeated run (n_runs > 1); a single-element list for a single run. |
transcript |
str
|
The rendered conversation (simulation only). |
Source code in parsbench/appeval/evaluation_result.py
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 | |
The outcome of a single check on a single golden.
Attributes:
| Name | Type | Description |
|---|---|---|
check |
str
|
The name of the check (e.g. "contains", "tools:subset"). |
score |
float
|
The check score between 0 and 1. |
passed |
bool
|
Whether the check passed. |
skipped |
bool
|
Whether the check was skipped (e.g. no judge configured). |
reason |
str
|
A readable explanation for failures/skips. |
Source code in parsbench/appeval/evaluation_result.py
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 | |
SimulationEvaluator simulates Persian users against a bot and judges the finished conversations against each goal and its criteria.
Attributes:
| Name | Type | Description |
|---|---|---|
goldens |
list[ConversationGolden]
|
The multi-turn expectations. |
user |
PersianUser
|
The simulated user's personality. Also accepts a string like "محاورهای+finglish_switch" mixing a register with trap names (see TRAPS); free text becomes extra instructions. |
simulator_model |
Model | Callable | str
|
The user-simulator LLM (falls back to PARSBENCH_SIMULATOR, then PARSBENCH_JUDGE env). |
judge |
Model | Callable | str
|
The conversation judge (falls back to the PARSBENCH_JUDGE env var). |
Methods:
| Name | Description |
|---|---|
evaluate |
Simulates the conversations against the app and scores them. |
Source code in parsbench/appeval/simulation.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | |
evaluate(app, n_runs=1, max_turns=None, save_evaluation=False, output_path=None, record=True)
Simulate each golden's conversation against the app and judge it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Callable
|
The bot under evaluation — |
required |
n_runs
|
int
|
Repeated conversations per golden
(default is 1); see |
1
|
max_turns
|
int
|
Turn cap override; defaults to each
golden's own |
None
|
save_evaluation
|
bool
|
Flag to save the evaluation result (default is False). |
False
|
output_path
|
str
|
The output path to save the evaluation result. |
None
|
record
|
bool
|
Record this run into the local
|
True
|
Returns:
| Name | Type | Description |
|---|---|---|
AppEvaluationResult |
AppEvaluationResult
|
The evaluation result over all conversations. |
Source code in parsbench/appeval/simulation.py
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
The simulated user's personality. Editable, composable.
Attributes:
| Name | Type | Description |
|---|---|---|
style |
str
|
The speech register (e.g. "محاورهای", "رسمی"). |
persona |
str
|
Extra free-text persona description. |
traps |
list[str]
|
Keys of TRAPS or free-text instructions. |
Methods:
| Name | Description |
|---|---|
system_prompt |
Renders the simulator system prompt for a golden. |
Source code in parsbench/appeval/simulation.py
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 | |
GoldenGenerator turns the user's own documentation into Golden
expectations, ready to feed an AppEvaluator. Generated goldens carry
their source chunk as context, so faithfulness is judged automatically.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
Model | Callable | str
|
The generator LLM (falls back to PARSBENCH_GENERATOR, then PARSBENCH_JUDGE env). |
registers |
list[str]
|
Question registers to rotate through (default is formal and colloquial Persian). |
adversarial |
bool
|
Mix digit scripts, Jalali dates, and Finglish into some questions (default is False). |
Methods:
| Name | Description |
|---|---|
generate |
Generates goldens from documents. |
Source code in parsbench/appeval/generator.py
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
generate(docs, n=20)
Generate goldens from the given documents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
docs
|
str | Path | list
|
A file path, glob pattern, directory, or a list of those. Text-like files only (.txt, .md, .rst, .html, .json). |
required |
n
|
int
|
The number of goldens to generate (default is 20). |
20
|
Returns:
| Type | Description |
|---|---|
list[Golden]
|
list[Golden]: The generated goldens. |
Source code in parsbench/appeval/generator.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
JudgeCalibrator measures how well a judge model agrees with human labels on a labeled sample, so judge scores can be trusted (or fixed) before they are published.
Attributes:
| Name | Type | Description |
|---|---|---|
judge |
Model | Callable | str
|
The judge to calibrate (falls back to the PARSBENCH_JUDGE env var). |
Methods:
| Name | Description |
|---|---|
calibrate |
Scores the labeled items and computes agreement and kappa. |
Source code in parsbench/appeval/calibration.py
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
calibrate(items, prefer_concurrency=False, n_workers=4)
Score each labeled item with the judge and compare to the human label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[dict]
|
Items of the form
|
required |
prefer_concurrency
|
bool
|
Fan judge calls out over a thread pool (default is False); the judge callable must then be thread-safe. |
False
|
n_workers
|
int
|
The number of workers for concurrent processing (default is 4). |
4
|
Returns:
| Name | Type | Description |
|---|---|---|
CalibrationResult |
CalibrationResult
|
Agreement, Cohen's kappa, and disagreements. |
Source code in parsbench/appeval/calibration.py
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
The result of calibrating a judge against human labels.
Attributes:
| Name | Type | Description |
|---|---|---|
n |
int
|
The number of labeled items. |
agreement |
float
|
Fraction where judge pass/fail == human label. |
kappa |
float
|
Cohen's kappa vs human labels. |
disagreements |
list[str]
|
Readable descriptions for error analysis. |
Source code in parsbench/appeval/calibration.py
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 | |
Unify codepoints/digits, read ZWNJ as a space, drop thousands commas, collapse whitespace.
Source code in parsbench/appeval/normalize.py
34 35 36 37 38 39 | |
Substring check that survives digit scripts, ZWNJ and spacing variants.
Source code in parsbench/appeval/normalize.py
42 43 44 | |
Parse '۲۵۰ هزار تومان' → (2_500_000.0, 'rial'). Returns (value, unit|None).
Source code in parsbench/appeval/normalize.py
104 105 106 107 | |
Source code in parsbench/appeval/normalize.py
110 111 112 113 114 115 116 117 118 119 120 | |
Does the text state this amount, in any unit/scale/digit-script? amount_in('قیمت ۲٬۵۰۰٬۰۰۰ ریال است', '250 هزار تومان') → True.
Source code in parsbench/appeval/normalize.py
123 124 125 126 | |
Parse a date(-time) string to a Gregorian (y, m, d). Year <1600 → Jalali. The whole string must be the date — ranges and prose return None.
Source code in parsbench/appeval/normalize.py
227 228 229 230 231 232 233 | |
Source code in parsbench/appeval/normalize.py
236 237 238 | |
Does the text mention this date, in either calendar?
Source code in parsbench/appeval/normalize.py
241 242 243 | |
Equivalence chain for text expectations (contains / not_contains): normalized substring, then money-equivalence, then calendar-equivalence. Pass normalized=True when the haystack is already normalize()d.
Source code in parsbench/appeval/normalize.py
257 258 259 260 261 262 263 264 265 266 267 | |
Equivalence chain for tool arguments: date → number → normalized string.
Source code in parsbench/appeval/normalize.py
270 271 272 273 274 275 276 277 | |