PythonのAI・エージェントコードを、pydantic_evals(評価用ライブラリ)を使ってテストケースのデータセットに対して検証し、Logfireのデータセット・実験UI(画面機能)で結果を確認できます。また、既存のBraintrust Eval()スイート(テスト集合)をコードの変更なしにLogfireへ転送することもできます。 **次のような場合に使用:** - ユーザーが「評価を設定したい」「評価を追加したい」「自分のエージェントをテストケースで試したい」「テストケースのデータセットを作りたい」「LLMの出力をスコア付けしたい」「LLM判定役を追加したい」「ツール呼び出しが正しいか確認したい」「Braintrust評価をLogfireに送りたい」「Braintrustから移行したい」と言う場合 - pydantic_evals、Braintrust、データセット・実験、AIやエージェントの動作評価などについて言及する場合 **技術的な詳細:** - pydantic_evalsの機能はPython専用です - Braintrust転送機能はTypeScriptスイートにも対応し、環境変数だけで設定できます - どちらも、あらかじめ定義されたテストケースをオフライン(事前実行)で評価するためのもので、本番環境の実行トラフィック(その場合は `logfire-instrumentation` を使用)やインフラ監視(その場合は `logfire-infrastructure` を使用)には対応していません
Evaluate Python AI/agent code against a dataset of test cases using pydantic_evals, and review results in Logfire's Datasets & Experiments UI. Also covers redirecting an existing Braintrust Eval() suite to Logfire with no code changes. Use this skill whenever the user asks to "set up evals", "add an evaluation", "test my agent against cases", "write a dataset of test cases", "score my LLM output", "add an LLM judge", "check tool-call correctness", "send Braintrust evals to Logfire", "migrate from Braintrust", or mentions pydantic_evals, Braintrust, Datasets & Experiments, or evaluating AI/agent behavior against known inputs. The `pydantic_evals` workflow is Python-only; the Braintrust redirect also supports TypeScript suites, env-vars-only. Both are for scoring DEFINED test cases offline — not for instrumenting live production traffic (use `logfire-instrumentation` for that) and not for infrastructure monitoring (use `logfire-infrastructure`).
pydantic_evals は、実際の関数やエージェント(AI が自動で判断・実行する仕組み)に対して、入力と期待される出力、そしてメタデータ(補足情報)を含むケース群をテストし、1つ以上の評価器で採点して結果レポートを出力します。logfire(datasets 拡張をインストールするとセットで入ります)に依存していますが、logfire.configure() を実行するかどうかで決まるのは「結果を Logfire の UI にもアップロードするか」だけです。省略すれば結果はすべてローカルのターミナルに出力されるだけで、エラーではありません。
一方、エージェント用の評価器(ツール呼び出しの正確さ、実行の流れの追跡など)は、それ以上の機能が必要です。タスク自体の実行経路データを読む必要があるため、logfire.configure() が正しく動いていないと、結果のアップロードに失敗するだけでなく、すべてのケースで「実行経路データがありません」と報告されて、そもそも評価が実行されません。
何より先に簡単な確認:このプロジェクトには既に Braintrust(別の評価ツール)を使った実績がありますか?つまり、単なる依存関係の記載ではなく、実際に Eval(...) の呼び出しや from braintrust import Eval というコードが存在していますか?この場合は CLI 認証が一切不要 です。ステップ 2 をスキップしてください。
既存の Eval() コードはそのまま保ちますが(Python braintrust>=0.30.1 以上、TypeScript braintrust>=3.24.0 以上であることを確認)、環境変数を変えるだけで Logfire に切り替えられます。pydantic_evals への変更は不要です:
export BRAINTRUST_APP_URL="https://logfire-us.pydantic.dev/v1/braintrust" # EU の場合は logfire-eu.pydantic.dev
export BRAINTRUST_API_KEY="<logfire-project-write-token>" # プロジェクト → 設定 → 書込トークン
unset BRAINTRUST_API_URL BRAINTRUST_PROXY_URL # 設定されていると上記エンドポイントが上書きされてしまう
これは 互換性プレビュー版で、完全ではありません:インラインデータ、ローカルタスク、複数スコア、スコアラーに対応していますが、Braintrust でホストされているデータセット・プロンプト・関数、BTQL、モデルプロキシ、サーバー側でのスコア計算には対応していません。また summarize_scores=False、手動の flush() 呼び出し、Rust SDK は概要データをリクエストしないため、実行が成功したように見えても結果が反映されません。詳細と用語の対応表(Braintrust の「プロジェクト」→ Logfire のデータセット名、「スコアラー」→「評価器」など)については、以下を参照してください:https://pydantic.dev/docs/logfire/get-started/comparisons/migrate-from-braintrust/
ステップ 5(検証)に直接進んでください。SDK が出力する結果 URL は Logfire で直接開きます。このパスでは、以下のステップの認証やデータセット定義は不要です。
既存の Braintrust スイートがない場合は、ステップ 2 に進んでください。 ステップ 3 の詳しい確認よりも先に進めば大丈夫です。この段階では、関数やエージェント、データセットの形状を知る必要はまだありません。
実行経路データを読まない評価器だけを使う、ローカルのみの pydantic_evals 実行を明確に希望する場合は、認証と同時に進めます。ステップ 4 で logfire.configure() を省略すれば、結果はターミナルに留まります。結果のアップロード、ホスト型のデータセット、または実行経路ベースの評価器(ToolCorrectness など)を使う場合は Logfire が必要なため、評価ファイルを開く・実行する前に認証を済ませて、正確なプロジェクトを指定してください。
Logfire でバックアップした実行の場合、まず確認を実行します。uvx logfire --non-interactive whoami(JavaScript の場合:npx logfire whoami)。正しいプロジェクトと地域が表示されていれば、ステップ 3 に進みます。そうでない場合は、フルコマンド、フラグ、注意点(--non-interactive の必須性、auth がブラウザを開かない理由、LOGFIRE_TOKEN と認証ファイルの競合、トークンファイルの安全性)を 認証と正確なプロジェクト選択 で確認してください。この CLI フローは logfire.configure() 向けです。ステップ 3 のホスト型データセット操作は、スコープが異なる別の API キーを使います。
テスト対象の関数またはエージェント(PydanticAI エージェント、LLM を呼ぶ関数、入力を受けて出力を返す何らかの処理)と、データセットが既に存在するかを確認します:
Case / Dataset を直接定義する。エージェント駆動のワークフローが標準です。LogfireAPIClient(from logfire.experimental.api_client import LogfireAPIClient)で取得・送信できます。client.get_dataset(name) を型引数なしで呼ぶと生の辞書が返ってきて、push_dataset や .evaluate_sync() に渡せません。入力・出力(使っている場合はメタデータ)の型を渡すと、pydantic_evals.Dataset が返されます:client.get_dataset(name, MyInputType, MyOutputType)。保存されたデータセットがカスタム評価器を含む場合、それらのクラスも custom_evaluator_types=[ExactMatch](カスタムレポート評価器の場合は custom_report_evaluator_types=[...])で渡して、逆シリアル化できるようにします。送信は client.push_dataset(dataset) で行います。このために別の API キーが 設定 → API キー から必要です(スコープ:project:read_datasets/project:write_datasets)。ステップ 2 の CLI 認証ではありません。ユーザーが特にコード外でケースを編集したい場合のみ関連があります。uv add 'logfire[datasets]'
from dataclasses import dataclass
import logfire
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext, IsInstance
logfire.configure() # 省略すると結果はローカルのみ
@dataclass
class ExactMatch(Evaluator[str, str]):
def evaluate(self, ctx: EvaluatorContext[str, str]) -> bool:
return ctx.output == ctx.expected_output
def classify_sentiment(text: str) -> str:
... # テスト対象の関数
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=[ExactMatch(), IsInstance(type_name='str')],
)
report = dataset.evaluate_sync(classify_sentiment) # または `await dataset.evaluate(...)`
report.print(include_input=True, include_output=True)
大規模なデータセットまたは LLMJudge(LLM を審査官として使う)などの実際に課金される呼び出しを含む場合は、フルセット前に 2~3 ケースでスモークテスト(事前確認)を実行してください。 バグを 3 ケースで発見すれば 3 回の呼び出しで済みますが、300 ケースで発見すれば 300 回かかります:
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)
スモークテストで予期しないエラーがなく、想定されるアサーションが成功することを確認します。その後、フルデータセットが大規模または課金ベースの呼び出しを使う場合は、ケース数とモデル呼び出しを行う評価器をユーザーに明示し、明確な承認を得てからフルセットを実行してください。スモークテストが成功したからといって、何も言わずにフルセットを実行してはいけません。
カスタム評価器は 必ず @dataclass デコレータ を付けたサブクラスである必要があります。通常のクラスは実行時にエラーになります。ケース名はデータセット内で一意である必要があります。よく使われる評価器は以下のとおりです:
| 評価器 | 確認内容 |
|---|---|
Equals(value) / EqualsExpected() |
指定値または expected_output への完全一致(expected_output が未設定の場合はスキップ。暗黙に対応しないため) |
IsInstance(type_name) |
出力の型が名前で一致 |
LLMJudge(rubric, model=None, score=False) |
LLM を審査官として使う採点。ケースごと・審査官ごとに実際のモデル呼び出しが発生。1~10 のスケールより、はい/いいえ、複数選択肢のようなカテゴリカルな基準が安定しています。審査官を信頼する前に 20~100 のケースで手作業検証をしてください |
ToolCorrectness(expected_tools, ...) |
エージェントが呼んだツール。実行経路データを読むため、ステップ 2 の logfire.configure() がないと、アップロード以前に動作しません |
その他:Contains、MaxDuration、TrajectoryMatch、ArgumentCorrectness、MaxToolCalls、MaxModelRequests も利用可能です。ツール・実行経路関連のものはすべて ToolCorrectness と同じく実行経路データが必要です。完全なリストは pydantic_evals.evaluators を参照してください。これら 5 つのエージェント用(実行経路ベース)評価器は pydantic-evals>=2.4.0 が必要です。古いバージョンを指定している場合は、pyproject.toml / uv.lock を確認してアップグレードしてください。インポート自体が失敗し、暗黙の無効化ではありません。
Python 評価器(任意コード実行)はセキュリティ上の理由で削除されました。古い例に記載されていても使わないでください。
ホスト型データセットを編集している場合:client.push_dataset(dataset) は 毎回サーバー側の評価器を上書き します。ローカルで削除した評価器も削除されます。UI で他のユーザーが編集したデータセット上に古いローカルコピーをプッシュしないでください。
ターミナルに表示されたレポートが Logfire に到達した証拠ではありません。実際に到達したことを確認してください。ケースが成功した、スコアが出た、実行が完了したと報告する前に、この セッション中に実際に確認してください。 実行が失敗した、キャンセルされた、スコアが出なかった場合は、その失敗を明確に報告してください。想像上のスコアや推測で結果を埋めないでください。
ステップ 1 の Braintrust パスから来た場合(ステップ 2 をスキップ)? ここで参照するプロジェクトがありません。SDK が出力した結果 URL を使ってください。正しい Logfire プロジェクトで直接開きます。以下の項目(完了、成功率、ケース詳細)をそのページから確認してください。プロジェクト名で検索する必要はありません。
evaluate {name} という名前で、gen_ai.operation.name = 'experiment'、dataset_name、task_name 属性を持っています。データセット名に合う最新のものを見つけて、logfire.experiment.metadata でケース数と成功率が期待値と合致することを確認してください。そうでない場合は、ステップ 2 で確認したプロジェクトのpydantic_evals runs your actual function or agent against a Dataset of Cases (input + expected output + metadata), scores each with one or more Evaluators, and produces a report. It depends on logfire itself (the datasets extra pulls in the real SDK, not a mock), so whether logfire.configure() has run determines only whether results also upload to Logfire's Datasets & Experiments UI — omitting it keeps results entirely local and printed to the terminal, silently, not an error.
Agentic evaluators (tool-call correctness, trajectory matching) need more than that: they read the task's own execution span tree, so without a working logfire.configure() they don't just fail to upload — every case reports "No span tree available" and the check never ran at all.
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.
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-write-token>" # Project -> Settings -> Write tokens
unset BRAINTRUST_API_URL BRAINTRUST_PROXY_URL # these override the endpoint above if set — the #1 "it still hit Braintrust" cause
This is a compatibility preview, not full parity: covers inline/callable data, local tasks and scorers, multiple scores, one label per name, and normal summary finalization. It does not cover Braintrust-hosted datasets/prompts/functions, BTQL, the model proxy, server-side scoring, or post-finalization feedback — and summarize_scores=False, a manual flush() without a comparison, or the Rust SDK never request the summary this endpoint needs, so nothing lands even though the run appears to succeed. Full detail and the concept-translation table (Braintrust "project" → Logfire dataset name, "scorer" → evaluator, etc.): https://pydantic.dev/docs/logfire/get-started/comparisons/migrate-from-braintrust/.
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.
Skip authentication and continue to Step 3 only when the user explicitly wants a local-only pydantic_evals run using evaluators that do not need span data; omit logfire.configure() in Step 4 so results stay in the terminal. Uploading results, using a hosted dataset, or running a span-based evaluator such as ToolCorrectness requires Logfire, so authenticate before opening or running evaluation files and target the exact project first.
For a Logfire-backed run, check first — uvx logfire --non-interactive whoami (JS: npx logfire whoami) — and skip to Step 3 if it already reports the right project and region. Otherwise, follow the full command sequence, flags, and gotchas (the --non-interactive requirement, why auth won't open a browser for you, the LOGFIRE_TOKEN-vs-credentials-file conflict, and token-file safety) in Authenticate and Select the Exact Project. This CLI flow is for logfire.configure(); Step 3's hosted-dataset operations use a separate API key with different scopes.
Identify the function or agent under test (a PydanticAI agent, an LLM-calling function, any callable that takes an input and returns an output) and whether a dataset already exists:
Case/Dataset directly — the default for an agent-driven workflow.LogfireAPIClient (from logfire.experimental.api_client import LogfireAPIClient). client.get_dataset(name) with no type arguments returns a raw dict, not something push_dataset or .evaluate_sync() can take — pass the input/output (and metadata, if used) types to get back a real pydantic_evals.Dataset: client.get_dataset(name, MyInputType, MyOutputType). If the stored dataset contains custom evaluators, also pass their classes with custom_evaluator_types=[ExactMatch] (and custom report evaluators with custom_report_evaluator_types=[...]) so they can be deserialized. Push with client.push_dataset(dataset). This needs its own API key from Settings → API Keys (scoped project:read_datasets/project:write_datasets), not Step 2's CLI auth flow. Only relevant if the user specifically wants case editing outside code.uv add 'logfire[datasets]'
from dataclasses import dataclass
import logfire
from pydantic_evals import Case, Dataset
from pydantic_evals.evaluators import Evaluator, EvaluatorContext, IsInstance
logfire.configure() # omit this and results stay local only, silently
@dataclass
class ExactMatch(Evaluator[str, str]):
def evaluate(self, ctx: EvaluatorContext[str, str]) -> bool:
return ctx.output == ctx.expected_output
def classify_sentiment(text: str) -> str:
... # the function under test
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=[ExactMatch(), IsInstance(type_name='str')],
)
report = dataset.evaluate_sync(classify_sentiment) # or `await dataset.evaluate(...)`
report.print(include_input=True, include_output=True)
Before running the full dataset, run a smoke test on 2-3 cases if the dataset is large or uses LLMJudge/any evaluator that makes a real, billed model call — a bug caught on 3 cases costs 3 model calls, the same bug caught on 300 costs 300:
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)
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.
Custom evaluators must be @dataclass subclasses — a plain class raises at run time. 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) |
LLM-as-judge scoring; costs a real model call per case per judge — prefer boolean/categorical rubrics over 1-10 scales (judges are unstable on continuous scores), and benchmark the judge against ~20-100 hand-labeled cases before trusting it |
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.
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.
evaluate {name} and carries gen_ai.operation.name = 'experiment', dataset_name, and task_name attributes; find the most recent one matching your dataset's name and confirm logfire.experiment.metadata shows 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.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.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。