Evaluation¶
ExecutionKit's eval helpers run a function, check its result, and return structured pass/fail data. They do not choose datasets, define product quality, or make model results repeatable.
Run repeatable cases¶
from executionkit import EvalCase, MockProvider, consensus, run_eval_suite
async def run_case() -> str:
provider = MockProvider(responses=["FR", "FR", "DE"])
result = await consensus(
provider,
"Return only the ISO country code for France.",
num_samples=3,
)
return result.value
def check_case(value: str) -> str | None:
return None if value == "FR" else f"expected FR, got {value!r}"
report = await run_eval_suite(
[EvalCase(name="country_code", run=run_case, check=check_case)]
)
print(report.summary())
assert report.passed
run and check may be sync or async. The check result is interpreted as:
| Return value | Outcome |
|---|---|
None, True, or "" |
Pass |
False |
Fail with a generic reason |
| Non-empty string | Fail with that string as the reason |
An exception in run or check becomes a failed EvalResult. The remaining
cases still run.
For repeatable cases, use MockProvider or pure functions and require
report.passed.
Use an accuracy threshold¶
Live model output can vary. Set min_accuracy only when a partial pass rate is
an intentional part of the eval definition:
report = await run_eval_suite(cases, min_accuracy=0.8)
if not report.accuracy_passed:
for failure in report.failures:
print(failure.name, failure.reason)
report.passed still means every case passed. report.accuracy_passed
compares the measured accuracy with min_accuracy.
Do not lower a threshold merely to make an unstable run green. Record the
model, endpoint, prompt version, dataset version, and sample size outside the
basic EvalReport when those values matter.
Evaluate conversation state¶
ConversationScript drives several text-only turns through one Kit:
from executionkit import (
ConversationScript,
Kit,
MockProvider,
Turn,
run_conversation_script,
)
def mentions_order(result) -> str | None:
return None if "12345" in result.value else "order id missing"
script = ConversationScript(
name="order_context",
turns=(
Turn("Remember order 12345.", mentions_order),
Turn("Which order did I mention?", mentions_order),
),
)
kit = Kit(MockProvider(responses=["Order 12345 noted.", "You mentioned 12345."]))
report = await run_conversation_script(script, kit)
The helper calls kit.turn() without a tool list. Use a normal EvalCase when
the conversation must register tools or inspect application state around each
turn.
Enable a live provider¶
from executionkit import live_provider_from_env
provider = live_provider_from_env()
if provider is None:
print("live eval disabled")
Set:
EXECUTIONKIT_LIVE_EVAL=1
EXECUTIONKIT_BASE_URL=<OpenAI-compatible base URL>
EXECUTIONKIT_MODEL=<model ID>
EXECUTIONKIT_API_KEY=<optional key>
When the enable flag is absent, the helper returns None. When it is set,
missing base URL or model values raise ValueError.
Keep live checks separate from required repeatable checks. A live run can fail because of endpoint availability, rate limits, model changes, or sampling variation even when package behavior is unchanged.
Repository eval tiers¶
The repository uses several tiers for different questions:
| Tier | Question | Required for normal CI |
|---|---|---|
| Unit and golden cases | Does package code return the expected values and metadata for fixed inputs? | Yes |
| Failure corpus | Does package code handle named malformed or hostile inputs as specified? | Yes |
| Live provider | Can the package run against a configured real endpoint, and what outputs does that endpoint produce? | No |
| Judge calibration | Does the chosen judge rank controlled good and bad answers in the expected order? | No |
| Claude corpus review | Does an external model agree with the written expected outcome for each failure case? | No |
Only the first two tiers are repeatable package gates. Live and judge tiers are additional evidence; they are not proof of general model quality.
Build useful cases¶
A good eval case states:
- the behavior being checked;
- the input and relevant configuration;
- the expected result or failure mode;
- why the check is stable; and
- which change should cause it to fail.
Avoid checks that only assert that a non-empty string was returned. Prefer exact values, bounded invariants, metadata, error types, or application rules.