Skip to content

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 expected duplicate, distinct, or insufficient_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:

.venv/bin/python -m scripts.ai_evals collect --output ai-eval-artifacts/current-evidence

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_seconds and judge_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.