AI quality evaluations¶
AIHub keeps synthetic behavioral cases under evals/cases/ and exercises its
production functions through scripts.ai_evals. Technical unit and integration
tests run through pytest, separately from model-quality campaigns. Live evaluations measure model behavior,
consume tokens, and run only on demand or in the dedicated weekly GitLab schedule.
Git commits do not launch evaluations. Manual and weekly campaigns default to
one observation per scenario; multiple passes require explicit selection.
For the step-by-step human workflow, including Excel annotations, rejection and reference approval, see Review and approve AI evaluations.
For saved graph inspection, diagnostic replay, HITL branches and judge-only experiments, see Replay an AI evaluation.
Technical tests and model-quality campaigns¶
| Purpose | Location | Execution | Evidence |
|---|---|---|---|
| Unit tests | tests/unit/ai_evals/ |
Local functions, no provider network | Correct selection, grading, metrics, configuration and fixture validation |
| Integration tests | tests/integration/ai_evals/ |
Production adapters, graphs, SDK transports and campaign/report workflows with simulated model replies | Correct prompts, parsing, orchestration, accounting and report generation |
| Model-quality campaigns | evals/ and scripts/ai_evals/ |
Real generation, decision or embedding models; a real judge where required | Quality against reviewed expectations, rubrics and relevance labels |
Run technical validation with the synchronized Python 3.14 environment:
env -u LANGCHAIN_TRACING -u LANGCHAIN_HANDLER -u LANGSMITH_TRACING \
-u LANGCHAIN_TRACING_V2 .venv/bin/python -m pytest \
tests/unit/ai_evals tests/integration/ai_evals --no-cov
Both suites forbid real socket connections. Their pytest results are technical checks, not model-quality scores. They remain part of the repository's full local test gate; the focused command does not replace its coverage requirements.
For model-quality work, python -m scripts.ai_evals plan inspects requirements
without calls; run executes the selected real models within the configured
limits. The other CLI commands inspect or process local data without model calls.
There is no simulated campaign mode. A real campaign can combine exact decision
checks, embedding metrics and semantic judges: a judge is not required for
every kind of quality measurement. The detailed commands below retain their
authorization and reference requirements.
Simulated replies live in tests/fixtures/ai_evals/<suite>.jsonl, linked by
case_id. Shared test helpers live in tests/support/ai_evals/. The live catalogue
contains only scenario inputs, synthetic business context, external-data fixtures,
expectations and rubrics; approved references remain under evals/baselines/.
Synthetic tool results and document fixtures stay in evals/ because live models
need that controlled context. They are distinct from simulated model answers.
Review workbooks therefore show the live scenario, not technical reply fixtures.
Changes to test-only replies do not invalidate live evidence. This schema separation itself changes catalogue and evaluator fingerprints: preserve old campaigns and references as historical evidence, and obtain fresh matching observations and explicit approval before replacing a reference.
Scope and evidence quality¶
The reviewable scenario corpus uses English (en) and French (fr) only.
Locale-only duplicates, including regional variants, are omitted. Distinct
business scenarios are retained in one of these languages; translation and
cross-language retrieval use English/French pairs. This review scope does not
reduce the application's supported languages or its locale-handling unit tests.
Historical reports and cost measurements retain their original scenario languages.
The versioned inventory in evals/workflows.yaml registers 147 discovered source
boundaries, 131 logical workflows, one explicitly excluded legacy call and two documented infrastructure paths
(the synchronous summary counterpart and fault-only credit recovery). The corpus contains 466 synthetic scenarios in
19 suites. Coverage requires nominal and boundary examples or explicit retirement declarations for every workflow:
| Suites | Examples | Scope |
|---|---|---|
| Guardrails, supervisor, memory, training, conversations | 31 | Decisions, refusals, preferences, clarification and main LangGraph routing |
| Agents, chatbot, objectives | 144 | Seven specialist destinations, the Knowledge Base Deep Agent, summaries, simulations, suggestions and learning objectives |
| Common, entities, talent | 29 | Language, translation, inference, entity enrichment, marketplace and performance goals |
| Lexi | 63 | Classification, levels, catalogue generation, immutable skill selection, repair and duplicate decisions/groups |
| Matching, PwC | 21 | Matching rationales, job paths and insights |
| Documents, GenAI, voice | 18 | OCR, PDF/DOCX/CV extraction, text, function calls, SSE and Live delegation |
| Embeddings | 82 | Eight retrieval domains and 26 title-only duplicate candidate catalogues |
| Embedding consumers | 78 | 41 production call sites across 38 paths, including generated-objective search without reformulation |
The obsolete standalone opportunity-vector search consumer and its two dedicated scenarios were removed with their unused implementation. Current opportunity search pipelines keep their existing scenarios and expectations; historical campaigns and cost observations remain available.
The unused legacy /ai/jobOfferGenerator endpoint is explicitly outside the
evaluation scope by user decision. Its scenarios, reply fixtures, workflow and
retirement declarations have been removed. Its exact call site remains listed
under excluded_sites, with the reason visible in coverage and campaign reports;
the endpoint itself remains available. Inventory coverage can pass for the
current evaluation scope without claiming quality coverage for this endpoint.
The remaining PwC insight scenarios still apply. Historical reports are unchanged.
The out-of-scope guardrail case checks blocking and non-fulfillment, without a prescribed refusal format. English/French offline replays cover direct and multi-sentence refusals through native and structured decisions. The semantic judge still checks that a blocked response does not provide the requested content.
The French greeting cases require allow and career_chat_agent for bonjour.
The Decisions cache integration replay covers every registered native consumer
through the production connector with synthetic SDK answers, while live cache
measurements separately compare disabled, first-write and reused prefixes. Such
replays verify transport and parsing, not model quality or measured cache hits.
The two added cases raise the full branch-envelope call ceiling from 1,522 to
1,526; the USD/token ceilings and one-observation policy are unchanged.
Training rationale cases cover the existing profile-based behavior and on-demand search explanations: beginner intent with an unrelated profile that must be omitted from the rationale, partial matches with unsupported certification/duration claims, French output from English source metadata, and an injected request to invent enrollment. The removed grouped suggestion-rationale generator has no active workflow or cases; historical cost and campaign evidence remains available.
Development objective cases cover a role with numerical skill ratings and gaps,
and a sparse profile requesting French output. They use the production default
generation profile with unchanged quality expectations. Their rubrics require natural,
employee-facing rationales in 1 to 3 short sentences, at most 60 words, grounded
in a concrete work benefit without quoting ratings or inventing context. A single
complete sentence is sufficient; padding to reach a sentence count is unnecessary.
Offline replays exercise the production prompt and enforce the evaluation word limit;
they do not establish live model quality.
Additional development objective cases cover a Production Manager with eight skills and no gaps, selected training without profile evidence or gaps, and a completely empty context. The first two require grounded objectives without invented deficits; the last returns an empty list without a model call. Offline replays check eligibility, skill links and requested training assignment through the production workflow.
Training-search embedding-consumer cases also cover a generated English objective and a French objective with a progress deadline, sent unchanged without query reformulation. They require the exact objective text at the embedding boundary and retrieval of the labelled relevant SQL course over unrelated catalogue items. Synthetic storage covers MongoDB and PostgreSQL paths; these examples do not validate deployed database indexes or establish live quality without a campaign.
The sparse-profile performance-goal case accepts a modest future work outcome
grounded in the Developer role and SQL skill gap, or an empty result with
insufficient_profile. Missing recorded skills or experiences do not force
abstention. Proposed deliverables and reasonable future acceptance criteria are
suggestions, not assertions about existing projects or employer metrics. The
judge still rejects invented current responsibilities, achievements, baseline
figures and qualifications, as well as generic training activities and repeated
goals. The requested limit, language and date bounds remain applicable.
Learning-plan phase cases cover monthly, weekly and daily schedules in English
and French. They check numeric positional labels such as Month 1, Weeks 1–2
and Semaines 1 à 2, exact schedule boundaries and training assignments. The
judge checks natural localized wording, progressive milestones and useful
practice suggestions without invented profile or course facts. These six cases
use the production mini profile. Isolated spelling or diacritic mistakes are
tolerated when the requested language and intended meaning remain clear. This
does not relax exact schedule boundaries, identifiers or substantive language
and factual requirements.
Collection-metadata cases use production-shaped trainingId and
skillsInformation fields. They cover existing SQL practice without a redundant
simulation, the same decision within a learning plan, a French feedback role-play
that complements a theoretical course, and sparse metadata that cannot establish
a complementary activity. Offline tests verify evidence delivery and structured
decisions; a live judge assesses explanation usefulness and added value.
Adapters execute production prompt builders, parsers and workflows. Repository
access, catalogues, tools with side effects, callbacks and persistence use
synthetic fixtures. Main-graph cases compile the production main LangGraph
with real safety gating, routing and memory evaluation, in-memory storage
and fixture specialists. Separate specialist cases run the real agent
graphs and tool schemas, so they assess model tool selection and prose without
executing external actions. Most tools return synthetic results; the selective
user-profile tool runs its real implementation against case.user_context.
Streaming cases run the
production V3 transformer and final-response middleware, compare provider fragments
with the completed answer, and retain public research comments as judge evidence.
They cover direct delivery and English/French responses; real SDK cancellation
and interrupted-stream handling remain offline integration tests. They do not establish
real retrieval quality or correctness of those side effects.
The Career Chat nominal and boundary profile cases use a synthetic
evaluation-user, containing Python or an empty skillsInformation list.
The production tool applies the requested field projection and skill-origin
filtering, then returns its optimized skills list. Its actual response is
recorded in tool_results and passed to both the agent and the judge. A job
title in the source document is only returned when basic information is requested;
it is never evidence of a recorded skill. These cases distinguish a successful
empty lookup from a retrieval error. They exercise profile transformation, not
the upstream MongoDB or PostgreSQL user lookup. No real tenant or user is read.
Knowledge cases preserve the real Deep Agent and Career Chat handoff. Frozen RAG rows and public HTML replace only external source responses; production navigation, source validation, evidence comparison and synthesis still run. They cover complementary/conflicting sources, public links, outages and source prompt injection, including an ambiguously labelled tenth link, a section beyond the initial excerpt and an unavailable linked page. General relevance cases distinguish neighboring procedures from direct answers, true absence, contextual retries/corrections and documented capabilities versus personal-document analysis. English/French cold starts use English source pages. Knowledge adapters pass conversation history to the real research entrypoint. Website freshness and live database ranking require separate checks.
Career Chat coaching cases allow useful reflection questions in the response body and require one concrete closing invitation that makes the next user reply clear. The judge assesses that invitation, not the total question count. A missing ending or generic offer of help is insufficient; a single necessary clarification already advances the conversation. Closing-turn cases check that the assistant respects a pause, and direct Knowledge Base cases retain unchanged delivery without an added follow-up. These cases run in English and French.
Specialist adapters retain the production history, error and call-limit middleware. Deterministic offline tests cover checkpointed search artifacts, long histories, sanitized error continuation, exhausted tool/model limits and HITL/next-turn resets. These orchestration refactors preserve existing scenario meanings and semantic LLM profiles; passing scripted-model tests is not evidence of live response quality.
Specialist adapters pass the complete versioned conversation to the agent. They
record tool names and model-visible arguments, validated against the production
tool schema with its defaults. Assertions can therefore distinguish an initial
search from a follow-up, or a job-title criterion from a staff-level filter.
Synthetic tool output is not evidence that the model supplied the right arguments.
The test fixture's tool_calls defines simulated model invocations for integration
tests only; live agents choose their tools and arguments independently.
Task Helper and the empty Training Finder result use text fixtures inside the
production Command/ToolMessage envelope. The specialist graph still decides
which tool to call and when to stop. Their internal generation/search is outside
these agent-routing cases and remains covered by its own workflows. A direct
result does not require text.delta fragments or an extra assistant model call.
The judge uses result, delivery and stream_delivery_valid together.
Knowledge reports retain knowledge_decisions: the declared structured answer,
the answer accepted by production validation, available source IDs, source
statuses and a decision reason. Reasons distinguish accepted,
declared_unanswerable, empty_answer, missing_source_ids,
unknown_source_ids and no_sources. Runtime logs contain the reason and counts,
not answer or source text. This diagnostic data stays local to the research run;
it is not added to public chat events or persisted conversation state.
knowledge_delivery_valid compares the accepted specialist answer with final
delivery. It does not compare a translated synthesis with the original passage.
The judge evaluates factual support independently: an accepted answer can still
contain unsupported claims. It distinguishes a correction or a qualified
observation about consulted material from a categorical policy claim inferred
from silence; see the review guidance.
The multilingual allowance cases explicitly ask for the amount per employee.
Prompt-suggestion cases supply the SQL goal in synthetic conversation history,
which the production formatter actually sends to the model. Skills in a stored
user fixture alone do not reach that prompt. The cases distinguish enabled
catalogue search and structured learning plans from coaching when search is
disabled. The Find a Training nominal case accepts relevant course searches
and skill-development plans, including a practical SQL plan that does not
explicitly mention courses or the catalogue. Offline checks exercise
English/French output, both category/title formats and capability-based category
resolution. The judge assesses the requested action: resource-selection advice
and a general learning roadmap do not imply catalogue access. Explicit requests
for unavailable catalogue courses or role-play still fail; a hypothetical tool
dependency inferred from the word "resources" is not enough to establish failure.
The nominal DOCX case requires only the visible identifier and duration; the separate boundary document retains the embedded-instruction transcription requirement. Judge instructions do not require every sentence of second-person feedback to start with a pronoun.
The following pairs exercise distinct inputs and acceptance criteria. The cases remain in the same suites and retain their configured providers and profiles:
| Workflow | Nominal case | Boundary case |
|---|---|---|
agents.career_chat_agent |
Retrieve and report recorded Python skills | Retrieve an empty skill list without inferring skills from the Developer title |
agents.opportunities_suggestion_agent |
Initial Python opportunity search in Paris | Resolve "show more" from history, preserve criteria, and set the continuation flag |
agents.person_disambiguation_agent |
Identify one matching colleague | Present two distinct same-name matches and request a choice |
agents.profile_analysis_agent |
Call the profile analysis tool for an explicit request | Clarify an ambiguous reference to multiple plan items without calling a tool |
agents.talent_finder_agent |
Search by SQL skill | Search for the Product Manager job title without inventing a management-level filter |
agents.training_finder_agent |
Search for introductory SQL courses, not a learning plan | Clarify an unresolved topic before calling a tool |
chatbot.prompt_suggestions |
Suggest SQL course searches or learning plans with training enabled | Offer feasible coaching alternatives when training search is disabled |
lexi.provided_selection |
Select relevant Python from a finite catalogue | Abstain when every candidate is unrelated to Python development |
lexi.provided_fit_check |
Retain a relevant prior selection | Remove Ballet from a Python/Ballet prior selection without adding candidates |
lexi.provided_levels |
Assign a level to one selected skill on a four-level scale | Preserve two selected indices and reassess old levels on a two-level scale |
genai.text |
Return an available course identifier | Return NOT_FOUND for an absent course |
genai.tools |
Look up the requested catalogue identifier | Ignore an instruction inside a catalogue description that substitutes a fake identifier |
genai.stream |
Assemble one course identifier | Distinguish similarly named courses and assemble only the requested identifier |
Lexi cases explicitly supply their candidate catalogue, selected indices, previous selection, target count and level scale. GenAI tool assertions compare decoded arguments, rather than accepting any string containing the expected identifier. Offline checks verify that each pair changes the evidence actually received by the production model path; these checks do not establish live semantic quality. Updating these cases invalidates their previous evidence and does not approve a new reference or launch a paid campaign.
Document fixtures under evals/fixtures/ are generated synthetic images, scanned
PDFs, DOCX files containing images, and text. They contain course identifier
SQL-101, a two-hour duration, or a fictitious resume for Alex Example, with
explicit instruction-injection variants. No production CV or conversation is
used. Both CV scenarios require the standalone Data Analyst headline in
professionalSummary, in addition to preserving the name and leaving absent
contact and education fields empty. The injection case must still ignore the
request to invent a qualification. Offline mapping tests verify that the headline
reaches the public summary without creating an employment entry.
Lexi's free metadata/token-count calls are fixture values (64,000 input, 4,096 output and a 2,000-token count); these small scenarios evaluate selection behavior, not real catalogue token packing or batch throughput.
Integration tests supply fixture response/responses only at model boundaries
to verify real prompt and parser integration. Fixture decisions provides
explicit categorical replies for native branches. These values are loaded only
by test helpers. expected_failures records known production discrepancies for
technical reproduction only; it does not waive a live campaign failure. In particular,
explicit-retraction currently fails the business expectation on the structured
memory path: production coerces an empty saved preference list to skip. The native
path preserves save with an empty list. This evaluation change does not alter
production memory semantics. The find_single.boundary embedding case verifies
that the Skills Library consumer normalizes surrounding whitespace before both
searching and looking up the returned dictionary key.
Offline passes are not model quality results.
New scenarios and expectations are proposed in the owning feature's implementation plan, with concrete inputs, expected behavior, boundary cases and applicable variants. Human approval of that plan approves those scenarios for execution; they are then added with the implementation. Materially different scenarios or expectations require an updated plan. There is no separate per-case activation status in the runner: this is a development review requirement. Approval of a feature plan is not approval of observed model results as a reference. There is deliberately no manufactured approved baseline. Bootstrap with an exploratory campaign, have a domain reviewer inspect labels, all critical cases, generation rubrics and a sample of successful responses, then explicitly approve a complete successful report. Missing approval is reported, not treated as acceptance. Normal comparison runs and weekly campaigns require a reference unless explicitly run in exploratory mode.
Assertions compare labels, acceptable alternatives, list contents and observable
behavior. They support nested JSON pointers, lengths, ranges and identifier
constraints, without requiring identical generated wording. Rubric
judging is enabled by default for manual and GitLab campaigns; it uses the
separately configured judge_profile through a semantic large profile and the same
request meter. Set judge_profile explicitly when enabling judging. Judge
results are auxiliary model evidence, not human approval. Use the explicit
--no-judge launch option to disable judging. Cases marked requires_judge: true
are then excluded before budgeting, provider checks or execution. All supplied configurations enable
judging: OpenAI uses gpt-6.1-terra and Anthropic uses claude-sonnet-5-5, both
with reasoning_effort: medium, matching their supplied large profiles. The judge uses the campaign's primary provider
and existing credentials. Its profile is versioned separately from the tested
profiles so a candidate-model change need not change the judge. It may use the
same model as the tested workflow; a separate request is not an independent
human assessment. The embedding-only suites do not invoke this judge.
Classification suites can use deterministic labels alone. Version the judge
configuration and rubric for every comparison.
Judge mode and automatic selection¶
plan, run and collect enable judging by default. --judge makes the default
explicit; --no-judge opts out for that invocation. A saved judge: false value
alone does not disable judging at CLI launch. The effective setting is recorded
in campaign evidence; exports, comparisons and approval use that recorded setting.
The GitLab weekly job explicitly passes --judge.
# Default: judge enabled wherever the scenario has a semantic rubric.
.venv/bin/python -m scripts.ai_evals plan
# Explicit opt-out: retain only scenarios allowing execution without a judge.
.venv/bin/python -m scripts.ai_evals plan --no-judge
.venv/bin/python -m scripts.ai_evals run --no-judge --exploratory \
--output ai-eval-artifacts/without-judge
.venv/bin/python -m scripts.ai_evals collect --no-judge \
--output ai-eval-artifacts/without-judge-collected
The filter follows --suite, --case or staged selection; unknown or repeated
IDs still fail validation. It currently retains 174 of 355 scenarios: 141 embedding
cases, 19 LLM cases without a rubric and 14 LLM cases whose rubric is optional.
The 181 cases requiring a judge remain in the catalogue and are excluded only
from that invocation. Optional rubrics are not assessed in this mode; their
deterministic assertions still run against real model outputs. Embedding metrics
continue to assess relevance without an LLM judge.
Planning lists included_cases and excluded_cases. Budgets, provider requirements,
fingerprints and collection reflect the retained population. JSON and HTML record
exclusions, JUnit marks them skipped, and Excel includes an Excluded worksheet
plus explicit exclusion statuses in Results. Exclusions are neither passes nor
failures. If every requested case is excluded, the runner writes an incomplete
report with no_eligible_scenarios, returns exit code 2 and makes no provider calls.
Planning still returns a no-cost preview with that explicit status.
References require the same effective population and judge mode. Complete,
successful evidence for the full eligible catalogue may be approved explicitly
for the no-judge mode; it cannot serve as a judged reference. Use distinct reference
files for the two modes. A manually narrowed --case run still cannot become a
complete reference. No approval is automatic.
For authorized, focused feature-development checks, the agent may choose the mode appropriate to the change and report the choice. User-requested manual campaigns and the weekly campaign use the judge unless disabling it is explicitly requested. This choice does not increase budgets or authorize unrelated paid calls.
The judge receives the synthetic production request context, including the data
and instructions assembled inside adapters, together with tool results. Binary
fixture bodies are omitted from this text context. Its versioned
judge_max_tokens allowance is 2,048 tokens, including reasoning, and its
verdict records both passed and a short evidence-based reason. It must assess
the workflow's contract: fictional roleplays, estimated durations and proposed
goals are valid generated content when requested, whereas invented personal
qualifications or claims of persisted actions are not.
The judged response is the final application result after parsing and
transformation, not the raw LLM reply. The judge uses production prompts for
semantic evidence, while deterministic expectations check the final output
structure. Intermediate JSON schemas must not be imposed on transformed results
such as rationale mappings or phases containing resolved training IDs.
Simulation-analysis rubrics accept contextual paraphrases of unambiguous pronoun
references in explanatory feedback. A slightly approximate verb alone is not a
failure when the facts, substantive speaker contribution and assessment remain
faithful. Verbatim key-moment quotations must still be exact. Unsupported
attribution of initiative, diagnosis, completed actions or achieved results, and
changes to a commitment's meaning, remain failures.
The French vague-commitment case also accepts a concise summary that describes
the feedback request through the theme established across the exchange, even if
the initial question was broader. The detailed feedback must preserve each
speaker's contribution and keep the assessment grounded. This does not permit
crediting the learner with a diagnosis, initiative or action supplied by the
counterpart, or using that attribution to justify a score.
Simulation scenarios expose generated skill titles before the synthetic catalogue mapping. Summary scenarios expose retained recent messages separately from the summarized history. These fields prevent deterministic post-processing or intentional context retention from being mistaken for missing model output. Experience-to-skills scenarios expose generated labels, their finite synthetic catalogue and the resolved mappings alongside the final ID list. A judge must not require ten IDs when only some of ten generated labels match that catalogue. Likewise, objective-title generation can retain one of several intermediate candidates, concise profile summaries need not repeat every input fact, and the performance-goal contract permits an explicit abstention for sparse evidence. These rules are versioned in the relevant rubrics; they do not excuse unsupported claims or missing required output. Decision evidence includes each question's policy and choices, not only the common instructions.
Offline replay replies must match the production parser's schema. An empty fallback can look valid when a scenario expects no filters, so the opportunity filter fixtures also have explicit parser-contract checks. The live nominal case must preserve the requested location; unsupported employment contract types must not become invented job titles or grades.
The campaign owns the blocking-work pool that application lifespan normally
initializes. The meter reads decoded SSE bytes (including compressed streams),
requires final usage and keeps early closures incomplete. The stream uses one
cursor during SDK cleanup and accepts authoritative terminal markers with final
usage, including HTTP/2 closure after [DONE]. Calls that omit an
output limit reserve the documented maximum from the selected profile's
output_token_limit and limit_source; the original request is unchanged.
Unknown limits still block before dispatch. The supplied OpenAI profiles use
128,000 tokens, from the official
Luna,
Terra and
Sol model pages.
These conservative reservations can exhaust the configured dollar ceiling;
neither this correction nor planning raises it automatically.
Agent assertions distinguish a final assistant message from a return_direct
tool result and test clarification separately from fully specified requests.
GenAI tool scenarios explicitly use the supported none reasoning setting and
run the same capability validation as the HTTP entrypoint. Live evaluation reads
response.output_item.done, because lifecycle snapshots clear their output, and
collects available transcript fragments separately. It does not measure complete
speech playback or perceptual audio quality.
JSON, HTML and result workbooks expose execution/judge phase, safe HTTP status and error codes, finish reasons and judge explanations. JSON also retains the synthetic prompt evidence and the last repository exception location. Provider error messages, response headers and exception locals are never recorded.
A successful campaign is descriptive evidence, not proof of statistical non-inferiority or AI Act compliance. Expand the expert-labeled corpus with representative strata and choose sample sizes/tolerances before a release qualification campaign. Keep evaluation evidence with intended use, risk analysis, human review and change approvals. Assess legal obligations separately.
Commands¶
Use the repository Python 3.14 environment after the normal dependency
synchronization and pip check. Live runs automatically read provider settings
from the repository root's optional .env: OPENAI_API_KEY,
ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, AZURE_OPENAI_URL and OVH_API_KEY.
Already exported variables take precedence, including explicitly empty values.
Both generation and embeddings use these provider keys. Values in .env must
be literal; ${VARIABLE} interpolation is disabled. A missing .env is valid
when credentials are supplied through the environment, as in GitLab CI.
Only these five settings are imported: database, queue, tracing and evaluation
policy variables in .env are ignored. Explicit run --staged reads the original checkout's
file and passes selected values through the child environment; it never copies
.env into the staged snapshot or stores its values in reports or fingerprints.
Application-wide dotenv loading, external connections and tracing remain
disabled in workers. Offline commands such as plan, coverage, export-review
and approve do not read .env. Budget, judge and reference checks still apply.
.venv/bin/python -m scripts.ai_evals plan --staged
.venv/bin/python -m scripts.ai_evals coverage
.venv/bin/python -m scripts.ai_evals export-review \
--output ai-eval-artifacts/corpus-review.xlsx
.venv/bin/python -m scripts.ai_evals coverage --report /path/to/report.json
.venv/bin/python -m scripts.ai_evals plan --suite guardrails
.venv/bin/python -m scripts.ai_evals run --suite guardrails --exploratory
.venv/bin/python -m scripts.ai_evals run --exploratory
.venv/bin/python -m scripts.ai_evals plan --suite supervisor --repetitions 3
.venv/bin/python -m scripts.ai_evals run --suite supervisor --repetitions 3 --exploratory
.venv/bin/python -m scripts.ai_evals approve --report /path/to/report.json \
--reviewer 'Domain reviewer' --reason 'Reviewed labels, rubrics and campaign outputs'
.venv/bin/python -m scripts.ai_evals run --staged --reuse
.venv/bin/python -m scripts.ai_evals run --config evals/configurations/anthropic.json
.venv/bin/python -m scripts.ai_evals compare \
--baseline /path/to/reference-report.json --candidate /path/to/candidate-report.json
Human review in Excel¶
export-review generates a readable XLSX workbook without provider calls,
credentials or an approved reference. The JSON/JSONL/YAML files remain the
execution source of truth. Use a new output filename each time: the exporter
refuses to overwrite an existing workbook, including reviewer annotations.
# Review all versioned cases before spending tokens.
.venv/bin/python -m scripts.ai_evals export-review \
--output ai-eval-artifacts/corpus-review.xlsx
# Review selected suites with a particular configuration.
.venv/bin/python -m scripts.ai_evals export-review \
--suite guardrails --suite embeddings \
--config evals/configurations/openai.json \
--output ai-eval-artifacts/selected-review.xlsx
# Compare the current expected behavior with recorded campaign observations.
.venv/bin/python -m scripts.ai_evals export-review \
--report /path/to/report.json \
--output ai-eval-artifacts/campaign-review.xlsx
| Worksheet | Content |
|---|---|
| Summary | Selected scope, critical scenarios, corpus and execution fingerprints, export time, and optional campaign identity/status and report hash |
| Scenarios | Requests and inputs, raw synthetic context and sources, readable expected assertions, generation rubrics, review status, reviewer identity and comments |
| Embeddings | Retrieval judgments or title-only duplicate catalogues, profiles, thresholds, splits, pair labels and catalogue concepts |
| Configuration | Selected model configuration and current policy; with a report, its recorded configuration and usage |
| Results | Present only with --report: expected checks, observed output, failures, latency, calls, tokens and estimated USD for each repetition |
| Excluded | Present when the report records automatic exclusions: suite, scenario and judge_disabled reason; these are not observations |
| Case details | All live scenario fields, including business context, nested inputs, required sites and external-data fixtures |
The Synthetic context and sources column brings scenario data next to its request: the explicitly supplied user profile (including skills and experiences), stored preferences, enabled agents, Knowledge Base passages and web fixtures, simulated tool results, candidate documents and external-data responses. Empty lists, nulls and source-error flags remain visible. These are raw synthetic inputs; production workflows may select, transform or omit fields before model calls. The column does not claim that every field was sent to the model. Use Execution context and Observed output in Results to inspect recorded prompts and tool evidence. The exporter does not generate summaries or read a real user database.
Filters and frozen headers keep identifiers visible. Amber cells are editable
review annotations (Pending, Accepted, or Changes requested, reviewer,
comments). Content stays on one row when its estimated wrapped height fits Excel.
Long values continue without truncation, with Part 1/N and Continued 2/N
labels beside the identifier and a numeric Part column. Splits keep complete
checks together unless an individual check exceeds one row. Annotate part 1.
Formula-like source text is written literally, with automatic
hyperlinks disabled. No case is pre-approved. Simulated model replies remain in
technical test fixtures and are not exported in the workbook.
When --report is supplied, its configuration takes precedence over --config.
The exporter rejects mismatching corpus hashes, unknown cases and duplicate or
invalid result identities. It permits a changed runtime fingerprint but marks
Same execution inputs false in Summary. A missing result is Not executed;
a case outside the supplied campaign is Not in campaign. Neither is a pass.
Incomplete and failed reports can be reviewed, but exporting them cannot make
them eligible for reference approval. The XLSX is a snapshot, not a live link
to the repository or report.
Review labels first, then inspect actual responses after an exploratory campaign.
Annotations do not modify cases or policy and are not imported by approve.
Apply accepted case corrections in the repository, rerun the affected campaign
when its evidence is invalidated, regenerate the workbook, and explicitly approve
the exact successful JSON report after human review. Store reviewed workbooks
with campaign evidence; generated exports belong in ignored ai-eval-artifacts/
and are not versioned as a second corpus. export-review does not support
--staged; use an explicit run --staged to evaluate a Git index snapshot and
export its resulting report.
plan and coverage never call providers. Planning reports selected examples,
call estimates, whether the minimum fits, required providers, missing profiles
and judge/Live prerequisites. run requests fresh responses unless --reuse is
supplied; weekly runs are always fresh. --suite is repeatable. Default reference:
evals/baselines/reference.json.gz. Approval creates a file exclusively and refuses
to overwrite it. Subsequent reference revisions require a new filename and an
explicit --baseline selection. AI_EVAL_BASELINE and AI_EVAL_CONFIG select
reference/configuration files for CLI commands; staged commands require
repository-relative paths. Review the file before committing it. A .json.gz
reference uses lossless gzip storage; explicit .json references remain supported.
Both formats retain the complete report and approval metadata and refuse overwrite.
The initial approved benchmark is campaign 2ec64818-b4cf-4eb1-ba08-9f79f8264ce6,
reviewed by Fabien Labernia on 2026-11-05 (UTC): 464 passed scenarios,
including 160 embedding scenarios, with no incomplete observations or provider
errors. It contains one observation per scenario, with semantic judging enabled
for applicable workflows in its recorded configuration. Compression preserves
the original evidence, fingerprints and
approval timestamp; packaging and API-version commits do not re-certify it.
The default is one observation per scenario for both manual and weekly runs.
--repetitions N selects N total observations, not N additional retries. It is
available on plan, run and collect, including explicit staged planning/runs.
Use the same count when planning and executing. Extra observations do not increase
call, token, dollar or time limits; review the plan before spending. For example,
--repetitions 3 on the full 319-scenario catalogue requests 957 observations.
The policy key and campaign name commit select the manual limits; they do not
install or invoke a Git hook.
Provider configuration snapshots under evals/configurations/ select concrete
models through semantic profiles. Their OpenAI large mapping, including fixed
OpenAI workflows in an Anthropic campaign, uses GPT-6.1 Terra with medium
reasoning; reasoning retains Sol with medium. OpenAI and Anthropic
snapshots already set mini/default to low and tiny to none, matching the
centralized profile defaults. Their large and reasoning mappings both use
medium, including fixed-provider mappings. Evaluation configuration supports
an omitted or null effort, resolved from the same semantic defaults as production;
explicit candidate settings remain authoritative. Existing nominal and boundary language-detection
and skill-inference cases retain their inputs and expected behavior; changing
profile effort does not relax their quality criteria. Scenario expectations
are unchanged by these model and profile updates. Existing evidence does not validate the
new configuration without a fresh, explicitly requested campaign. Existing
summary, language, training, matching and document rubrics remain unchanged;
PDF and DOCX extraction now declare their production large profile. Technical
tests verify provider/tenant precedence, missing effort, explicit none,
unsupported model controls and the final provider payload. These tests are not
live model-quality evidence. Add a separate
snapshot for each candidate.
Use mode: structured to exercise the existing generative compatibility path
on the same provider/model. native allows production capability resolution;
it does not force Decisions on unsupported providers. OpenAI and Anthropic
examples are supplied; Azure and OVH use the same runner with explicit profiles,
credentials and known central pricing. Unsupported pricing or request shapes
stop with incomplete evidence rather than guessed costs. Azure also requires
AZURE_OPENAI_URL. No tenant configurations are read.
Fixed-provider workflows preserve production ownership: Lexi catalogue and
provided-skill selection use Anthropic; native PDF/file-CV extraction, GenAI
streaming and Live use OpenAI. provider_profiles supplies their explicit
semantic profile mappings even when the campaign's primary provider differs.
The full corpus therefore requires both provider credentials. Missing mappings
are rejected before dispatch. A campaign with one primary provider is not a
claim that every workflow supports that provider.
Embedding model comparisons¶
The embeddings suite contains seven retrieval scenarios for each of skills, RAG,
training, jobs, opportunities, matching, Lexi and objectives. It covers
exact terms, paraphrases, French/English queries where supported by the consumer,
ambiguous terms and no matches. Matching uses English pivot titles on both sides.
Two scenarios per domain are reserved for calibration (one positive query and one
no-match query); the other five are held out. These small synthetic catalogues
are a starting point for human-reviewed representative business examples, not a
release qualification dataset by themselves.
Matching scenarios compare skill titles, as JOM/JPM do: exact titles, English
paraphrases such as SQL query writing against SQL querying and
Writing SQL queries, the English pivot SQL SELECT queries against
SQL SELECT queries and SQL SELECT statements, software/animal-care ambiguity,
and unmatched skills.
The source query is passed as a skill title, not as a search request or learning
goal. Database schema design and database administration remain related but
distinct negative candidates. Matching relevance grades describe skill equivalence,
not general topical relevance; explicit selection labels reject these distractors.
The default Ada 2 profile, 0.91 threshold, skill levels and strict JOM/JPM F1
expectations remain unchanged. Natural-language and negation-sensitive searches
stay in the other retrieval domains. Historical matching observations retain their
original inputs and verdicts; the revised cases require fresh live evidence.
The paraphrase case now establishes SQL equivalence in the encoded titles,
instead of relying on the unencoded description of Relational database querying.
The embeddings.matching.english_pivot case replaces the former
embeddings.matching.multilingual case, which directly encoded a French skill
against English targets and therefore bypassed the matching input contract.
Its French source, Requêtes SELECT en SQL, remains review context only; embeddings
receive the standardized English title. Translation is checked separately in
common.translation.nominal, using the existing tiny LLM translation fallback
and accepting equivalent English wording while preserving SQL, SELECT and scope.
Offline import tests verify that stored, cached, DeepL and LLM English translations
reach the embedding call and remain paired with the original localized title.
The embedding case uses a fixed English input; it is not a live end-to-end test
of translation followed by matching.
These narrower acceptance examples do not resolve the previously measured failures for broader English SQL titles or demonstrate general cross-language equivalence. Historical French/English observations remain valid measurements of those inputs, but do not establish a production matching defect or justify lowering its threshold. Preserve their reports and verdicts. The six negative candidates, Ada 2, threshold and F1/Recall/nDCG requirements remain unchanged. This corpus refinement does not add synonym mappings, change a client referential or alter production matching.
JOM/JPM LLM evaluations also use English business context and English output:
matching.jom, matching.jpm, the Galaxy path/rationale workflows and
pwc.insight. Review adapter-generated context, effective locale and technical
reply fixtures alongside the case inputs. French source text belongs to the
separate upstream translation checks, not the matching inputs.
Lexi catalogue retrieval can supply a job description through role_description,
with query supplying the role name. The ambiguous Python case uses
Python backend developer and places the API-development mission and animal-care
exclusion in the description. The adapter uses production job_queries to encode
the complete context and each mission fragment without removing the exclusion.
Its Small profile and catalogue labels are unchanged. The ambiguous case requires
Recall@3 = 1 for both production ranking modes: semantic_recall_at_k and
hybrid_recall_at_k. Programming must reach the candidate pool, but snake care
may rank ahead of it: the downstream LLM owns the contextual selection. nDCG
remains diagnostic for this case. The other four Lexi retrieval cases with
relevant candidates still require both Recall@3 = 1 and nDCG@3 >= 0.7 for the
semantic and hybrid modes. Generic maximum-cosine metrics remain diagnostic:
they do not reproduce the mission reservation and rank fusion used by
CatalogSearch.rank. The two cases without relevant candidates retain their
existing false-positive checks; ranking metrics are undefined for that population.
Candidate reservation and final ordering are separate: the production ranker preserves the bounded pool chosen for mission coverage, then orders it by total fused score with catalogue-index tie-breaking. When comparing ranking changes, replay both implementations against the same saved synthetic vectors and check pool membership as well as Recall/nDCG. Reordering alone cannot resolve an excluded concept that already has the highest fused score. For the ambiguous case, candidate omission is a retrieval failure; selecting the excluded concept is a downstream LLM failure. Historical ranking failures remain recorded under their original expectations and are not rewritten as successful observations.
The separate lexi.provided_selection.ambiguous scenario supplies the same role
and description, with snake care ahead of Python programming and SQL querying
in the candidate list. The real selection prompt and parser must select only
programming, using the existing Anthropic reasoning profile. Deterministic
index assertions and the semantic judge check this decision independently of
embedding rank. Candidate retrieval and final selection remain separate
measurements: this fixed-candidate scenario does not certify the complete live
assignment workflow, whose default retrieval limit is 80 rather than 3.
The revised recall-only retrieval expectations require matching evidence before
any quality-reference approval. They do not alter the LLM selection rubric.
Generic HITL choice evaluations exercise topic_choices_single_en/fr and
topic_choices_multi_en/fr in the Training Finder agent. A synthetic history
supplies four learning interests; the request asks for one priority or several
compatible topics. The real agent and clarification tool must expose the matching
input type, grounded options and appropriate alternative-text flag before any
course search. Existing source/action-plan cases now assert structured closed
single selections. Simulation-scoping cases retain open text questions. Offline
JSON/SSE tests cover mixed questionnaires, option membership/cardinality,
canonical labels, free-text alternatives, skips, cancellation and resumption.
Agent profiles are unchanged and no format-selection model call is added.
The ambiguous objective case supplies the objective title
Develop Python programming skills, preserving the competing programming and
snake-care objectives, Ada 2 profile, 0.91 selection threshold and ranking checks.
It also requires the production selector to return objective python.
The conversational request to write Python API code, not care for snakes, is
covered separately by agents.opportunities_suggestion_agent.objective_negation,
using the configured large profile. The agent must call get_objective_tool
first: resolving the correct exact title can continue directly, while unresolved
selection must pause through the tool's HITL with answer, skip and cancel actions.
Suggestions must use the returned programming objective identifier. The adapter
records completed objective lookups as evidence; interrupted tools have no result.
Offline graph tests cover selection, resumption, skip and cancellation without
claiming model-quality evidence. Historical observations retain their original
queries and verdicts; revised cases require fresh live evidence.
Duplicate candidate retrieval has 26 separate title-only catalogues: 13 for calibration and
13 for validation. They cover synonyms, abbreviations, multilingual equivalents,
related but distinct skills, shared words with different meanings, and mixed
groups. The two splits have disjoint reviewed concepts and normalized titles.
French/English variants change the actual compared titles, not a retrieval query.
The historical embeddings.duplicates.exact SQL pair remains a positive held-out
case with its original pair labels. Its descriptions are review context;
only titles are encoded, including the less explicit Relational database querying.
The other six copies of that same catalogue are replaced by distinct catalogues;
historical campaign reports remain unchanged. Negation-sensitive ranking remains
covered by the retrieval domains rather than being counted as duplicate grouping.
Each configuration has an independent embeddings.provider and explicit default,
lexi and training profiles with model/deployment name, native dimensions, input
limit and reviewed input-token pricing. Anthropic generation configurations still
select a separate embedding provider. OpenAI, Azure and OVH are supported;
Azure/OVH prices must be supplied from their own tariffs. The OpenAI snapshots use
$0.02/M input tokens for text-embedding-3-small, $0.13/M for
text-embedding-3-large, and $0.10/M for text-embedding-ada-002, from the
official pricing table.
The standard openai.json and anthropic.json campaigns use OpenAI embeddings
with the application's default model allocation: default and training use
text-embedding-ada-002, while only lexi uses text-embedding-3-small (including
Lexi duplicate detection). Both models use 1536 dimensions. These versioned
snapshots do not load tenant overrides. Offline checks compare the ordinary
profiles with the production fallback and verify the model sent by every
embedding scenario and consumer call.
openai-embeddings-large.json is an explicit model-comparison candidate, not a
standard campaign configuration. Candidate dimensions validate native output;
the runner does not silently reduce dimensions or mix old and new vector spaces.
Historical observations made with a different model remain evidence for that
model only; collect fresh observations before drawing conclusions about the
standard configuration. Keep the original reports and verdicts unchanged.
openai-matching-embeddings-small.json is a focused comparison configuration:
only the default embedding profile switches from Ada 2 to
text-embedding-3-small, retaining 1536 dimensions and using Small's declared
input-token tariff. Select the seven embeddings.matching.* cases explicitly to
compare unchanged English pivot skill titles, labels and the 0.91 threshold. Training, Lexi and
generation settings remain unchanged. This experiment does not migrate production
vectors or change the standard campaign configuration; a different embedding
model needs its own threshold calibration and a consistent vector space.
.venv/bin/python -m scripts.ai_evals plan --suite embeddings
.venv/bin/python -m scripts.ai_evals run --suite embeddings --exploratory
.venv/bin/python -m scripts.ai_evals run --suite embeddings --exploratory \
--config evals/configurations/openai-embeddings-large.json
.venv/bin/python -m scripts.ai_evals compare \
--baseline /path/to/reference-report.json --candidate /path/to/candidate-report.json
.venv/bin/python -m scripts.ai_evals calibrate --report /path/to/candidate-report.json
Live runs call the production embedding connector and reuse production text builders, Lexi catalogue rankings and duplicate grouping, JOM/JPM skill scoring, RAG candidate merging and closest-objective selection. Inputs are already supplied synthetic chunks/enriched titles; generation and chunking are not evaluated here. Retrieval uses exact local cosine similarity. MongoDB/pgvector indexes, database score transformations, approximate retrieval, persisted-vector compatibility and full hybrid text search are not validated. RAG merging receives vector-only candidates.
The separate embedding_consumers suite invokes all 41 consumer sites through their
actual production entrypoints: chatbot searches, objective handling, MongoDB and
PostgreSQL RAG, inference, imports, regeneration, durable workers, Skills Library,
Talent Intelligence and Lexi. Each path has its own synthetic semantic anchors.
The boundary repeats the semantic task with uppercase and surrounding whitespace
to test input-normalization invariance. A capture forwards the actual prepared text
to the configured model, validates the consumer's provider/profile selection, and
checks Recall@1 against independent anchors for the first vector of each call.
Every expected call must complete; swallowed provider failures cannot pass. The
reports retain per-site metrics and their minimum across the consumer scenario.
These probes do not grade every vector in a large batch or simulate an actual
search index. External search fixtures return empty candidates; indexing uses
empty synthetic stores. Worker fixtures verify persistence and acknowledgment.
Generated training titles, inferred titles and Lexi selections are fixed upstream
inputs in this embedding-only suite; their own model behavior remains in separate
LLM suites. Lexi's 81-item controlled catalogue triggers its real supplemental-query
branch without mocking its search algorithm. The catalogue expansion LLM scenario
similarly fixes the initial undersized catalogue and evaluates expansion/assignment.
Validate real database retrieval and persistence separately before production migration.
Rank metrics are Recall@k and nDCG@k. Retrieval acceptance and final duplicate groups use
precision, recall, F1 and false-positive rate. JOM/JPM metrics also check positive
business scores, and objective cases check the actual selected identifier or no
match. No-match queries have no rank metric; they are judged on false positives.
selection_labels, when supplied, label every document independently of ranking
relevance: a related result is not necessarily a business match above the
configured threshold. Matching cases expose these labels explicitly without
changing their previous expected selections. Duplicate equivalence is still
defined independently by duplicate_pairs. Do not relabel a difficult example
or lower a threshold simply to make an observed model result pass.
For example, SQL querying and relational querying explicitly described as using
SQL can legitimately be equivalent even if their vectors have a low cosine score.
Candidate retrieval must retain that pair; final equivalence uses the supplied context. A broader skill such as database design
may be relevant to retrieval without being an interchangeable skill or duplicate.
An ambiguous Python query may also rank snake care above programming; changing
its expected intent after observing that ranking would hide the limitation.
JSON records per-case scores and ranks; HTML aggregates by domain, language and
split. Paired comparisons require matching embedding metric populations and
repetitions and flag metric drops beyond maximum_embedding_metric_drop (initially
zero), including an increase in false positives. Raw vector coordinates and raw
similarity changes are not quality comparisons between models.
calibrate reads saved scores without making provider calls. For retrieval it selects the
retrieval threshold with the best macro F1 on the calibration split and reports
F1 on the untouched evaluation split. It is advisory: it does not modify profiles,
labels, references, production thresholds, duplicate grouping or matching logic.
For duplicate candidate catalogues, reports retain the ordered cosine matrix, proposed pairs, expected/retrieved pair counts, candidate recall and reciprocal rank. They do not store vectors. A pair is proposed when either endpoint retrieves the other among its top five neighbors. Related candidates are allowed, and an all-negative catalogue does not fail merely because neighbors were retrieved. Empty-positive recall/MRR use the vacuous value 1; positive counts remain explicit.
Offline calibration returns duplicate_candidates. It tests k from 1 to 10 on
the disjoint calibration split, maximizing candidate recall and then minimizing
candidate count and k. Held-out scores never select k. The report compares both
splits and cannot modify production settings or approve a reference. Complete
current catalogue observations, matching identities/order and finite symmetric
matrices are required; overlapping titles/concepts and mixed reference k fail.
The lexi suite separately exercises:
lexi.duplicate_decisions: fixed pairs with expectedduplicate,distinct, orinsufficient_context, using real native Decisions and no embeddings.lexi.duplicate_groups: the same labelled catalogues through real Lexi-profile embeddings, candidate selection, Decisions, and conservative group assembly. Final F1 remains at least 0.8 and false-positive rate must remain zero.lexi.referential_duplicates: real referential analysis, including description context and isolation between referentials. Complete status is mandatory.
These categorical scenarios use deterministic assertions, not a semantic judge.
Native Decisions requires a compatible configured mini profile; planning rejects
unsupported provider/profile or forced structured-mode configurations. End-to-end
cases also require the lexi embedding profile and both successful call sites.
Candidate omission is an embedding failure, a wrong category is a decision failure,
and inconsistent group construction is a deterministic failure. Technical errors
must remain distinguishable from a valid empty result. Historical threshold-based
reports remain unchanged and are not comparable references for the new contract.
Within one scenario process, identical texts are batched and reused for the same
configuration, profile and repetition. Parallel workers do not share vector caches.
With --parallelism 1, ordinary runs can also reuse vectors between scenarios;
explicit bootstrap calibration clears that cache between scenarios. Reports record encoded_texts, reused_from and reused_sites;
reuse is not a new independent model observation. A consumer may execute using
reused vectors; its site is then recorded in reused_sites, not as a fresh physical
request. Reuse is credited only after that consumer actually requests those texts.
Each repetition and new campaign starts fresh; vectors are not cached on disk.
All physical batches share the existing cost/call ceilings and durable reservation
journal. Missing usage, malformed, reordered, zero, nonfinite or wrong-dimensional
vectors make evidence incomplete. Embedding-only runs require no LLM judge and
only the selected embedding provider's credentials. The lower call estimate is
one call per scenario per repetition for generic domain cases in parallel mode
(one per requested profile in ordinary sequential mode); consumer
scenarios also include their production calls and labelled anchors. The branch
envelope includes supplemental requests and actual provider batching. Large suite
selections may exhaust existing ceilings; no budget is raised automatically.
The shared weekly and explicitly selected staged scope includes this suite without raising budgets. A complete campaign remains subject to the full-corpus preflight limits described below. No reference is approved automatically.
Maintaining inventory and examples¶
Include scenario additions, updates and removals in the feature implementation plan before changing AI behavior. Describe inputs/context, expected decisions or generative rubrics, nominal/boundary cases and relevant provider/mode or embedding profiles so the reviewer can assess them. Once the human approves the plan, implement those scenarios alongside the code; no second activation review is required. Revisit the plan only for material changes to the approved scenarios. This review does not authorize paid campaigns or approve their resulting baseline.
Evaluation maintenance is part of implementation, including work without a commit. The repository evaluation skill owns the procedure; Python validation invokes it for AI-affecting changes, and commit preparation checks that the maintenance is staged with its owning code. Review indirect changes to prompts, input preparation, parsing, routing, retrieval, ranking and thresholds as well as new model calls. Record a reason when there is no evaluation impact.
| Feature change | Required maintenance |
|---|---|
| Addition | Register entrypoints, sites and provider/mode variants; map suite dependencies; add meaningful nominal/boundary cases and offline adapter contracts. |
| Intentional behavior update | Adjust affected inputs, expectations, rubrics, fixtures and adapters to the new product contract; update suite selection paths. |
| Model/provider migration or implementation-only change | Preserve expected behavior and acceptance criteria to measure regressions. |
| Removal | Delete dedicated cases, registrations, dependency mappings and unused adapters/fixtures/tests; retain shared resources used by surviving features and historical campaign evidence. |
Check inventory and affected offline contracts before handing work back; run the complete evaluator unit suite when shared evaluator infrastructure changes. Passing inventory checks cannot establish semantic coverage by itself. Do not weaken assertions or approve a replacement reference to conceal a regression.
OCR and resume PDF scenarios supply the synthetic source's visible text to the
judge through user_context.document_reference.visible_text, separately from
production extraction inputs. The nominal and injection cases use distinct
rubrics and references. Text actually present in the document is source content,
not added extraction commentary, even when it describes missing information.
The judge must not infer document contents from a file path or binary placeholder,
or assume an injection exists merely because the prompt discusses that risk.
Apply each workflow's contract: OCR transcribes visible injected commands as text
without executing them; resume parsing excludes those commands from documentText
and all other fields while preserving ordinary candidate content.
Maintenance makes no provider calls and does not authorize a paid campaign,
budget increase or reference approval. Changed corpus content invalidates prior
evidence under the rules below; paid execution remains a separate bounded step.
coverage statically discovers known connector calls, shared business wrappers,
SDK aliases, agent declarations and summarization boundaries. It rejects
unregistered or stale sites and undeclared gaps in nominal/boundary examples.
It is a check of known model boundaries, not a complete dynamic Python call
graph: extend the discovery rules and tests when introducing a new wrapper or
SDK invocation pattern. Connector internals are exercised through consumers;
42 embedding consumer sites map to dedicated executable scenarios, in addition to
the nine generic embedding domains. site_examples exposes the complete
site → variant → case → assertion matrix. Every workflow variant needs explicit
required_sites; a site without any applicable example fails inventory validation.
An intentionally deterministic no-call case must declare no_call_reason.
The runner requires all expected sites for the selected provider/mode to have
successful transport or verified vector-reuse evidence. A single successful call
cannot stand in for a multi-call workflow. Reports record the selected variant,
required sites and missing sites. Structured runnable construction retains its
exact call origin through LangChain child tasks; constructing a runnable alone
never proves execution. Run separate configurations for native OpenAI, forced
structured OpenAI and other providers; one campaign does not cover all variants. Standalone STT/TTS fidelity requires a separate audio corpus.
Removed scenario classes that remain within evaluation scope must be declared on the owning workflow
in evals/workflows.yaml, with a nonblank reason for each nominal or boundary
entry under retired_scenarios. This records an approved coverage gap; it does
not replace a scenario or count as a successful observation. A declaration for
a class that still has active examples is rejected. Unrelated missing scenarios,
variants and sites remain errors. When both classes are retired and no cases
remain, the workflow and its discovered sites remain registered until the
production feature is actually removed.
Acknowledged retirements make inventory coverage incomplete, with no inventory
errors. coverage retains a nonzero exit status to make that gap visible.
Campaigns may execute the remaining cases under the usual exploratory/reference,
provider and budget prerequisites. JSON and HTML reports retain the inventory
status and retirement reasons; JUnit exposes them as properties, and the review
workbook Summary separates current declarations from the campaign snapshot.
Historical reports without this metadata display Not recorded. Removed cases
generate no fabricated observations, scores or costs. Global reference approval
is prohibited while either the current inventory or the campaign records these
gaps, even if every remaining observation passes.
An explicit user decision to remove a complete workflow from evaluation scope
removes its cases and registry entry. If its production code still exists, list
each exact call site under excluded_sites with a nonblank reason. This is a
scope exclusion, not a successful evaluation or a pending coverage gap. Excluded
sites remain visible in inventory output, campaign JSON/HTML/JUnit and review
workbooks; they are not counted as registered evaluation sites. Stale exclusions,
exclusions overlapping registered sites and unrelated unregistered calls fail
inventory validation. Never use this mechanism merely to hide a failed result.
For a new workflow, register its production entrypoint, suite, profiles, provider,
call sites and estimated branch call envelope. Add versioned JSONL inputs and
explicit expectations in evals/cases/, plus a separate simulated connector reply
in tests/fixtures/ai_evals/ for adapter integration tests.
A boundary case must change meaningful inputs or execution conditions, such as
missing evidence, no matching label, conflicting instructions or a disabled
capability. Use deterministic expected labels for decisions and a domain-specific
rubric with requires_judge: true for prose. Preserve external identifiers and
explicit no-match outcomes. Do not replace a production prompt with a simplified
evaluation prompt when a production builder exists.
Use a registered adapter or add a focused adapter that fixtures external data
without replacing the model under evaluation. Run coverage and the affected
technical adapter tests with --no-cov before proposing a paid campaign; use
pytest tests/unit/ai_evals tests/integration/ai_evals --no-cov for shared evaluator
changes, with tracing disabled as shown above. The corpus hash includes the
registry and document bytes, so a changed live input fixture or expectation
invalidates associated evidence/reference. Test-only model replies do not.
Selection, caching and staged integrity¶
Explicit plan --staged and run --staged commands use git checkout-index in
a temporary directory, execute the staged runner/cases/source, and check that
the index remains unchanged. Partial staging therefore tests the selected index
content. Configurations and references for staged
runs must be repository-relative. Working-tree manual results can be reused only
when their fingerprint matches that staged snapshot exactly.
evals/manifest.yaml maps source owners to suites. The selector also follows
transitive repository-local imports. Unknown runtime changes conservatively
select all suites; documentation and ordinary test-only edits select none.
Within selected suites, reuse is per scenario and repetition. Each fingerprint
includes the scenario, its workflow entrypoint and call-site modules, transitive
local imports, literal dynamic import/fixture targets, referenced fixture files,
evaluator code, policy, requirements, resolved packages and model configuration.
Declare additional dynamic dependencies through dependencies on a workflow in
evals/workflows.yaml or on an individual case. Directory dependencies are
conservative; a source file shared by several functions invalidates every
scenario depending on that file. Unrelated source files and other scenarios'
label edits do not invalidate matching observations.
run --reuse assembles compatible observations from multiple campaigns and calls
providers only for missing or changed observations. Failed and incomplete cached
observations remain visible and are not automatically retried. A matching quality
failure takes precedence over a later lucky pass, with both campaign identities
retained. Original timestamps enforce the 72-hour cache lifetime; collecting or
reusing evidence never renews it. Model aliases can drift within this window.
Weekly runs remain fresh and do not reuse observations.
Use collect to assemble current evidence offline, without keys or tokens:
Missing/stale observations are recorded as incomplete. Complete, successful
assembled evidence can be explicitly approved after human review; partial or
failed evidence cannot. JSON, HTML and Excel retain original campaign IDs,
observation timestamps and prior outcomes. Campaign usage counts new calls only;
per-case usage retains the original cost. Historical reports without per-case
fingerprints remain historical and are not retroactively declared compatible.
Commits never launch AI evaluation campaigns or collect reports automatically.
Scenario quality failures, technical errors, missing references, credentials or
budgets do not reject a commit. Reports retain pass, fail or incomplete;
committing is not a quality verdict. Use explicit collect for existing evidence
without provider calls. Ordinary linting, typing, security, unit/coverage and
documentation gates remain mandatory. Cancellation still stops a running campaign.
Manual run, collect and compare retain meaningful exit codes: 0 pass,
1 quality failure, 2 incomplete/setup failure. No reference is approved automatically.
A quality failure, judge exception, provider error or observation timeout affects
that observation only: subsequent repetitions, scenarios and suites still run.
Quality failures remain fail; execution errors remain incomplete, even when
later scenarios pass. Campaign-wide reservation limits, the campaign timeout,
cancellation and failures to persist evidence still stop execution.
Provider calls can recover from a transient error within the same observation,
using the production SDK policy: at most three physical attempts with exponential
backoff on OpenAI, Azure OpenAI, Anthropic and OVH, including GenAI and embeddings.
Existing operation deadlines remain in force, including Decisions' 15 seconds. This does not
rerun a scenario or change the repetition count. Exhausted retries remain an
incomplete observation; successful recovery proceeds to normal quality grading.
Agent observations retain the actual synthetic tool results alongside the final response and tool names. The judge receives that same evidence, so it can check whether the answer is grounded in what the tools returned. The report and Excel export preserve those results for human review.
Cost and resource limits¶
For a focused verification after a fix, select exact scenario IDs instead of
rerunning their entire suites. Repeat --case to include nominal and boundary
examples of each changed workflow:
.venv/bin/python -m scripts.ai_evals plan \
--case matching.galaxy_rationale.nominal --case matching.galaxy_rationale.boundary
.venv/bin/python -m scripts.ai_evals run --exploratory \
--case matching.galaxy_rationale.nominal --case matching.galaxy_rationale.boundary \
--output ai-eval-artifacts/galaxy-fix
.venv/bin/python -m scripts.ai_evals export-review \
--report ai-eval-artifacts/galaxy-fix/report.json \
--output ai-eval-artifacts/galaxy-fix/results-review.xlsx
Planning, provider prerequisites and execution use the exact selection; unknown
or repeated IDs fail before any paid call. The report records that selection and
has distinct fingerprints, and its Excel export contains only those scenarios.
This mode requires an exploratory manual run and accepts --reuse. Its per-case
evidence can contribute to a later complete collection; the partial report itself
cannot approve a complete reference or narrow a staged or weekly selection.
Keep interrupted attempts and their reservations; selecting cases never resets
the shared daily ledger.
evals/policy.yaml defines absolute authorizations for the complete corpus with judging:
| Campaign | Repetitions | Provider calls | Token ceiling | Absolute USD ceiling |
|---|---|---|---|---|
| Manual | 1 | 1,522 | 20,000,000 | 10 |
| Weekly | 1 | 1,522 | 20,000,000 | 10 |
The active USD ceiling is normally much smaller than USD 10. For each scenario, take the highest repriced cost among the five most recent comparable, technically complete observations. Multiply that reference by 1.5 to obtain the forecast, then multiply the forecast by 2 for the safety ceiling. Sum only observations that will actually execute, including the requested repetitions and judge calls. The campaign ceiling is capped by the existing absolute authorization. Repetitions never increase the absolute campaign or daily authorization.
plan exposes cost_plan, its source observations, pending execution counts,
forecast and active ceiling. Pass --reuse to both planning and execution when
reuse is intended. extrapolated identifies a changed runtime, model, reasoning or
tariff. Repricing uses observed input/output/cache quantities with target tariffs;
it cannot predict a new model's response length. Ambiguous profile migrations and
new or modified examples are marked needs_calibration. Quality failures remain
usable cost evidence when transport usage is complete; incomplete responses and
reused observations are not new measurements.
Costs are independent of quality-reference approval. Fresh reports include
per-request usage and tariff snapshots in usage.call_costs. Local planning
combines reports in AI_EVAL_HOME/runs with portable measurements in
evals/costs/reference.json. Export measurements for GitLab using:
.venv/bin/python -m scripts.ai_evals export-costs \
--report ai-eval-artifacts/calibration/report.json \
--output evals/costs/reference.json
The export contains identifiers, hashes, timestamps and cost quantities, not prompts or generated answers. It never approves a semantic reference. Older reports without complete per-request attribution remain historical evidence and are not automatically converted into repricing references.
Every export automatically compacts the portable file after merging local
reports and the supplied report. It removes measurements for deleted scenarios
or obsolete scenario hashes, then retains at most costs.history_size
observations (currently five) per current scenario, judge mode, execution
mode and compatible repricing requirements. Provider/profile requirements stay
separate when they cannot be repriced under the same configurations. Different
model names, tariffs, runtime fingerprints or numbers of calls do not by
themselves create another retention group. The newest observations win, with
report ID and repetition breaking timestamp ties deterministically.
This bounds the cost reference without changing forecasts: discarded measurements cannot enter the latest-five selection for the current scenarios. Complete campaign reports and quality references are not deleted or modified. Local planning can still read those reports; exporting again reapplies the same purge, so archived reports cannot make the portable file grow indefinitely.
If calibration is required, specify an explicitly authorized one-run envelope
with --bootstrap-budget-usd, within the existing absolute ceiling:
.venv/bin/python -m scripts.ai_evals plan --suite lexi --bootstrap-budget-usd 1
.venv/bin/python -m scripts.ai_evals run --suite lexi --exploratory \
--bootstrap-budget-usd 1 --output ai-eval-artifacts/calibration
Choose the envelope after inspecting the plan and request bounds; the example amount is not a promise that every request fits. Bootstrap mode replaces the derived ceiling for that run and reserves separately priced input/output bounds before dispatch. Weekly CI uses the same empirical calculation and versioned measurements, with no implicit bootstrap or additional repetitions. Calibration clears the local vector cache between scenarios so each observation can price a standalone run. Ordinary sequential campaigns can retain cross-case reuse, but those subsidized observations do not replace independent cost measurements. Historical prompt-cache hits are not transferred to a replacement model during repricing.
The call ceilings include headroom above the current declared branch envelope
and judge calls. Use plan or plan --campaign weekly to recompute estimates
after changes. The token and absolute USD values are limits, not a predicted
bill or a guarantee that every generated branch will finish. Conditional calls,
retries and response lengths remain subject to the actual meter. If the minimum
call estimate exceeds the configured ceiling, run returns
campaign_budget_insufficient before dispatch; it never truncates coverage or
raises a ceiling automatically. Targeted suites use the same ceilings but spend
only for missing selected observations when reusing evidence. Reference approval
remains required for normal comparison runs; use --exploratory to gather initial evidence.
The shared daily local ceiling is USD 30 across all campaigns in the same UTC
day within the same evaluation home. Campaign timeout
is 20 minutes and observation timeout 60 seconds. The campaign deadline was
calibrated from a complete 375-scenario judged run on October 30, 2026, measured at
325.7 seconds with eight processes after the accounting and inventory optimizations.
Scaling that duration by 8/4 for the previous four-process default, adding a 50% margin and
rounding up to five minutes gives 1,200 seconds. This is an extrapolation for four
processes, not a measured four-process campaign. The five-process default retains
this 1,200-second deadline; revisit it when the corpus, provider latency or
execution environment changes. For explicitly requested timing
calibration, pass --no-campaign-timeout to both plan and run. This disables
only that execution's global deadline; observation deadlines, cancellation and
shared cost/call/token limits remain enforced. Reports and review workbooks record
the effective deadlines. The next invocation without the flag retains the normal
campaign deadline. A continuation limited to unstarted cases provides an indicative
combined duration, not a measurement of one uninterrupted full campaign.
Manual and GitLab campaigns
run 5 observations concurrently by default, each in a fresh process. Each slot
starts the next observation after collecting its result, without waiting for
slower scenarios. Shared budget admission is checked before each dispatch.
Use --parallelism N (1–32) on
plan and run; --parallelism 1 selects the sequential runner. Repetitions stay
at one unless explicitly requested. Production graph branches retain their own
concurrency within each worker.
The parent discovers model call sites and loads the workflow inventory once for execution, then serializes that snapshot into each worker's input. Workers still use fresh interpreters and independent clients and patches; they do not rescan the source tree. Budget journal validation reuses a compiled schema while retaining the same cross-process lock, complete history and durable writes.
Processes isolate adapter patches, provider clients and per-case metrics. A shared file lock serializes budget admission and settlement across workers. Calls, tokens, USD and daily ceilings are global; parallelism never multiplies them. Live sessions reserve their session and two possible backend calls together before opening. Worker failures and timeouts retain partial evidence and do not cancel other cases. Budget exhaustion prevents further dispatch. The parent owns child termination and reaping on cancellation and campaign timeout; unresolved reservations are retained. In addition to the 60-second adapter/judge deadline, a child has a hard deadline with 30 seconds for startup and evidence serialization. Progress is printed after each observation, and JSON/HTML/JUnit checkpoints retain completed observations.
JSON, HTML and the review workbook expose monotonic timing diagnostics. Empty
timings in older reports mean not recorded, not zero. Campaign
inventory_seconds measures shared inventory preparation. Per-observation fields
are:
startup_seconds: child initialization from its entry point to scenario setup completion; sequential execution records this once, on its first observation.dispatch_seconds: parent dispatch admission, including queue and budget waits.worker_seconds: parent-observed child lifetime, including process creation, initialization, execution, serialization and cleanup.execution_secondsandjudge_seconds: the scenario adapter and semantic judge respectively, including their nested model calls and budget operations.budget_seconds: summed HTTP reservation and settlement duration, including lock/thread waits, validation and durable writes.provider_seconds: summed HTTP transport and response-read duration, including SSE reads but excluding consumer pauses and budget settlement. This measures the transport, not server-only model inference. WebSocket Live sessions are not covered by these HTTP diagnostics.collection_seconds: parent evidence collection, report-lock wait and usage aggregation, before checkpoint file serialization.
Individual HTTP diagnostics retain provider/budget timings and their execution or judge phase, including failed or cancelled operations. These fields overlap: provider/budget totals are nested within execution/judge, and concurrent call durations can exceed wall time. Do not sum all columns to estimate campaign time. Hard-killed workers retain only diagnostics that reached a checkpoint. Timings do not alter grading, deadlines or budgets; recalibrate the global deadline using a separately authorized campaign after performance changes.
Each request reserves its historical cost
estimate before dispatch; new conditional branches reserve the scenario forecast.
Successful completion replaces that reservation with confirmed usage priced at
the recorded tariff. Errors, cancellations and missing stream usage retain their
reservations. reserved_usd is therefore confirmed spend plus unresolved
reservations, not the sum of all original worst-case reservations. The journal
records reservation IDs and settlement adjustments durably, including UTC-day
rollovers. Historical journals keep their original conservative debits.
The ceiling controls admission of subsequent requests. A dispatched request can exceed its empirical estimate; that overrun is recorded and stops further calls. The evaluator does not shorten production generation limits to guarantee a strict invoice cap. Provider-side spending limits remain an additional account boundary.
The HTTP transport meter includes conditional generation, judges and SDK retries.
OpenAI and Anthropic streams are consumed incrementally and require final usage;
an abandoned, failed or truncated stream is incomplete evidence. Local function
tool schemas are permitted with fixture execution. Background requests, hosted
tools and unconfigured destinations/models are rejected. For all supported provider
SDK requests, the meter lets the SDK apply its bounded retry policy, preserving retry-control
headers while sanitizing provider error messages. Every physical attempt reserves
budget before dispatch and retains its diagnostics. Failed-attempt reservations
remain unresolved even when a later attempt succeeds; such observations are
excluded from cost-reference export because their full cost is not confirmed.
Budget/policy stops and cancellation are never converted into retryable provider
failures. An established stream is not replayed after an interruption.
After an exhausted or non-retryable provider failure,
the meter rejects further paid requests within that observation, even when an
application function swallows the exception. The next observation can run within
the remaining campaign and daily budgets; failed-call reservations are retained.
Campaigns
hold a process lock; durable reservation journals survive crashes. The daily
limit is local to AI_EVAL_HOME, not an organization-wide quota. GitLab uses its
own cached journal; clearing caches or using another machine creates a different
accounting scope.
Live cases use the production session relay and close lifecycle with synthetic
text input, a muted microphone and a bounded delegated mini response. Their
versioned live configuration reserves duration plus up to two backend responses
before opening a session, requires three remaining call slots, and records actual
session seconds separately from backend tokens. An unexpected extra backend
response aborts the session when observed; server-owned delegation is not an
HTTP dispatch controlled by this process. Duration pricing is taken from the
OpenAI pricing reference and
versioned in the configuration ($0.05/minute for gpt-live-1, backend tokens
separate). No microphone recognition or perceptual audio-quality claim follows
from these text-driven Live scenarios.
Reports and weekly operation¶
Default local evidence: ~/.cache/aihub/ai-evals/runs/<id>/ (override with
AI_EVAL_HOME or --home). JSON contains configuration, hashes, case outputs,
status, tokens, calls, latency and reservations. Source-stack attribution records
attempted sites (observed_sites) and sites with successfully metered responses
(executed_sites), including partial evidence on errors. Each workflow must
reach at least one of its own declared sites; unrelated model traffic cannot
satisfy that check. Unreached branches and provider alternatives remain listed
as not_executed_sites. An attributed call does not imply all branches or
providers passed. HTML includes this coverage distinction and case summaries; JUnit
exposes regressions and incomplete execution to GitLab. Reports are checkpointed
after every observation. Compare only matching case populations; totals are
meaningful only with equal repetitions. Review distribution changes, critical
errors and per-case transitions alongside aggregate scores.
The ai-evals-full job requests a full fresh campaign with one observation per
case. It compares against the approved versioned reference, retains JSON, HTML
and JUnit artifacts for one year even on failure, uses one resource group, and
has no automatic retry. The latest previous weekly campaign with the same corpus is also compared
when its cached report is available (previous-comparison.json); GitLab cache is best effort,
so artifacts are the durable record. Export artifacts to your evidence archive
if your retention obligations exceed one year. No deployment, image build or
Renovate job runs in this dedicated schedule.
After publishing the job on develop, create a GitLab pipeline schedule:
| Setting | Value |
|---|---|
| Branch | develop |
| Cron | 0 9 * * 1 |
| Timezone | Europe/Paris |
| Variable | AI_EVAL_CAMPAIGN=weekly |
Use a dedicated evaluation provider project/key in masked GitLab CI variables
(and protected variables if develop is protected). The schedule owner needs
permission to run pipelines on that branch. The timezone preserves 09:00 across
summer/winter time; runner capacity can delay actual start. YAML does not create
or activate the schedule. Existing Renovate schedules keep their own marker-free
behavior. Insufficient budgets, a disabled required judge, an inactive reference
or missing credentials make this job fail clearly before model dispatch.
Use the repository aihub-llm-evaluation skill to review local or downloaded
weekly evidence. Never publish raw production conversations in this corpus.