case_control="review"AI drafts, you approve
Use for high-stakes or tightly specified work. AI proposes cases and expected behavior; none counts until approve().
Evalt Python SDK
Install Evalt, define durable routes, validate suites, control provider spend, inspect promotion decisions, and enforce measured quality in CI.
pip install evalt
Set OPENROUTER_API_KEY in your environment or a .env file in the directory where you run Python. Evalt() finds it automatically; an explicit api_key= takes precedence. The key is never written to Evalt’s route database or result files.
from evalt import Evalt
ticket = "Please help—the website won't load."
evalt = Evalt() # interactive terminals show design, tournament, route, and cost
answer = evalt.run(
"Classify this request. Return exactly one lowercase label: billing, account, or technical.",
ticket,
task="Route recurring support tickets to billing, account, or technical.",
route="support-routing",
target_accuracy=0.95,
test_budget_usd="auto",
)
print(answer.content)
The SDK remains the production runner. The optional workspace gives its routes a visual history and live progress view without moving customer content into the dashboard.
python3 -m evalt connect
The command creates one private workspace token in your user-level Evalt config, opens evalt.dev/app, and publishes existing sanitized route summaries from the current .evalt/evalt.db without making a model call. That connection follows scripts across project folders; pass --state path/to/evalt.db only when one project needs a separate workspace. The CLI and browser show a safe ws_... ID; those IDs must match. Future Evalt.run(...) calls publish bounded route progress and final operational summaries, and the terminal explicitly reports whether each sync succeeded. Every invocation has an opaque run ID and an explicit running, completed, or failed state, so two tournaments on the same route never blend together. Prompts, inputs, outputs, cases, provider keys, request bodies, and raw responses stay local. Dashboard outages never fail a production call.
python3 -m evalt dashboardOpen the workspace connected to this Python installation.python3 -m evalt --statusShow the safe workspace ID without opening a browser; the longer dashboard --status form also works.python3 -m evalt doctorCompare the imported package, Python executable, PATH console shim, local routes, hosted routes and workspace reachability without provider calls.python3 -m evalt dashboard --sync-existingRecover current sanitized route summaries without rerunning a tournament.python3 -m evalt disconnectRemove the local connection. This does not change a serving route or delete remote metadata.EVALT_WORKSPACE_TOKENUse an environment variable instead of the local config file in CI or a deployed service.The first evw_... capability is the owner key. Keep it out of CI and shared channels. Open Workspace access in the dashboard to create expiring evc_... Viewer, Publisher, or Editor capabilities. The CLI reports the effective role and permissions without printing the capability.
Evalt · support-routing · TEST DESIGN STARTED · 25 cases · current catalog-selected designer · deadline 120s · one workflow cap $1.00
Evalt · support-routing · DESIGNING TESTS · openai/gpt-5.4-mini · 10s elapsed · still working
Evalt · support-routing · TEST DRAFT READY · 25 AI-authored cases · $0.94 remains for the tournament
Evalt · support-routing · BROAD SCREEN · 8 model configuration(s) · up to 8 in parallel
Evalt · support-routing · SCREENED · qwen/qwen3.5-9b#reasoning=low · 100% validation · p90 920 ms · $0.041200 spent
Evalt · support-routing · ROUTE SELECTED · qwen/qwen3.5-9b#reasoning=low · 100% observed final test · $0.31 test spend
Evalt · support-routing · PROVISIONAL EVIDENCE · 74.1% one-sided 95% lower bound · 10 distinct scenarios
accept() or correct() to start this tournament; those methods optionally label later production outcomes. The route remains labeled AI_GENERATED_AI_JUDGED until production feedback strengthens the contract. Use first_run="bootstrap" only for an explicitly untested single call.Evalt selects a capable test designer from the current intelligence-and-price catalog, then drafts 25 varied routine, ambiguous, adversarial, boundary, and realistic-domain scenarios in five parallel batches before the split. It recommends exact, semantic, or explicit numeric-tolerance judging. A 0–100 subjective rating can therefore accept a nearby approved score without asking another model to pretend one arbitrary number is uniquely correct. The designer fallback chain is a separate live-catalog shortlist and never borrows the judge. The draft spends from the same workflow cap as the later tournament. Evalt names the active model and attempt; if a provider returns malformed structured output, it rejects that draft and makes one visible budget-bounded recovery attempt before falling back.
from evalt import Evalt
evalt = Evalt()
draft = evalt.optimize_task(
task="Route recurring support tickets to billing, account, or technical.",
prompt="Return exactly one lowercase label: billing, account, or technical.",
route="support-routing",
case_control="review",
workflow_budget_usd=1.00,
)
draft.save("support-routing-draft.json")
for case in draft.examples:
print(case.id, case.conversation())
if input("Type APPROVE after reviewing every expected output: ").strip() != "APPROVE":
raise SystemExit("Draft saved; no tournament ran.")
# Explicit trust boundary; pass edited examples to approve(...) when needed.
suite = draft.approve()
result = evalt.run(suite)
case_control="review"Use for high-stakes or tightly specified work. AI proposes cases and expected behavior; none counts until approve().
Evalt.run(...)The default creates, calibrates, judges, and runs the suite immediately, with permanent AI_GENERATED_AI_JUDGED provenance.
After approval, the tournament tries current model candidates and supported reasoning levels in parallel. Prompt rewrites and approved training-only few-shot packages compete too. With the default balanced 25-case suite, ten cases train prompt packages, five choose among them, and ten unique unseen cases confirm the frozen winner. Each final case runs twice, for twenty final executions; prompt tuning never sees them.
A route is one stable production task with its own prompt, examples, budget, selected configuration, and maintenance history.
Evalt.run(..., route=name)On a new route, design and test a package before answering; afterward, reuse the promoted package and return a feedback-capable answer.Evalt.run(..., route_version=id)Serve that exact current qualified package or raise RouteVersionMismatch before provider spend. Use this in deployed services.Evalt.run(..., reoptimize=True)Deliberately run a new qualifying test for an existing text route. The current package stays in place unless the replacement clears the frozen gates.answer.contentRead the production output before recording a verdict.answer.accept()Store this input and output as approved evidence for this route only.answer.correct(expected)Store the corrected expected behavior without changing another route.evalt.route_status(name)Inspect the promoted model, prompt version, evidence count, budget, and maintenance reasons.status = evalt.route_status("support-routing")
print(status["selected_model"])
print(status["current_package_id"])
print(status["selected_prompt_version"])
print(status["target_accuracy"])
print(status["test_budget_usd"])
print(status["maintenance_due"])
support = evalt.run(support_prompt, ticket, route="support-routing")
summary = evalt.run(summary_prompt, transcript, route="call-summary")
fraud = evalt.run(risk_prompt, transaction, route="fraud-review")
Each route keeps separate prompts, evidence, winners, budgets, and retest history. Feedback never crosses route names.
Put Chat Completions settings in request_options. Evalt sends the same canonical envelope to every target-model training, validation, and final-test call, stores its SHA-256 fingerprint with the winning route, and reuses it when later calls omit the argument. Test design, prompt optimization, and judging keep independent settings.
answer = evalt.run(
prompt,
ticket,
route="support-routing",
max_tokens=2048,
request_options={
"temperature": 0.2,
"top_p": 0.95,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_route",
"strict": True,
"schema": route_schema,
},
},
"tools": tools,
"tool_choice": "auto",
"parallel_tool_calls": False,
"provider": {
"require_parameters": True,
"data_collection": "deny",
"zdr": True,
},
"plugins": [{"id": "response-healing"}],
},
)
print(answer.content)
print(answer.tool_calls)
print(answer.request_envelope_validated)
request_options={...}Accepts current and future JSON-serializable Chat Completions fields, including structured output, sampling controls, penalties, stop sequences, tools, prediction, provider routing, plugins, web search, verbosity, and transforms.max_tokens=...Evalt owns the output ceiling and tests it as part of the route contract. Omit it later to reuse the tested value.answer.tool_callsReturns normalized function calls without treating a content-free tool response as an empty completion.strict_request_options=TrueReject a production override before provider spend. Without strict mode, Evalt allows it but emits RequestEnvelopeDriftWarning and marks the answer unvalidated.route_status(name)Shows the persisted envelope, output ceiling, fingerprint, and every recorded drift event.model, fallback models, messages, prompt, usage, streaming lifecycle, output-token fields, and reasoning effort cannot be hidden inside request_options. Evalt varies or accounts for those directly. Zero Data Retention, denied provider data collection, and required-parameter routing remain enforced safety defaults. The pass-through covers the Chat Completions runner, not separate image, embeddings, rerank, speech, or video endpoints.Evalt accepts local PNG, JPEG, WebP, and GIF files or public HTTPS image URLs whose path ends in one of those extensions through the Chat Completions image-understanding path. It filters out text-only candidates before spend, keeps the image modality in the tested request contract, and never sends raw images, filenames, paths, source URLs, or thumbnails to the hosted dashboard.
You do not need AI to make the examples: choose images whose answers you already know, write the expected label yourself, and review the set before running it. AI-generated or automatically inferred image labels do not count as approved ground truth.
evalt import-dataset package-labels.csv \
--prompt "Inspect the package. Return exactly one label: damaged or intact." \
--input-text "Inspect this image." \
--image-field image_path \
--expected-field label \
--id-field case_id \
--group-field condition \
--evaluator exact_text \
--model google/gemini-2.5-flash-lite \
--model openai/gpt-5.4-nano \
--confirm-expected-human-approved \
--output package-condition.json
The importer reads CSV, JSONL, or a JSON array entirely offline. Mappings are exact; Evalt does not guess the input, expected answer, ID, group, weight, or image columns. Use --input-field text instead of --input-text when text differs by row. Image paths must be relative to the dataset file. The importer validates local PNG, JPEG, and WebP files, rejects URLs and directory traversal, refuses ambiguous mappings, duplicate IDs, malformed records, unsafe sizes, and existing outputs, then emits a deterministic evalt-suite-v2 file. Its machine-readable summary contains only counts and a hash—never prompts, rows, labels, paths, or images—and it does not read provider or dashboard credentials.
from evalt import Evalt, Example, ImageInput, Suite, multimodal_input
examples = tuple(
Example(
multimodal_input("Return exactly one label: damaged or intact.", ImageInput.from_path(path)),
approved_output=label,
id=f"package-{index}",
)
for index, (path, label) in enumerate(approved_images, start=1)
)
suite = Suite(
name="package-condition",
prompt="Inspect the package image. Return exactly one label: damaged or intact.",
examples=examples,
models=("google/gemini-2.5-flash-lite", "openai/gpt-5.4-nano"),
evaluator={"type": "exact_text"},
optimize_prompt=False,
max_optimization_cost_usd=0.25,
)
result = Evalt().run(suite)
print(result.winner.model, result.winner.holdout_pass_rate)
ImageInput.from_path(path)Validates the file signature and size locally, then creates the provider data URL in memory. The original file is never changed.ImageInput.from_url(url)Accepts public HTTPS URLs without embedded username/password credentials when the URL path ends in .png, .jpg, .jpeg, .webp, or .gif. URL query data is never written to route call metadata.multimodal_input(text, *images)Builds the ordered text-first multipart content used in every target-model case.optimize_prompt=FalseRequired for image suites in this release. Evalt compares models against the frozen prompt and labeled images instead of pretending an automatic text-only prompt writer saw the evidence.Turn an approved suite into an immutable local history. Each revision names its exact parent, requires stable case IDs, and records whether cases were added, removed, changed, or left untouched. A stale base fails instead of silently replacing another reviewer's work.
evalt dataset create support support-v1.json \
--message "Initial human-reviewed cases" \
--tag reviewed
evalt dataset revise support support-v2.json \
--base dv_0123456789abcdef0123456789abcdef \
--message "Correct labels and add refund edge cases"
evalt dataset diff support \
--from-version dv_0123456789abcdef0123456789abcdef
evalt dataset export support \
--output support-production.json
dataset createValidate and freeze one Evalt suite as the first immutable version.dataset reviseFreeze a complete successor suite only when --base is still the latest version.dataset diffShow added, removed, changed, and unchanged counts. Case IDs remain hidden unless --show-case-ids is explicit.dataset list / showVerify local history and print bounded metadata without prompts, cases, expected answers, or images.dataset exportRecover any exact historical version as a runnable evalt-suite-v2 file..evalt/datasets, never reads provider or dashboard credentials, and never synchronizes content or version metadata. Objects and the index are integrity-checked; Evalt exposes no delete or history-rewrite command.Create a private queue from one frozen suite and one matched set of judge decisions. Reviewers claim explicit cases, record a human pass or fail, and can skip or reopen an item without losing the queue's event history. Calibration then separates true passes, false passes, true failures, and false failures instead of hiding disagreement inside one average.
evalt review create support support-production.json \
--decisions judge-decisions.json \
--rubric "Pass only when the approved behavior is fully satisfied."
evalt review claim support case-17 \
--reviewer reviewer-a \
--revision qr_1_0123456789abcdef
evalt review resolve support case-17 \
--reviewer reviewer-a \
--human-fail \
--corrected-output-file corrected.txt \
--revision qr_2_0123456789abcdef
evalt review calibrate support \
--output judge-calibration.json
review createRequire an exact decision for every stable suite case ID, then freeze the private rubric, cases, judge pass/fail labels, and optional scores locally.review claim / resolve / skip / reopenApply strict state transitions against an exact qr_... revision. A stale writer fails instead of replacing another reviewer's decision.review list / showVerify queues and print progress without prompts, cases, approved outputs, corrections, or rubrics. One case is revealed only with explicit --include-content.review calibrateReport agreement, false-pass and false-fail rates, confusion counts, coverage, and a score-threshold sweep. Case IDs remain hidden unless --show-case-ids is explicit.review exportWrite the exact resolved decision set for later local analysis or qualification evidence.evalt review commands are offline. The store defaults to .evalt/reviews, reads no provider or dashboard credential, and synchronizes no case, rubric, decision, correction, reviewer label, score, path, hash, or progress metadata.false_pass_rate is the share of judge passes rejected by a human. false_fail_rate is the share of judge failures accepted by a human. A calibration report describes only the reviewed queue revision; it does not prove that one reviewer is universal ground truth. Run python -m evalt.examples.review_calibration for a complete packaged offline example.
Compare customer-owned Python functions, agents, retrieval code, or RAG pipelines against one frozen set of approved cases. Every system receives the same stable case identity and an isolated deepcopy-compatible input. Evalt records quality, failures, p50 and p90 latency, configured cost, and the lowest-cost system clearing the floor without making a provider call or uploading the cases.
from evalt import CallableSystem, SystemExample, evaluate_systems
cases = (
SystemExample(
{"query": "How long are refunds available?", "documents": documents},
"30 days",
id="refund-window",
critical=True,
),
)
result = evaluate_systems(
cases,
(
CallableSystem("baseline", baseline),
CallableSystem("rag-v2", rag_v2, cost_per_call_usd=0.0003),
),
evaluator={"type": "exact_text"},
quality_threshold=1.0,
)
print(result.selected_system)
result.save("application-eval.json")
SystemExampleFreeze structured Python input, approved output, stable ID, group, difficulty, weight, critical flag, and optional multi-turn contract.CallableSystemRegister one explicit local sync or async callable with a deadline, output bound, configured per-call cost, and content-free metadata.ProcessSystemRun a trusted local module:function entrypoint in a supervised child process that is terminated when its deadline expires.evaluate_systemsRun bounded concurrent local comparison with exact-text, exact-JSON, numeric-tolerance, or explicit local custom scoring.evaluate_systems_asyncUse the same contract inside an existing event loop without nesting asyncio.run.SystemOutputReturn JSON-serializable content plus measured incremental cost and bounded metadata when one fixed cost is not enough.CallableSystem for in-process functions and ProcessSystem("rag-v2", "my_app.rag:answer") when a non-cooperative deadline must terminate the child. Process supervision is a lifecycle boundary, not an operating-system security sandbox; the local entrypoint must still be trusted.Result export is content-free by default: it stores case IDs, decisions, metrics, and hashes instead of inputs or outputs. Use include_content=True only for an intentional private local artifact. The adapters read no provider or dashboard credential and perform no synchronization. Run python -m evalt.examples.callable_rag for the in-process example or python -m evalt.examples.process_supervision for the supervised-process example.
The free workspace is organized around durable named routes, with the latest local invocation scoped inside each route. Open one to see whether the current run is active, completed, or failed; its progress list excludes older invocations while the serving configuration and route history remain durable.
RoutesThe task portfolio: current quality, production cost, serving configuration, and last check.EvidenceThe exact validation and final-test contract that a challenger must clear.HistoryPromotions, rejections, prompt changes, price snapshots, and explicit approvals.IntegrateThe stable route name production code calls instead of hard-coding today’s model.Workspace access remains accountless. An owner creates a named, expiring capability and Evalt shows its secret once. Access links keep the capability in the URL fragment, so it is not sent in the page request, server logs, or referrer. The server stores only a one-way capability digest plus the bounded label, role, creation, expiry, revocation, and last-used times.
ViewerList and inspect synchronized routes. Cannot publish, delete, or manage access.PublisherPublish aggregate route metadata from the SDK or CI. Cannot list, read, delete, or manage access.EditorList, inspect, and publish routes. Cannot delete dashboard copies or manage access.OwnerFull route and access control. Existing evw_... workspaces remain compatible.Feedback is explicit. Evalt does not silently infer that a delivered answer was correct. Accept an outcome only when it meets the real task; correct it with the expected behavior when it does not.
answer.accept()Adds the input and returned output as approved evidence.answer.correct(expected)Adds the input with the corrected expected output.answer.contentThe production response; usable before feedback is recorded.evalt.route_status(name)Returns the promoted route, evidence counts, price snapshot, and maintenance state.evalt.route_versions(name)Lists immutable qualified local packages without a provider call.evalt.annotate_route_version(name, id, alias=…, note=…)Adds a private local name or note without provider spend or dashboard sync.evalt.rollback_route(name, version)Atomically restores a qualified package by exact ID or local alias and records the rollback.Use a suite when you already have approved cases, need an auditable tournament, or want a CI artifact. Validation is offline; optimization is the only command below that calls a provider.
python3 -m evalt init evalt.jsonCreate a documented starter suite.python3 -m evalt validate evalt.jsonValidate schema and contracts offline.python3 -m evalt optimize evalt.json --output evalt-result.jsonRun the bounded search and save JSON.evalt.qualify_route(approved_suite, route="support-routing"). This is also the production installation path for human-labeled image suites. Qualified packages are versioned locally; image bytes and visual few-shot examples are not persisted.status = evalt.route_status("support-routing")
version = status["current_package_id"]
answer = evalt.run(
prompt,
input,
route="support-routing",
route_version=version,
)
Safe rollout: qualify locally, read the new current_package_id, deploy that pin, and deliberately replace it only after review. A stale, missing, cross-route, unqualified, or damaged pin fails before any provider call. Rollback creates a new current package ID, so deploy that new ID rather than silently reusing the old one.
python3 -m evalt versions --route support-routingInspect qualified local packages without a provider call.python3 -m evalt annotate-version --route support-routing --version rv_… --alias known-good --note "Approved candidate"Add a private local name and note. Neither value is synchronized to the hosted dashboard.python3 -m evalt rollback --route support-routing --version known-good --yesRestore by unambiguous local alias; the result always returns the canonical exact ID.route_version= pinned to the exact immutable rv_… ID so a renamed alias cannot change serving behavior.{
"schema": "evalt-suite-v2",
"name": "support-routing",
"prompt": "Return billing, account, or technical.",
"examples": [
{"id": "billing-1", "input": "Charged twice", "approved_output": "billing"},
{"id": "account-1", "input": "Reset link expired", "approved_output": "account"},
{"id": "technical-1", "input": "App freezes", "approved_output": "technical"}
],
"evaluator": {"type": "exact_text"},
"quality_threshold": 0.95,
"max_optimization_cost_usd": 2.0,
"rounds": 3,
"max_parallel_models": 16,
"max_parallel_scenarios": 32,
"request_timeout_seconds": 600
}
An overall 95% can be misleading when routine requests dominate traffic. Use group to stratify related cases across training, validation, and final test; weight to approximate production frequency; and difficulty_thresholds to require separate routine, complex, and adversarial floors.
{
"examples": [
{"id":"routine-01", "group":"ordinary-refund", "difficulty":"routine", "weight":6, "input":"...", "approved_output":"approve"},
{"id":"hard-01", "group":"policy-conflict", "difficulty":"adversarial", "weight":1, "critical":true, "input":"...", "approved_output":"manual_review"}
],
"quality_threshold": 0.95,
"difficulty_thresholds": {
"routine": 0.95,
"complex": 0.90,
"adversarial": 0.85
}
}
This is an excerpt. When grouping is enabled, every case needs a group and every group needs at least five cases. Promotion requires the overall weighted target and every named difficulty floor. Put rare catastrophic rules in a deterministic veto too.
Prefer deterministic evaluators whenever the contract allows it. Semantic judging is useful for nuanced outputs, but it adds provider cost and must be calibrated against route-specific human decisions before promotion.
exact_textStrict normalized labels or strings. No judge-model call.
exact_jsonRequired keys, optional additional-property vetoes, and rational normalization. No judge-model call.
customYour versioned local rubric. The suite names an ID and version; trusted code is registered separately and costs no evaluator tokens.
semanticMeaning-based verdicts for open outputs. Use an explicit evaluator model and approved rubric.
from evalt import CommandScorer, Evalt, Suite
import sys
suite = Suite.load("evalt.json")
scorer = CommandScorer(
"domain-rubric", "1.0",
[sys.executable, "tools/score.py"],
timeout_seconds=5,
)
result = Evalt(
custom_scorers={scorer.scorer_id: scorer}
).run(suite)
A suite can contain only type, scorer_id, and scorer_version for a custom evaluator. It cannot choose a command, module, executable, or path. Command scorers use strict JSON stdin/stdout, no shell, bounded I/O, a per-case timeout, and a minimal environment that excludes provider credentials by default. Missing registration, version drift, timeout, nonzero exit, malformed output, and non-finite scores stop the run.
A multi-turn scenario uses a turns array. Evalt keeps the full conversation together when creating training, validation, and final-test splits, and replays prior assistant context in order.
{
"id": "refund-followup",
"turns": [
{"role": "user", "content": "I was charged after cancelling."},
{"role": "assistant", "content": "When did you cancel?"},
{"role": "user", "content": "Three days before renewal."}
],
"approved_output": "billing"
}
Turns inside one scenario remain ordered. Independent scenarios and repeated executions may run concurrently.
max_optimization_cost_usd covers prompt improvement, candidate calls, judging, retries, and final testing. Evalt reserves estimated in-flight cost before dispatch; work that cannot fit does not start.
Raise concurrency to reduce wall time, not to expand spend. Model lanes are configurable up to 32 and independent scenario executions up to 128; every request still competes for the same reserved budget. The terminal reports the broad parallel screen, then prints each settled configuration's validation rate, p90 latency, spend, and total elapsed time. Automatic first-route tests generate five coverage batches concurrently and use a 120-second deadline for both designer and candidate requests. Set designer_request_timeout_seconds or test_request_timeout_seconds when the job needs different limits. A candidate that times out cannot earn a higher reasoning rung. Automatic routes try one prompt rewrite by default; set optimization_rounds from 1 to 8 for a deeper search. Explicit suites keep a configurable 600-second default and can raise request_timeout_seconds as high as 7200 seconds.
continuation_recommendation only when budget-limited configurations remain unfinished. It lists those configurations and a bounded suggested next cap. automatic_spend is always false; a rerun still requires explicit approval.Evalt begins across a current price/intelligence frontier, then spends the remaining budget near the observed task-capability band. Ordinary supported reasoning levels may run in the same parallel wave. Extra-high and maximum effort are staged: the prior rung must complete, remain within one validation case of the quality target, and satisfy the route's p90 latency ceiling. A timeout blocks escalation, and final-test luck never unlocks a higher rung. Reasoning-only hone lanes reuse the same frozen prompt and few-shot package so the comparison isolates effort.
max_latency_seconds=3.0 only when each real response needs one. Evalt requires the measured p90 across the frozen examples—not a flattering average—to stay under it before promotion. This does not cap the parallel tournament's total wall time.optimize_prompt: false to hold the supplied prompt exactly fixed and compare only model, reasoning, and provider configurations.source_split: "train".The versioned Evalt Action can run a budget-capped tournament or gate an existing result without provider calls. It validates the suite before spending, writes JSON, HTML, and JUnit evidence, and exposes stable outputs for later deployment steps.
name: Evalt
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
performance-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Find the cheapest route that passes
id: evalt
uses: JarJarBeatyourattitude/evalt-action@v1
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
with:
suite: evalt.json
min-pass-rate: "0.95"
- uses: actions/upload-artifact@v7
if: always()
with:
name: evalt-evidence
path: |
evalt-result.json
evalt-report.html
evalt-junit.xml
max_optimization_cost_usd covers target calls, prompt improvement, judging, and final testing. The Action does not silently increase it. Use optimize: "false" to apply the same gate to a saved result fully offline.Store the OpenRouter key as a repository Actions secret. Ordinary secrets are not passed to workflows from forks; do not switch untrusted pull requests to pull_request_target merely to expose a provider key. See the Action repository for every input and output.
The local evidence library gives approved suites and exported results immutable names such as @support-v1 and @support-baseline-v1. Evalt validates every import, preserves the original UTF-8 JSON bytes, verifies the SHA-256 digest on every read, and keeps the catalog on this computer. Library names, tags, paths, hashes, suites, results, prompts, images, cases, and outputs are never synchronized to the hosted workspace.
evalt library add evalt.json \
--name support-v1 \
--tag production
evalt library add evalt-result.json \
--name support-baseline-v1 \
--tag baseline
evalt validate @support-v1
evalt check @support-baseline-v1 --min-pass-rate 0.95
evalt monitor @support-baseline-v1 \
--route support-routing \
--max-cost-usd 0.10
library addValidate and import one suite or result under a new immutable name. Repeat --tag for local organization.library listFilter bounded metadata with --kind, --tag, or --query without printing customer content.library showVerify one object and show its kind, size, tags, digest, timestamp, and safe summary only.library resolveVerify one object and print its private content-addressed path for local tooling.library exportWrite the exact verified original bytes. Existing different files are never replaced unless --force is explicit.@name works anywhere the CLI reads a suite or result: validate, optimize, monitor, report, compare, and check. For a non-default catalog, add --library-root path to those commands or set EVALT_LIBRARY_HOME. The default is .evalt/library in the current project.
@support-v2; this keeps baselines reviewable and prevents a path edit from changing the evidence behind a deployment decision. The catalog is not a backup, so copy or export important evidence into your normal encrypted backup policy.For image routes, import the original reviewed suite too, then run evalt monitor @support-baseline-v1 --suite @support-images-v1 .... The library stores the exact local suite; the dashboard still receives only the aggregate health verdict and deltas.
evalt monitor reruns only the selected prompt, model, few-shot package, evaluator, request options, final-test cases, and repeat count from an earlier result. It does not search, rewrite, promote, roll back, maintain, or otherwise change the serving route. A positive provider-spend cap is mandatory.
python3 -m evalt monitor @support-baseline-v1 \
--route support-routing \
--max-cost-usd 0.10 \
--max-regressions 0 \
--max-quality-drop-pp 0 \
--max-cost-increase-pct 15 \
--max-p90-increase-ms 250 \
--output evalt-monitor-result.json \
--history .evalt/monitor-history.jsonl
0 · HEALTHYThe frozen configuration still clears the requested absolute and baseline gates.1 · REGRESSIONThe measurement completed, but quality, a case, cost, or p90 latency crossed a configured limit.2 · ERRORThe contract is invalid, the cap cannot authorize the next call, or the provider/runtime did not complete the check.The full evalt-monitor-result-v1 file stays local and remains compatible with evalt check, evalt compare, HTML reports, and JUnit reports. Optional history is aggregate-only: timestamp, verdict, opaque suite hash, model, quality/cost/latency deltas, regression counts, and check spend. It never contains prompts, inputs, images, approved answers, model outputs, judge reasons, generation IDs, request bodies, or credentials. With --route and an existing workspace connection, only that aggregate verdict and those deltas synchronize to the route dashboard.
--suite @reviewed-image-suite-v1 or a normal file path. Evalt verifies the suite's privacy-safe image descriptors and approved outputs against the frozen contract, and recurring checks require embedded local fixtures because an HTTPS image can change without its URL changing.A scheduled GitHub workflow is the notification boundary: REGRESSION or ERROR fails the job and GitHub applies the repository's normal Actions notifications. The schedule is never enabled by Evalt, and every run consumes provider budget only up to the cap shown in the workflow.
name: Evalt route health
on:
workflow_dispatch:
schedule:
- cron: "17 8 * * 1"
permissions:
contents: read
jobs:
frozen-recheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: python3 -m pip install --upgrade -r https://evalt.onrender.com/python-sdk/latest.txt
- name: Recheck the frozen winner
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
python3 -m evalt monitor evalt-baseline.json \
--route support-routing \
--max-cost-usd 0.10 \
--max-regressions 0 \
--max-quality-drop-pp 0 \
--output evalt-monitor-result.json
- uses: actions/upload-artifact@v7
if: always()
with:
name: evalt-route-health
path: evalt-monitor-result.json
Run schedules only on a trusted branch. Commit a baseline only when its approved text cases are repository-safe. Keep private baselines and image suites in an encrypted runner secret or on a controlled self-hosted runner. HEALTHY means no configured regression was observed; it does not turn a small or provisional test set into a universal reliability guarantee.
Evalt can send a versioned aggregate event after a frozen route-health check or GitHub Action gate. The default payload contains an opaque route reference, verdict, suite hash, quality delta, regression and missing-case counts, cost change, p90 change, and measured check spend. It never contains prompts, inputs, approved answers, images, outputs, judge reasons, scorer identity or code, provider keys, the destination URL, or the signing secret.
export EVALT_WEBHOOK_SECRET="replace-with-a-long-random-secret"
python3 -m evalt monitor @support-baseline-v1 \
--route support-routing \
--max-cost-usd 0.10 \
--webhook-url https://alerts.example.com/evalt \
--webhook-destination-id incident-pipeline \
--webhook-secret-env EVALT_WEBHOOK_SECRET \
--webhook-required \
--webhook-audit .evalt/webhook-deliveries.jsonl
X-Evalt-Signaturesha256=HMAC(secret, timestamp + "." + exact_body).Idempotency-KeyStable evt_… identity across retries and explicit replay.route.health.degradedThe prior audited state was healthy and the new frozen check regressed.route.health.recoveredThe prior audited state regressed and the new frozen check is healthy.ci.gate.pass|fail|errorOne aggregate GitHub Action decision.Delivery uses HTTPS only, rejects credentials and fragments in the URL, follows no redirects, resolves and pins a public address for each attempt, and denies private, loopback, link-local, reserved, multicast, and unspecified destinations by default. It retries only bounded transport failures and selected retryable HTTP statuses, honors a capped numeric Retry-After, and retains no response body.
The local JSONL audit contains the exact aggregate event, status codes, retry timing, opaque destination label, and delivery result. Replay preserves the original event and idempotency identity:
python3 -m evalt webhook replay evt_0123456789abcdef0123456789abcdef \
--webhook-url https://alerts.example.com/evalt \
--webhook-destination-id incident-pipeline \
--webhook-secret-env EVALT_WEBHOOK_SECRET \
--webhook-audit .evalt/webhook-deliveries.jsonl
webhook-secret: ${{ secrets.EVALT_WEBHOOK_SECRET }}. The dashboard accepts only the URL, opaque label, audit path, and secret variable name needed to generate a local command; it never accepts the secret value.The private-network override exists for explicitly trusted self-hosted environments. Enabling it weakens the default SSRF boundary. Route names are also omitted by default; add --webhook-include-route-name only when the destination is authorized to receive that label.
python3 -m evalt check candidate.json \
--baseline baseline.json \
--min-pass-rate 0.95 \
--max-cost-per-success 0.002 \
--max-regressions 0 \
--max-quality-drop-pp 0 \
--require-complete-coverage
0The result clears every requested gate.1The measured result is valid but fails an absolute or frozen-baseline gate.2The input is invalid or the check could not run.python3 -m evalt check is offline. With --baseline, it first requires identical non-empty suite hashes, then rejects missing cases, newly failing frozen cases, or aggregate quality loss. Optional --max-cost-increase-pct and --max-p90-increase-ms limits add measured efficiency guardrails. It does not call OpenRouter or mutate either result.
Render saved results or compare two frozen runs without provider access. A comparison is promotion-safe only when both results pin the same suite hash.
evalt report evalt-result.json \
--html evalt-report.html \
--junit evalt-report.xml
evalt compare baseline.json candidate.json \
--output comparison.json \
--html comparison.html
evalt compare is the detailed local diagnosis; evalt check --baseline is the deployment decision. Both use the same case-level comparison and refuse promotion when suite hashes differ or are absent.
The prompt package and the specific inputs needed for a production or evaluation request.
API key, route database, approved/corrected evidence, suite files, result files, and promotion history.
Evalt follows semantic versioning for the evalt Python API, CLI command names, suite schema, result schema, and exit-code contract. Deprecations remain for at least one minor release and are documented before removal.
The historical modelsieve, last_good_prompt, and lgp names are compatibility shims. New integrations should use the evalt import and command.
Confirm the provider exposes a current ZDR endpoint for at least one candidate and that its price, context, output, and required parameters fit the suite. Evalt does not silently downgrade the data contract.
The hard budget or provider-response deadline was reached. Inspect the partial-coverage result, then raise the explicit test budget, raise the task-appropriate request_timeout_seconds, or narrow the candidate set. Partial coverage is never labeled globally best.
Evalt starts with generous reasoning-aware completion headroom: 32,768 tokens without reasoning, 65,536 at low, 98,304 at medium, and up to 131,072 at high, clamped to the route's natural output limit and remaining context. An empty or explicitly truncated response is retried only when a genuinely larger valid ceiling exists. A second cutoff remains an honest unavailable configuration rather than an infinite paid retry loop.
With the default 20% final-test split, provide at least 25 distinct approved scenarios to obtain five final-test scenarios. Repeats measure consistency but do not inflate the distinct case count.
Run offline validation, preserve the sanitized suite and result JSON, include evalt --version, and open a GitHub issue without API keys or private production inputs.
Ready to test a real repeated task?