AI EngineeringZero to ProductionHome·About·Contact
Interoperability & Agent Ops · Chapter I4

Evaluation and Tracing with LangSmith

O4 taught you to trace and evaluate agents by hand. LangSmith is the productized version: a platform that captures every step of every run, turns them into datasets, runs evals against them, and lets you compare versions — closing the LLMOps loop with a real tool instead of print statements.

⏱️ ~50 min📊 Hands-on🎯 Intermediate
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • an Anthropic API key (ANTHROPIC_API_KEY) + pip install anthropic
Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.

Learning objectives

  • Explain what LangSmith adds over hand-rolled tracing (O4) and evals (Ch 5).
  • Instrument an app to send traces, framework or not.
  • Turn real traces into datasets and run offline evaluations.
  • Use LLM-as-judge and custom evaluators inside the platform.
  • Compare prompt/model versions and wire an eval gate into CI (O3).
This is O4 + Ch 5, productizedYou already built the concepts: traces as the request's step-tree (O4), golden sets and LLM-judge evals (Ch 5), the eval gate before rollout (O3). LangSmith is one popular tool that packages all of it. Framework-agnostic (works with the raw SDK, LangChain, or anything), and vendor tools change — learn what job each feature does, not exact UI.

Why a platform beats print statements advanced

Your O4 span() helper works — until you have thousands of runs, want to search them, compare two prompt versions across a hundred cases, or let a non-engineer inspect a failure. Hand-rolled logging doesn't scale to those. A tracing/eval platform does: it stores runs durably, makes them searchable and visual, and connects tracing to evaluation so the two reinforce each other.

Job (from O4/Ch 5)By handWith LangSmith
See one run's stepsRead log lines, reconstruct the treeVisual trace tree, timed & costed per step
Search runsgrep, if you logged the right fieldsFilter/search over all runs
Build a golden setCopy inputs into a file manuallyPromote real traces into a dataset in a click
Run evalsWrite a harness (Ch 5)Run evaluators over a dataset; store scores
Compare versionsEyeball two outputsSide-by-side diff across the whole dataset

Lab I4.1 · Tracing an app advanced

The foundation is capturing runs. LangSmith auto-instruments LangChain/LangGraph, and offers a decorator/wrapper for arbitrary code — so even your raw-SDK apps (C2) get traced.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab I4.1
shellpip install langsmith
export LANGSMITH_API_KEY=...          # plus LANGSMITH_TRACING=true
trace.pyfrom langsmith import traceable
from anthropic import Anthropic
client = Anthropic()

@traceable                              # captures inputs, outputs, timing, errors
def answer(question: str) -> str:
    r = client.messages.create(
        model="claude-opus-4-8", max_tokens=512,
        messages=[{"role":"user","content": question}],
    )
    return next(b.text for b in r.content if b.type == "text")

answer("What is prompt caching?")   # run appears in LangSmith, fully traced
▶ How this works

This is the smallest possible traced app. "Tracing" means recording what your code did on each run — the inputs it got, the output it produced, how long it took, and any error — so you can inspect it later instead of guessing. Here a single decorator turns an ordinary function that calls the model into one whose every run is captured by LangSmith.

  1. The two import lines bring in traceable (LangSmith's recorder) and Anthropic (the client that talks to the model). client = Anthropic() builds the client once so the function can reuse it.
  2. @traceable is a decorator — a line placed directly above a function that wraps it with extra behaviour. This one records a span (one timed step) every time answer() runs: what question came in, what text went out, how many milliseconds it took, and whether it crashed. You did not have to write any logging.
  3. Inside, client.messages.create(...) is the actual model call — it names the model, caps the reply length with max_tokens, and passes the user's question as one message.
  4. The return next(b.text ... ) line pulls the first text block out of the reply. (The model can return several blocks; this grabs the text one.) That returned string is what LangSmith records as this span's output.
  5. The last line, answer("What is prompt caching?"), simply calls the function once. Because it is decorated, that single call now shows up as a full trace in the LangSmith UI.

What the output means: Nothing dramatic prints locally — the payoff is remote: a new run appears in LangSmith showing the input question, the model's answer, the timing, and the token cost, all captured automatically.

Try this: Add a second @traceable function (say a fake retrieve() step) and call it from inside answer(). LangSmith will nest the two into a tree — parent answer with retrieve as a child — which is the whole trace-tree idea from O4, built for you.

@traceable nests — that's the whole trace treeDecorate your model call, your retriever, and each tool, and LangSmith assembles the nested tree from O4 automatically: which step ran, how long, what it cost, what errored. For a LangChain/LangGraph app you often get this with zero code changes — just the env vars. It's your hand-rolled span(), but it builds the tree for you and stores it durably.
Tracing sends your prompts & outputs to a service — that's a data decisionTraces contain full inputs and outputs, which may include PII, secrets, or confidential content (O4, T1). Sending them to a hosted platform is exactly the governance decision from O4: redact sensitive fields, control who can view traces, check data-handling/residency requirements, and consider a self-hosted/enterprise deployment where regulation demands it. Observability that leaks data is a governance failure.

Lab I4.2 · From traces to datasets advanced

The platform's superpower is the loop between the two halves: real traces become your evaluation dataset. When a run is interesting — a great answer, or a production failure — you promote it into a dataset. Over time your golden set (Ch 5) is built from real usage, not guesses.

prod tracesreal runs datasetgolden set evaluationscored failures found → promoted to the dataset → can't regress again The flywheel that makes a system reliable. Real runs feed a dataset; the dataset drives evals; eval failures (and the O4 incident runbook) feed more cases back into the dataset. This is exactly the "every incident becomes a test case" habit from O4 — LangSmith is the machinery that makes promoting a trace to a golden case a one-click action.
🗺️ How to read this diagram

This diagram shows the flywheel at the heart of LangSmith: real runs feed a dataset, the dataset feeds evaluations, and the failures those evaluations find get promoted back into the dataset — so the system keeps getting harder to break.

  • Read it left to right. The first green box, prod traces, is the real runs your live app produced (captured by the tracing from Lab I4.1).
  • The arrow into dataset (the "golden set") means you promote interesting runs — great answers, or bad ones — into a saved collection of test cases. This is your evaluation data, built from real usage instead of guesswork.
  • The next arrow into evaluation ("scored") means the evaluators from Lab I4.3 run over that dataset and produce scores.
  • The curved dashed arrow looping back from evaluation to the first box is the key part: when an eval surfaces a failure, you add that case to the dataset. The red caption spells it out — failures found → promoted to the dataset → can't regress again.

In short: It is a loop, not a line: trace → collect → evaluate → feed failures back. Every bug you find becomes a permanent test, so the same mistake can never sneak back in.

Lab I4.3 · Running evaluators expert

With a dataset, you run evaluators over it — the same eval types from Chapter 5, executed and scored by the platform.

Lab I4.3

Requires: pip install langsmith

evaluate.pyfrom langsmith import evaluate

def exact_match(run, example):        # deterministic evaluator
    return {"key":"correct",
            "score": run.outputs["output"] == example.outputs["expected"]}

def grounded_judge(run, example):     # LLM-as-judge evaluator (Ch 5)
    verdict = judge_model_says_grounded(run.outputs, example.inputs)
    return {"key":"grounded", "score": verdict}

evaluate(
    my_app,                            # the function/chain under test
    data="my-golden-set",               # the dataset
    evaluators=[exact_match, grounded_judge],
)   # scores stored & visualized per example
▶ How this works

Once you have a dataset (a saved list of example inputs plus their expected answers), an evaluator is a small function that scores how well your app did on each example. This block defines two evaluators and then runs them across the whole dataset with one call to evaluate().

  1. exact_match(run, example) is a deterministic evaluator — no AI, just a fixed rule. Every evaluator receives the run (what your app actually produced) and the example (the saved input + expected answer). It compares run.outputs["output"] to example.outputs["expected"] and returns a score of True/False.
  2. The returned dict — {"key": "correct", "score": ...} — is the required shape: key names the metric so LangSmith can chart it, and score is the value.
  3. grounded_judge(run, example) is an LLM-as-judge evaluator: instead of an exact rule it asks another model whether the answer was grounded in the input. That is the Chapter-5 idea — use a model to grade subjective quality — plugged into the same score-dict interface.
  4. evaluate(my_app, data="my-golden-set", evaluators=[...]) ties it together: run my_app on every example in the dataset named my-golden-set, then apply both evaluators to each result. LangSmith stores and visualises the scores.

What the output means: For each example you get two scores — a true/false correct and a grounded verdict — aggregated in the LangSmith UI so you can see, at a glance, what fraction of your golden set the current version passes.

Try this: Add a third evaluator that checks a latency budget — return {"key":"fast","score": run.latency < 2.0} — and pass it in the evaluators list. Any Python rule you can write becomes a tracked metric.

Evaluator typeWhat it checksFrom…
DeterministicExact match, regex, JSON-schema validity, contains-XCh 5 deterministic evals
LLM-as-judgeGroundedness, helpfulness, tone, rubric adherenceCh 5 LLM-judge
CustomAny Python: latency budget, cost cap, tool-call correctnessYour logic
HumanManual annotation queues for the subjective callsHuman review (O4)
The evaluator concepts are all from Chapter 5Deterministic checks, LLM-as-judge, and the caveat that a judge is itself fallible — none of that changed. LangSmith gives you a place to run them at scale, store the scores, and track them over time. The judgment about what to measure (and that a judge can be wrong) is still yours, from Chapter 5.

Comparing versions & the eval gate expert

The payoff of storing evals: compare two versions of your app across the whole dataset. Change a prompt or swap a model, re-run the eval, and see per-example whether you improved or regressed — the safe-rollout discipline from O3, with data instead of vibes.

version A version B compareper example gate ship B only if it doesn't regress the dataset (O3) Data-driven rollout. Run both versions over the same dataset, compare scores example-by-example, and gate the deploy on the result — this is exactly O3's eval gate + canary discipline, backed by stored evaluations. In CI, a regression fails the build; in prod, a canary confirms it live.
🗺️ How to read this diagram

This diagram shows how you decide, with data, whether a change is safe to ship. You run two versions of your app over the same dataset and let the scores — not a gut feeling — decide.

  • On the left are two boxes, version A (today's app) and version B (your change — a new prompt or a swapped model). Both are run against the same golden set.
  • The arrow leads to compare ("per example"): LangSmith lines the two runs up example by example, not just as one average, so you can see exactly which cases got better and which got worse.
  • The final arrow reaches gate — the go/no-go decision. The caption states the rule: ship B only if it doesn't regress the dataset.
  • Why per-example matters: a version can raise the average score while quietly breaking a few important cases. The side-by-side comparison catches that; a single average would hide it.

In short: This is the O3 "eval gate": compare A vs B on the same data, then let the score gate the deploy. In CI, a regression fails the build automatically — no prompt change ships on vibes.

Wire it into CI (O3)The evaluate() call can run in your pipeline: on every prompt/model change, run the golden-set eval and fail the build if scores drop below threshold. That's the O3 eval gate made concrete — no prompt change ships without clearing the dataset. LangSmith stores the history so you can see quality trend over releases (the O4 continuous-eval idea).

Online monitoring expert

Beyond offline evals, LangSmith runs on live traffic too: dashboards over production traces (the four signals from O4 — quality via sampled judges, cost, latency, errors), and alerts. This is O4's monitoring pillar with a UI, closing the loop from "trace one run" to "watch the whole system."

Offline + online is the full pictureOffline evals (against a dataset) catch regressions before deploy; online monitoring (over live traces) catches drift and new failure modes after (O4). A platform like LangSmith does both from the same trace data — which is why tracing is the foundation everything else stacks on. No traces, no evals, no monitoring.

Not the only tool expert

LangSmith is popular and tightly integrated with LangChain/LangGraph, but the capabilities — tracing, datasets, evaluators, version comparison, monitoring — are the transferable knowledge. Other observability/eval platforms and open-source options (including OpenTelemetry-based tracing) offer the same jobs. Pick by your stack, data-residency needs (self-host vs hosted), and budget, exactly as you chose stack layers in O2.

The jobs transfer; the vendor is a choiceWhatever tool you land on, you'll want: capture traces, build datasets from real runs, run deterministic + judge + custom evaluators, compare versions, gate CI, and monitor prod. Evaluate a tracing/eval platform by how well it does those O4/Ch 5 jobs — not by brand.

Common pitfalls expert

PitfallFix
Tracing with no data-handling planRedact PII/secrets; control access; check residency (O4, T1)
Building a golden set from imaginationPromote real traces (incl. failures) into the dataset
Trusting the LLM judge blindlyJudges are fallible (Ch 5); spot-check & calibrate
Evals only offlineAlso monitor live traffic for drift (O4)
Prompt changes with no version comparisonCompare across the dataset before shipping (O3)
Only tracing the top-level callDecorate steps/tools so you get the full tree

Exercises expert

Exercise I4.1 — Trace an agent

Context: The payoff of a tracing platform is seeing exactly where an agent run failed and what it cost — the O4 exercise, now inside a real tool.

Your task: Instrument an agent from an earlier lesson with @traceable (or LangChain auto-tracing) on the model call and each tool, then run a query that triggers a tool error.

Requirements:

  • Trace the model call and every tool as separate spans
  • Run a query that deliberately makes a tool fail
  • Confirm the trace tree pinpoints which span failed
  • Confirm the trace shows the cost/latency of the run

💡 Hint: Auto-tracing wraps each step for you; the goal is to read the resulting tree and find the failing span, not to hand-log.

Exercise I4.2 — Build a dataset from failures

Context: The evaluation flywheel starts the moment you promote a real failure into a golden case — after which it can never regress silently.

Your task: Run your app on 10 inputs, find the 2–3 that produce bad answers, and promote them into a dataset as golden cases with the expected output.

Requirements:

  • Exercise the app on ~10 real inputs
  • Identify 2–3 genuinely bad outputs
  • Add each as a dataset example with the correct expected output
  • Note that these cases now guard against silent regression

💡 Hint: Bad outputs are the highest-value eval cases — curate the failures rather than inventing synthetic inputs.

Exercise I4.3 — Version comparison

Context: This is Chapter 5's manual A/B test, now scaled and stored: two prompt versions, scored across a dataset, with the winner kept for a reason.

Your task: Write two prompt versions for a task, run evaluate() for each over your dataset with a deterministic and a judge evaluator, compare per-example, and keep the winner.

Requirements:

  • Two prompt versions, one dataset
  • Score each with both a deterministic evaluator and an LLM-judge
  • Compare per-example, not just on the aggregate
  • Keep the winner and write down why
  • Watch for a version that improves the mean but regresses specific cases

💡 Hint: The per-example diff is what catches a regression an average would hide — that's why you gate on the dataset, not a single example.

Show what to look for

Watch for a version that improves the average but regresses specific cases — the per-example diff catches what an aggregate score hides. That's exactly why you compare across the dataset, not just on one example, before gating a deploy.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · What a trace recordsBeginner

Context: Observability platforms like LangSmith start from one primitive: a trace that records what each run did. You'll model that primitive rather than read log lines.

Your task: Model one traced call offline as a decorator that captures inputs, output, latency, and any error — the job LangSmith's @traceable does.

Requirements:

  • Implement it as a decorator using only the standard library
  • Capture the function name, inputs, latency in ms, and error (None on success)
  • Record the run even when the wrapped function raises, then re-raise
  • Store captured runs somewhere inspectable and print one

💡 Hint: A try/finally around the call lets you time and record the run on both the success and the exception path; functools.wraps keeps the name.

Show solution

Tracing = recording what each run did. Model it in stdlib (the real thing is @traceable):

import time, functools

RUNS = []
def traced(fn):
    @functools.wraps(fn)
    def wrap(*a, **k):
        t0 = time.perf_counter()
        err = None
        try:
            out = fn(*a, **k)
            return out
        except Exception as e:
            err = repr(e); raise
        finally:
            RUNS.append({"name": fn.__name__, "inputs": {"args": a},
                         "latency_ms": round((time.perf_counter()-t0)*1000, 2),
                         "error": err})
    return wrap

@traced
def answer(q):
    return f"echo: {q}"

answer("what is prompt caching?")
print(RUNS[-1])   # captured inputs, latency, error=None

That is exactly the job LangSmith's decorator does — you just don't hand-roll or read log lines.

Exercise 2 · A trace is a tree of spansIntermediate

Context: A real trace is not a flat log — it's a tree of timed spans (retrieve → generate → parse). That structure is what lets a platform beat grep.

Your task: Model a span tree: a parent run with nested, individually-timed child spans, and print the tree with indentation.

Requirements:

  • A span has a name, a duration, and a list of child spans
  • Support arbitrary nesting (a child can have its own children)
  • Print the tree indented by depth, showing each span's time
  • Build a small example (e.g. answer → retrieve, generate → format)

💡 Hint: A tiny recursive node class plus a recursive print indented by depth is all you need; the visual, timed tree is the whole payoff.

Show solution

Traces are step-trees, not flat logs. Runnable:

class Span:
    def __init__(self, name):
        self.name, self.children, self.ms = name, [], 0
    def child(self, name, ms):
        s = Span(name); s.ms = ms; self.children.append(s); return s

def show(span, depth=0):
    print("  "*depth + f"- {span.name} ({span.ms} ms)")
    for c in span.children:
        show(c, depth+1)

root = Span("answer")
root.child("retrieve", 40)
gen = root.child("generate", 210)
gen.child("format", 5)
root.ms = 255
show(root)

The visual, timed tree is what makes a platform beat grep: you see where the time and the failure actually went.

Exercise 3 · Promote real traces into a datasetAdvanced

Context: LangSmith's flywheel turns real traffic into evaluation data: promote good runs into a golden dataset instead of inventing inputs by hand.

Your task: Given a list of captured runs, build a dataset of {input, reference_output} examples, dropping any run that errored or produced no output.

Requirements:

  • Input is a list of run dicts carrying input, output, and error
  • Keep only runs with no error and a non-empty output
  • Emit each survivor as an {input, reference} example
  • Report how many examples resulted (and that errored runs were dropped)

💡 Hint: A single list comprehension with the error/output filter is enough — the curation rule is the lesson, not the plumbing.

Show solution

A dataset is just curated examples pulled from real traffic. Runnable:

runs = [
    {"input": "reset password?", "output": "Go to Settings > Security", "error": None},
    {"input": "refund policy?",  "output": "30 days",                   "error": None},
    {"input": "crash case",      "output": None, "error": "Timeout"},
]
def to_dataset(runs):
    return [{"input": r["input"], "reference": r["output"]}
            for r in runs if r["error"] is None and r["output"]]

ds = to_dataset(runs)
for ex in ds:
    print(ex)
print(len(ds), "examples (errored run dropped)")

Real traffic is the best source of eval cases — you promote the good ones instead of inventing inputs by hand.

Exercise 4 · Run evaluators over a datasetExpert

Context: An evaluator scores an app's output against a dataset's reference. Real suites mix deterministic checks with an LLM-as-judge; both are just scoring functions.

Your task: Model two evaluators — exact-match and a keyword "LLM-judge" stand-in — and a harness that reports per-example and mean scores over a dataset.

Requirements:

  • Exact-match returns 1.0/0.0 on a normalized string comparison
  • The judge stand-in scores by whether the reference is contained in the output
  • The harness runs a given evaluator over the dataset and returns per-example scores plus the mean
  • Run both evaluators over the same dataset and show they can disagree

💡 Hint: Keep the evaluator as a parameter to the harness so swapping exact-match for the judge is a one-line change — exactly how LangSmith runs custom evaluators.

Show solution

Evaluation = score outputs against references. Model the harness offline:

dataset = [
    {"input": "capital of France?", "reference": "Paris"},
    {"input": "2+2?",               "reference": "4"},
]
def my_app(q):
    return {"capital of France?": "Paris", "2+2?": "four"}[q]

def exact_match(pred, ref):
    return 1.0 if pred.strip().lower() == ref.strip().lower() else 0.0

def judge_contains(pred, ref):          # stand-in for LLM-as-judge
    return 1.0 if ref.lower() in pred.lower() else 0.0

def run_eval(dataset, evaluator):
    scores = [evaluator(my_app(ex["input"]), ex["reference"]) for ex in dataset]
    return scores, sum(scores)/len(scores)

s, mean = run_eval(dataset, exact_match)
print("exact-match:", s, "mean", mean)   # [1.0, 0.0] 0.5
s, mean = run_eval(dataset, judge_contains)
print("judge:", s, "mean", mean)

LangSmith runs your evaluators (exact, custom, or LLM-as-judge) over the dataset and stores the scores; the logic is what you're modeling here.

Exercise 5 · Compare two versions on the same datasetProfessional

Context: The reason to store datasets and evaluators is version comparison: proving a prompt or model change actually helped, across the whole set, not on one lucky example.

Your task: Run two app versions over one dataset and report the per-example diff and the mean score delta.

Requirements:

  • Score v1 and v2 with the same evaluator over the same dataset
  • Show a per-example comparison flagging improvements and regressions
  • Compute the mean delta between the two versions
  • Make it obvious when the average improves but a specific case regressed

💡 Hint: Zip the two score lists with the dataset and label each row improved/regressed; the per-example view catches what the aggregate hides.

Show solution

Side-by-side scoring across the whole set beats eyeballing two outputs. Runnable:

dataset = [{"q": "a", "ref": "Paris"}, {"q": "b", "ref": "4"}, {"q": "c", "ref": "blue"}]
v1 = {"a": "Paris", "b": "four", "c": "blue"}
v2 = {"a": "Paris", "b": "4",    "c": "blue"}

def score(app):
    return [1.0 if app[ex["q"]].lower() == ex["ref"].lower() else 0.0
            for ex in dataset]

s1, s2 = score(v1), score(v2)
for ex, a, b in zip(dataset, s1, s2):
    flag = "" if a == b else ("  <- improved" if b > a else "  <- regressed")
    print(f"{ex['q']}: v1={a} v2={b}{flag}")
print("mean delta:", round(sum(s2)/len(s2) - sum(s1)/len(s1), 3))

This is how you prove a prompt/model change helped rather than hoping — the platform just does it over hundreds of cases.

Exercise 6 · An eval gate in CIIndustry scenario

Context: The LLMOps loop closes in CI: an eval gate blocks a deploy when quality drops. This is the O3 release-control pattern, wired to an exit code.

Your task: Model an eval gate that blocks the deploy if the mean score is below a threshold OR any critical example regressed versus the last release, returning a CI-style exit code.

Requirements:

  • Fail if mean score < a threshold
  • Fail if any named critical example scores worse than its previous score
  • Return whether it passed plus the human-readable reasons
  • Map the decision to an exit code (0 deploy / 1 block), as CI would
  • Show both a passing run and a blocked run (e.g. a safety regression)

💡 Hint: Accumulate reasons into a list; empty reasons means pass. The critical-example check needs both the current and the previous per-example scores.

Show solution

The gate turns eval into a release control (O3). Runnable, returns a CI-style exit code:

def eval_gate(mean_score, per_example_now, per_example_prev,
              threshold=0.9, critical=("safety-01",)):
    reasons = []
    if mean_score < threshold:
        reasons.append(f"mean {mean_score:.2f} < {threshold}")
    for k in critical:
        if per_example_now.get(k, 0) < per_example_prev.get(k, 0):
            reasons.append(f"critical '{k}' regressed")
    passed = not reasons
    return passed, reasons

now  = {"safety-01": 1.0, "faq-02": 1.0}
prev = {"safety-01": 1.0, "faq-02": 1.0}
ok, why = eval_gate(0.95, now, prev)
print("deploy" if ok else "BLOCK", why)          # deploy []

now2 = {"safety-01": 0.0, "faq-02": 1.0}          # a safety regression
ok, why = eval_gate(0.95, now2, prev)
exit_code = 0 if ok else 1
print("deploy" if ok else "BLOCK", why, "exit", exit_code)   # BLOCK ... exit 1

Closing the LLMOps loop: traces become datasets, datasets feed evals, and the eval gate stops a regression before it ships. LangSmith hosts each step; the real client wraps your app with @traceable and its evaluate() API (needs the SDK).

✓ Checkpoint — you can move on when you can…

  • Say what LangSmith adds over hand-rolled O4 tracing and Ch 5 evals.
  • Instrument an app (framework or raw SDK) to capture traces.
  • Promote real traces into a dataset and explain the flywheel.
  • Run deterministic, judge, and custom evaluators over a dataset.
  • Compare versions and wire an eval gate into CI (O3).
🏗️ Toward the capstoneThe AI DevOps Engineer's eval suite (Chapter 8d) and its production monitoring (O4) are exactly what a platform like LangSmith operationalizes: every incident it handles becomes a traced run you can promote into the golden set, its accuracy and hard-fail safety evals run in CI as the gate, and its live dashboard watches the four signals. Tracing is how you keep a safety-critical agent honest over time. See the evals build →

Knowledge check check yourself

✓ Knowledge check

What does LangSmith add over the hand-rolled span() tracing from O4, and why is tracing the foundation for everything else?

Show answer
It stores runs durably, makes them searchable and visual (a timed, costed trace tree), and connects tracing to evaluation — scaling past the thousands of runs, cross-version comparisons, and non-engineer inspection that hand-rolled logging can't handle. Tracing is foundational because datasets are promoted from real traces and both offline evals and online monitoring run off that same trace data — no traces, no evals, no monitoring.
✓ Knowledge check

Describe the trace→dataset→eval flywheel and why comparing versions per-example (not just on average) matters before gating a deploy.

Show answer
Real production traces are promoted into a golden-set dataset; evaluators score the dataset; failures found feed back as new cases so the same bug can't regress again. Per-example comparison matters because a new version can raise the average score while quietly breaking a few important cases — a side-by-side per-example diff catches that regression, whereas a single aggregate would hide it, which is why it gates the deploy (O3 eval gate).
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in