agent-observability-eval-bootstrap について
Datadog の本番トレースから評価器を作成するスキルです。無効状態のオンライン草稿、オフライン SDK コードや JSON 仕様を生成でき、サンプル済みトレースのデータセット出力にも対応します。
主な機能
- 01本番トレースのサンプリングと調査
- 02オンライン LLM ジャッジ評価器の提案
- 03確認後に無効状態の草稿を作成
- 04オフライン Python SDK コードの生成
- 05JSON 仕様またはデータセットレコードの出力
活用シーン
- 01本番の動作に基づく評価器の草稿作成
- 02オフライン評価器実験の実施
- 03実験用データセット作成に向けたトレースの出力
MCP 対応
Datadog MCP ツールで LLMObs のトレースと評価器を検索・調査し、評価器の草稿を作成します。代わりに pup CLI も利用できます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ datadog-labs/ agent-skills --skill agent-observability-eval-bootstrapインストール後、Claude Code で/agent-observability-eval-bootstrapを入力して読み込まれたか確認します。
詳細
バックエンド
判定 — 呼び出しのたびに、何らかのアクションを実行する前に、使用するバックエンドを判定してください。
- ユーザーが呼び出しのどこかで
--backend pupを渡した場合 → MCP ツールの有無にかかわらず、直ちに pup モードを使用します。手順 2〜4 はスキップしてください。 - 有効なツール一覧に MCP ツールがあるか確認します。判定の基準は、利用可能なツールに
mcp__datadog-llmo-mcp__list_llmobs_evalsが含まれているかどうかです。 - MCP ツールがある場合 → 以降は MCP モードを使用します。このスキルのワークフローのセクションに記載されているとおりの名前で、MCP ツールを呼び出してください。
- MCP ツールがない場合 → Bash で
pup --versionを実行して、pupが実行可能か確認します。"version"を含む JSON レスポンスが返れば、pup を利用できます。 - pup が応答した場合 → 以降は pup モードを使用します。このファイルの末尾にあるツールリファレンスの付録を使い、すべての MCP ツール呼び出しを対応する pup コマンドに置き換えてください。
- どちらも利用できない場合 → 処理を中止し、ユーザーに次のように伝えてください。
「Datadog MCP サーバーも pup CLI も利用できません。MCP サーバー(
claude mcp add --scope user --transport http datadog-llmo-mcp 'https://mcp.datadoghq.com/v1/mcp?toolsets=llmobs')を接続するか、pup をインストールしてください。」
--backend pup は呼び出し引数のどこに指定しても受け付けます。残りの引数をスキルのロジックに渡す前に、この引数を取り除いてください。
pup の呼び出しルール:
- Bash で
pup llm-obs <subcommand> [flags]を実行します。 - pup は常に JSON を出力します。内容ブロックからの展開処理は行わず、そのまま解析してください(MCP の結果は
[{"type": "text", "text": "<json>"}]のように JSON をラップする場合があります)。 - pup が認証エラーを返した場合は、ユーザーに
pup auth loginを実行するよう伝え、処理を中止してください。 - 並列化:1 つのメッセージで複数の Bash ツール呼び出しを実行します(各呼び出しにつき pup コマンドは 1 つ)。
- 時間フラグ:pup は期間を表す文字列(
1h、7d、30m)と RFC3339 タイムスタンプをそのまま受け付けます。now-で始まる文字列は使用しないでください。スキルの--timeframe引数から変換する場合は、接頭辞を取り除きます。例:now-7d→7d、now-24h→24h、now-30d→30d。 pup llm-obs spans searchの--summaryは、ペイロードのフィールドを必須メタデータのみに絞り込みます。内容が不要な一括処理や検索の段階で使用してください。
呼び出し ID: 各呼び出しの開始時、MCP ツールを呼び出す前に、8 文字の 16 進数の呼び出し ID(例:3a9f1c2b)を生成してください。呼び出し全体を通じて、同じ ID を使用してください。
インテントのタグ付け: MCP ツールを呼び出すたびに、telemetry.intent の先頭に skill:agent-observability-eval-bootstrap[<inv_id>] — を付け、その後にツールを呼び出す理由を記述してください。最初の MCP ツール呼び出しに限り、代わりに skill:agent-observability-eval-bootstrap:start[<inv_id>] — を使用してください(:start のサフィックスを付けます)。最初の呼び出しの例:skill:agent-observability-eval-bootstrap:start[3a9f1c2b] — Phase 0: map existing eval coverage for task-cruncher
Eval Bootstrap — 本番トレースから評価器を生成
本番 LLM トレースのサンプルをもとに、入出力のパターンと品質の観点を分析し、そのまま使える評価器スイートを提案します。出力モードは 4 つあります。デフォルトはオンライン評価器です。SDK コード、JSON 仕様、データセット出力モードは、リクエストに応じて生成されます。
publish(デフォルト) — オンラインの LLM ジャッジ評価器を提案し、スイートの確認後に限り、create_or_update_llmobs_evaluatorを介して Datadog に無効なドラフト(enabled: false)として書き込みます。Phase 2 のチェックポイントで確認するまでは何も作成されず、UI でユーザーが有効にするまでは、どのスパンもスコアリングされません。このスキルが稼働中の評価器を自動公開することはありません。ドラフトを有効にすると、データセットやタスク関数は不要で、一致する本番スパン、トレース、またはセッションに対して自動的に実行されます。このスキルは、各評価器で必要となる判定内容に基づいて、スパン単位、トレース単位、セッション単位のいずれかに自動分類します(LLM 呼び出しごとのトーン確認、トレース全体を必要とするエージェントの目標達成確認、複数のトレースにまたがる会話全体でのユーザー満足度確認など)。チェックポイントで分類を承認するか、変更できます。セッション単位の評価器は、アプリのスパンにsession_idが含まれている場合にのみ提案されます(Phase 1 のプローブで確認します)。sdk_code(リクエスト時 —--sdk-codeを指定するか、publish の実行後に依頼) — オフライン実験向けに、Datadog Evals SDK(BaseEvaluator/LLMJudge)を使用した Python の.pyファイルを生成します。data_only(リクエスト時 —--data-only) — フレームワークに依存しない、自己完結型の JSON 仕様を生成します。emit_dataset(リクエスト時 —--emit-dataset <path>) — 本番トレースをサンプリングし、LLMObs.create_dataset(records=...)用の形式に整えたDatasetRecordRaw[]JSON ファイルを書き出します。評価器の提案と生成は一切行いません。このモードでは評価器ではなくデータセットを生成します。agent-observability-eval-pipelineが、実験データセットのシードとして本番の挙動を使用するために(Phase 4 で)利用します。
publish の実行後に、同じスイートをオフラインコードや移植可能な仕様としてユーザーが希望した場合は、依頼するだけで生成できます。このスキルは再調査を行わず、すでに確認済みのスイートを sdk_code / data_only モードで再生成します(Phase 3 の「リクエストに応じたコード生成」を参照)。emit_dataset モードは評価器ワークフローとは独立しており、以前の提案を再利用することはありません。毎回必ずトレースを再サンプリングします。
使い方
/eval-bootstrap <ml_app> [--timeframe <window>] [--sdk-code | --data-only | --emit-dataset <path>] [--trace-limit <N>]
引数:$ARGUMENTS
入力
| 入力 | 必須 | デフォルト | 説明 |
|---|---|---|---|
ml_app | はい | — | トレースの対象とする ML アプリケーション |
timeframe | いいえ | now-7d | どの期間までさかのぼって調べるか |
rca_report | いいえ | — | eval-trace-rca スキルによる失敗分類、または自由記述の失敗仮説 |
--sdk-code | いいえ | オフ | オンライン公開の代わりに、オフライン実験向けの Python SDK .py ファイルを出力します。--data-only および --emit-dataset とは併用できません。 |
--data-only | いいえ | オフ | オンライン公開の代わりに、自己完結型の JSON 仕様ファイルを出力します。--sdk-code および --emit-dataset とは併用できません。 |
--emit-dataset <path> | いいえ | オフ | **データセットのみのモード。**本番トレースをサンプリングし、DatasetRecordRaw[] JSON を <path> に書き出します。評価器ワークフローは一切実行しません。--sdk-code および --data-only とは併用できません。 |
--trace-limit | いいえ | 20(上限 50) | emit_dataset モードでサンプリングするトレースの最大数 |
ml_app が指定されていない場合は、処理を続行する前にユーザーに確認してください。モードフラグが指定されていない場合、このスキルはデフォルトで publish を使用します。オンライン評価器を提案し、確認後に限り無効なドラフトとして作成します(自動で有効にすることはありません)。--sdk-code、--data-only、--emit-dataset のうち複数が指定されている場合は、エラーを返し、希望するモードをユーザーに確認してください。
利用可能なツール
| ツール | 用途 |
|---|---|
search_llmobs_spans | 評価の有無、タグ、スパン種別、クエリ構文でスパンを検索します。カーソルでページネーションします。 |
get_llmobs_span_details | メタデータ、評価(スコア、ラベル、推論)、利用可能なフィールドとサイズを示す content_info マップを取得します。 |
get_llmobs_span_content | スパンフィールドの実際のコンテンツを取得します。path パラメーターで JSONPath を使用し、対象を絞って抽出できます。 |
get_llmobs_trace | スパン種別ごとのスパン数を含むスパンツリーとして、トレース階層全体を取得します。 |
get_llmobs_agent_loop | エージェントの実行タイムライン(LLM 呼び出し、ツールの呼び出し、判断)を時系列順に取得します。 |
list_llmobs_evals | 呼び出し元の組織で、すべての ml_apps に設定されている各評価器を、結果ごとの enabled ステータスと ml_app とともに一覧表示します。新しい評価器を提案する前に、Phase 0 で一度呼び出して既存の適用範囲を把握してください。結果はクライアント側で ml_app によって絞り込んでください。 |
get_llmobs_evaluator | 名前を指定して、保存済みの評価器設定全体(対象の ml_app、サンプリングとフィルター、プロバイダー、プロンプトテンプレート、解析タイプ、出力スキーマ、評価基準)を取得します。Phase 0 では既存のカスタム評価が何を測定しているかを把握するために使用し、公開モードでは、更新の前に必ず使用してください。create_or_update_llmobs_evaluator は全置換のため、フィールドを上書きしてしまわないよう、設定全体を取得して引き継ぐ必要があります。設定が保存されていない評価器もあります(特に source=ootb)。その場合の not-found エラーは想定どおりなので、スキップしてください。 |
create_or_update_llmobs_evaluator | (公開モード) LLM ジャッジ評価器の設定を Datadog に書き込みます。全置換の仕様であり、省略した任意フィールドはデフォルト値にリセットされます。必須フィールドと、構造化出力から JSON スキーマへのマッピングについては、「公開規約」を参照してください。 |
delete_llmobs_evaluator | (公開モード) ユーザーが評価器の削除を明示的に依頼した場合にのみ使用します。推測で呼び出してはいけません。 |
get_llmobs_span_content の主なパターン
path パラメーターを使用すると、ペイロード全体を取得せずに対象のデータを抽出できます。
| フィールド | パス | 取得できる内容 |
|---|---|---|
messages | $.messages[0] | システムプロンプト(最初のメッセージ。通常は system ロール) |
messages | $.messages[-1] | 最後のアシスタント応答 |
messages | (パスなし) | ツール呼び出しを含む会話全体 |
input / output | — | スパンの入出力 |
documents | — | 取得されたドキュメント(RAG アプリ) |
metadata | — | カスタムメタデータ(プロンプトのバージョン、機能フラグ、ユーザーセグメント) |
search_llmobs_spans の使い方
追加のフィルターはスペースで区切って組み合わせます(AND):@status:error @ml_app:my-app。専用パラメーター(span_kind、root_spans_only、ml_app)は query と併用できますが、query は tags より優先されます。
特定の評価があるスパンを検索するには、@evaluations.custom.<eval_name>:* を指定します。検索できるのは評価の有無のみで、特定の結果は検索できません。
アプリがセッションを使用しているかどうかを検出するには、session_id:* で session_id を持つすべてのスパンに一致します(session_id は第一級フィールドであり、@ プレフィックスは不要です)。Phase 1 のセッションプローブはこれを使用して、セッションスコープの評価器を適用するかどうかを判定します。
並列化のルール
get_llmobs_span_details:trace_idごとにspan_idをグループ化します。各trace_idについて、そのすべてのspan_idを含めて1回呼び出します。1ページ分の呼び出しはすべて、1つのメッセージで実行してください。get_llmobs_span_content:各呼び出しは独立しています。必ずすべてを1つのメッセージで実行してください。get_llmobs_trace/get_llmobs_agent_loop:異なるトレースに対する呼び出しは、1つのメッセージ内で並列化してください。- パイプラインの並列化:ページ1の結果については、すべてのページを集め終わるのを待たずに、直ちに
get_llmobs_span_detailsを開始してください。
Evaluator SDK リファレンス
sdk_codeモードのみに適用されます。data_onlyモードでは、ルーブリックのプロンプトを作成する際のドメイン知識としてこのセクションを使用してください。SDK クラスは出力されません。
インポート
# Core classes
from ddtrace.llmobs._experiment import BaseEvaluator, EvaluatorContext, EvaluatorResult
# LLM-as-judge
from ddtrace.llmobs._evaluators.llm_judge import (
LLMJudge,
BooleanStructuredOutput,
ScoreStructuredOutput,
CategoricalStructuredOutput,
)
# Built-in evaluators (use only if needed)
from ddtrace.llmobs._evaluators.format import JSONEvaluator, LengthEvaluator
from ddtrace.llmobs._evaluators.string_matching import StringCheckEvaluator, RegexMatchEvaluator
生成ファイルで実際に使用するものだけをインポートしてください。
EvaluatorContext(evaluate()が受け取る内容)
@dataclass(frozen=True)
class EvaluatorContext:
input_data: dict[str, Any] # Task inputs (from dataset record, NOT from span)
output_data: Any # Task output (from task function return, NOT from span)
expected_output: Optional[JSONType] = None # Ground truth (if available)
metadata: dict[str, Any] = {} # Additional metadata
span_id: Optional[str] = None # LLMObs span ID
trace_id: Optional[str] = None # LLMObs trace ID
重要 — スパンのデータと評価器のデータ:本番トレースを確認すると、スパンの入出力(例:input.value、output.messages)を目にします。しかし、評価器はオフライン実験で実行され、input_dataとoutput_dataにはスパンではなく、ユーザーのデータセットのレコードとタスク関数から取得したデータが渡されます。データセットのスキーマはユーザー定義であり、スパンの構造と一致するとは限りません。評価器のプロンプトには汎用的な{{input_data}}/{{output_data}}プレースホルダーを使用し、評価器がどのようなデータを想定して設計されたかを説明するコメントを追加して、ユーザーが自身のデータセットの形式に合わせて調整できるようにしてください。
EvaluatorResult(evaluate()が返す内容)
EvaluatorResult(
value=..., # Required. JSONType (str, int, float, bool, None, list, dict)
reasoning="...", # Optional. Explanation string
assessment="pass" or "fail", # Optional. Pass/fail assessment
metadata={...}, # Optional. Evaluation metadata dict
tags={...}, # Optional. Tags dict
)
LLMJudge — LLM-as-Judge評価器
judge = LLMJudge(
user_prompt="...", # Required. Supports {{template_vars}}
system_prompt="...", # Optional. Does NOT support template vars
structured_output=..., # Optional. Boolean/Score/Categorical output, or a dict for custom JSON schema
provider="openai", # "openai" | "anthropic" | "azure_openai" | "vertexai" | "bedrock"
model="gpt-4o", # Model identifier
model_params={"temperature": 0.0}, # Optional. Passed to LLM API
name="eval_name", # Optional. Must match ^[a-zA-Z0-9_-]+$
)
user_promptのテンプレート変数:{{input_data}}、{{output_data}}、{{expected_output}}、{{metadata.key}} — EvaluatorContextのフィールドから、ネストされた辞書内のドットパスを使って解決されます。
構造化出力の型
ブール値 — 任意の合格/不合格判定を伴う真/偽:
BooleanStructuredOutput(
description="Whether the response is factually accurate",
reasoning=True, # Include reasoning field in LLM response
reasoning_description=None, # Optional custom description for reasoning field
pass_when=True, # True → pass when true, False → pass when false, None → no assessment
)
スコア — 任意のしきい値を伴う、範囲内の数値:
ScoreStructuredOutput(
description="Helpfulness score",
min_score=1, # Minimum possible score
max_score=10, # Maximum possible score
reasoning=True,
reasoning_description=None,
min_threshold=7, # Scores >= 7 pass (optional)
max_threshold=None, # Scores <= N pass (optional)
)
カテゴリ — 定義済みカテゴリから選択:
CategoricalStructuredOutput(
categories={
"correct": "The response correctly answers the question",
"partially_correct": "The response is partially correct but missing key information",
"incorrect": "The response is factually wrong or irrelevant",
},
reasoning=True,
reasoning_description=None,
pass_values=["correct"], # Which categories count as passing (optional)
)
カスタムJSONスキーマ — 多次元評価に対応する任意の構造化応答:
# Pass a raw dict as structured_output — used as the JSON schema directly
structured_output={
"type": "object",
"properties": {
"relevance": {"type": "boolean", "description": "Whether the response addresses the question"},
"confidence": {"type": "number", "description": "Confidence score (0.0 to 1.0)"},
"reasoning": {"type": "string", "description": "Explanation for the evaluation"},
},
"required": ["relevance", "confidence", "reasoning"],
"additionalProperties": False,
}
常に標準のJSONスキーマを記述してください。SDKがプロバイダーごとに自動で調整します(例:Anthropicは数値フィールドのminimum/maximumに対応していないため、SDKは範囲制約をdescriptionに移します。Vertex AIはconst/anyOfをenumに変換します)。解析されたJSON辞書全体が評価のvalueになります。"reasoning"キーがある場合は、自動的に抽出されます。合格/不合格の自動判定はありません。
LLMJudgeのプロンプトガイドライン
structured_outputパラメーターによって、JSONスキーマを使って応答形式を強制します。プロンプト内で形式を指定しないでください(「YES/NOで回答」「1~10で評価」など)。代わりに、評価基準を説明し、形式は構造化出力に任せてください。
- system_prompt:評価者の役割とアプリのドメインコンテキストを設定します。テンプレート変数には対応していません。
- user_prompt:
{{input_data}}/{{output_data}}を使ってデータを提示し、そのうえで、この評価軸において何が良く、何が悪いのかを説明します。
BaseEvaluator — カスタムコードベースの評価器
LLMによる判断が不要な決定論的チェックに使用します:
class MyEvaluator(BaseEvaluator):
def __init__(self, name=None, ...custom_params...):
super().__init__(name=name)
self._param = ... # Store config as private attrs
def evaluate(self, context: EvaluatorContext) -> EvaluatorResult:
# Access: context.input_data, context.output_data, context.expected_output, context.metadata
# Must NOT modify self attributes (thread safety)
passed = ... # Your logic here
return EvaluatorResult(
value=passed,
reasoning="...",
assessment="pass" if passed else "fail",
)
組み込み評価器
# Validate JSON syntax + optional required keys
JSONEvaluator(required_keys=["name", "age"], output_extractor=None, name=None)
# Validate length (characters, words, or lines)
LengthEvaluator(count_by="words", min_length=10, max_length=500, output_extractor=None, name=None)
# count_by: "characters" | "words" | "lines"
# String matching
StringCheckEvaluator(operation="contains", expected="success", case_sensitive=False, name=None)
# operation: "eq" | "ne" | "contains" | "icontains"
# Regex matching
RegexMatchEvaluator(pattern=r"\d{4}-\d{2}-\d{2}", match_mode="search", name=None)
# match_mode: "search" | "match" | "fullmatch"
評価器の種類を選択するための判断表
| シグナル | 評価器の種類 |
|---|---|
| 出力が有効なJSONである必要がある | JSONEvaluator |
| 出力が正規表現パターンに一致する必要がある | RegexMatchEvaluator |
| 出力に長さの制約がある | LengthEvaluator |
| 出力に特定の文字列が含まれる/含まれない必要がある | StringCheckEvaluator |
| 意味的な品質の判断(トーン、正確性、完全性) | LLMJudge + BooleanStructuredOutput |
| 尺度に基づく段階的な品質評価 | LLMJudge + ScoreStructuredOutput |
| カテゴリへの分類 | LLMJudge + CategoricalStructuredOutput |
| 多次元の判断(一度に複数の側面を評価) | LLMJudge + カスタムJSONスキーマdict |
| 複数のチェックを組み合わせる複雑なドメインロジック | BaseEvaluatorのサブクラス |
ソースの確認
ローカルでdd-trace-pyを利用できる場合は、対応するモジュールを読んでAPIを確認してください:
ddtrace.llmobs._evaluators.llm_judge—LLMJudge、BooleanStructuredOutput、ScoreStructuredOutput、CategoricalStructuredOutputddtrace.llmobs._experiment—BaseEvaluator、EvaluatorContext、EvaluatorResultddtrace.llmobs._evaluators.format—JSONEvaluator、LengthEvaluatorddtrace.llmobs._evaluators.string_matching—StringCheckEvaluator、RegexMatchEvaluator
ワークフロー
Phase 0: 入力とエントリーモードの解決
エントリーモードの検出:
| モード | シグナル | 動作 |
|---|---|---|
| コールドスタート | ml_app のみが指定されている(RCA も仮説もない) | オープンな探索を全面的に行います。アプリの機能を把握し、測定に値する品質の側面を特定し、網羅性を高めるための評価を提案します |
| RCA から開始 | 会話に RCA レポートが含まれている、またはユーザーが失敗の仮説を提示している | オープンな探索は省略し、既存の失敗分類を評価対象として使用します |
引数の解析:ml_app(最初のフラグ以外の引数)、--timeframe(デフォルトは now-7d)、--trace-limit(デフォルトは 20)、--sdk-code、--data-only、および --emit-dataset <path> フラグを抽出します。以下のように output_mode を設定します(3 つのモードフラグのうち設定できるのは最大 1 つです。複数指定されている場合はエラーにします)。
--emit-dataset <path>が設定されている →output_mode = emit_dataset。以降のワークフローのエントリーモード処理を省略し、以下の Phase 3D に直接進みます。--sdk-codeが設定されている →output_mode = sdk_code。--data-onlyが設定されている →output_mode = data_only。- それ以外 →
output_mode = publish(デフォルト。ユーザーの確認後に有効化するオンライン評価器を提案し、無効なドラフトとして作成します)。
解決手順:
-
ml_appが指定されていない場合 → ユーザーに尋ねます。 -
エントリーモードを自動検出します。
- 会話に RCA レポートが含まれている場合(「Failure Taxonomy」という見出し、構造化された失敗モード、または重大度の評価を探します) →
from_rca。分類を抽出します。 - ユーザーが自由形式の失敗仮説(例:「システムプロンプトにグラウンディングが不足している」)を提示している場合 →
from_rca。その仮説を評価対象の出発点として使用します。 - それ以外 →
cold_start。
- 会話に RCA レポートが含まれている場合(「Failure Taxonomy」という見出し、構造化された失敗モード、または重大度の評価を探します) →
-
timeframeが指定されていない場合 → デフォルトのnow-7dを使用します。 -
既存の評価カバレッジをマッピング —
output_mode = data_onlyの場合は省略します(照合する Datadog 評価プロジェクトがないため)。list_llmobs_evalsを呼び出します(組織全体を対象とし、結果はクライアント側でml_app == <ml_app>の項目に絞り込みます)。次に、各評価のsource=customについてget_llmobs_evaluator(eval_name=...)を呼び出し、プロンプトテンプレート、対象、サンプリング、フィルターを調べ、どの品質の側面をカバーしているかを推測します。すべての評価器呼び出しは単一のメッセージで行います(並列化します)。source=ootbの評価はスキップします。名前から内容が明らかであり、取得可能な設定がない場合もあります。この手順の完了時点で、カバレッジマップ
{eval_name → source, enabled, dimension}が完成しています。重複排除のため、これを Phase 2 に引き継ぎます。publishモードでは、既存のカスタム評価器で使われているテンプレート変数の規約も確認します(新しい評価スイートの一貫性を保つため)。オンライン評価器のテンプレートは、EvaluatorContextではなくスパン全体の JSON を対象に解決されます。サポートされている構文({{span_input}}、{{span_output}}、ドットパス、配列セレクター、フィルターアクセス子)については、「Publishing Conventions」の「Online Template Variables」セクションを参照してください。 -
ノートブックコンテキストの検出:現在の会話をスキャンし、
/eval-trace-rcaによって生成された Datadog ノートブックの URL を探します(パターン:https://app.datadoghq.com/notebook/{numeric-id})。見つかった場合はrca_notebook_urlとして保存し、数値 ID をrca_notebook_idとして抽出します。これは Phase 3 の後、新しいノートブックを作成する代わりに、そのノートブックへ評価器スイートを追加する方法を案内するために使用します。
Phase 1: トレースを調査して評価対象を特定
目標:本番トレースをサンプリングし、アプリの機能を把握し、測定に値する品質の側面を特定します。
コールドスタートパス
-
アプリをサンプリング:
search_llmobs_spans(query="@ml_app:\"<ml_app>\" @status:ok", root_spans_only=true, limit=50, from=<timeframe>)。評価対象となる出力がないエラースパンを除外するため、@status:okで絞り込みます。 セッションプローブ (セッションスコープの提案を判定します。publishモード):同じメッセージ内で、search_llmobs_spans(query="@ml_app:\"<ml_app>\" session_id:*", limit=20, from=<timeframe>)も呼び出します。- 1 件以上の結果 →
sessions_present = trueに設定します。異なるsession_idの値を記録し、特に同じsession_idが複数のtrace_idにまたがって現れるかどうかを確認します。このトレースをまたぐ span こそ、セッションスコープの評価器に値するコンテキストをセッションが持つことを示す実際のシグナルです。session_idが常に 1 つのトレースにしか対応しない場合、トレーススコープを超える価値はありません。 - 結果が 0 件 →
sessions_present = falseに設定します。セッションスコープの評価器は提案しないでください。提案には「セッションスコープは対象外 — サンプリングした span にsession_idがない」という 1 行の注記を記録します。
- 1 件以上の結果 →
-
アプリのプロファイルを作成し、評価対象の span を特定します:
trace_idごとにまとめたspan_idsに対してget_llmobs_span_detailsを呼び出します。content_infoを調べ、次のように分類します。シグナル アプリのプロファイル content_infoにmessagesがあるLLM/チャットアプリ content_infoにdocumentsがあるRAG アプリ span に agentkind が含まれるエージェントアプリ content_infoにmetadataがあるカスタムメタデータあり 1 つのトレース内に複数の span kind がある( get_llmobs_traceのagent+tool/retrieval+llm)マルチステップアプリ — 少なくとも 1 つのトレーススコープ評価器が評価器スイートに含まれる可能性が高い( publishモード)複数の trace_idにまたがって同じsession_idがある(セッションプローブから判定)マルチトレースセッション — 少なくとも 1 つのセッションスコープ評価器が評価器スイートに含まれる可能性が高い( publishモード、sessions_presentの場合に限定)エージェントアプリ/マルチステップアプリの場合は、2〜3 個のトレースで
get_llmobs_traceも呼び出し、span の階層全体を確認します。ルート span とサブ span のcontent_infoを比較します。次に、品質の候補となる各側面について、次の順序で2 つの質問をします。- 判定は複数の span に依存しますか?(例:忠実性は
retrievalspan のドキュメントとllmspan の回答の両方に依存します。目標達成はtool呼び出しの連鎖と最終応答に依存します。)依存する場合 →publishモードのトレーススコープにします。これを 1 つの span にまとめようとしないでください。 - (1)の答えが「いいえ」の場合に限り:その側面に対して最も豊富なシグナルを持つ単一の span を選びます(ルート span には要約があり、LLM サブ span にはシステムプロンプト全体、ツール呼び出しの結果、推論の連鎖があります)。
span kind のヒストグラム(agent + tool + llm + retrieval)を記録します。1 つのルート配下に複数の kind があることは、評価器スイートに少なくとも 1 つのトレーススコープ評価器が含まれることを示す強いシグナルです。必須となる標準的なトレーススコープのユースケースの確認手順については、Phase 2 の「Span vs. Trace vs. Session Scope Classification」を参照してください(
sessions_presentの場合は、標準的なセッションスコープのユースケースも確認してください)。 - 判定は複数の span に依存しますか?(例:忠実性は
-
コンテンツを抽出し、対象を特定します:代表的な span に対して
get_llmobs_span_contentを呼び出します。アプリのプロファイルに応じて、次のフィールドを取得します。アプリのプロファイル 取得するフィールド LLM/チャット messages(システムプロンプトにはpath=$.messages[0])、outputRAG documents、input、outputエージェント エージェント span に対して get_llmobs_agent_loopを呼び出し、その後詳細情報としてmessagesを取得メタデータを持つすべてのアプリ metadataすべての呼び出しを 1 つのメッセージ内で実行します。読み進めながら、2 種類のシグナルを記録します。
汎用的な品質シグナル — 「成功」とはどのような状態でしょうか?出力にはどのようなばらつきがありますか?観察された品質の各側面を評価器の候補とし、先ほど確認したトレースを根拠にします。また、安全性のシグナル(スコープ違反、出力内の機密データ、キャラクターにそぐわない応答)も探し、見つかった場合は安全性評価器を追加します。 ドメイン固有のシグナル — これらはフェーズ 2 で、最も効果の高いカテゴリである ドメイン固有の評価器 になります。トレースを 5〜10 件読むごとに、次の点を書き留めます。
- 繰り返し現れるインテント/質問カテゴリ — このアプリはどのような種類のリクエストを処理しますか?(
applying for benefit X、comparing flight options、summarizing a policy、creating a widget) - アプリが出力するエンティティ — URL、機関名/会社名、コード識別子、金額、日付、ID、ファイルパス、電話番号などです。ユーザーが後続の作業で実際に 使用する もの(正確性を評価する評価器を用意する価値があります)と、単なる参照情報を区別します。
- ツール引数の形式(エージェントアプリの場合)— エージェントが呼び出す各ツールの名前と、入力の大まかなスキーマを記録します。複雑なスキーマ(フィールドが 3 つ以上、または構造化型)を持つツールは、引数の正確性を評価する評価器の候補です。
- ペルソナ/口調のルール — アプリは常に情報源を引用しますか?特定のトピック(医療、法律、金融に関する助言)を常に拒否しますか?特定の口調で話しますか?観察した出力で暗黙的に守られているルールを抽出します。
- ドメイン固有の失敗モード — 架空の識別子、古いポリシーへの言及、通貨/ロケールの不一致、ID の 1 つずれ、単位の誤りなどです。観察された例が 1 件あれば、候補となる評価器を考えるきっかけになります。
トレースを読む前に、ドメイン固有のシグナルを網羅的に列挙しようとしないでください。読み進めるうちにパターンが見えてきます。この探索ステップの目標は、網羅性ではなく、最終的な提案の幅を広げることです。
- 繰り返し現れるインテント/質問カテゴリ — このアプリはどのような種類のリクエストを処理しますか?(
RCA パスの場合
-
RCA レポートから失敗の分類を抽出します。重大度が「高」または「中」の各失敗モードを評価対象にします。また、フェーズ 1 の セッションプローブ(
query="session_id:*")を実行してsessions_presentを設定します。複数のトレースにまたがる会話でのみ発生する失敗(コンテキストの消失、同じ誤りの繰り返し、募る不満)は、セッションスコープの対象です。 -
インフラの障害が原因のカテゴリを確認します。 評価器を提案する前に、分類表の Root Cause 列を確認し、
Instrumentation Deficiency、Harness Deficiency、Runtime Error、Upstream Data Issue、またはモデルの動作ではなくインフラ/環境を示すその他の根本原因がないか確認します。該当するものがあれば、いったん作業を止めて、次のように尋ねます。「一部の失敗モードは、モデルの動作ではなく、インフラまたは計測の問題と診断されました(例:
{list the infra root causes})。評価器は、次の 2 通りの方法で設計できます。- 動作を対象とする評価(継続的な品質評価に推奨):モデルが正確で具体的な出力を生成するかを測定します。インフラ修正後に、実際の品質を追跡するのに役立ちます。
- 生成物を対象とする評価(リグレッションガードとして有用):観察された特定の不適切な出力(例:汎用プレースホルダー応答)を検出します。インフラが再び壊れた場合に、リグレッションを捕捉できます。
どちらの方法を希望しますか?それとも両方ですか?」
- 動作を対象とする評価の場合:不適切な出力の特徴ではなく、正しい出力の特徴に基づいて評価器を設計します。品質基準には、RCA の
expected_output/ゴールドスタンダードの例を使用します。 - 生成物を対象とする評価の場合:特定の失敗の兆候を検出する評価器(既知の不適切な文字列を検出する
StringCheckEvaluator、汎用プレースホルダーをチェックするLLMJudgeなど)を設計します。 - 両方の場合:各カテゴリを明確に分けて提案します。
根本原因がすべて動作に関するもの(System Prompt Deficiency、Tool Gap、Tool Misuse、Retrieval Failure など)であれば、このステップを省略して次に進みます。
-
各対象について、RCA にトレース ID が含まれている場合はそれを直接使用します。含まれていない場合は、該当するトレースを検索します。
get_llmobs_span_contentを使用して対象ごとに 2〜3 件のトレースを取得し、具体的なパターンを把握します。
フェーズ 2:評価器スイートの提案
目標:ユーザーに確認してもらうため、具体的な評価器の提案を提示します。
sdk_code/data_only モードでは、また publish モードの eval_scope: span では、各評価器が判定するのは 1 つのデータポイント です。つまり、トレース全体やバッチではなく、単一のレコード/スパンの入力と出力を判定します。publish モードでは、eval_scope: trace はトレース全体を、eval_scope: session は複数のトレースで構成されるセッション全体を判定します。これらはトレース/セッションのペイロードを対象に設計してください(下記の「スパン/トレース/セッションのスコープ分類」を参照)。評価器はそれぞれのスコープに合わせて設計してください。
対象は output_mode によって異なります。
sdk_code/data_only→ オフライン実験。テンプレート変数にはEvaluatorContextのフィールドを使用します({{input_data}}、{{output_data}})。実際のデータ形状は、ユーザーのデータセットとタスク関数によって異なります(SDK リファレンスの EvaluatorContext に関する注記を参照)。publish→ 本番スパンのオンライン評価。テンプレート変数は、ドットパス({{meta.input.value}}、{{meta.output.messages[*].content}}、…)またはスパンの種類に対応した組み込みエイリアス({{span_input}}、{{span_output}})を使って、スパン全体の JSON を参照します。eval_scope: traceとeval_scope: sessionでは、代わりにテンプレートはトレースのペイロード({{spans[...]}})またはセッションのペイロード({{traces[*].spans[...]}})を参照します。完全な構文については、「Publishing Conventions」の「Online Template Variables」を参照してください。各評価器にはeval_scope、sampling_percentage、および(任意で)filterも必要です。公開前にユーザーが確認できるよう、これらも提案テーブルに表示してください。セッションスコープは、フェーズ 1 のプローブでsessions_presentが設定された場合にのみ使用します。
提案は、最も広範なシグナルから最も細かなものへと並べます。幅広く提案し、ユーザーが取捨選択できるようにしてください — 下記の「提案する評価器の数」を参照してください。
-
ドメイン固有の評価器 — この特定のアプリにおいて、「良い」とは何でしょうか。一般的な評価器では見逃される品質基準を捉えられるため、これらは最も効果の高い提案です。フェーズ 1 で収集したドメインシグナルから導き出してください。
- アプリが扱う繰り返し現れるインテント/質問カテゴリ(例:「連邦給付金の申請」「フライトの選択肢の比較」「ポリシーの説明」)。主要なインテントを対象にした
intent_classificationまたはintent_handling_correctness評価器を提案します。 - アプリが生成する特定のエンティティ(URL、機関名、コード識別子、金額、日付、ID)。誤りがあった場合に実際の下流コストにつながるものについて、エンティティごとの正確性評価器を提案します(例:
cited_url_is_real、agency_name_matches_request、monetary_amount_is_consistent_with_input)。 toolスパン全体で確認されたツール引数の形状。複雑なスキーマを持つツールについて、ツールごとの引数正確性評価器を提案します(例:search_flights_args_match_user_request、update_dashboard_widget_targets_correct_widget)。- ペルソナ/声の期待値 — アプリは常に出典を引用しますか、対象外のリクエストを必ず拒否しますか、特定の口調で話しますか。確認された出力から抽出できる声のルールについて評価器を提案します(
cites_a_source、refuses_medical_advice、tone_matches_brand)。 - トレース全体で見られるドメイン固有の失敗パターン(識別子の捏造、古いポリシーへの言及、単位の不一致、通貨/ロケールの不一致)。繰り返し現れる失敗パターンごとに 1 つの評価器を提案します。
技術的なチェック内容ではなく、ユーザーが重視する懸念事項に基づいて各評価器に名前を付けます(
regex_url_matchよりagency_url_is_real)。フェーズ 1 で確認したトレース ID を根拠として使い、成功例と失敗例の両方を確認できた場合は、評価器ごとに少なくとも 1 件ずつ示してください。 - アプリが扱う繰り返し現れるインテント/質問カテゴリ(例:「連邦給付金の申請」「フライトの選択肢の比較」「ポリシーの説明」)。主要なインテントを対象にした
-
結果評価器 — このスパン/トレースはリクエストに対して良い結果を生みましたか?
- 例:
task_completion、answer_correctness、response_groundedness
- 例:
-
形式評価器 — 出力は構造上の要件を満たしていますか?
- 例:
valid_json_output、response_length、citation_format
- 例:
-
安全性評価器 — 出力は適切な境界を守っていますか?
- 例:
no_pii_leakage、scope_adherence、no_hallucination
- 例:
提案する評価器の数
以前のスキルバージョンでのデフォルトの上限 4-6 は厳しすぎました。そのため、スキルが汎用的な評価器に偏り、ドメイン固有のシグナルを十分に活用できていませんでした。ガイダンスを次のように更新しました。
- 提案には8~15個の評価器を目指してください。4つすべてのカテゴリに分散させます(通常、ドメイン固有が最も多く、次に成果、フォーマットと安全性は少なめです)。単純な単一LLM呼び出しのアプリなら少なくても構いません。ドメイン固有のシグナルが豊富なエージェント/RAGアプリなら、上限に近い数を目指してください。
- 汎用性より品質を優先:ドメイン固有の提案はすべて、サンプル取得したトレースで観察されたパターンを少なくとも1つ根拠としてください。根拠がない場合、汎用的なドメイン評価器("
response_quality"など)を考案しないでください。 - ユーザーが取捨選択できるようにする:以下の必須チェックポイントでは、ユーザーに単に承認を求めるのではなく、該当しないものを削除するよう明示的に求めています。提案は、ユーザーが絞り込む候補セットとして扱ってください。
既存のカバレッジとの重複排除
data_only モードの場合:このセクションは完全にスキップしてください(Phase 0でカバレッジマップを作成していません)。そのまま提案テーブルに進みます。
提案を作成する前に、Phase 0のカバレッジマップを適用してください。カバレッジのキーは(ディメンション、スコープ)の組み合わせです。ディメンション単独ではありません。OOTB評価器はすべてスパンスコープで実行され、有効になっているOOTB評価があっても、同じディメンションのトレーススコープまたはセッションスコープの評価器を提案できます。3つのスコープはそれぞれ異なる問いに答えます。
-
ディメンションDについて、有効なスパンスコープ評価(OOTBまたはカスタム)がある場合:
- Dはスパンスコープですでにカバーされているため、新たなスパンスコープ評価器を提案しないでください。
- トレースまたはセッションの構造上必要な場合(複数ステップのアプリ、または複数トレースのセッションなど。判断にはスパン間またはトレース間のコンテキストが必要です)、トレーススコープまたはセッションスコープの評価器を提案してください。その関係を根拠に記載します。例:「OOTBの
Goal Completenessは各LLMスパンを個別に評価します。このトレーススコープのgoal_completionは、エージェントの一連のステップ全体でユーザーのリクエストを達成できたかを確認し、セッションスコープのsession_goal_completionは会話全体を対象に確認します。それぞれ異なる問いに答えます。」
-
ディメンションDについて、有効なトレーススコープのカスタム評価がある場合:同じディメンションに対して別のトレーススコープ評価器を提案しないでください。これは実際の重複です。データが単一スパンにも適合するなら、同じディメンションのスパンスコープ評価器は引き続き提案できます。また、そのディメンションにトレース間のコンテキストも必要なら、セッションスコープも提案できます。同様に、Dについて有効なセッションスコープのカスタム評価がある場合、重複するのは同じDの別のセッションスコープ評価のみです。スパンスコープとトレーススコープは引き続き提案できます。
-
無効なOOTB評価:そのディメンションについて、新しいカスタムのスパンスコープ評価器を提案しないでください。代わりに、提案内の短い注記で取り上げ、重複を作成するのではなく、Datadog UIで有効にすることを提案してください。例:
hallucination(OOTB、無効)— カスタムのスパンスコープ評価を作成するのではなく、Datadog UI(Evaluations → Configure)での有効化をご検討ください。(トレーススコープのrag_faithfulnessは引き続き対象であり、異なる問いを扱います。) -
不足箇所の特定:提案の冒頭に、カバレッジの要約を1行で記載してください。「既存のカバレッジ:評価器がN個設定済みです({names}。特記がない限りすべてスパンスコープ)。カバーされていないディメンションとスコープについて評価器を提案します。」
-
すべてのディメンションがカバーされている場合:ディメンションが「完全にカバーされている」とみなされるのは、アプリの構造に応じて必要なスコープ(スパンに加えて、トレースおよび/またはセッション)が揃っている場合のみです。カバレッジマップですべての特定済み品質ディメンションが適切なスコープでカバーされている場合は、その旨を明示し、ユーザーに希望する対応を確認してください:(a)既存の評価プロンプトを確認/改善する、(b)追加のディメンションをカバーする、(c)それでも先に進む。
提案する各評価器について:
- Name:
^[a-zA-Z0-9_-]+$に一致する必要があります(英数字、アンダースコア、ハイフンのみ) - Type:
LLMJudge(Boolean/Score/Categorical/カスタム JSON スキーマ)、組み込み(JSONEvaluator、RegexMatchEvaluatorなど)、またはBaseEvaluatorのサブクラスです。publishモードでは、MCP ツールがサポートするのは LLM judge 評価器のみです。コードベースのチェックを暗黙に除外してはいけません。同じ提案テーブルにTypeをコードベースのクラス名として記載し、提案内の「このモードでは公開不可」サブセクションに含め、ユーザーにリクエストに応じてオフラインコード(--sdk-code、または公開実行後に依頼)として、あるいは--data-only仕様として取得できることを伝えてください。コードベースの提案も、件数とカバレッジの算出ではスイートの一部として扱ってください。 - 評価対象:何を測定するかを、平易な言葉で1~2文で説明します。
- 対象スパン:評価器が対象とするスパンのデータを指定します(例:「ルートのエージェントスパン」、「LLM のサブスパン
anthropic.request」、「すべてのllmスパン」)。品質の評価軸に対してルートスパンの I/O の情報が不十分な場合(例:ツール呼び出しの結果が表示されない場合)は、その旨を記載し、シグナルを含むサブスパンを指定してください。publishモードでは、これはeval_scope(span/trace/session)、root_spans_only、および EVP のfilterクエリ(例:@meta.span.kind:llmやservice:web)の組み合わせに対応します。 - 合格/不合格の基準:
pass_when=True、min_threshold=7、pass_values=["correct"]、またはカスタム JSON スキーマの場合は「自動評価なし」です。 - テンプレート変数:使用する
input_data、output_data、expected_output、metadata.*(オフラインの場合)、または取得元となるスパンのパス/エイリアス(公開モードの場合:{{span_input}}、{{span_output}}、{{meta.input.messages[*].content}}、{{meta.metadata.<key>}}など)を指定します。 - 根拠:失敗を検出できた、または正しい動作を確認できたトレースを少なくとも1つ示します。
- 公開時のみのフィールド(
publishモードの場合のみ):integration_provider(デフォルトopenai)、model_name(デフォルトgpt-5.4-mini)、sampling_percentage(デフォルト10)、eval_scope(デフォルトspan)、および適切なスパンに対象を絞るために必要なfilterクエリです。公開前にユーザーが上書きできるよう、提案にはデフォルト値を明示してください。 integration_account_id(publishモードの場合のみ):judge LLM の呼び出しに使用するインテグレーションアカウントです。同じ ml_app 内の既存の評価器から自動検出されます(Phase 0 のカバレッジマップ)。UUID をそのままユーザーに尋ねてはいけません。既存の評価器に該当するアカウントがない場合は、このフィールドを省略し、有効化の前に UI でアカウントを選択してもらいます。いずれの場合も、すべての評価器はenabled: falseで公開されます。有効化のワークフロー全体については、Phase 3C の「常にドラフトとして公開」を参照してください。
スパン/トレース/セッションのスコープ分類(publish モード)
ユーザーには尋ねず、評価器ごとに分類し、チェックポイントで変更できるようにしてください。
必須:まず、トレーススコープに関する4つの代表的なユースケースを確認する
Phase 1 で複数ステップのトレース(スパン種別が2つ以上、または agent ルートの下に tool/retrieval/workflow スパンがある)が見つかった場合は、スイートを最終決定する前に、以下のトレーススコープに関する4つの代表的なユースケースを必ず確認してください。それぞれについて、該当する(eval_scope: trace を含める)か、該当しない(提案内の「スキップしたトレーススコープ候補」サブセクションに1行で理由を記載する)かを明確に判断してください。項目ごとの理由を示さずに4つすべてをスキップする場合、スパンスコープに偏りすぎている可能性があります。再確認してください。
| 代表的なユースケース | 適用される条件 |
|---|---|
goal_completion — エージェントはユーザーの依頼を完了したか? | エージェントアプリ、または複数ステップのアプリの場合。ほぼ常に該当します。 |
tool_use_correctness — 適切なツールを適切な引数で使用したか? | トレースに tool 種別のスパンが含まれる場合。 |
rag_faithfulness — 回答は取得したドキュメントに基づいているか? | トレースに retrieval 種別のスパンが含まれる場合。 |
conversation_quality — 複数ターンにわたる LLM 呼び出しの一貫性はあるか? | トレースに llm スパンが2つ以上含まれる場合、またはアプリが複数ターンのセッションを計測する場合。 |
必須:セッションスコープの標準的なユースケースを確認する(sessions_present が設定されている場合のみ)
条件: Phase 1 のセッションプローブで sessions_present = true が設定された場合に限り、この確認を実施します。セッションが存在しない場合は、セッションスコープを完全にスキップし、提案に「session scope skipped — no session_id on sampled spans」と記載します。
sessions_present が設定されている場合、以下の4つの標準的なセッションスコープのユースケースを必ず確認してください。それぞれについて、明確に判断します:該当する(eval_scope: session を指定して含める)または該当しない(「スキップしたセッションスコープ候補」サブセクションに理由を1行で記載)。セッションスコープは、同じ session_id の下で複数のトレースにまたがる質問に答えます。単一のトレースでは、前後のターンを確認できません。
| 標準的なセッションユースケース | 該当する条件 |
|---|---|
session_goal_completion — セッション全体を通してユーザーの目標は達成されたか? | 1つの session_id が2つ以上のトレースにまたがる場合。複数トレースのセッションでは、ほぼ常に該当します。 |
multi_turn_conversation_quality — ターン間の一貫性、記憶、安定したトーン | 複数トレースにまたがるチャット/アシスタントのセッション。 |
user_frustration_signals — セッションを通じた不満、混乱、同じ質問の繰り返し、中断 | 複数ターンのセッション全般(トレースをまたいで質問が繰り返されたり、言い換えられたりする場合)。 |
agent_consistency_across_session — エージェントはトレース間で一貫性を保ち、エラーから回復できたか? | セッションが2つ以上のトレースにまたがるエージェントアプリ。 |
その他の評価器(例:トーン、形式、安全性)については、以下の順にスコープを判定します。
- 1つのスパンの
meta.inputとmeta.outputだけから、正しく判定できますか?ここで「正しく」とは、トレース内の他のスパンを考慮しても判定結果が変わらないことを意味します。→eval_scope: span。 - それ以外の場合、単一のトレースのスパンから判定できますか?→
eval_scope: trace。評価器名に grounding、faithfulness、hallucination、completeness、correctness across steps、consistency、workflow が含まれる場合は、デフォルトで trace にします。これらはほぼ常にスパンをまたいだコンテキストを必要とします。 - それ以外の場合、同じ
session_idにおける複数のトレースのコンテキスト(全体的な満足度、時間の経過に伴う振る舞い、複数ターンの一貫性)が必要で、かつsessions_presentが設定されていますか?→eval_scope: session。名前に session、conversation、across turns、over time、satisfaction、frustration、abandonment が含まれる場合は、デフォルトで session にします。sessions_presentが false の場合は trace スコープに切り替え、制限事項を記載します。
トレードオフ(選択を左右する主な要因にしないでください)
Trace スコープは span スコープよりコストが高くなります。完了したトレースごとに1回の判定(対象となるスパンごとではありません)、より大きなプロンプトペイロード、3分のトリガー遅延が発生します(Datadog はトレースの完了を判断する前に3分間の非アクティブ状態を待機し、その後に到着したスパンは除外されます)。Session スコープは最もコストがかかります。完了したセッションごとに1回の判定が行われ(session_id は30分間の非アクティブ状態の後に完了します。トレースの場合は3分です。前のスパンから30分を超えて到着したスパンは除外されます)、ペイロードも最大になります(セッション内のすべてのトレースの全スパン。上限は10,000スパン)。これらはコスト管理のための調整項目です。スコープを下げるのではなく、sampling_percentage と filter で対処してください。スコープを決めるのは評価の正確さです。
分類を明確にする
提案表に Scope 列を追加し、評価器ごとに1文の根拠を添えてください。代表的なトレーススコープ またはセッションスコープ のユースケースを省略した場合は、その理由とともに、対応する「省略した…スコープの候補」サブセクションに記載してください。ユーザーに表示され、必要に応じて変更できます。
根拠の例:
tone_check— span。単一の応答が丁寧かどうかの判定には、1つの LLM スパンのmeta.output.messages[*].contentだけで十分です。トレース内のほかのスパンによって判定が変わることはありません。goal_completion— trace。エージェントがユーザーの依頼を完了したかどうかは、ツール呼び出しの順序と最終的な LLM の応答を組み合わせて判断します。単一のスパンのmeta.outputには、そのステップの出力しか示されません。tool_use_correctness— trace。ツール入力を依頼や最終応答と照合するには、3つ以上のスパン(ルート、ツール、最終 LLM)を関連付ける必要があります。rag_faithfulness— trace。グラウンディングでは、retrievalスパンのドキュメントと LLM スパンの回答を組み合わせます。session_goal_completion— session。ユーザーの全体的な目標が達成されたかどうかは、最後のトレースだけでなく、session_id内のすべてのトレースに依存します。会話全体を把握できるのはセッションスコープだけです。user_frustration_signals— session。フラストレーションは、トレースをまたいだ依頼の繰り返しや言い換えとして表れます。単一のトレースでは、そのパターンを把握できません。「省略したトレーススコープの候補」の記載例:
conversation_quality— 省略:トレースに含まれるのは単一の LLM 呼び出しのみです(このアプリの計測では複数ターンの兆候がありません)。「省略したセッションスコープの候補」の記載例:
session_goal_completion— 省略:各session_idが単一のトレースに対応しています(トレースをまたぐコンテキストがないため、トレーススコープですでにカバーされています)。
必須チェックポイント
提案を出力し、先に進む前にユーザーの確認を待つ必要があります。
## Proposed Evaluator Suite
**App profile**: {LLM | RAG | Agent | Multi-agent}
**Entry mode**: {cold_start | from_rca}
| # | Name | Type | Scope | Measures | Pass Criteria |
|---|------|------|-------|----------|---------------|
| 1 | task_completion | LLMJudge (Boolean) | span | Whether the task was completed on this span | pass_when=True |
| 2 | tool_use_correctness | LLMJudge (Categorical) | trace | Right tool with right arguments across the agent run | pass_values=["correct"] |
| 3 | session_goal_completion | LLMJudge (Categorical) | session | Whether the user's goals were met across the whole multi-trace session | pass_values=["completed"] |
| 4 | ... | ... | ... | ... | ... |
(Drop the **Scope** column when not in `publish` mode.)
For each evaluator:
- **{name}**: {what it measures}
- Target span: {which span's data it was designed for}
- Rationale: {which quality dimension it covers and why}
- {Only in publish mode:} Scope: {span | trace | session} — {one-sentence rationale}
- Evidence: [Trace {id_short}](https://app.datadoghq.com/llm/traces?query=trace_id:{full_id})
{Only in publish mode, for multi-step apps. Required if any of the four canonical trace-scope use cases was not included above:}
**Skipped trace-scope candidates:**
- `{canonical_use_case}` — {one-line reason it does not apply to this app}
{Only in publish mode, when `sessions_present`. Required if any of the four canonical session-scope use cases was not included above:}
**Skipped session-scope candidates:**
- `{canonical_use_case}` — {one-line reason it does not apply, e.g. "every `session_id` maps to a single trace"}
{Only in publish mode, when the session probe found no sessions:}
**Session scope skipped** — no `session_id` on sampled spans; session-scope evaluators not proposed.
{Only in publish mode, when the suite contains code-based evaluators (JSONEvaluator, RegexMatchEvaluator, LengthEvaluator, StringCheckEvaluator, BaseEvaluator). Required when any code-based proposal exists.}
**Not publishable in this mode** (code-based evaluators — the publish API is LLM-judge only):
- `{name}` ({type}) — {what it would check}. Ask me to emit these as offline SDK code (or run `/eval-bootstrap {ml_app} --sdk-code`), or `/eval-bootstrap {ml_app} --data-only` for a framework-agnostic JSON spec.
どの評価器を生成しますか? 提案は候補のセットとして扱ってください。以下のスイートは意図的に幅広くしてあるため、チームの品質基準にとって重要なものを選べます。残すもの、削除するもの、名前を変更するものを返信してください。ドメイン固有の提案がすべて、優先事項に合うとは限りません。sdk_code モードでは、カスタム評価器の追加やプロバイダー/モデルの変更もできます。publish モードでは、評価器ごとに integration_provider、model_name、sampling_percentage、eval_scope、root_spans_only、filter を変更できます。(デフォルトの publish モードでは、確認後に Datadog でオンラインの下書きとして作成されます。UI で内容を確認して有効化してください。オフラインの SDK コードまたは JSON 仕様を希望しますか?その場合は、確認済みのスイートをその形式で生成します。)
ユーザーが確認するまで、コード生成に進まないでください。
フェーズ 3:出力を生成する
output_mode に応じて分岐します。
publish(デフォルト) → フェーズ 3C に進みますsdk_code→ 以下の フェーズ 3A に進みますdata_only→ フェーズ 3B に進みますemit_dataset→ フェーズ 3D に進みます(フェーズ 0 のステップ 4、フェーズ 1、フェーズ 2 は省略します。データセットモードのワークフローについてはフェーズ 3D を参照してください)
リクエストに応じたコード生成(公開処理の実行後)
デフォルトのパスでは、評価器をオンラインで公開します。その後、ユーザーがオフラインコードまたはポータブルな仕様としてスイートを求めた場合(例:「これらの SDK コードを生成してください」「JSON 仕様をください」)、Phase 1~2 を再実行しないでください。すでに確認済みの評価器スイートを再利用し、各公開済みオンライン評価器をオフライン形式に変換して、直接Phase 3A(sdk_code)またはPhase 3B(data_only)に進みます。
- オンラインのプロンプトテンプレート(span/trace/session のプレースホルダー)は、汎用の
{{input_data}}/{{output_data}}プレースホルダーを使うオフラインの LLMJudge になります(EvaluatorContext の注記を参照。オフラインデータは span ではなく、ユーザーのデータセット/タスク関数から取得されます)。ルーブリックと合格基準は維持します。 - オンラインでは公開できなかったコードベースのチェック(「このモードでは公開できません」のセット)は、ここでは実際の
BaseEvaluator/組み込み評価器として出力されます。
逆の場合も可能です。--sdk-code から始めたユーザーは、確認済みのスイートをオンラインで公開するよう依頼できます(Phase 3C)。emit_dataset パスは評価器ワークフローとは別であり、変換対象となる「確認済みスイート」はありません。
Phase 3A:評価器コードを生成して書き込む
目標:最終的な .py ファイルを生成し、ディスクに書き込みます。
確認済みの評価器ごとに、上記の SDK リファレンスのパターンに従って、本番品質の Python コードを生成します。
コード生成のルール
-
プロンプトは trace に基づける:LLMJudge のシステムプロンプトとユーザープロンプトでは、本番 trace で実際に確認されたパターンを参照する必要があります。「応答が適切かどうかを評価する」といった汎用的なプロンプトは決して書かず、アプリのドメイン、確認された失敗パターン、成功基準に基づけてください。
-
テンプレート変数は汎用的なものにし、コンテキストはコメントで追加する:プロンプトの最上位プレースホルダーとして
{{input_data}}と{{output_data}}を使用してください。{{input_data.messages[-1].content}}のようなネストされた span パスは参照しないでください。評価器のデータは span から直接ではなく、ユーザーのデータセットとタスク関数から取得されます。その代わり、各評価器の上に、その評価器がどのようなデータを対象に設計されているか、またユーザーが何を調整すべきかを説明するコメントを追加してください。# Designed for: input_data = user query, output_data = assistant response text # Observed from: root agent span (input.value → output.value) # If your dataset uses a different structure, adapt the prompt references below. -
最も限定的な評価器タイプを使用する:
JSONEvaluator、RegexMatchEvaluator、StringCheckEvaluator、LengthEvaluatorでチェックできる場合は、LLMJudge を使用しないでください。コードベースの評価器は高速で、低コストで、決定論的です。 -
BaseEvaluator のサブクラス:
__init__でsuper().__init__(name=name)を呼び出しますevaluate()からEvaluatorResultを返しますevaluate()内でインスタンス属性を変更しないでください(スレッドセーフのため)
-
名前:
^[a-zA-Z0-9_-]+$に一致させる必要があります。説明的な snake_case 名を使用してください。 -
インポート:ファイルの先頭にまとめます。実際に使用するクラスのみをインポートしてください。
-
評価器リスト:ファイルの末尾で、すべての評価器を
evaluatorsリストにまとめます。 -
PII の匿名化:LLMJudge のプロンプトまたはヘッダーコメントに含める trace の内容から、メールアドレス、名前、機密データを削除します。
出力形式
生成する .py ファイルは、次の構造に従ってください。
"""
Auto-generated evaluators for {ml_app}
Generated: {YYYY-MM-DD} by eval-bootstrap
App profile: {LLM | RAG | Agent | Multi-agent}
Quality dimensions covered:
- {target_name}: {description}
Evidence: https://app.datadoghq.com/llm/traces?query=trace_id:{full_id}
...
Usage:
from ddtrace.llmobs import LLMObs
experiment = LLMObs.experiment(
name="my-experiment",
task=my_task_fn,
dataset=dataset,
evaluators=evaluators,
)
experiment.run()
"""
{imports — only what is used}
# --- Outcome Evaluators ---
{evaluator code}
# --- Format Evaluators ---
{evaluator code}
# --- Safety Evaluators ---
{evaluator code}
# --- Evaluator Suite ---
evaluators = [
{eval_1_variable_name},
{eval_2_variable_name},
...
]
評価器があるカテゴリに限り、セクションコメント(Outcome/Format/Safety)を含めてください。
ファイルを書き込む
生成したコードを出力先に書き込みます(指定がない場合は ./evals/{ml_app}_evaluators.py を推奨)。その後、概要を表示します。
## Generated Evaluators
Wrote {N} evaluators to `{output_path}`:
| # | Name | Type | Covers |
|---|------|------|--------|
| 1 | ... | ... | ... |
### Next Steps
1. **Review**: Check the generated prompts and criteria match your expectations
2. **Test offline**: Use `LLMObs.experiment(evaluators=evaluators)` to batch-evaluate against a labeled dataset and verify scores
ノートブックへのエクスポート(要約の後)
要約を表示した後、ノートブックへのエクスポートを提案します。
-
Phase 0 で
rca_notebook_urlが検出された場合:このセッションでは、先ほど RCA ノートブックが作成されました:
{rca_notebook_url}評価スイートの要約をこのノートブックに(a)追加しますか、それとも(b)新しい単独のノートブックを作成しますか?追加する場合:下記のノートブック作成フォールバック手順に従い、
mcp__datadog-mcp__edit_datadog_notebook(id={rca_notebook_id}、append_only=true、評価スイートの要約セル)を使用します。新規作成する場合:下記のノートブック作成フォールバック手順に従い、
mcp__datadog-mcp__create_datadog_notebookを使用します。 -
rca_notebook_urlがない場合:この評価スイートの要約を Datadog ノートブックにエクスポートしますか?
はいの場合:下記のノートブック作成フォールバック手順に従い、
mcp__datadog-mcp__create_datadog_notebookを使用します。name:Eval Bootstrap: {ml_app} — YYYY-MM-DDtype:reportcells:評価スイートの要約を含む単一の Markdown セルtime:{ "live_span": "1h" }
ノートブック作成のフォールバック手順(すべてのcreate_datadog_notebook/edit_datadog_notebook呼び出しに適用):
- まず MCP ツールを試します。
- MCP 呼び出しが失敗した場合、エラーを確認します。
- 認証/権限エラー(401、403) → 中止してユーザーに伝えます。
- フィールド検証エラー(エラーに特定のフィールド名が含まれる)→ そのフィールドを修正し、MCP 呼び出しを1回だけ再試行します。
- その他のエラー(バインディング、シリアル化、予期しない応答)→ pup にフォールバックします。
- ペイロードを完全な API エンベロープとして
/tmp/nb_bootstrap_{ml_app}.jsonに書き込みます:{"data": {"attributes": {"name": "...", "time": {...}, "cells": [...]}, "type": "notebooks"}} pup notebooks create --file /tmp/nb_bootstrap_{ml_app}.jsonを実行します。- pup も利用できない場合は、ノートブックの内容をチャットに Markdown として表示します。
- ペイロードを完全な API エンベロープとして
- どちらかの方法で作成に成功した後、URL を出力します。
Evaluator suite exported to notebook: <url>
ノートブックのセル内容 — Markdown セルには次の内容を含めます。
## Eval Bootstrap: {ml_app}
**Generated**: YYYY-MM-DD | **App profile**: {LLM | RAG | Agent | Multi-agent} | **Entry mode**: {cold_start | from_rca}
**Generated code**: `{output_path}`
{One sentence: what does this app do?}
**Coverage**: {N} new evaluators ({comma-separated dimension names}) | {N} existing (unchanged: {names}) | {gaps if any: dimensions identified but not covered, and why}
### Evaluator Suite
| # | Name | Type | Measures | Pass Criteria |
|---|------|------|----------|---------------|
| 1 | ... | ... | ... | ... |
### Evidence
{For each evaluator: name — 1-line description — [Trace link]}
### Next Steps
1. Review generated prompts in `{output_path}`
2. Run against a labeled dataset to validate scores
3. Deploy to Datadog LLM Experiments
Phase 3B:評価仕様 JSON の生成と書き込み
目標:確定した評価スイートと代表的なトレースサンプルを、SDK への依存なしで単一の自己完結型 JSON ファイルにシリアル化します。
出力先:./evals/{ml_app}_eval_spec.json
JSON スキーマ
{
"schema_version": "1",
"generated_at": "<ISO 8601 UTC>",
"generated_by": "eval-bootstrap",
"app": {
"ml_app": "<string>",
"app_type": "LLM | RAG | Agent | Multi-agent",
"trace_window": "<timeframe param, e.g. now-7d>",
"trace_count": "<integer>"
},
"evaluators": [
{
"name": "snake_case_name",
"category": "outcome | format | safety",
"type": "llm_judge | code_check",
"description": "<1-2 sentence plain-language description>",
"target_span": "<which span: root, llm sub-span, etc.>",
"scoring": {
"scale": "boolean | score_1_10 | categorical",
"categories": ["<only present when scale=categorical>"],
"pass_criteria": "<human-readable: true, >= 7, in [correct], etc.>"
},
"rubric": "<full prompt text for llm_judge; null for code_check>",
"implementation_hints": {
"type_if_code_check": "json_valid | regex | contains | length_words | null",
"pattern_if_code_check": "<pattern string or null>",
"notes": "<optional framework-agnostic implementation guidance>"
},
"evidence": [
{
"trace_id": "<32-char hex>",
"span_id": "<16-char hex>",
"url": "https://app.datadoghq.com/llm/traces?query=trace_id:<trace_id>",
"observation": "<why this trace illustrates the evaluator>"
}
]
}
],
"sample_records": [
{
"trace_id": "<string>",
"span_id": "<string>",
"input": {},
"output": "<string>",
"suggested_labels": {
"<evaluator_name>": "pass | fail | <score>"
}
}
]
}
フィールドの説明
evaluators[].type:意味的な評価には"llm_judge"、決定論的なチェック(正規表現、長さ、JSON の妥当性など)には"code_check"を使用します。evaluators[].rubric:llm_judgeの場合は、観測されたトレースパターンに基づく完全なプロンプトテキストを指定します。汎用プレースホルダーとして{{input}}と{{output}}を使用します({{input_data}}は使用しないでください。これは ddeval 固有です)。code_checkの場合はnullを指定します。evaluators[].implementation_hints.notes:任意のフレームワーク非依存のガイダンスです。例:「OpenAI Evals では、rubricをモデル採点基準として使用します。Braintrust では、LLM スコアラーとして使用します。Promptfoo では、llm-rubricアサーションとして使用します。」sample_records:Phase 1 の代表的なトレース10~20件です。suggested_labelsは、トレースを確認して Claude が最善の判断で付けたラベルであり、正解ではありません。このフィールド名は、その点を明示しています。- PII ルール:書き込み前に、すべての
input、output、evidence[].observationフィールドからメールアドレス、名前、機密データを削除します(Phase 3A と同じ)。
書き込み手順
- 上記のスキーマに従って、メモリ上に JSON オブジェクトを組み立てます。
- Phase 1 ですでに取得したトレースから
sample_recordsを作成します。読み取った件数が10件未満の場合は、追加のトレースを取得します(合計最大20件)。 - すべての
input、output、evidence[].observationフィールド内の PII を匿名化します。 - Write ツールを使用して、2スペースのインデントでファイルを書き込みます。
- 完了サマリーを表示します。
## Generated Eval Spec
Wrote `./evals/{ml_app}_eval_spec.json`:
- **{N} evaluators** ({outcome_count} outcome, {format_count} format, {safety_count} safety)
- **{M} sample records** with suggested labels
| # | Name | Category | Type | Pass Criteria |
|---|------|----------|------|---------------|
| 1 | ... | ... | ... | ... |
### Next Steps
1. **Review**: Open `./evals/{ml_app}_eval_spec.json` and verify the rubrics match your expectations
2. **Implement**: Use the `rubric` field to configure evaluators in your framework of choice:
- OpenAI Evals: use `rubric` as a model-graded criterion
- Braintrust: create an LLM scorer with the rubric text
- Promptfoo: use as an `llm-rubric` assertion
- Custom code: call your LLM API with the rubric and parse the structured output
3. **Label**: `suggested_labels` are Claude's best guesses from trace inspection — verify against ground truth before using as training data
ノートブックへのエクスポート(要約後)
Phase 3A と同じロジックです。rca_notebook_url が検出された場合は RCA ノートブックへの追記を提案し、検出されなかった場合は新しい単独ノートブックを作成します。output_path を JSON 仕様ファイルのパスに置き換え、Phase 3A と同じノートブックセル形式を使用します。pup モードでは、Phase 3A の説明に従って pup notebooks create / pup notebooks edit を使用します。
Phase 3C:オンライン評価器を Datadog に公開する(無効なドラフトとして)
Phase 2 のチェックポイントでユーザーがスイートを確認した後にのみ実行します。確認前に Datadog へ書き込むことはありません。
目標:確認済みの各評価器について、create_or_update_llmobs_evaluator を使って LLM 判定設定を Datadog に 無効なドラフト(enabled: false)として書き込みます。ユーザーが UI でレビューして有効にするまで、スパンは採点されません。有効にすると、一致する本番スパンに対して自動的に実行されます。
公開前のチェック(1 回のメッセージで並列実行)
提案されたすべての eval_name について、get_llmobs_evaluator(eval_name=...) を呼び出します。
-
見つからない場合 → 安全に作成できます。
-
見つかった場合 → 同じ名前の評価器が既に存在します。差分(既存の評価軸/プロンプトと提案内容)をユーザーに提示し、次のように尋ねます。
評価器
{name}は既に存在します。上書き、名前変更、またはスキップのどれにしますか?上書きの場合:取得した設定をベースとして保持し、生成したフィールドをその上にマージしてから、オブジェクト全体を返します。MCP ツールは全置換方式です。省略したフィールド(例:
temperature、max_tokens、filter、sampling_percentage)は既定値に戻ります。既存の設定を取得して引き継がずに再公開してはいけません。名前変更の場合:サフィックス(例:
_v2)を追加し、新規として扱います。スキップの場合:公開対象から除外します。
公開時の規約
各 create_or_update_llmobs_evaluator 呼び出しで必須のパラメーター:eval_name、application_name(=ml_app)、enabled、integration_provider、model_name、prompt_template、parsing_type、output_schema、および telemetry.intent の文字列。
ユーザーが上書きしない限り、次の既定値を使用します。
| フィールド | 既定値 |
|---|---|
enabled | false(常に — 「常にドラフトとして公開」を参照) |
integration_provider | openai |
model_name | gpt-5.4-mini |
temperature | 0 |
parsing_type | structured_output |
sampling_percentage | スパンのスコープでは 10、トレースのスコープでは 5、セッションのスコープでは 5(最も負荷が高いため、引き下げを検討してください) |
eval_scope | span(Phase 2 の分類ルールに従って、必要に応じて trace または session に自動昇格します) |
プロンプトテンプレート:LLMJudge プロンプトを、{role, content} メッセージの順序付き配列である MCP 形式に変換します。システムプロンプトは {role: "system"}、ユーザープロンプトは {role: "user"} にします。以下のスパンデータプレースホルダーを使用してください。EvaluatorContext にのみ存在するオフライン用の {{input_data}} / {{output_data}} 形式は使用しないでください。
オンラインテンプレート変数
オンライン評価プロンプトは、dd-source template ライブラリ(domains/ml-observability/shared/libs/template)を通じて実行されます。パスが見つからない場合は、空文字列になります。テンプレートが参照するデータの形は、eval_scope によって異なります。
eval_scope: span(デフォルト) — プレースホルダーは、単一スパンの JSON(マップにマーシャリングされたllmobs.Spanの JSON)を参照します。以下のスパンエイリアス/ドットパスをそのまま使用してください。eval_scope: trace— プレースホルダーは、トレースのペイロード{ spans: [...] }を参照します。フィールドパスを適用する前にスパンを選択するには、{{spans[N]...}}、{{spans[*]...}}、または{{spans[field.path:value]...}}を使用してください。{{span_input}}/{{span_output}}エイリアスはトレーススコープでは使用できません。代わりに、spans配列を通じてスパンデータを参照してください。eval_scope: session— プレースホルダーは、セッションのペイロード{ session_id, traces: [ { trace_id, root_span_id, spans: [...] }, … ] }を参照します。以下の配列セレクターとともに、{{traces[*].spans[*]...}}を使ってスパンにアクセスしてください(トレーススコープより1階層深くなります)。{{span_input}}/{{span_output}}エイリアスと、トレーススコープのトップレベルにある{{spans[...]}}は使用できません。スパンはtraces[*].spansの下にあります。sessions_presentの場合(フェーズ1のプローブ)にのみ使用されます。
スコープ:スパン(eval_scope: span)
組み込みのスパン種別対応エイリアス(複数のスパン種別で共通の評価器を使う場合に推奨):
| エイリアス | LLM スパン(meta.span.kind = "llm") | その他のスパン(エージェント、ワークフロー、タスクなど) |
|---|---|---|
{{span_input}} | meta.input.messages[*].content | meta.input.value |
{{span_output}} | meta.output.messages[*].content | meta.output.value |
一般的な明示的ドットパス(特定のスパン種別専用の評価器を構築する場合に使用):
| パス | 取得できる内容 |
|---|---|
{{meta.input.value}} / {{meta.output.value}} | エージェント/ワークフロー/タスク/ツールのスパンのプレーンテキスト入出力 |
{{meta.input.messages[*].content}} | LLM スパンのすべての入力メッセージ内容(改行で結合) |
{{meta.input.messages[0].content}} | 最初のメッセージ(通常はシステムプロンプト) |
{{meta.output.messages[*].content}} | アシスタントの応答 |
{{meta.input.documents}} | 取得したドキュメント(RAG)— JSON 形式でシリアライズ |
{{meta.metadata.<key>}} | カスタムメタデータフィールド |
{{meta.tool_definitions}} | 利用可能なツール — JSON 配列 |
{{*}} | スパン全体をコンパクトな JSON 形式で表示(デバッグ/フォールバック用の包括的な指定) |
スコープ:トレース(eval_scope: trace)
| パターン | 取得できる内容 |
|---|---|
{{spans}} | トレース内のすべてのスパンの JSON |
{{spans[N].meta.input.value}} | インデックスで指定した単一スパン — spans[0] はトレースのルートです |
{{spans[*].name}} | すべてのスパン名を順に表示し、改行で結合 |
{{spans[*].meta.output.value}} | すべてのスパンの出力を改行で結合(「最終回答=最後の出力」の指定に便利) |
{{spans[name:my-span].meta.input.value}} | スパン名でフィルタリング |
{{spans[meta.span.kind:llm].meta.output.value}} | LLM 種別のすべてのスパンの出力 |
{{spans[meta.span.kind:tool]}} | ツールのスパン全体を JSON で表示し、入出力を対にする — ツール使用の正確性の確認に便利 |
{{spans[meta.span.kind:retrieval].meta.output.documents[*].text}} | 取得したすべてのドキュメントのテキスト — RAG の忠実性の確認に便利 |
{{*}} | トレースのペイロード全体を JSON で表示(デバッグ用フォールバック) |
セッションスコープ(eval_scope: session)
| パターン | 得られる内容 |
|---|---|
{{traces}} | セッション内のすべてのトレースを含む JSON。各トレースは { trace_id, root_span_id, spans: [...] } です |
{{session_id}} | セッションの ID |
{{traces[*].trace_id}} | セッション内のすべてのトレース ID を改行で結合したもの |
{{traces[0].spans[0].meta.input.value}} | 最初のトレースの最初のスパン |
{{traces[*].spans[*].name}} | セッション全体のすべてのスパン名 |
{{traces[*].spans[*].meta.output.value}} | セッション全体のすべてのスパンの出力 |
{{traces[*].spans[meta.span.kind:llm].meta.input.messages[*].content}} | セッション全体のすべての LLM スパンの入力メッセージ(ユーザーの発話) |
{{traces[*].spans[meta.span.kind:llm].meta.output.messages[*].content}} | セッション全体のすべてのアシスタントの応答 |
{{traces[*].spans[meta.span.kind:tool]}} | セッション全体のすべてのツールスパン(入出力のペア) |
{{traces[*].spans[meta.span.kind:retrieval].meta.output.documents[*].text}} | セッション全体で取得されたすべてのドキュメントのテキスト(RAG) |
{{*}} | セッションのペイロード全体を JSON で表示(デバッグ用のフォールバック) |
注:フィルターセレクターが適用されるのは spans 配列(traces[*].spans[meta.span.kind:llm])であり、traces ではありません。トレースオブジェクトには trace_id、root_span_id、spans しかないため、トレースのレベルではフィルター対象がありません。
配列セレクターの構文(すべてのスコープに適用)
[N]— インデックス(0 始まり)[START,END]— 両端を含む範囲。ENDはスライスの長さに合わせて調整されます[*]— ワイルドカード(すべての要素に展開)[field.path:value]— ネストされたフィールドの値が一致する配列要素でフィルターします。例:messages[role:user]、spans[meta.span.kind:tool]
プロンプトを作成する際に留意すべき解決ルール:
- 文字列の配列 → 改行で結合
- オブジェクトまたは混在値の配列 → コンパクトな JSON
- 空のスライスが 1 つ → 空文字列
- 暗黙の展開:
messages.contentはmessages[*].contentと同じ動作をします - 負のインデックスはサポートされていません(解析エラー)。既知のインデックスには
[N]を使用し、「最後のアシスタントの発話」の意味には[*]を使用してください
どの形式を選ぶか:
- 汎用スパン評価器(例:
tone_check、output_format)→ さまざまなスパン種別で動作するよう、{{span_input}}/{{span_output}}を使用します。 - LLM スパン専用の評価器(例:
system_prompt_adherence)→ システム、ユーザー、アシスタントの各発話を分けられるよう、meta.input.messages[*].content/meta.output.messages[*].contentを明示的に指定します。 - スパンスコープの RAG 評価器(単一の取得+生成スパン)→
{{meta.input.documents}}と{{span_output}}を組み合わせます。 - トレーススコープの評価器→ 4 つの代表的なパターン(目標達成、ツール使用の正確さ、RAG の忠実性、会話品質)については、以下の「トレーススコープの評価器の例」を参照してください。
- セッションスコープの評価器→ 4 つの代表的なパターン(セッションの目標達成、複数ターンの会話品質、ユーザーの不満の兆候、セッション全体でのエージェントの一貫性)については、以下の「セッションスコープの評価器の例」を参照してください。
- メタデータ対応の評価器→
{{meta.metadata.<key>}}を直接参照します。
同じ ml_app に既存のカスタム評価器がある場合(Phase 0 のカバレッジマップ)、変更する強い理由がなければ、その規約に合わせてください。
トレーススコープの評価器の例
4 つの標準的なトレーススコープのユースケースに対応する、具体的なユーザープロンプト本文です。公開ドキュメント(トレースレベルの評価)から引用しています。それぞれ、評価基準を記述した静的なシステムプロンプトと組み合わせて使用します(プレースホルダーなし)。
| ユースケース | filter | ユーザープロンプト本文 |
|---|---|---|
| 目標の達成 — エージェントがユーザーのリクエストを完了したか | @parent_id:undefined @meta.span.kind:agent | ユーザーの目標:\n{{spans[0].meta.input.value}}\n\nエージェントの手順:\n{{spans}} |
| ツール使用の正確性 — 適切なツールを適切な引数で使用したか | @parent_id:undefined @meta.span.kind:agent | ユーザーの質問:\n{{spans[0].meta.input.value}}\n\nツール呼び出し:\n{{spans[meta.span.kind:tool].meta.input.parameters}}\n\n最終応答:\n{{spans[*].meta.output.value}} |
| RAG の忠実性 — 回答が取得したドキュメントに基づいているか | @parent_id:undefined | 取得したコンテキスト:\n{{spans[meta.span.kind:retrieval].meta.output.documents[*].text}}\n\n最終回答:\n{{spans[meta.span.kind:llm].meta.output.value}} |
| 会話の品質 — ターン全体にわたる一貫性と整合性 | @parent_id:undefined | 会話:\n{{spans[meta.span.kind:llm].meta.input.messages[*].content}}\n\nアシスタントの応答:\n{{spans[meta.span.kind:llm].meta.output.messages[*].content}} |
これらを出発点として使用してください。filter とスパンのパスは、アプリが実際に出力するスパン名/種類に合わせて調整してください(フェーズ 1 で確認)。
セッションスコープの評価器の例
4 つの標準的なセッションスコープのユースケースに対応する、具体的なユーザープロンプト本文です(公開ドキュメントのセッションレベルの評価から一部変更)。それぞれ、評価基準を記述した静的なシステムプロンプトと組み合わせて使用します(プレースホルダーなし)。filter はセッションのルートスパンに対して照合されます(すべてのスパンではありません)。そのため、ルートスパンの属性で指定してください。それ以外に @parent_id:undefined は必要ありません。
| ユースケース | filter | ユーザープロンプト本文 |
|---|---|---|
| セッション目標の達成 — セッション全体でユーザーの目標が達成されたか | @meta.span.kind:agent(またはアプリのルートスパンの種類) | セッションのトレース(時系列順):\n{{traces}} |
| 複数ターンの会話品質 — ターン間の一貫性と記憶 | @meta.span.kind:agent | ユーザーメッセージ:\n{{traces[*].spans[meta.span.kind:llm].meta.input.messages[*].content}}\n\nアシスタントの応答:\n{{traces[*].spans[meta.span.kind:llm].meta.output.messages[*].content}} |
| ユーザーの不満の兆候 — セッション中の不満/離脱 | @meta.span.kind:agent | セッション全体:\n{{traces}} |
| セッション全体にわたるエージェントの一貫性 — トレース間で一貫性を保ち、回復できるか | @meta.span.kind:agent | セッションのトレース(時系列順):\n{{traces}} |
filter とスパンのパスは、アプリが実際に出力するスパンの種類に合わせて調整してください(フェーズ 1 で確認)。
output_schema のラッパー形式(すべてのプロバイダーで必須)
output_schema フィールドは、単独の JSON Schema ではありません。OpenAI の json_schema オブジェクト形式を使用する必要があります。name は固定の型判別子であり、評価器名ではありません。UI は厳格な許可リストと照合して検証するため、それ以外の値は拒否されます。
| LLMJudge の種類 | name の値 | schema 内のプロパティキー |
|---|---|---|
| Boolean | "boolean_eval" | boolean_eval |
| Score | "score_eval" | score_eval |
| Categorical | "categorical_eval" | categorical_eval |
schema.properties 内のプロパティキーは、name と完全に一致させる必要があります。required 配列には ["<type_key>"] または ["<type_key>", "reasoning"] のみ指定できます。それ以外の値は拒否されます。UI 表示用に、必ず "reasoning": {"type": "string"} を含めてください。
Boolean(BooleanStructuredOutput(pass_when=True)):
{
"output_schema": {
"name": "boolean_eval",
"strict": true,
"schema": {
"type": "object",
"properties": {
"boolean_eval": {"type": "boolean", "description": "Whether the criterion is met"},
"reasoning": {"type": "string", "description": "Explanation for the evaluation"}
},
"required": ["boolean_eval", "reasoning"],
"additionalProperties": false
}
},
"assessment_criteria": {"pass_when": true}
}
スコア(ScoreStructuredOutput(min_score=1, max_score=10, min_threshold=7)):
{
"output_schema": {
"name": "score_eval",
"strict": true,
"schema": {
"type": "object",
"properties": {
"score_eval": {"type": "number", "description": "Score from 1 to 10", "minimum": 1, "maximum": 10},
"reasoning": {"type": "string", "description": "Explanation for the score"}
},
"required": ["score_eval", "reasoning"],
"additionalProperties": false
}
},
"assessment_criteria": {"min_threshold": 7}
}
設定されている場合は、assessment_criteria に max_threshold を追加します。
カテゴリ(CategoricalStructuredOutput(categories={...}, pass_values=[...])):
{
"output_schema": {
"name": "categorical_eval",
"strict": true,
"schema": {
"type": "object",
"properties": {
"categorical_eval": {
"type": "string",
"anyOf": [
{"const": "correct", "description": "The response correctly answers the question"},
{"const": "partially_correct", "description": "Partially correct but missing information"},
{"const": "incorrect", "description": "The response is wrong or irrelevant"}
]
},
"reasoning": {"type": "string", "description": "Explanation for the category chosen"}
},
"required": ["categorical_eval", "reasoning"],
"additionalProperties": false
}
},
"assessment_criteria": {"pass_values": ["correct"]}
}
注:カテゴリでは、オフライン SDK がプロパティのルートに単独の anyOf を使用するのとは異なり、anyOf とともに "type": "string" を使用します(各 const は文字列値です)。
カスタム/多次元:固定名のスキーマでは直接サポートされていません。可能であればスコアまたはカテゴリの評価器として実装するか、複数の評価器に分割してください。name は上記の3つの固定値のいずれかである必要があります。
フィルターのスコープ設定:提案の対象が特定のスパン種別(例:LLM のサブスパン)の場合は、EVP の filter クエリに変換します。例:@meta.span.kind:llm、service:checkout-agent、またはより具体的なタグ。対象がトレースのルートである場合に限り、root_spans_only:true と組み合わせます。
eval_scope: trace の場合:
- 評価器は、完了したトレースごとに、3分間の非アクティブ期間の後に1回実行されます。同じトレース上で前のスパンから3分を超えて到着した遅延スパンは、評価から除外されます。レイテンシと、アクティビティがまばらなエージェント(ステップ間隔が3分を超える長時間実行エージェント)でデータが取りこぼされる可能性の両方を、ユーザーが把握できるよう提案に明記してください。
filterクエリは、トレースのルートスパンのみに一致させる必要があります。子孫スパンで重複して実行されないよう、必ず@parent_id:undefined(またはroot_spans_only: true)を含めてください。対象を絞り込むには、@meta.span.kind:agent(または Phase 1 で確認した、アプリがルートスパンに使用する種別)と組み合わせます。- トレーススコープでのサンプリングは、スパンスコープより負荷が高くなります(判定側では1つのトレースに多数のスパンがあります)。トレーススコープの評価器では、デフォルトの
sampling_percentageをスパンのデフォルト10ではなく5に設定してください。ユーザーは手動レビュー後に引き上げることができます。
eval_scope: session の場合:
- セッションスコープの評価器を公開するのは、
sessions_presentが確認できた場合(Phase 1 のプローブ)のみです。そうでない場合、どのスパンにもsession_idが含まれないため、評価器は一度も実行されません。 - 評価器は、完了したセッションごとに1回実行されます。
session_idは、30分間の非アクティブ期間の後に完了します(トレースの場合は3分です)。前のスパンから30分を超えて到着したスパンは除外されます。レイテンシと除外の両方を明記してください。長時間またはアクティビティがまばらなセッションは分割または切り詰められる可能性があり、非常に大きなセッションは10,000スパンで上限に達します。 filterはセッションのルートスパンに対して照合されます(トレーススコープで使用するものと同じ事前フィルターです)。そのため、ルートスパンの属性でスコープを指定します。例:@meta.span.kind:agent(または Phase 1 で確認した、アプリのルートスパン種別)。セッションルートはすでにルートスパンなので、@parent_id:undefinedは不要です。- セッションスコープでのサンプリングは、すべてのスコープの中で最も負荷が高くなります(判定側では1つのセッションに多数のトレースが含まれ、各トレースに多数のスパンがあります)。セッションスコープの評価器では、デフォルトの
sampling_percentageをトレースと同じ5に設定し、最も負荷の高いスコープであることを明記してください。ユーザーは手動レビュー後、さらに引き下げることができます。
常にドラフトとして公開する(enabled: false)
integration_account_id が既存の評価器から自動検出されたかどうかにかかわらず、評価器は常に enabled: false で作成または更新してください。有効化の正しい状態を決めるのは UI であり、スキルがユーザーに代わって評価器を自動で有効化してはなりません。ユーザーは UI で各ドラフトを確認し、インテグレーションアカウントが正しいことを確かめます(自動検出された ID が、このアプリで使用したい判定用 LLM とは異なるものに属している可能性があります)。問題がなければトグルを切り替えます。
これにより、ワークフローはデフォルトで安全になります。誤った integration_account_id、調整不足のプロンプト、または範囲が広すぎるフィルターが、人による確認なしに有効になることはありません。アカウント ID の自動検出も引き続き役立ちます。ドラフトが適切なアカウントをあらかじめ選択した状態で表示されるため、レビューをすばやく行えます。
integration_account_id の解決
integration_account_id は不透明な UUID で、UI が組織のインテグレーションアカウント一覧と照合し、アカウントセクションのドロップダウンに値を表示します。ユーザーは通常この値を知らないため、ユーザーに UUID をそのまま指定するよう求めてはいけません。
解決の優先順位:
-
既存の評価器から引き継ぐ — フェーズ 0 で、既存のカスタム評価器ごとに
get_llmobs_evaluatorを呼び出しています。それらの応答にあるllm_provider.integration_account_idフィールドを確認してください。いずれかに値があれば、公開するドラフトにも同じ ID を使用します。既存の評価器間で異なる ID が複数見つかった場合は、最も多く使われている ID を選び、UI レビューの際にユーザーが修正できるよう、選んだ ID を明記してください。 -
既存の評価器に値がなければ省略する — ml_app 内のカスタム評価器に
integration_account_idがない場合は、公開ペイロードからこのフィールドを省略します。アカウントが事前選択されていない状態でドラフトが表示され、ユーザーは有効化の前に UI レビューでアカウントを選択します。
いずれの場合も、評価器は enabled: false の状態で公開します。判断するのはユーザーです — 上記の「常にドラフトとして公開する」を参照してください。
公開(単一メッセージ — 並列実行)
すべての create_or_update_llmobs_evaluator 呼び出し(評価器ごとに 1 回)を単一のメッセージで実行します。telemetry.intent には、"skill:agent-observability-eval-bootstrap — Bootstrap evaluator suite for ml_app=<ml_app> from production trace analysis." のような短い英語の説明を設定します。
呼び出しに失敗した場合は、エラーを記録して残りの評価器の処理を続けます。バッチ処理を黙って中断してはいけません。失敗は要約に明記してください。
要約
## Published Evaluators (drafts — pending UI review)
Wrote {N} online evaluators to ml_app `{ml_app}`. **All published as drafts (`enabled: false`)** — review and activate them in the UI before they start scoring spans.
| # | Name | Action | Provider/Model | Sampling | Scope | Account auto-detected | Status |
|---|------|--------|----------------|----------|-------|-----------------------|--------|
| 1 | task_completion | created (draft) | openai/gpt-5.4-mini | 10% | span | yes | ok |
| 2 | response_groundedness | overwrote (draft) | openai/gpt-5.4-mini | 10% | span | yes | ok |
| 3 | scope_adherence | renamed (`_v2`) (draft) | openai/gpt-5.4-mini | 10% | span | no — pick in UI | ok |
| 4 | citation_format | failed | openai/gpt-5.4-mini | 10% | span | — | error |
| 5 | session_goal_completion | created (draft) | openai/gpt-5.4-mini | 5% | session | yes | ok |
{If any failed:}
**Errors**:
- `{name}`: {error message}
{If any code-based proposals were dropped:}
**Not published** (code-based, not supported by online evaluator API):
- `{name}` ({type}) — ask me to emit it as offline SDK code, or run `/eval-bootstrap {ml_app} --sdk-code`.
### Next Steps — review and activate in the UI
The drafts are intentionally not running yet. Walk through each one in the Datadog UI before flipping the enable toggle:
1. **Open the drafts**: Datadog → Agent Observability → Evaluations → filter by ml_app `{ml_app}` (the new drafts appear with status `Disabled`).
2. **For each draft**:
- **Verify the integration account** in the Provider section. If the column above shows `auto-detected: yes`, confirm it's the correct account for the judge LLM you want this evaluator to call through. If `no`, pick an account from the dropdown.
- **Skim the prompt template** and the structured-output schema — make sure the span / trace / session scope, filter, and sampling match what you actually want to measure.
- **Click into a sample span/trace** and use the test pane to dry-run the prompt against real data. Confirm the result matches your expectation.
3. **Enable**: once each draft passes review, toggle it to enabled. Datadog starts scoring incoming spans immediately.
4. **Wait for first scores**: with `sampling_percentage=10` (span scope) or `5` (trace / session scope), expect first results within minutes for high-traffic apps — except **session scope**, where the first result appears only after the session's 30-minute inactivity window closes.
5. **Tune sampling/filter**: if results are noisy or volume is too high, reduce `sampling_percentage` or tighten the `filter` from the UI. Re-running `/eval-bootstrap {ml_app}` will round-trip the existing config before overwriting — your manual tweaks survive across reruns.
6. **Prefer offline code?** These evaluators run online in Datadog. If you'd rather iterate on the same suite as offline SDK code (`sdk_code`) or a portable JSON spec (`data_only`), just ask — I'll regenerate the **confirmed** suite without re-exploring.
ノートブックへのエクスポート(要約の後)
Phase 3A と同じロジックを使用します。rca_notebook_url が検出されている場合は、RCA ノートブックへの追記を提案し、そうでない場合は新しいスタンドアロンのノートブックを作成します。ノートブックのセルには、公開した評価器とその UI リンク、および対象の ml_app を記載してください。pup モードでは、Phase 3A の説明に従って pup notebooks create / pup notebooks edit を使用します。
Phase 3D: 本番トレースからデータセットを生成する
目的:ml_app の本番トレースをサンプリングし、入力と出力のペアを抽出して、LLMObs.create_dataset(records=...) 用の形式に整えた DatasetRecordRaw[] JSON ファイルを書き出します。このモードでは評価器の提案も生成も行いません — Phase 1 と Phase 2 はスキップされます。データセットのみが成果物です。
このモードの実行条件:--emit-dataset <path> によって起動されます。Phase 0 のステップ 4(評価カバレッジマップ)はデータセットの抽出とは無関係なため、スキップします。
サンプリング
search_llmobs_spans(query="@ml_app:\"<ml_app>\" @status:ok", root_spans_only=true, limit=<trace-limit>, from=<timeframe>)を呼び出します。@status:okでフィルタリングし、データセットのレコードが意図されたアプリの動作を反映するようにします(エラーのあるトレースには、利用可能な出力のベースラインがありません)。- サンプリングした各ルートスパンについて、
get_llmobs_span_details(trace_idごとにグループ化)とget_llmobs_span_contentを呼び出し、ルートスパンのinputフィールドとoutputフィールドを読み取ります。ページ内のすべての呼び出しを単一のメッセージで実行します(並列実行)。
レコードの抽出
サンプリングした各ルートスパンについて、DatasetRecordRaw エントリを1つ作成します。
| フィールド | ソース | 備考 |
|---|---|---|
input_data | ルートスパンの meta.input | meta.input.value がプレーンな文字列の場合は、{"input": "<value>"} としてラップします。meta.input.messages が存在する場合は、{"messages": [...]} を使用します(role/content の構造を保持)。RAG のルートに documents も含まれている場合は、{"documents": [...]} を含めます。 |
expected_output | ルートスパンの meta.output.value(LLM 種別のルートの場合は meta.output.messages[-1].content) | これは本番環境での動作のベースラインであり、正解データではありません。ユーザーが過信しないよう、この点を要約に明記してください。 |
metadata | {"source": "production", "trace_id": "<id>", "span_id": "<id>", "ml_app": "<ml_app>", "extracted_at": "<ISO 8601 UTC>"} | トレース ID とスパン ID を使うと、ユーザーは Datadog のソーストレースに戻ることができます。 |
tags | ["env:prod", "source:traces", "ml_app:<ml_app>"] | 常に "key:value" 形式の文字列を使用してください。裸の文字列では Dataset.append() が失敗します。agent-observability-experiment-bootstrap/references/python/python.md のレコードごとのタグルール(「key:value」で検索)を参照してください。 |
PII のスクラブ。 書き込む前に、agent-observability スキルセット内の他の箇所で使われているものと同じスクラブ処理を実行します。すべての文字列値に含まれるメールアドレス/電話番号/SSN/API キーの正規表現マッチを <REDACTED:pii-type> に置換します。影響を受けたレコードのインデックスを列挙した警告を、要約に1行で記載してください。
タグの正規化(書き込み前の必須手順)。 SDK の Dataset.append() は validate_tags_list を呼び出し、: 区切りのないタグを拒否します(Tag '<tag>' is malformed. Tags must be in 'key:value' format.)。JSON を書き込む前に、すべてのレコードの tags リストに以下の正規化を適用してください。
- 削除:文字列以外、
None、空文字列のエントリを削除します。 - すでに
key:value形式(":" in tagかつ前後どちらの部分も空でない)→ そのまま保持します。 - 裸の文字列(
:を含まない)→"tag:<value>"としてラップします(たとえば"interactive"は"tag:interactive"になります)。決して黙って削除しないでください。元の情報は汎用のtag:キーの下に保持されます。 - 末尾または先頭に
:がある場合(例:"foo:"、":bar")→ 不正な値を保持しつつ SDK でエラーが発生しないように、"tag:<full-original-string>"としてラップします。
正規化の件数を記録し、要約に1行の注記として記載してください(Tag normalizations applied: N (bare strings auto-prefixed with 'tag:'))。正規化後もレコードの tags リストに key:value 形式でない文字列が残っている場合は、データセットを書き込まないでください。これはこの手順の不具合です。
次のレコードはスキップしてください:
meta.outputが空または存在しない(ベースラインとして使用できるものがない)。- ルートスパンの I/O が構造情報のみ(例:ツール呼び出しだけで、自然言語による出力がない)。
スキップしたレコード数を記録し、要約に記載してください。
ファイルの書き込み
- 2スペースでインデントした JSON 配列を、Write ツールを使って
<path>に書き込みます。パスはユーザーが--emit-dataset <path>で指定しています。そのまま使用してください。 - このモードでは Datadog にプッシュしないでください。Datadog への公開はオンボーディングフローの次のステップ(
LLMObs.create_dataset(records=...))であり、呼び出し元(オンボーディングオーケストレーターまたはユーザー自身)が処理します。
要約
書き込み後、次を出力します。
## Generated Dataset
Wrote `<path>` — {N} records sampled from `<ml_app>` ({timeframe}).
| Stat | Value |
|---|---|
| Records emitted | {N} |
| Records skipped (no output) | {M} |
| PII redactions applied | {P} (indices: {list or "none"}) |
| Tag normalizations applied | {T} (bare strings auto-prefixed with `tag:`) |
**Important**: `expected_output` is the **current production behavior baseline**, not ground truth. Use this dataset for regression-style experiments (does my refactor change observed outputs?) before promoting it to a labelled gold set.
### Next Steps
1. **Review**: open `<path>` and spot-check a few records. Adjust `expected_output` where the production output was wrong.
2. **Publish to Datadog**:
```python
from ddtrace.llmobs import LLMObs
import json, os
LLMObs.enable(api_key=os.getenv("DD_API_KEY"), app_key=os.getenv("DD_APPLICATION_KEY"),
site=os.getenv("DD_SITE", "datadoghq.com"),
project_name="<your-project>", agentless_enabled=True)
records = json.load(open("<path>"))
ds = LLMObs.create_dataset(dataset_name="<your-name>", records=records)
print(ds)
agent-observability-experiment-bootstrap --dataset-name <your-name>を使って、公開済みデータセットに対する実験を実行します。
Skip the notebook export prompt in this mode — datasets aren't well-served by a markdown summary in a notebook, and the next step (experiment generation) will produce its own artifacts.
---
## Operating Rules
- **Breadth over precision; let the user curate**: Propose **8–15 evaluators** distributed across **domain-specific** (largest bucket — derived from Phase 1 domain signals), outcome, format, and safety. Users can always remove what doesn't fit their quality bar; they cannot easily add what was not proposed. Anchor every domain-specific proposal in at least one observed trace pattern — don't invent generic domain evaluators without evidence.
- **Don't overfit**: Write criteria that generalize beyond the specific sampled traces. Use examples as grounding, not as the sole criteria.
- **Show your work**: Every proposed evaluator cites at least one trace as evidence with a clickable link: `[Trace {first_8}...](https://app.datadoghq.com/llm/traces?query=trace_id:{full_32_char_id})`.
- **New file only**: Never modify existing evaluator code or experiment configurations.
- **Honest about uncertainty**: If fewer than 5 traces support a proposed evaluator, flag it as tentative.
- **Session scope is gated**: only propose or publish `eval_scope: session` evaluators when the Phase 1 probe confirmed `sessions_present` (spans carry a `session_id`, ideally one that spans multiple traces). If sessions are absent, skip session scope and say so — never publish a session evaluator that can't fire.
---
## Tool Reference
This appendix applies only in **pup mode**. In MCP mode, use the tool names in the workflow sections directly.
### Spans and traces
| MCP Tool | pup Command |
|---|---|
| `search_llmobs_spans(query, ml_app, from, to, limit, cursor, root_spans_only, span_kind, summary)` | `pup llm-obs spans search --query "@ml_app:A [other_filters]" [--from F] [--to T] [--limit N] [--cursor C] [--root-spans-only] [--span-kind K] [--summary]` — **always use `--query "@ml_app:A"` to filter by ml_app**; the `--ml-app A` flag is unreliable and silently returns spans from other apps. |
| `get_llmobs_span_details(trace_id, span_ids, from, to)` | `pup llm-obs spans get-details --trace-id T --span-ids S1,S2,...` |
| `get_llmobs_span_content(trace_id, span_id, field, path)` | `pup llm-obs spans get-content --trace-id T --span-id S --field F [--path P]` |
| `get_llmobs_trace(trace_id, include_tree)` | `pup llm-obs spans get-trace --trace-id T [--include-tree]` |
| `get_llmobs_agent_loop(trace_id, span_id)` | `pup llm-obs spans get-agent-loop --trace-id T [--span-id S]` |
| `find_llmobs_error_spans(trace_id)` | `pup llm-obs spans find-errors --trace-id T` |
| `expand_llmobs_spans(trace_id, span_ids, max_depth, filter_kind)` | `pup llm-obs spans expand --trace-id T --span-ids S1,S2,... [--max-depth N] [--filter-kind K]` |
### Evaluators
| MCP Tool | pup Command |
|---|---|
| `list_llmobs_evals()` | `pup llm-obs evals list` (filter by `ml_app` client-side) |
| `list_llmobs_evals_by_ml_app(ml_app)` | `pup llm-obs evals list-by-ml-app --ml-app A` |
| `get_llmobs_evaluator(eval_name)` | `pup llm-obs evals get-evaluator EVAL_NAME` |
| `get_llmobs_eval_aggregate_stats(eval_name, ml_app, from, to)` | `pup llm-obs evals get-aggregate-stats EVAL_NAME [--ml-app A] [--from F] [--to T]` |
| `delete_llmobs_evaluator(eval_name)` | `pup llm-obs evals delete EVAL_NAME` |
| `create_or_update_llmobs_evaluator(...)` | `pup llm-obs evals create-or-update EVAL_NAME --file /tmp/eval_EVAL_NAME.json` — see flat schema note below |
#### `create_or_update_llmobs_evaluator` in pup mode
pup uses a **flat** JSON file (all fields top-level). `get-evaluator` returns a **nested** object. Transform as follows:
1. **Round-trip check**: Call `pup llm-obs evals get-evaluator EVAL_NAME` first. If it exists, start from its config.
2. **Flatten `llm_provider`**: hoist `integration_provider`, `model_name`, `integration_account_id`, `temperature` to top level, dropping the `llm_provider` key.
3. **Merge and set `enabled: false`**.
4. **Write to temp file and call**:
```bash
pup llm-obs evals create-or-update EVAL_NAME --file /tmp/eval_EVAL_NAME.json
複数の評価器を並行して公開する場合は、一意の一時ファイル名を使用してください(例:/tmp/eval_toxicity.json)。
get-evaluator のフィールド | フラットな JSON キー |
|---|---|
llm_provider.integration_provider | integration_provider |
llm_provider.model_name | model_name |
llm_provider.integration_account_id | integration_account_id |
llm_provider.temperature | temperature |
| その他すべてのフィールド | 変更なし(すでにトップレベル) |
ノートブック
| MCP ツール | pup コマンド |
|---|---|
create_datadog_notebook(name, cells, ...) | pup notebooks create --title "TITLE" --file /tmp/nb_cells.json — 正確なフラグは pup notebooks create --help で確認してください |
edit_datadog_notebook(id, cells, append_only=true) | pup notebooks edit NOTEBOOK_ID --file /tmp/nb_cells.json(現在のノートブックを取得し、指定されたセルを追加して書き戻します) |
セルファイルは、セルオブジェクトの JSON 配列です。
[{"attributes": {"definition": {"type": "markdown", "text": "## Section\n\nContent."}}, "type": "notebook_cells"}]
- MCP 結果の解析時の安全性:MCP ツールの結果のフィールドを反復処理または参照するスクリプト(Python、jq など)を書く前に、まず生の構造を確認してください。
type(result)、トップレベルのキー、ペイロードがコンテンツブロック内にネストされているか(例:[{'type': 'text', 'text': '<json>'}])を調べます。必要に応じて内部のペイロードを取り出し、解析前にjson.loads()を実行してください。MCP の結果がそのままの dict や list であると決めつけないでください。