Skip to main content
Let your agent set this up. Copy the evaluations prompt into your coding agent to get started automatically.
The LangWatch SDK runs experiments from your Python or TypeScript code and tracks each run in LangWatch. Not using Python or TypeScript? The SDKs call the same REST endpoints you can call yourself: create an experiment, run it, then poll the run and read its results.

Quickstart

1. Install the SDK

2. Set your API Key

Be sure to log in or create an account using the displayed link, then provide your API key when prompted.

3. Start tracking

Your evaluation metrics are now tracked and visualized in LangWatch.
Evaluation Results Sample

Core Concepts

Evaluation Initialization

The evaluation is started by creating an evaluation session with a descriptive name:

Iterating over data

Use evaluation.loop() around your iterator so the entries are tracked:

Metrics logging

Track any metric you want with evaluation.log():

Comparing Multiple Targets

When comparing different models, prompts, or configurations, use targets to organize your results. Both SDKs provide a target(), withTarget() context that automatically captures latency and enables context inference. Once a row’s targets have each produced an output, Comparison judges them against each other and names a winner.
Use evaluation.target() for automatic latency capture and context inference:
evaluation.target() automatically captures latency, creates isolated traces per target, and enables context inference so log() calls don’t need explicit target parameters. Use log_response() to store the model’s output.
Alternatively, use the target parameter directly with evaluation.log():

Target Registration

The first time you use a target name, it’s automatically registered with the provided metadata:
If you provide different metadata for the same target name, an error will be raised. Use a different target name if you want different configurations.

Metadata for Comparison

Target metadata is used for comparison charts in the LangWatch UI. You can group results by any metadata field:
In the LangWatch UI, you can then visualize how quality varies across temperature values.

Parallel Execution

LLM calls can be slow. Both SDKs support parallel execution to speed up your evaluations.
Use the built-in parallelization by putting the content of the loop in a function and submitting it:
By default, threads=4. Adjust based on your API rate limits and system resources.

Async-native mode

The default loop(), submit() path above already parallelises, each submitted task runs in a worker thread, so sync and async tasks both speed up with no extra work on your side. That’s the right choice for most users.Reach for aloop(), asubmit() only when your code is fully async-first and your task relies on async state whose identity is tied to one event loop. The threading path spins up a fresh event loop per worker, so those objects raise "Future attached to a different loop" on first use. aloop, asubmit keep every submitted task on the caller’s event loop, so that state stays valid across concurrent items.
Sync callables passed to asubmit are automatically offloaded to a worker thread so they don’t block the event loop for concurrent async siblings.

Built-in Evaluators

LangWatch provides a library of evaluation metrics out of the box.
Use evaluation.evaluate() with pre-built evaluators:
Browse our complete list of available evaluators including metrics for RAG quality, hallucination detection, safety, and more.

Comparison

Scoring each target on its own leaves you calibrating numbers: is 0.78 better than 0.74 for your users, or is that noise? Comparison asks the easier question instead. It shows a judge every target output for the same row and asks which one is best. compare() is the code-first way in. It uses the same judge as the Comparison evaluator in the workbench, so a comparison you run from a notebook and one you set up in the UI read the same way on the results page.

Comparing a row

Every target that produced a non-empty output for the row is a candidate, and one comparison weighs all of them together rather than a round of one-against-one matchups. Behind it the judge is called twice per row by default, the second time with the candidate order reversed, which is what swap_and_reconcile does; turn that off and one comparison is one judge call. There is no candidate list to assemble, and no judge prompt you have to choose, since the shipped one runs unless you write your own:
The candidates are whatever log_response() recorded inside each target() block.
The winner comes back named with the target name you registered. compare() judges however many candidates the row has, and a target that produced nothing for the row, or only an empty output, is not a candidate.
Compare a row once its targets have finished. In Python that is after the row’s last target() block has closed, and in TypeScript after its withTarget() calls have settled, which under the usual Promise.all is right after the await.
compare() names the row the way log() does in the same SDK. Python is handed the index by loop(), so it passes it on as the first argument, and it needs a whole number. TypeScript infers it from the iteration the comparison runs in, so index is only needed when you compare outside a run() callback, and it is a whole number there too.Candidate order is shuffled per row by default, seeded by the row index, so re-comparing the same row presents the candidates in the same order and a different row shuffles differently. randomize_order / randomizeOrder governs it: turn it off and the judge sees the candidates in the order their targets were registered.

Judging against a reference answer

By default the judge compares the candidates on their own merits, with no reference answer involved. Pass golden to have it judge each candidate against a reference instead. That one option is the whole switch, so there is no second flag to forget:
Use a reference answer when correctness is the question. Leave it out for open-ended work, where tone, completeness, structure or policy adherence decide the winner and no single answer is the right one.

Customizing the judge prompt

Leave prompt unset and the judge picks the shipped prompt matching what the row actually carries: a reference answer, task context, both, or neither. The framing for whatever the row lacks is dropped rather than left empty, which is why the default is worth keeping unless you have a rubric of your own. When you do have one, pass it and it is used exactly as written. Three placeholders are filled in for you:
Only those three placeholders are substituted, and they are substituted literally. Any other braces in your prompt reach the judge as written, so a rubric that says score out of {10} is passed through unchanged.

Options

Every option is optional, because the judge already has a default for each one. An option you do not set is not sent at all, so the judge’s default applies to it.

The verdict

A verdict carries four things: the status, the winner, the judge’s reasoning, and candidates, the target names it actually judged.
A comparison never raises when a judge fails, and never raises on a row too thin to judge. One unreachable judge in a thousand-row run costs you that row, not the run.
Naming a target in targets that recorded no output for the row does raise. Comparing whatever remains would hand you a two-way verdict where you asked for a three-way one, which is worse than a failure.

Seeing the results

Verdicts show up on the experiment’s results page:
  • A Winner column names the winning target on each row, with the judge’s reasoning underneath it, so you can see why a row went the way it did.
  • A win-rate chart gives each target its share of the wins, plus a bar for ties. A target that never won a single row is still charted, with zero wins.
With three or more targets there is also a leaderboard that ranks them from every matchup at once, with a confidence interval so the ranking’s own uncertainty is visible. It is being rolled out gradually, so ask us to turn it on for your organization if you want it.

A complete comparison

Three variants, one comparison per row, and a closing summary that fails a CI build when the run had failures:

Comparing from an async loop

In async-native mode, use acompare() so the judge is awaited rather than blocked on, leaving the event loop free for the rows running alongside it. It takes the same options and records the same verdict:
The older langevals/pairwise_compare evaluator is deprecated. Existing calls keep working, because they are routed to the same judge described here, but new code should use compare(): it takes any number of candidates, names the winner with your own target name, and needs no candidate columns to assemble.

Complete Example

Tracing Your Pipeline

To get complete visibility into your LLM pipeline, add tracing to your functions:
Learn more in our Python Integration Guide.
With tracing enabled, you can click through from any evaluation result to see the complete execution trace, including all LLM calls, prompts, and intermediate steps.

Exporting Results to CSV

After running your evaluations, you can export results to CSV for further analysis in spreadsheet tools like Excel or Google Sheets.

How to Export

Click the Export to CSV button in the top-right corner of the evaluation results page to download a complete CSV file with all your data.

CSV Structure

The exported CSV organizes the data by dataset rows and targets. Here’s the complete column structure:

Row Index

Dataset Columns

All columns from your input dataset are included with their original names.

Target Columns (per target)

For each target in your evaluation, the following columns are exported:

Evaluator Columns (per target, per evaluator)

For each evaluator applied to a target:

Comparison Columns (per comparison)

A comparison grades the row as a whole rather than one target, so it gets its own block of columns after every target block, named after the comparison: What each verdict writes into those three cells: An empty winner is never a tie. The three cells go empty together for error and for a row the comparison never ran on, so tie in the winner column always means the judge saw the candidates and called the row even. no_verdict is the shared export token for inconclusive and skipped. The two are separate in code, on the verdict.status your call returns, but they are recorded under one status, so a row read back from a completed run cannot be sorted into one or the other and naming either in the export would be a guess. The reasoning cell explains why no verdict was recorded, either in the judge’s own words or, when the judge itself never answered, in the evaluator’s account of what came back. Keep the two apart by reading verdict.status at the point of comparison. A row’s candidates can be fewer than the comparison’s full set of targets, because a target that produced no output for that row is left out of the matchup. The winner reads unresolved on the rare row where the judge’s answer matches none of the candidates it was shown, and a winner naming a target the run no longer lists is written under its own name.

Example CSV Output

For an evaluation comparing two targets, gpt-5-mini and claude, with a faithfulness evaluator:

Using the Data

The CSV export supports these analysis workflows:
Use spreadsheet filters to compare specific models or configurations:
  • Filter by {target}_model to analyze specific model performance
  • Sort by {target}_{evaluator}_score to find best/worst performing samples
  • Filter by {target}_error to identify failed executions
Calculate aggregate metrics across your evaluation:
  • Sum {target}_cost columns for total evaluation cost per model
  • Average {target}_duration_ms to compare response times
  • Identify outliers with high latency or cost
Analyze performance across different configurations:
  • Pivot tables by temperature, max_tokens, or custom metadata
  • Compare prompt versions side-by-side
  • Track improvements across iterations
Investigate problematic samples:
  • Filter rows where {target}_error is not empty
  • Cross-reference index with the UI for detailed inspection
  • Click through to traces using {target}_trace_id
All column headers are normalized to lowercase with spaces replaced by underscores for consistency and compatibility with data analysis tools.

Running in CI/CD

You can run SDK experiments in your CI/CD pipeline. The print_summary() method outputs a structured summary and exits with code 1 if any evaluations fail:
See CI/CD Integration for complete examples with GitHub Actions, GitLab CI, and more.

What’s Next?

CI/CD Integration

Run experiments in your CI/CD pipeline

View Evaluators

Explore all available evaluation metrics

Datasets

Learn about dataset management

View Examples

Check out example notebooks
Last modified on August 23, 2026