AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to running offline evaluations for AI agents using pydantic_evals (Python) or logfire/evals (Node.js). It supports setting up test datasets, applying various evaluators—including span-based checks for agent behavior—and migrating existing Braintrust evaluation suites to Logfire. The process emphasizes verifying results within the Logfire platform to ensure accuracy and debugging failed cases.
What it does
- Executes offline evaluation suites for Python and Node.js projects.
- Redirects existing Braintrust Eval() suites to Logfire via environment variables.
- Supports span-based evaluators to inspect agent tool usage and trajectories.
- Enables management of hosted datasets via the Logfire API client.
- Provides a smoke test workflow to validate setups before running full, potentially costly evaluations.
- Facilitates verification of evaluation runs directly within the Logfire UI.
When to use it
- When you need to evaluate AI agent performance against a defined test dataset.
- When migrating existing Braintrust evaluation workflows to Logfire.
- When you need to perform subjective or rubric-based judgment using LLM judges.
- When you need to verify agent tool-calling behavior using span-based evaluators.
- When you want to manage evaluation datasets outside of code via the Logfire UI.
Advantages
- Seamless migration path for existing Braintrust users.
- Deep integration with OpenTelemetry span trees for advanced agent diagnostics.
- Supports both code-defined and hosted datasets for flexible evaluation management.
- Includes built-in safeguards like smoke testing to prevent unnecessary costs.
- Provides a unified interface for reviewing evaluation results and traces.
Usage examples
- “How do I redirect my existing Braintrust evaluation suite to Logfire?”
- The agent will guide you to set the BRAINTRUST_APP_URL and BRAINTRUST_API_KEY environment variables to point to your Logfire project, ensuring you unset other Braintrust-specific variables.
- “I need to run a sentiment analysis evaluation on my Python agent.”
- The agent will help you define a Dataset with cases and evaluators like EqualsExpected, configure Logfire, and execute the evaluation using dataset.evaluate_sync().
- “How can I verify that my evaluation run completed successfully in Logfire?”
- The agent will instruct you to navigate to the AI Evaluations section in Logfire, check the experiment metadata for completion status, and drill into failing cases via the trace view.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/pydantic/logfire-evals/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip logfire-evals.zip -d .agents/skills
Documentation by Pydantic
Evaluate AI code with Logfire
How This Works
Python's pydantic_evals and Node.js's logfire/evals run a task against cases, apply evaluators, and return a report. An active Logfire or OpenTelemetry provider may export inputs and outputs even if this skill did not configure it. Intentional upload needs logfire.configure() in Python or a configured Node.js exporter.
Span-based evaluators inspect the task's OpenTelemetry span tree. Without working Logfire instrumentation, Python reports "No span tree available"; Node.js HasMatchingSpan can produce no evaluator result at all. Treat either signal as a setup failure, not evidence about the agent.
Step 1: Check for an Existing Braintrust Suite First
Cheap check, before anything else: does this repo already have an existing Braintrust suite — actual Eval(...) calls or from braintrust import Eval in source, not just a braintrust dependency listed without any real usage? This path needs no CLI auth at all — don't run Step 2 for it. The compatibility endpoint is documented only for Logfire Cloud US and EU; for any other supplied origin, use the native evaluation path instead.
Keep the existing Eval() code (Python braintrust>=0.30.1 / TypeScript braintrust>=3.24.0 — verified versions) and redirect its next run to Logfire by changing environment variables only, no pydantic_evals involved:
export BRAINTRUST_APP_URL="https://logfire-us.pydantic.dev/v1/braintrust" # EU: logfire-eu.pydantic.dev
export BRAINTRUST_API_KEY="<logfire-project-api-key>" # Settings -> API Keys
unset BRAINTRUST_API_URL BRAINTRUST_PROXY_URL # these override the endpoint above if set — the #1 "it still hit Braintrust" causeThe destination project's API key needs project:write_otlp and project:read_datasets: the SDK writes the run, then reads experiment metadata for its summary. An ingest-only write token fails with 403.
This compatibility preview covers inline/callable data, local tasks and scorers, multiple scores, one label per name, and normal summary finalization. It excludes Braintrust-hosted resources, BTQL, the model proxy, server-side scoring, and post-finalization feedback. Rust, summarize_scores=False, and manual flush() without comparison do not request the summary needed for projection. See the full coverage and concept mapping.
Skip straight to Step 5 (Verify) — the SDK's own printed result URL also opens directly in Logfire, and nothing else here (auth, dataset definition) applies to this path.
No existing Braintrust suite? Continue to Step 2 now, before the more detailed identification in Step 3 — nothing past this point requires knowing the function/agent or dataset shape yet.
Step 2: Authenticate When the Run Needs Logfire
For an explicitly local-only run without span evaluators, use a fresh process that neither preloads nor imports application telemetry; omit Python's logfire.configure() and any Node.js exporter bootstrap. If the task configures an exporter and has no documented disable switch, stop rather than claiming local-only. Uploading, hosted datasets, and span evaluators require Logfire authentication to the exact project.
For a Logfire-backed run, use Authenticate and Select the Exact Project to derive the CLI target from the supplied Logfire URL and run its target-aware whoami check with a verified CLI path — for JS/TS projects without uv, use the external-prefix npm fallback instead of plain npx, which can execute a repository-local binary. Skip to Step 3 if that already reports the right project and resolved --region or --base-url target; otherwise, continue through the full authentication and project-selection sequence there. This CLI flow is for logfire.configure(); Step 3's hosted-dataset operations use a separate API key with different scopes.
Step 3: Detect What to Evaluate
Identify the real task and any dataset. Follow repository package, test, and dependency conventions. For a first eval, prefer 3-5 cases from tests, schemas, examples, or synthetic fixtures, with deterministic checks for defined behavior. Do not copy the example unless it fits, replace an eval framework, or refactor unrelated code. Without a runnable task or safe expected behavior, ask one focused question instead of inventing either.
In-code dataset: a Python module using
pydantic_evals, or a Node.js module usinglogfire/evals. This is the default for an agent-driven workflow.Hosted/managed dataset: cases live in the Logfire UI, edited by non-engineers, pulled/pushed via a separate
LogfireAPIClient(from logfire.experimental.api_client import LogfireAPIClient). Hosted inputs and outputs must be JSON objects; scalar roots accepted by code-defined datasets are rejected.client.get_dataset(name)with no type arguments returns a raw dict, not somethingpush_datasetor.evaluate_sync()can take — pass the input/output (and metadata, if used) types to get back a realpydantic_evals.Dataset:client.get_dataset(name, MyInputType, MyOutputType). If the stored dataset contains custom evaluators, also pass their classes withcustom_evaluator_types=[MyEvaluator](and custom report evaluators withcustom_report_evaluator_types=[...]) so they can be deserialized. Push withclient.push_dataset(dataset). This needs its own API key from Settings → API Keys (scopedproject:read_datasets/project:write_datasets), not Step 2's CLI auth flow. Only relevant if the user specifically wants case editing outside code.
Step 4: Define the Dataset and Run It
Use the repository's existing package manager and lockfile. Install only the missing integration for its language.
Python
Add pydantic-evals[logfire] with the detected Python manager: uv add, poetry add, or pdm add. For a pip/requirements project, update its declared requirements and install from that file; do not introduce a second manager or lockfile.
import logfire
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import EqualsExpected, IsInstance
logfire.configure() # omit only in the isolated local-only process described above
def classify_sentiment(text: str) -> str:
return 'positive' if 'love' in text else 'negative'
dataset = Dataset[str, str, None](
name='sentiment-eval',
cases=[
Case(name='positive', inputs='I love this', expected_output='positive'),
Case(name='negative', inputs='This is terrible', expected_output='negative'),
],
evaluators=[EqualsExpected(), IsInstance(type_name='str')],
)
report = dataset.evaluate_sync(classify_sentiment) # or `await dataset.evaluate(...)`
report.print(include_input=True, include_output=True)Use logfire[datasets] instead only when the task specifically needs the hosted-dataset API from Step 3.
JavaScript or TypeScript on Node.js
Do not apply this section to Deno, Bun, browsers, or workers; their exporter setup is not validated by this skill.
Add logfire and @pydantic/logfire-node with the manager selected by the existing lockfile: pnpm add, yarn add, bun add, or npm install. Do not introduce a second lockfile.
Configure Logfire before loading the task. Reuse an existing instrumentation entry point rather than configuring it twice.
import * as logfire from '@pydantic/logfire-node'
import { Case, Dataset, EqualsExpected, renderReport } from 'logfire/evals'
logfire.configure()
const classifySentiment = (text: string) => (text.includes('love') ? 'positive' : 'negative')
const dataset = new Dataset<string, string>({
name: 'sentiment-eval',
cases: [new Case({ name: 'positive', inputs: 'I love this', expectedOutput: 'positive' })],
evaluators: [new EqualsExpected()],
})
await dataset.evaluate(classifySentiment).then((report) => {
console.log(renderReport(report, { includeInput: true, includeOutput: true }))
}).finally(() => logfire.shutdown({ timeoutMillis: 5000 }))Other built-ins include Equals, Contains, IsInstance, MaxDuration, HasMatchingSpan, and LLMJudge. Node.js custom evaluators extend Evaluator, and LLMJudge needs a judge callback. Use @pydantic/logfire-node/datasets only for hosted datasets.
Smoke test before a paid or full run
Before running the full dataset, run a smoke test on 2-3 cases if the dataset is large or uses LLMJudge or any evaluator that makes billed model calls. This catches setup errors before they multiply cost across the dataset.
Python:
smoke = Dataset(
name=dataset.name,
cases=dataset.cases[:3],
evaluators=dataset.evaluators,
report_evaluators=dataset.report_evaluators,
)
smoke_report = smoke.evaluate_sync(classify_sentiment)
smoke_report.print(include_input=True, include_output=True)Node.js:
const smoke = new Dataset({
name: dataset.name,
cases: dataset.cases.slice(0, 3),
evaluators: dataset.evaluators,
reportEvaluators: dataset.reportEvaluators,
})
await smoke.evaluate(classifySentiment).then((report) => {
console.log(renderReport(report, { includeInput: true, includeOutput: true }))
}).finally(() => logfire.shutdown({ timeoutMillis: 5000 }))Confirm the smoke run has zero unexpected errors and the assertions that should pass do. Then, if the full dataset is large or uses paid model calls, tell the user the case count and which evaluators will make model calls, and get explicit confirmation before running the full dataset — don't run an expensive full pass on the strength of a clean smoke test alone without saying so.
The remaining details in this section are Python-specific. Custom evaluators inherit Evaluator and implement evaluate; use @dataclass for configurable fields and portable serialization. Case names must be unique within a dataset. The evaluators reached for most:
| Evaluator | Checks |
|---|---|
Equals(value) / EqualsExpected() | Exact match against a literal / expected_output (no-op if expected_output is unset — don't rely on it silently catching that) |
IsInstance(type_name) | Output's type matches by name |
LLMJudge(rubric, model=None, score=False) | Subjective or rubric-based judgment; makes billed model requests, so validate the rubric against human-reviewed examples before treating it as a quality gate |
ToolCorrectness(expected_tools, ...) | Which tools an agent called — reads the span tree, so needs Step 2's logfire.configure() to work at all, not just to upload |
Also available: Contains, MaxDuration, TrajectoryMatch, ArgumentCorrectness, MaxToolCalls, MaxModelRequests — same span-tree dependency as ToolCorrectness for the tool/trajectory ones; see pydantic_evals.evaluators for the full set. These five agentic (span-based) evaluators need pydantic-evals>=2.4.0 — on an older pin, check pyproject.toml/uv.lock and upgrade before reaching for them, since the import itself is what fails, not a silent no-op.
The Python evaluator (arbitrary code execution) was removed for security reasons — don't reach for it even if an older example references it.
If editing a hosted dataset: client.push_dataset(dataset) overwrites server-side evaluators on every push, including removing ones you deleted locally — don't push a stale local copy over a dataset others have edited in the UI.
Step 5: Verify
A report printing to the terminal isn't proof it reached Logfire — confirm the run actually landed. Never report a case as passed, a score, or a run as complete without having actually checked it in this session — if a run fails, cancels, or produces no scores, report that failure plainly; never substitute an invented score or a manual guess at what the result "should" be.
Came from the Step 1 Braintrust path (Step 2 skipped)? There's no whoami-resolved project to look up here — use the SDK's own printed result URL instead, which already opens directly in the right Logfire project. Confirm the same things below (completion, pass mix, case detail) from that page rather than searching by name.
Query for the run directly, if a Logfire MCP server or API is connected — the root span for a run is named
evaluate {name}and carriesgen_ai.operation.name = 'experiment',dataset_name, andtask_nameattributes; find the most recent one matching your dataset's name and confirmlogfire.experiment.metadatashows the case count and pass rate you expect. Otherwise, open AI Evaluations → Datasets & Experiments → Experiments in Logfire for the exact project from Step 2, and find the run by name/timestamp.Read the Overview tab (or the queried metadata) first: completion count, assertion pass mix, task errors, average duration. If completion says "Not reported," the run sent case data but never signaled it finished — treat that as a broken run, not a passing one.
Open the Cases tab, starting from Needs Review / Failed / Errors, not the full list.
Drill into a failing case's trace in Live view for the actual evidence, rather than trusting the summary score alone.
Fix and re-run until the cases that should pass do, and any tool-call/trajectory checks show real span data, not "No span tree available."
Close with a final report built from what you just confirmed — the run name, exact case count and pass rate you queried, and which evaluators ran — not a template. Include the direct link to this experiment (the SDK's own printed result URL, or the Datasets & Experiments page you opened it from), so the user can see the run without having to ask where to look.
Files
- ATTRIBUTION.mdAdded by AutoGineer843 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md13.9 KiB
LICENSE
The MIT License (MIT) Copyright (c) 2025 Pydantic Services Inc. and individual contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs an API key, Needs Python, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Pydantic
- Publisher
- Pydantic (Official)
- Source
- pydantic/skills · skills/logfire-evals · commit 98f591c
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- pydantic/skills
AutoGineer is not affiliated with or endorsed by Pydantic.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download logfire-evals.zip