Educational material · course case studyAgent evaluationAE.07
Skip to the code walkthrough

Educational material · technical case study

Evaluating and Improving Agentic Applications

From agent execution
to verified behavior.

Follow the code from a customer request to a regression test. Five modules build one evaluation workflow—each adding a data structure, a check and a way to inspect what happened.

Kaan Kaya’s documented DeepLearning.AI coursework, taught by Laurie Voss and built with Arize AI. The scenario is Northwind Apparel, a fictional retailer whose agent looks up products, checks orders and handles refunds.

Course completed 14 November 2026

The system being evaluated

The answer is only
one output.

An agent connects a language model to tools. A request may produce both a response and a change in a database. Evaluation checks the facts, the actions and the resulting state. In these labs, Arize Phoenix is the workspace for inspecting linked traces, test datasets and evaluator scores.

INPUTCustomer request

For example: an eligible refund.

AGENT EXECUTIONLanguage model + tools
Catalog lookupOrder toolsRefund action

Produces a reply and may change state.

OBSERVABLE OUTPUTSResponse + state

What was said, which calls ran, and whether the refund exists.

Linked spans record inputs, outputs, tool arguments and order → test cases → layered scores → paired experiments.

Why separate those checks?

A grounded reply can leave a refund unfinished.

In one saved course practice case, v2 processes a $98 refund; v3 asks for confirmation. Both pass grounding and policy checks, while v3 fails selection and outcome. That is why the pipeline below tests more than the final sentence.

Saved practice case
v2

$98 processed

Outcome pass
v3

Confirmation requested

Outcome fail
Inspect the recording

Two kinds of evidence, clearly separated. The Python snippets and outputs come from a new, runnable deterministic companion with three synthetic DEMO-* cases and a $42 refund. That code is one executable pipeline. The GIFs illustrate the course’s conceptual progression across different saved labs and sessions, including the separate $98 example. They were not generated by the companion, which does not reproduce Phoenix or call a model.

Open the complete technical walkthrough

Every stage below is visible. Recordings start as still images; Play loads that GIF, Stop restores its poster, and only one recording plays at a time.

01

Foundations / instrument the execution

Turn a request into linked spans.

A trace is the execution record. Give the overall request a root span, attach each tool call as a child, and retain inputs, outputs and final state. You can then ask which operation produced—or failed to produce—the answer.

Inherits
A synthetic request and a version of its behavior
Adds
Parent/child IDs, ordered tool events and retained state
Produces
Run → response + claims + spans[] + state

In the course: Phoenix OpenTelemetry instrumentation connects the agent entry point and wrapped tools as parent/child spans.

Python excerpt / runnable companionm1_trace.py
def record(name: str, inputs: dict, outputs: dict, milliseconds: int) -> None:
    tools.append(Span(f"{root_id}/{len(tools) + 1}", root_id, name,
                      len(tools) + 1, inputs, outputs, milliseconds))

# ... replay the synthetic tool actions ...
root = Span(root_id, None, "support_request", 0,
            {"case_id": case.case_id, "request": case.prompt}, {"response": response},
            sum(span.synthetic_ms for span in tools))
return Run(case.case_id, version, response, claims, (root, *tools), state)

Selected lines from replay(): record() links tool spans to the root request, while Run preserves both the response and the resulting state. The full function is linked below. Timing fields in this fixture are invented, not performance measurements.

Companion output

Deterministic replay
case_id: DEMO-REFUND
version: baseline
spans[0].span_id: baseline/DEMO-REFUND
spans[1].parent_id: baseline/DEMO-REFUND
linked_spans: 4
refund_records: 1
recorded_refund: $42

The baseline replay records the expected refund. Its trace and persisted result travel together to the benchmark stage.

Saved course run / Phoenix tracing
Saved Phoenix walkthrough showing the first received span, a tool span and its parent agent request

What to watch

  1. Follow the connection check into the first received span: this confirms that tracing is connected.
  2. Open the tool span and compare its input with its returned product data.
  3. Follow the parent agent span to see how that tool evidence supports the final response.
Observed in the saved coursework

The saved tracing labs make the link between agent requests, model calls and tool results visible. A successful span tells you the operation ran; it does not establish that the answer or task was correct.

The GIF is a curated course screenshot sequence. It was not generated by this companion, and tracing ingestion is not a quality score.
Why module 02 comes next

A trace explains one execution. Module 2 gives selected requests stable identities and expected behavior, so the same failure can be tested again.

02

Benchmarking / turn observations into cases

Make a benchmark from requirements and traces.

A benchmark is a fixed set of cases with explicit expectations. Keep a stable case ID, the requirement it tests and a link back to the observed run. Curate coverage deliberately instead of treating every generated question as a useful test.

Inherits
Module 1 runs plus declared case requirements
Adds
Identity joins, provenance and coverage validation
Produces
case_id → BenchmarkEntry(case, observed_run)

In the course: The Phoenix client exports root agent-run rows; labels and curated questions retain trace provenance.

Python excerpt / runnable companionm2_benchmark.py
observations[run.case_id] = run
if set(expectations) != set(observations):
    raise ValueError("Observed runs must cover exactly the expected case identities")
covered = {case.requirement for case in expectations.values()}
missing = REQUIRED_COVERAGE - covered
if missing:
    raise ValueError(f"Missing requirement coverage: {sorted(missing)}")
return {key: BenchmarkEntry(case, observations[key])
        for key, case in expectations.items()}

Selected lines from build_benchmark(): join by case identity and check declared coverage. BenchmarkEntry retains the source Run and exposes its root span ID and response. The full function also rejects duplicate IDs and malformed root spans.

Companion output

Deterministic replay
case_id: DEMO-REFUND
requirement: complete_refund
source_span_id: baseline/DEMO-REFUND
joined_trace_cases: 3
unique_case_ids: 3
declared_requirements: 3/3

The benchmark retains the observed response and root span ID for each request. These three declared requirements are a teaching fixture, not comprehensive retail coverage.

Saved course run / Data Explorer / trace-derived tables
Saved course Data Explorer tables showing trace input, output and span identifiers followed by a curated four-case failure benchmark

What to watch

  1. Read input, output and context.span_id together: the label must refer to the exact response being reviewed.
  2. Watch the expanded question bank preserve the same trace-linked schema as coverage grows.
  3. Inspect the final four failures: category, severity, source and span ID explain why each case was retained.
Observed in the saved coursework

The saved practice capture records 43 traces and a curated four-case failure set. Its labeling notes explicitly preserve disagreements and correct response-to-trace provenance.

This recording shows Data Explorer tables derived from Phoenix traces, not the Phoenix UI. A curated failures-only set cannot estimate production error rates; the separate graded notebook retains a RateLimitError.
Why module 03 comes next

Now the cases and expected behavior are explicit. Module 3 defines checks—and validates the judge—before those scores are used to guide changes.

03

Evaluator reliability / check the measurement

Score evidence, then validate the judge.

Use code when the rule is precise. Use a model-based rubric when judgment is needed, then compare the judge’s decisions with reviewed reference labels. Keep case identity and held-out labels fixed so a changed reference cannot masquerade as improvement.

Inherits
Trace-linked cases, evidence and reviewed labels
Adds
A fact check and reference-aligned judge validation
Produces
Grounding decision + judge agreement report

In the course: Phoenix classification evaluators apply model rubrics; scikit-learn reports agreement across a 33/11/12 development/validation/test split.

Python excerpt / runnable companionm3_evaluators.py
def grounding_score(run: Run) -> bool:
    """Exact structured-fact check; not a general natural-language judge."""
    evidence = {}
    for span in run.tools:
        evidence.update(span.outputs)
    return all(key in evidence and evidence[key] == value
               for key, value in run.claims.items())

The companion compares structured claims with tool outputs. Its separate judge_agreement() function aligns four fixed synthetic label fixtures and computes accuracy and Cohen’s kappa; it does not call an LLM or validate natural-language answers.

Companion output

Deterministic replay
grounded: True
fixed_label_matches: 3/4
judge_accuracy: 0.750
judge_kappa: 0.500

The 3/4 agreement is a separate four-label fixture for teaching validation. It is not the outcome score for the three replayed requests, and it is not the course’s held-out result.

Saved course run / Phoenix annotations
Saved Phoenix annotations showing correctness, policy and product checks beside a jeans-related response

What to watch

  1. Read the jeans refusal: the response says the catalog does not offer jeans, rather than recommending an unavailable product.
  2. Compare the passing correctness/policy labels with the failing literal product check, which flags the word ‘jeans’ even in a refusal.
  3. Open the annotation explanations: this heuristic false positive is a reason to validate the evaluator, not to trust its label automatically.
Observed in the saved coursework

The product-word heuristic flags a refusal as a failure. Separately, the held-out policy-judge report matches 9 of 12 reference labels for both versions: 0.750 accuracy and kappa 0.400. A perfect intermediate score follows reference-label changes.

This GIF shows annotation inspection, not that validation report. The report comes from the saved notebook; small samples and mutable reference labels limit its interpretation.
Why module 04 comes next

A supported response can still omit a required action. Module 4 reuses the evidence check and adds explicit tool, ordering and state checks.

04

Actions and outcomes / test the work behind the reply

Verify the trajectory and the resulting state.

A trajectory is the ordered sequence of tool actions. Check the selected tools, arguments, identity verification before a refund and the refund record itself. Here the candidate version replays the same request but asks for confirmation instead of recording the refund.

Inherits
Benchmark expectations + a candidate replay of the same case
Adds
Selection, arguments, ordering, policy and state checks
Produces
case_id → a six-dimension score vector

In the course: Ordered tool spans become trajectories; expected-tool/argument rules and refunds-table checks produce layered annotations.

Python excerpt / runnable companionm4_outcomes.py
identities = [span.sequence for span in tools
              if span.name == "verify_identity" and span.outputs.get("verified") is True]
refunds = [span for span in tools if span.name == "issue_refund"]
ordering = all(any(verified < refund.sequence for verified in identities) for refund in refunds)
matching_refunds = [row for row in run.state["refunds"]
                    if row["order_id"] == case.order_id and row["refunded_amount"] == case.amount]
if case.requirement == "complete_refund":
    outcome = bool(matching_refunds)

Selected lines from evaluate_run(): ordering inspects earlier identity events; outcome inspects the retained refund state. Empty refund calls can pass the ordering rule, so a separate selection/outcome failure is essential.

Companion output

Deterministic replay
DEMO-REFUND / candidate
grounding:  True    selection:  False
invocation: True    ordering:   True
outcome:    False   policy:     True

The candidate asks for confirmation without recording a refund. Its structured claims remain supported, but the required action and outcome fail. This is the new companion’s $42 example.

Saved course run / Phoenix layered evaluation
Saved Phoenix view showing aggregate layer scores, an individual refund trace and its separate annotations

What to watch

  1. The selected run has selection/policy failures beside passing invocation, identity and outcome checks. Read the layers together.
  2. Follow the actual tool trajectory: no refund was called. Here, an outcome pass means no prohibited refund occurred—not that a refund was completed.
  3. The identity-order rule also passes when there is no refund call. Its pass is not proof that identity was verified; inspect the explanation and action trace.
Observed in the saved coursework

In the saved 12-case graded test, selection fails 6 and invocation fails 8; outcome and policy each fail 1. The layers expose different failure modes on the same cases.

The failures overlap and cannot be added as distinct failed requests. The course’s expected-tool rules are specific to its tests; these counts are not production reliability estimates.
Why module 05 comes next

A version now produces one score vector per case. Module 5 pairs those identities across versions and checks whether an attempted improvement breaks working behavior.

05

Experiments / compare and preserve working behavior

Pair the same cases and expose regressions.

Run the same benchmark against each version. Match results by case identity rather than row position, inspect new failures, and keep known-good cases beside targeted failures. A promising score is only useful if the comparison is valid.

Inherits
Baseline and candidate score maps from Module 4
Adds
Identity-safe pairing and an explicit regression list
Produces
Paired scores + layer regressions + gate result

In the course: Phoenix asynchronous experiments compare versioned prompts; saved production-style failures are added as future regression cases.

Python excerpt / runnable companionm5_compare.py
paired = {case_id: {"baseline": baseline[case_id], "candidate": candidate[case_id]}
          for case_id in baseline}
layer_regressions = {
    case_id: [layer for layer in PROTECTED_LAYERS
              if pair["baseline"][layer] and not pair["candidate"][layer]]
    for case_id, pair in paired.items()
}
layer_regressions = {key: layers for key, layers in layer_regressions.items() if layers}
return {"cases": len(paired), "paired": paired, "regressions": list(layer_regressions),
        "layer_regressions": layer_regressions, "gate_passed": not layer_regressions,
        "baseline_outcomes": sum(row["outcome"] for row in baseline.values()),
        "candidate_outcomes": sum(row["outcome"] for row in candidate.values())}

Selected lines from compare_versions(): pair stable keys and flag any true→false change across all six protected layers. The full function rejects missing cases or malformed score vectors. Deliberately reversed candidate output order does not change the result.

Companion output

Deterministic replay
paired_cases: 3
baseline_outcomes: 3/3
candidate_outcomes: 2/3
regressions: ["DEMO-REFUND"]
failed_layers: ["selection", "outcome"]
gate_passed: False

The fixture identifies the omitted refund even though grounding passes; a policy-only regression would also fail the gate. A passing gate means no prior pass became a failure—not that all cases passed or that a release is approved.

Saved course run / Phoenix experiment comparison
Saved Phoenix practice comparison showing v2 processing a 98 dollar refund while v3 asks for confirmation

What to watch

  1. Start with the known-good experiment overview: look for the dimension that regresses.
  2. Compare the same eligible-refund case in v2 and v3: v2 processes $98; v3 requests confirmation.
  3. Read the scores beside the behavior: grounding and policy pass in both, while selection and outcome fail in v3.
Observed in the saved coursework

This saved practice snapshot contains 3 failure cases and 3 known-good cases. Separately, identity-aligning the graded notebook’s five-case failure set gives the same grounding score for v1/v2/v3: 3/5, with the same failures.

The practice and graded experiments are different datasets and rubrics. These captures do not establish a general prompt improvement or a causal latency/cost saving.
Feed back into the next test

Preserve the failing identity, expected action and observed trace as a regression case. The next version starts with evidence and a test, rather than another unsupported prompt change.

All recordings show still images.

Run the cumulative example

Read it here.
Inspect it in the repository.

The companion runs locally with Python’s standard library. It replays fixtures so every stage can be inspected without credentials, model costs or a hosted course runtime.

python -m technical_companion

Educational material / course attribution

Course foundation.
Inspectable technical work.

This showcase documents coursework from Evaluating and Improving Agentic Applications by DeepLearning.AI, taught by Laurie Voss and built with Arize AI. Northwind, the supplied agent and the course exercises belong to their creators.

The public repository contains original analysis, reviewed recording excerpts and independently authored companion code prepared with AI assistance. Original course notebooks and solutions remain local. Their cell order is preserved; missing helpers and data snapshots prevent a complete local reproduction of the hosted labs.