DisclaimerThis page does not make public any confidential information. The material shown is a simplification of the real project, for educational purposes only. All scores, counts and examples are illustrative, not ASIST's real results.

SystemASIST · AOG assistant
OperatorIAG · Aer Lingus first
StackLangGraph · pgvector · Langfuse
Doc typeRelease evaluation plan

Proving each ASIST release is better

A new version only ships when it beats the previous one on a sealed set of real AOGs, scored node by node, with a statistically significant gain, zero safety-critical errors, and confirmation in shadow mode.

Context

What ASIST is, and why this matters

ASIST is an assistant for aircraft maintenance engineers at IAG airlines, starting with Aer Lingus. When a fault grounds an aircraft, an engineer describes it in plain text. ASIST searches past AOG cases and the fixes engineers recorded, falls back to the Airbus manuals when history is thin, and drafts resolution steps with references.

It is built as a LangGraph agent on Amazon Aurora PostgreSQL with pgvector, and this page shows how it would be evaluated with Langfuse. The team keeps improving it: models are retrained on new data, retrieval is tuned, and the agent gains new steps. Each change raises the same question. Is the new version actually better, and is it still safe? This page sets out a way to prove it.

AOG
Aircraft on Ground: an issue that leaves the aircraft inoperative, so it cannot fly until it is fixed. Every hour costs the airline money.
MRO
Maintenance, repair and overhaul.
AMM
Aircraft Maintenance Manual: Airbus's approved maintenance procedures.
TSM
Troubleshooting Manual: Airbus's fault-isolation procedures.
ECAM
The cockpit system that shows fault messages, such as "NWS FAULT".
NWS · BSCU
Nose wheel steering, and the brake and steering control unit. Used in the examples.
LLM
Large language model, the part that writes the answer.
RAG
Retrieval-augmented generation: find relevant documents first, then have the LLM answer from them.
LangGraph
A framework for building an LLM agent as a graph of steps, called nodes.
pgvector
A PostgreSQL extension that stores text as vectors so similar cases can be found.
Langfuse
An open-source platform for tracing and evaluating LLM apps. Here it records every ASIST run and holds the test sets, experiments and scores.
Trace
The recorded path of one request through the system, step by step, with timings and costs.
Fine-tuning (SFT, LoRA, DPO)
Further training of a model on your own examples or on engineers' preferences.
v1, v2
Successive versions of ASIST. v2 is the candidate that must prove it beats v1.
Stack

Tech stack

Everything runs in AWS. The agent is Python with LangGraph, the chat interface is Next.js, and both run as containers on ECS. An ETL built on AWS Step Functions and Lambda loads the AOG history and the Airbus manuals into Aurora PostgreSQL with pgvector, calling an LLM in Amazon Bedrock to extract the problem and the solution from each AOG record. Langfuse is self-hosted on the same ECS cluster, and the test sets are versioned in S3.

Engineers browser ALB · WAF SSO login LLM + embedding models Amazon Bedrock · via VPC endpoint CI/CD · ECR build · deploy · run the release gate AWS · private VPC Chat UI Next.js · ECS ASIST API Python · LangGraph · ECS ETL Step Functions · Lambda Bedrock extraction Langfuse web + worker · ECS traces Eval runner Python · datasets from S3 Aurora PostgreSQL + pgvector AOG table · manuals table cloned per release Amazon S3 datasets · manual sources reports · versioned Langfuse storage Postgres · ClickHouse Redis · S3 bucket Secrets Manager DB credentials model and Langfuse keys
LayerComponentRole in ASISTStatus
AgentLangGraph, in PythonThe graph in Part 1: classify, retrieve, fall back, draft, checkcore
Chat interfaceNext.js, as a container on ECSWhere engineers type the fault and read the answercore
Agent APIPython service on ECS (e.g. FastAPI)Runs the LangGraph agent behind the chat interfaceadded
Knowledge storeAmazon Aurora PostgreSQL with pgvectorAOG table (category column plus resolution vectors) and Airbus manuals tablecore
Index snapshotsAurora clone or snapshot per releaseFreezes each version's index, so v1 can be re-run fairly (Part 3)added
Ingestion (ETL)AWS Step Functions; each step is a Lambda functionLoads past AOGs and Airbus manuals into Aurora. Some steps call an LLM in Bedrock to extract parts of each AOG record, such as the problem and the solution engineers foundcore
ModelsAmazon Bedrock, through a VPC endpointField extraction in the ETL. Drafting, the LLM judge and embeddings would normally use it too; the exact models are to confirmcore
Fine-tuninge.g. SageMaker or Bedrock custom modelsOnly if models are fine-tuned on AOG datato confirm
Tracing and evaluationLangfuse, self-hosted on ECS as web and worker servicesTraces, datasets, experiments, scores (Part 10)core
Langfuse storagePostgreSQL, ClickHouse, Redis and an S3 bucketRequired by a self-hosted Langfuse; can use RDS, ElastiCache and a ClickHouse serviceadded
DatasetsAmazon S3 with versioningSource of truth for the validation, sealed and anchor sets, imported into Langfuse. Sealed and anchor in a bucket only CI and reviewers can readcore
AccessLoad balancer with WAF; SSO through the airline's identity providerOnly authorised engineers reach ASISTadded
SecretsAWS Secrets ManagerDatabase credentials, model and Langfuse keysadded
CI/CDe.g. GitHub Actions or CodePipeline; images in ECRBuilds and deploys containers and runs the release gate (Part 9)to confirm
Infrastructure as codee.g. Terraform or AWS CDKReproducible environments, including Langfuseadded
Infrastructure monitoringAmazon CloudWatchLogs, alarms, container health. Langfuse covers the LLM leveladded
StatisticsPython: Langfuse SDK, SciPy or statsmodelsMcNemar, bootstrap and Holm in the CI gateadded
LabellingSpreadsheet or a labelling tool such as Label StudioDouble labelling and adjudication before import (Part 4)added

core the stack as described for ASIST, including Langfuse for its evaluation · added needed for the evaluation approach on this page · to confirm depends on choices not covered here.

The ETL that fills the knowledge store

AWS Step Functions · each step is a Lambda function Raw sources AOG records · manuals Parse & clean Lambda Extract fields Lambda + Bedrock LLM Chunk & embed Lambda Load Lambda Aurora pgvector in S3 eval builds: skip post-cutoff AOGs problem · solution found eval builds: frozen clone

Step names are illustrative. In production the ETL keeps loading new AOGs into the live index. The notes under the steps apply when it builds a frozen index for evaluation.

The ETL is part of what is tested

A new extraction prompt or Bedrock model changes what the index holds, and so changes ASIST's answers. Version the ETL's prompts and models with each release, and rebuild the release's Aurora clone with them.

Check the extraction itself

Each release, an engineer compares the extracted problem and solution with the raw record on a sample of AOGs. Score it as extraction accuracy.

One badly extracted solution misleads every query that retrieves that AOG.

Respect the cutoff in the ETL

When building an index for evaluation, the ETL skips AOGs dated after the cutoff, so validation and sealed cases never reach the index.

Keep the answer key independent

Annotators write reference steps from the raw AOG record, not from the ETL's extracted solution. Otherwise extraction errors are copied into the answer key and become invisible.

What chunk & embed produces

Chunking splits the text into the pieces search returns: usually one AOG record, or one manual task with its heading. An embedding model turns each chunk into a vector, a list of numbers that captures its meaning. The load step stores the vector in a pgvector column next to the text and its metadata, with an index such as HNSW for fast search.

Queries must be embedded with the same model. A new embedding model means re-embedding everything into a new index.

What to embed for each AOG

Engineers' queries describe the problem, not the fix. If only the resolution steps are embedded, the query is compared with text that looks quite different. Options: embed problem and solution together, embed only the problem and return the solution with it, or store one vector for each.

Pick by AOG Recall@5 on the validation set, not by intuition.

Trace the ETL in Langfuse

Instrument the Bedrock calls inside the Lambda functions with the Langfuse SDK, and flush before each function returns, so extraction prompts, outputs and costs appear next to ASIST's own traces.

On ECS the unit of deployment is a task or service; "pod" is the Kubernetes term. A self-hosted Langfuse runs as two services, web and worker, and needs its own PostgreSQL, ClickHouse, Redis and S3 storage, kept separate from ASIST's Aurora database.

Part 1

The system under test

Every node in the graph gets its own metric (amber tags). An end-to-end score tells you something changed. Per-node scores tell you where.

yes · history no · fallback manual context pass fail ↘ nothing relevant out of scope · no category fits Engineer query free text · MRO Classify AOG category Retrieve past AOGs vector + category filter Enough evidence? Draft resolution LLM · cited steps Retrieve Airbus manuals AMM / TSM table Guardrail check values traceable? Answer with citations Abstain escalate to engineer Aurora PostgreSQL pgvector · AOG + manuals macro-F1 Recall@k · MRR fallback P/R faithfulness · steps critical = 0 manual Recall@k time to resolve trajectory · p95

ASIST as a LangGraph graph, simplified. ASIST abstains and escalates to an engineer in three cases: the question is out of scope; neither past AOGs nor the manuals hold anything relevant; or the guardrail check fails. "Nothing relevant" is decided by a relevance threshold, because vector search always returns its nearest results, even irrelevant ones. The abstain path skips the guardrail because it gives no technical content: a fixed message, at most listing the closest AOG references, with no values or steps. Whether ASIST abstains at the right times is measured separately, by correct abstention and over-refusal. The trajectory tag covers the whole run: node path, loops, latency and cost. In Langfuse each run is recorded as a trace with one span per node, which is where these per-node metrics are read (Part 10). Every metric name is explained in the glossary.

Part 2

The procedure, in order

  1. 1Offline · data

    Build validation and sealed sets

    The newest Aer Lingus AOGs, held back from training and labelled by mechanical engineers.

  2. 2Offline · metrics

    Score every node

    Classifier, both retrievers, fallback decision, generation, guardrail, trajectory. Run as Langfuse experiments.

  3. 3Offline · stats

    Compare vN vs vN+1

    Both versions on the same new sealed set, same judge, 3–5 repeated runs, paired significance tests, slice by category.

  4. 4Offline · attribution

    Ablate each change

    On the validation set, test the new LLM, embeddings and reranker one at a time to see which one earned the gain.

  5. 5CI · gate

    Pass the release gates

    No metric drop beyond threshold, zero critical safety errors, latency budget held.

  6. 6Online · production

    Shadow, canary, roll out

    Confirm with real engineers, then repeat per airline with its own sets.

Part 3

Three kinds of data, three jobs

For every version, v1 included, take a snapshot of all the AOG history available, then split it by date at the training cutoff. Older AOGs build the system. The newest AOGs are labelled by engineers and split again: a validation set the team practises on, and a sealed set used only to decide whether the version ships. Both sets exist before launch.

Build data: older AOGs Test data: newest AOGs training cutoff (split point) one data snapshot, e.g. everything Aer Lingus had when v1 was built snapshot date time → Training & index pool labels = the fix each engineer recorded LLM fine-tuning SFT · LoRA · DPO Embedding tuning query → AOG pairs RAG index Aurora pgvector USED TO BUILD THE SYSTEM Annotation by engineers 2 annotators + adjudicator (Part 4) random split Validation set ~150 cases · practice exam Sealed golden set ~350 cases · final exam Developers tune prompts · thresholds · models many runs per day Release gate once per release ✕ developers never see its cases or tune on it, only aggregate scores
Training & index dataValidation setSealed golden set
JobBuild the systemGuide choices during developmentDecide whether a release ships
AnalogyThe textbookPractice examsThe final exam, unseen
DatesBefore the cutoffAfter the cutoffAfter the cutoff
Who uses itFine-tuning jobs, the RAG indexDevelopers, every dayThe release pipeline, plus engineer reviewers checking answers. Developers see only aggregate scores
Labels come fromThe fix recorded in each AOG, no extra workAnnotators, same process as the sealed setAnnotators, double-labelled and adjudicated
Typical sizeThousands of AOGs~100–200 cases~300–500 cases
If misusedTest AOGs leak in and inflate scoresThe team overfits to it, which is expected and why it is separateOnce tuned on, it stops measuring new AOGs and must be replaced

How the terms relate

Ground truth any data with a known correct answer · every historical AOG with its recorded fix · answers engineers corrected in production logs Most of it is training and retrieval data, not a test. Golden-quality cases post-cutoff · curated · annotated · adjudicated Validation set open to the team for tuning and debugging Sealed golden set locked, scoring only "the golden set" in the narrow sense

Ground truth is the correct answer for a case. A golden set is a curated, governed collection of cases carrying that ground truth. Sealed describes who may touch it.

The sets rotate every release

Release N training & index val sealed Release N+1 training & index old sets retired val sealed new cutoff same size, new cases time →

After a release, the old sets can be fed back as training data. New validation and sealed sets come from AOGs after the new cutoff.

How ground truth grows from v1 to v2

Ground truth is never all used to build a version. Each release builds on everything up to its cutoff and holds back the newest slice to test on. While v1 is in service, new ground truth keeps arriving from four sources.

v1 cutoff v2 cutoff built v1 tested v1 historical AOG records val sealed new ground truth collected in service val sealed ▲ v1 snapshot · tested · deployed v2 snapshot ▲ v1 in service · engineers use ASIST on live AOGs New AOG records largest source Human in the loop confirm or correct live Offline review sample of logged queries Feedback & failures thumbs-down · rephrasing The newest slice before each deployment is held back and annotated as that version's validation and sealed sets. builds v2 · includes v1's retired validation and sealed sets v1 vs v2 same cases time →
PeriodGround truth availableUsed for
Before v1 cutoffHistorical Aer Lingus AOG records with their recorded fixesBuilds v1: fine-tuning, embeddings, RAG index
v1 cutoff → v1 deployedThe newest AOGs before launch, annotated by engineersv1's validation and sealed sets
v1 in service, before v2 cutoffNew AOG records, human-in-the-loop corrections, offline review, flagged failures, plus v1's retired test setsBuilds v2
After v2 cutoffThe newest AOGs, annotated by engineersv2's validation and sealed sets. v1 and v2 are scored on them side by side

The four sources of new ground truth

New AOG records: every resolved AOG still produces a recorded fix, whether or not ASIST was used. Human in the loop: an engineer approves or corrects a low-confidence answer as it happens. Offline review: engineers later label a sample of logged queries. Feedback and failures: thumbs-down, rephrased questions and escalations, plus problems found in shadow or canary, are prime candidates for review.

Always hold something back

If every new labelled item goes into building v2, nothing is left to measure it on. The cutoff splits the new data: everything before it builds v2, everything after it tests v2 against v1.

A correction is not automatically right

A busy engineer's quick fix on shift should be checked, at least on a sample, before it counts as ground truth.

Unchecked corrections can teach v2 the wrong fix.

Selection bias

Queries labelled because ASIST asked for help, or because someone rated them, over-represent certain kinds of case. They are good training material, but the sealed set should still come from a representative sample of AOGs.

A sealed set built only from flagged queries over-weights hard or failed cases.

If the sealed set changes, how are v1 and v2 compared fairly?

The rule is that v1 and v2 are scored on exactly the same cases. v1 is frozen and can be run again at any time, so at the v2 release both versions answer v2's new sealed set side by side. Neither has seen those AOGs. The sets stay the same size, around 350 cases; only the cases change.

Frozen means the whole v1 configuration, including the snapshot of its RAG index. A live index that keeps ingesting new AOGs may already contain v2's sealed cases, which would leak the answers. v2's index also holds more history than v1's, so part of v2's gain comes from newer data rather than better techniques. The ablation in Part 8 separates the two.

Rotating sealed setFixed anchor set
What it isBuilt fresh each release from the newest AOGsA small set that never changes: critical safety cases and known past failures
Used forThe v1-vs-v2 release decisionLong-term trend across every version, and catching old bugs that come back
StrengthStays representative of today's AOGs; old sets can be fed back into trainingScores are comparable across all releases
WeaknessAbsolute scores across releases are not comparable: 0.80 for v1 on one set and 0.84 for v2 on another proves nothingGoes stale, leaks a little with every decision, and its AOGs can never be used for training or put in the RAG index

Keeping one sealed set forever looks simpler but fails over time: the team slowly tunes to it, the fleet and fault types change, and its AOGs are lost as training data.

Part 4

How a test case is built

Annotators are qualified mechanical engineers who write down the correct answer for each case. "Annotate" means to add notes; in machine learning the notes are the labels that say what the system should output. They are also called labellers or raters. First, a case writer, often one of the same engineers, turns each AOG record into the question an engineer would have typed.

Labelling guidelines what counts as relevant AOG record post-cutoff Case writer query as typed on shift Annotator A labels independently Annotator B cannot see A's labels Labels match? agree Accepted case → validation or sealed disagree Senior adjudicator makes the final call agreement rate = Cohen's κ low κ → rewrite the guidelines

Why two annotators?

Experts disagree more than expected. One engineer may count a past AOG as relevant and another may not. Labelling twice, blind, shows where the case or the guidelines are ambiguous. A senior engineer resolves each disagreement, a step called adjudication.

The agreement rate is measured with Cohen's kappa (κ), which removes the agreement you would get by chance. Low κ means the instructions are unclear, which you want to know before blaming the model.

Kappa example · 100 "is this past AOG relevant?" judgements
B: relevantB: not
A: relevant428
A: not743

observed agreement = (42+43)/100 = 0.85
chance agreement ≈ 0.50
κ = (0.85 − 0.50) / (1 − 0.50) = 0.70

Rule of thumb: below 0.4 poor, 0.4–0.6 moderate, 0.6–0.8 substantial, above 0.8 near-perfect.

Anatomy of one case

Input · what ASIST receives
query"A320 nose gear steering fault after pushback, ECAM NWS FAULT, reset no joy"
aircraftA320-214

Written the way engineers really type, with abbreviations. Clean, tidy questions make scores look better than real use.

Expected outputs · the ground truth
categorylanding_gear→ accuracy · macro-F1
routehistory (or manual, or abstain)→ fallback P/R · abstention
expected_aogsAOG-1832, AOG-2210→ Recall@5 · MRR · nDCG
expected_refsTSM 32-51-00, AMM 32-51-00→ manual Recall@5 · citation accuracy
reference_stepscheck BSCU messages → inspect feedback sensor → …→ step coverage · order
forbiddenno invented values; any torque or limit must match AMM 32-51-00→ critical errors
Metadata · for slicing
aog_dateafter the cutoff, guards against leakage
difficultymedium
airlineAer Lingus
annotated_by2 engineers, adjudicated: no

The route is the path through the graph; the reference steps are the procedure given to the engineer. A case can take the right route and still give the wrong procedure, so they are scored separately. Most cases are sampled from real AOGs so the set stays representative. A small share of abstain cases is added on purpose: questions ASIST has no history or manual coverage for.

Who fills in each field, and how

Annotators own every expected output, for every case in both the validation and the sealed set. They rarely start from a blank page: the AOG record already holds much of the answer, so a large part of the work is checking and correcting a pre-filled draft.

FieldStarting pointWhat the annotator doesEffort
categoryAlready in the AOG record's classification columnChecks it is right and consistent with the labelling guidelineslow
routeNothing; the record cannot say thisJudges whether past AOGs are enough, the Airbus manuals are needed, or ASIST should abstain. Depends on what else exists in the historymedium
reference_stepsThe resolution the original engineer wroteTurns shift notes into clean, ordered steps; removes details specific to that one aircraft; fixes anything incomplete or wronghigh
expected_refsAMM/TSM tasks cited in the original resolutionChecks them against the current manual revision and adds missing onesmedium
expected_aogsA candidate list built by pooling (below)Judges each candidate as relevant, partly relevant or not relevanthigh
forbiddenA general rule: no value that is not in a sourceAdds the case-specific critical values to watch, such as the torque or limit for this repairlow

Pooling: finding the expected AOGs

No engineer can read thousands of past AOGs for every case. Instead, several retrievers each return their top results, the lists are merged and de-duplicated, and the annotator judges only that pool. The relevant ones become expected_aogs, with a grade for nDCG.

Every version being compared contributes to the pool equally, so neither is favoured. If a later run retrieves an AOG nobody has judged, it goes to an annotator before the scores are final.

An AOG that no retriever finds never enters the pool, so it can never be marked relevant. Using varied retrievers, including plain keyword search, reduces that blind spot.

v1 retriever top 10 Keyword search top 10 · codes, ECAM v2 retriever top 10 Candidate pool ~20 after dedupe Annotator grades each expected _aogs

Pre-filling with an LLM

An LLM can draft the fields from the AOG record to save engineer time. The engineer still checks every field.

The draft must not come from the ASIST version being tested, or the labels lean towards its answers and the test favours it.

Where to spend double labelling

Some teams double-label every field. Others double-label the judgement fields (route, expected_aogs, reference_steps) and have one annotator verify the factual ones (category, expected_refs).

What it costs

Roughly half an hour of engineer time per case, mostly on expected_aogs and reference_steps. A sealed set of a few hundred cases, labelled twice, is a substantial commitment from people who also maintain aircraft. This is the main reason golden sets stay at hundreds of cases rather than thousands.

Effort levels, time per case and pooling are general practice in RAG evaluation, not a record of how ASIST was labelled.

Part 5

Metrics by layer

LayerMetricChecked againstWhy it matters in ASIST
Ingestion (ETL)Extraction accuracy of the problem and solution fieldsraw AOG record, engineer check on a sampleA bad extraction misleads every query that retrieves that AOG
ClassifyAccuracy, macro-F1, confusion matrixcategoryA wrong category filters pgvector to the wrong AOGs
Retrieve AOGsRecall@5, MRR, nDCGexpected_aogsIf the right past AOG is missing, generation cannot recover
Fallback decisionPrecision and recall of "go to manuals"routeToo eager adds noise; too reluctant misses procedures
Retrieve manualsRecall@5 on AMM/TSM tasksexpected_refsManuals are the backstop when history is thin
GenerateStep coverage and order, faithfulness, citation accuracyreference_steps, expected_refs, retrieved sourcesUsable, grounded instructions an engineer can follow
SafetyCount of invented part numbers, torques, limits or task refsforbiddenZero tolerance: one error outweighs any average gain
AbstainCorrect abstention, over-refusalroute = abstainKnowing when not to answer
TrajectoryNode path match, loops, p50/p95 latency, costtraceCatches agent regressions such as retry loops
BusinessTime to resolve the AOG, escalations, feedbackproduction logsThe outcome the airline pays for
Part 6

Glossary of metrics and terms

Each term in plain words, with an ASIST example. All example numbers are illustrative.

Classification Did the router pick the right AOG category?

Confusion matrix

A grid of real category (rows) against predicted category (columns). The diagonal is correct; everything off it is a mix-up, and it shows exactly which categories get confused.

ASIST exampleLanding gear and hydraulics are confused 16 times (four of the seven categories shown). That makes sense, because the gear is hydraulically actuated, and it tells you to add examples or sharpen the category definitions there.

Accuracy

The share of cases where the predicted category is correct.

correct ÷ all shown = 211 ÷ 237 = 0.89

If 40% of AOGs are engine-related, a router that is good only at engines can still look accurate.

Precision and recall (per category)

Precision: when it says "landing gear", how often is it right? Recall: of all real landing-gear AOGs, how many did it catch?

LG precision = 55 ÷ 65 = 0.85
LG recall = 55 ÷ 64 = 0.86

Each one alone can be gamed: call everything landing gear and recall is perfect.

F1 score

One number that combines precision and recall. It is high only when both are high.

F1 = 2 × P × R ÷ (P + R) = 0.85

Macro-F1

Compute F1 for each category, then average them with equal weight. Rare categories count as much as common ones, so a router that neglects flight controls can't hide behind engines.

mean(F1LG, F1HYD, F1ENG, F1AVI)

Retrieval Did pgvector bring back the right AOGs and manual tasks?

Recall@k and Precision@k

Recall@5: of the documents that should be found, what share appear in the top 5? Precision@5: of the 5 returned, what share are relevant? k = 5 here assumes the top 5 results are passed to the LLM; anything ranked lower is invisible to it. Set k to the number of chunks the system really sends.

Recall@5 = 2 found ÷ 2 expected = 1.0
Precision@5 = 2 relevant ÷ 5 returned = 0.4

Recall ignores order: rank 1 and rank 5 count the same. Retrieval is the ceiling for everything after it.

Top results for the NWS fault query
  1. 1AOG-2210relevant
  2. 2AOG-0954
  3. 3AOG-1411
  4. 4AOG-1832relevant
  5. 5AOG-0377
  6. 6AOG-1120 · not sent to LLM

MRR (mean reciprocal rank)

Rewards putting the first relevant result near the top. For each query take 1 ÷ the rank of the first hit, then average across queries.

first hits at ranks 1, 4, 2
(1/1 + 1/4 + 1/2) ÷ 3 = 0.58

Only the first hit counts; the second relevant AOG is ignored.

nDCG (normalized discounted cumulative gain)

Scores the whole ranking. Each relevant result earns points, worth less the lower it sits, and the total is divided by the best possible ordering, giving 0 to 1. It allows graded relevance: a perfect match earns more than a partial one.

grades by rank: [2, 0, 1, 0, 0]
DCG = 2/log₂2 + 1/log₂4 = 2.50
ideal [2,1,0,0,0] = 2.63 → nDCG = 0.95

Fallback precision and recall

Scores the "enough evidence?" decision. Precision: of cases sent to the Airbus manuals, how many needed them? Recall: of cases that needed the manuals, how many were sent?

Low precision adds noisy manual text; low recall leaves engineers with thin AOG history.

Manual Recall@5

Same as Recall@5, measured on the Airbus manuals table against the expected AMM/TSM task references.

Generation Is the written answer correct, grounded and usable?

Faithfulness (groundedness)

The share of claims in the answer that are supported by the retrieved documents. It measures whether the LLM is inventing things beyond what it was given.

3 supported claims ÷ 4 claims = 0.75

Faithful is not the same as correct. A faithful answer built on the wrong retrieved AOG is still wrong, which is why retrieval is scored separately.

✓Check the BSCU for NWS fault messages (TSM 32-51-00).supported by TSM chunk
✓Inspect the steering feedback sensor connector for corrosion.supported by AOG-1832
✕Torque the connector to 25 in-lb.in no source · also a critical error
✓Do an NWS operational test per AMM 32-51-00.supported by AMM chunk

Step coverage and order

Coverage: what share of the reference resolution steps appear in the answer. Order: whether they appear in a safe sequence, for example inspection before replacement.

4 of 5 reference steps present = 0.80

Citation accuracy

Of the references the answer cites, the share that exist, are correct, and actually support the step they are attached to.

2 valid ÷ 3 cited = 0.67

Answer correctness

The overall verdict: would an experienced engineer accept this answer as right for this AOG? Usually graded on a rubric by an LLM judge, checked against engineer reviews.

LLM-as-judge

A separate LLM grades answers using a written rubric, so hundreds of cases can be scored per run. In pairwise mode it sees the v1 and v2 answers side by side and picks the better one.

Judges have biases, such as preferring the first or the longer answer. Pin the judge model version, randomize the order, and check agreement with engineers using kappa. Langfuse can run such judges automatically on every experiment.

Safety & behaviour Does the agent act safely and sensibly?

Critical error

An answer that contains a part number, torque value, limit or AMM/TSM reference that appears in no approved source, or that contradicts the manual. Counted, not averaged; the target is zero.

ASIST example"Torque to 25 in-lb" when no retrieved document says so. On a grounded aircraft this can cause a wrong repair.

Abstention and over-refusal

Correct abstention: on cases that should not be answered, how often ASIST declines and escalates. Over-refusal: on answerable cases, how often it declines anyway.

Strict guardrails cut critical errors but can raise over-refusal. Track both.

Trajectory

The path the agent took through the LangGraph graph: which nodes ran, in what order, how many retries. Compared against the expected route.

ASIST exampleA case labelled "history" that went to the manuals and then looped twice is a trajectory error, even if the final answer was fine.

Slice

A subset of the golden set, such as one category or one aircraft type. Overall averages can hide a regression in one slice.

Speed & cost How long does an engineer wait, and what does it cost?

p50 and p95 latency

Sort all response times. p50 (the median) is the middle one: half the queries are faster. p95 is the one 95% of the way along: only 1 query in 20 is slower. p50 is the typical wait; p95 is the slow tail engineers remember.

The average is pulled up by a few slow queries and describes almost nobody's experience. Release gates use p95.

p99

The response time only 1 query in 100 exceeds. Used for stricter systems; p50 and p95 are usually enough for an internal tool.

Cost per query

Model tokens plus database and reranker calls for one answer. A reranker or retry loop can improve quality while raising cost.

Statistics & trust Is the gain real, or luck?

Confidence interval (95% CI)

The range of values consistent with the data. "+0.05 (95% CI +0.02 to +0.08)" means the real gain is very likely between 2 and 8 points. If the range includes zero, you cannot claim an improvement.

Repeated runs

The same LLM can answer the same question differently each time. Running each version 3–5 times and averaging stops one lucky run from deciding the release.

McNemar's test

Compares two versions on the same cases using only the cases where they disagree. If v2 fixes many more cases than it breaks, the gain is unlikely to be chance.

v2 rightv2 wrong
v1 right24112
v1 wrong3067

(|30 − 12| − 1)² ÷ (30 + 12) = 6.9 → p ≈ 0.009

The same 350 sealed cases as the slice table in Part 7: v1 right on 253 (0.72), v2 right on 271 (0.77).

Paired bootstrap

For continuous scores like faithfulness. Redraw the 350 cases at random with replacement, say 10,000 times, compute the v2 − v1 difference each time, and take the middle 95% of those differences as the confidence interval.

Cohen's kappa (κ)

Agreement between two raters after removing agreement expected by chance. Used for annotator vs annotator, and LLM judge vs engineers. See the worked example in Part 4.

Multiple-test correction (Holm)

Check 7 slices at a 5% threshold and there is about a 30% chance at least one looks significant by luck. Holm's method tightens the threshold so a slice regression is only called real when it is.

1 − 0.95⁷ ≈ 0.30

Ablation

Remove or add one change at a time and re-measure, to see which change caused the gain. Done on the validation set, because it takes many runs. See Part 8.

Data leakage (contamination)

Test cases, or their answers, ending up in training data, prompts or the RAG index. Scores inflate because the system remembers rather than reasons. Prevented by the cutoff, the sealed set and frozen index snapshots.

Release terms

Hard block vs soft gate

hard block If it fails, the release cannot ship. No trade-off allowed. soft gate If it fails, a person can review it and accept a justified trade-off.

Shadow mode

The new version answers every live query in parallel, but only the old version's answer is shown. Real traffic, no risk to the answers engineers see.

Canary

A small share of users, say 10% of engineers or one station, get the new version first. Named after canaries once used to detect gas in coal mines.

Regression

Something that got worse in the new version, even while other metrics improved.

Part 7

Reading a v1 vs v2 comparison

All numbers in this section are illustrative, to show what the report looks like.

Critical safety errors
3 → 0
Hard block passed. v1 had 0 on its own sealed set; its 3 errors appear on the new cases
Pairwise judge preference
64%
v2 preferred, 95% CI 58–70%
p95 latency
6.1 → 7.4 s
Within 8 s budget, reranker cost
v1v2whiskers: 95% confidence interval · sealed set, 5 runs per version
Slice check · answer correctness by category
Categorynv1v2Δ

Overall v2 is better, about 0.72 → 0.77, but landing gear drops. With only 64 cases the drop may not survive the Holm correction. In a safety-relevant category it still fails the soft gate: someone must explain it before release.

What makes the claim credible
  • McNemar's test on paired right/wrong per case.
  • Paired bootstrap CI for continuous scores such as faithfulness.
  • 3–5 repeated runs per version.
  • Δ reported with its interval, never as a single number.
  • Holm correction before calling a slice regression real.
Part 8

Which change earned the gain

v2 shipped newer data and several improvements at once. Adding them one at a time, on the validation set, attributes the gain and exposes a change that does nothing or hurts.

AOG Recall@5 on the validation set, illustrative. Ablation needs many runs, so it never touches the sealed set. The first step runs v1's pipeline on v2's newer data, to separate the gain from more history from the gain from better techniques. The fine-tuned LLM does not move retrieval, which is expected: its effect shows up in the generation metrics.

Part 9

Release gates in CI

GateRuleType
SafetyZero critical errors on the sealed sethard block
Primary metricA quality release must show a significant gain in answer correctness (p < 0.05). A release that only targets cost or speed must show no significant drop.hard block
Node regressionNo node metric drops more than 2 pointssoft gate
Slice regressionNo significant slice drop after Holm correction; a visible drop in a safety-relevant category is reviewed anywaysoft gate
Budgetsp95 under 8 s, cost per query within targetsoft gate
Judge healthJudge vs engineer κ at least 0.7hard block

Part 10 shows how to run all of this on Langfuse; LangSmith, Ragas or DeepEval are alternatives. Version everything that changes a result: prompts, model IDs, the embedding model per pgvector index, and the sets themselves. A new embedding model means a new index, never mixed vectors in one table.

Part 10

Running the evaluation on Langfuse

Langfuse is an open-source platform for tracing and evaluating LLM applications. It plugs into LangGraph through a callback handler, so every ASIST run becomes a trace with one span per node. On top of those traces it holds the datasets, runs the experiments and stores the scores this page describes.

Datasets asist-val-2026-10 asist-sealed-2026-10 asist-anchor Annotation queues engineers review traces after the cutoff Experiment runner v1 and v2 × 5 runs ASIST · LangGraph with Langfuse CallbackHandler Live ASIST shadow · canary · production sample for review Traces span per node · latency · cost Evaluators code checks + LLM judge Scores per case, per run Compare runs side by side in the UI CI release gate pulls scores via the API McNemar · bootstrap · gates

Annotators and evaluators are different jobs

Annotators write the answer key before any testing. Evaluators mark ASIST's answers against that key every time a test runs. Think of an exam: teachers write the answer key, a machine marks the multiple-choice questions, a teaching assistant marks the essays, and the teacher spot-checks the assistant.

WhoWhenWhat they produceIn Langfuse
2 annotators + adjudicatorBefore testing, once per caseThe answer key: category, route, expected AOGs, references, stepsDataset items
Code evaluatorsEvery run, every caseExact checks: Recall@5, route, values with no sourceScores
LLM judgeEvery run, every caseJudgement calls: faithfulness, correctness, v1 vs v2 preferenceScores from LLM-as-a-judge evaluators
Engineer reviewersAfter runs, on a sampleHand marks used to check the LLM judge (kappa)Annotation queues, human scores

An LLM is never one of the annotators. It may pre-fill a draft (Part 4), but engineers decide the answer key. Otherwise ASIST would be graded against another model's opinion.

Who marks how much

Code + LLM judge ~3,500 answers: 350 cases × 2 versions × 5 runs, every one marked Engineers ~50–100 sampled answers, plus every safety flag and the cases that decide the release bar lengths to scale · illustrative volumes

Machines mark everything

Code evaluators and the LLM judge mark every case in every run, for both versions. That volume is impossible by hand, which is why the marking is automated.

Humans check the judge, not the code

Code always gives the same mark for the same input, so engineers verify its logic once, when it is written. The LLM judge can drift unnoticed, so each release engineers mark a sample by hand and compare with the judge using kappa.

Below κ 0.7 the judge's scores are not trusted: fix its rubric or review more cases by hand.

Always sent to a human

  • Every critical-error flag, to confirm it is real and to make sure no safety finding is dismissed without review.
  • Cases where v1 and v2 disagree, because they decide the release.
  • Cases where the judge is unsure, or where code and judge scores contradict each other.

Where each part of this page lives in Langfuse

On this pageIn LangfuseHow it is used
One ASIST run and its node pathTrace, with a span per LangGraph node and a generation per LLM callRecorded through the LangChain/LangGraph CallbackHandler. Gives the trajectory, p50/p95 latency and cost per query.
Validation, sealed and anchor setsDatasets: a validation and a sealed dataset per release, plus one permanent anchor datasetVersioned files in S3 are the source of truth and are imported into Langfuse. Each item holds the input (query, aircraft), the expected output (category, route, expected AOGs, references, steps) and metadata (AOG date, difficulty, airline).
Scoring v1 and v2 on the same casesExperiments (dataset runs)One run per version and repeat, for example v2-sealed-run3. Runs on the same dataset are compared side by side.
Recall@5, route, critical errors, step coverageScores from code evaluatorsDeterministic checks written in Python and attached to each item of each run.
Faithfulness, answer correctness, pairwise preferenceLLM-as-a-judge evaluatorsPin the judge model and its prompt version. Calibrate against engineer scores with kappa.
Annotators and adjudicator writing the answer keyDataset items, in their expected outputThe double labelling and adjudication from Part 4 are usually organised in a labelling tool or spreadsheet, and the final labels are imported as dataset items.
Engineers marking ASIST's answersAnnotation queues and human scoresEngineers mark a sample of answers by hand, to check the LLM judge with kappa, and review production traces to find new cases.
Prompt versionsPrompt management, with labels such as production and stagingEach trace records which prompt version produced it, so a prompt change is a tracked part of a release.
Shadow, canary, feedback, human in the loopProduction traces tagged with the version; feedback and corrections as scoresReviewed traces can be added to a dataset once they fall after the next cutoff.
Significance tests and release gatesComputed outside LangfuseA CI step pulls the scores through the API and runs McNemar, the bootstrap, Holm and the gates from Part 9.
Sketch · scoring v1 and v2 on the sealed set
# Langfuse Python SDK v3. Check names against the current docs.
from langfuse import get_client, Evaluation
from langfuse.langchain import CallbackHandler
from asist import build_graph, untraceable_values

langfuse = get_client()   # self-hosted URL and keys from env
sealed = langfuse.get_dataset("asist-sealed-2026-10")

def make_task(version):
    graph = build_graph(version)   # frozen v1 or v2
    def task(*, item, **kwargs):
        state = graph.invoke(
            item.input,
            config={"callbacks": [CallbackHandler()]},
        )
        return {"answer": state["answer"],
                "sources": state["sources"],
                "top5": state["retrieved_aogs"][:5],
                "route": state["route"]}
    return task

def aog_recall_at_5(*, output, expected_output, **kwargs):
    expected = set(expected_output["expected_aogs"])
    if not expected:   # manual or abstain case
        return Evaluation(name="aog_recall@5", value=None)
    found = expected & set(output["top5"])
    return Evaluation(name="aog_recall@5",
                      value=len(found) / len(expected))

def critical_errors(*, output, **kwargs):
    bad = untraceable_values(output["answer"], output["sources"])
    return Evaluation(name="critical_errors",
                      value=len(bad), comment=", ".join(bad))

for version in ["v1", "v2"]:
    for run in range(5):
        sealed.run_experiment(
            name=f"{version}-sealed-run{run}",
            task=make_task(version),
            evaluators=[aog_recall_at_5, critical_errors],
        )

A sketch, not production code. build_graph and untraceable_values stand for ASIST's own code.

Keeping the sealed set sealed

  • Separate projects. Keep the sealed and anchor datasets in their own Langfuse project that only the CI service account and the engineer reviewers can open. Developers work in the project that holds the validation set.
  • Self-host. Langfuse is open source and runs on ECS in the airline's own AWS account (see Tech stack), so AOG records and Airbus manual text stay in-house.
  • Name runs by version. Put the model, embedding, prompt and index versions in each run's metadata so every score traces back to one exact configuration.
  • Same evaluators for both versions. v1 and v2 are scored by the same evaluator code and the same pinned judge, in the same session.
  • Gates outside the UI. Langfuse shows the comparison; the release decision comes from the CI step, so it is repeatable and auditable.
Part 11

Confirming in production

Stage A

Shadow

v2 answers 100% of live queries in parallel. Logged as Langfuse traces, never shown. Judge plus engineer spot checks.

Stage B

Canary

About 10% of engineers or stations see v2. Watch feedback, rephrasing and escalations.

Stage C

Full rollout

Promote when online metrics hold. Track time to resolve the AOG against v1.

Stage D

Next airline

New IAG airline, new fleet mix and writing style. Build its own sets before reusing the Aer Lingus result.

Part 12

Improvement techniques and how each is proven

Each technique targets one layer, so you know in advance which metric should move and what could get worse.

The one-line version

Score every release node by node against a sealed, time-split set of real AOGs labelled by engineers. Ship only with a significant gain, zero safety-critical errors, and confirmation in shadow mode.