关于 agent-observability-eval-bootstrap
用于从 Datadog 生产追踪数据开发评估器的技能,可创建禁用状态的在线草稿、生成离线 SDK 代码或 JSON 规范,也可导出采样追踪数据集。
主要功能
- 01采样并检查生产追踪
- 02提议在线 LLM 评估器
- 03确认后创建禁用草稿
- 04生成离线 Python SDK 代码
- 05输出 JSON 规范或数据集记录
适用场景
- 01根据生产行为起草评估器
- 02运行离线评估器实验
- 03导出追踪以创建实验数据集
MCP 相关
使用 Datadog MCP 工具搜索和检查 LLMObs 追踪及评估器,并创建评估器草稿;也可改用 pup 命令行工具。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datadog-labs/ agent-skills --skill agent-observability-eval-bootstrap安装后在 Claude Code 中输入/agent-observability-eval-bootstrap确认已加载。
详细介绍
后端
检测 — 每次调用开始时,在执行任何操作之前,确定要使用的后端:
- 如果用户在调用的任意位置传入了
--backend pup→ 立即使用 pup 模式,无论是否存在 MCP 工具。跳过步骤 2–4。 - 检查当前可用工具列表中是否存在 MCP 工具。判断依据是
mcp__datadog-llmo-mcp__list_llmobs_evals是否出现在可用工具中。 - 如果存在 MCP 工具 → 全程使用 MCP 模式。严格按照本技能工作流部分中列出的名称调用 MCP 工具。
- 如果不存在 MCP 工具 → 检查
pup是否可执行:通过 Bash 运行pup --version。包含"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。直接解析即可——无需解包内容块(与可能以
[{"type": "text", "text": "<json>"}]形式封装 JSON 的 MCP 结果不同)。 - 如果 pup 返回身份验证错误,请告知用户运行
pup auth login,然后停止。 - 并行处理:在同一条消息中发起多个 Bash 工具调用(每次调用运行一条 pup 命令)。
- 时间标志: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 位十六进制调用 ID(例如 3a9f1c2b)。在整个调用过程中保持不变。
意图标记: 每次调用 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 跟踪记录,分析输入/输出模式和质量维度,然后提出一套可直接使用的评估器。共有四种输出模式——默认使用在线评估器;SDK 代码、JSON 规范和数据集生成模式需按需生成:
publish(默认) — 提出在线 LLM 裁判评估器,然后——仅在你确认这套评估器后——通过create_or_update_llmobs_evaluator将其写入 Datadog,作为已禁用的草稿(enabled: false)。在你于第 2 阶段检查点确认之前,不会创建任何内容;在你于 UI 中启用之前,也不会对任何跨度评分——此技能绝不会自动发布已启用的评估器。启用草稿后,它会自动对匹配的生产跨度、跟踪记录或会话运行(无需数据集或任务函数)。此技能会根据评判所需的范围,自动分类每个建议的评估器:跨度级、跟踪记录级或会话级(例如,针对单次 LLM 调用的语气检查、需要完整跟踪记录才能判断的 Agent 目标完成情况,或针对跨多条跟踪记录的完整对话进行的用户满意度评估)——你可以在该检查点接受或更改分类。仅当应用的跨度中带有session_id时,才会建议会话级评估器(在第 1 阶段通过探测验证)。sdk_code(按需使用——--sdk-code,或在发布后提出要求) — 生成使用 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会在第 4 阶段使用此模式,根据生产行为为实验数据集提供初始数据。
运行发布模式后,如果用户想要同一套评估器的离线代码或可移植规范,只需提出要求——此技能会在不重新探索的情况下,以 sdk_code / data_only 模式重新生成已确认的评估器套件(见第 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 | 按时间顺序展示 Agent 执行时间线(大模型调用、工具调用、决策)。 |
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);此类评估器出现未找到错误属于预期情况——跳过即可。 |
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_ids 分组。每个 trace_id 调用一次,并传入其所有 span_ids。针对同一页的所有调用应在一条消息中发出。get_llmobs_span_content:每次调用相互独立——始终在一条消息中发出所有调用。get_llmobs_trace/get_llmobs_agent_loop:在一条消息中并行处理不同的跟踪。- 流水线并行:立即开始获取第 1 页结果的
get_llmobs_span_details——无需等待所有页面收集完毕。
评估器 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
重要 — span 数据与评估器数据:查看生产环境的追踪记录时,你会看到 span 的输入/输出(例如 input.value、output.messages)。但评估器在离线实验中运行,此时 input_data 和 output_data 来自用户的数据集记录和任务函数,而不是 span。数据集的架构由用户定义,可能与 span 的结构不匹配。编写评估器提示词时,请使用通用的 {{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 字段的嵌套字典中解析。
结构化输出类型
布尔值 — true/false,可选通过/失败状态:
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 架构强制规定响应格式。不要在提示词中规定格式(例如“回答是/否”“按 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
工作流
阶段 0:解析输入和入口模式
入口模式检测:
| 模式 | 信号 | 行为 |
|---|---|---|
| 冷启动 | 仅提供 ml_app(没有 RCA,也没有假设) | 全面开放式探索——了解应用的功能,识别值得衡量的质量维度,并提出评估方案以确保覆盖 |
| 基于 RCA | 对话中包含 RCA 报告,或用户提供了故障假设 | 跳过开放式探索——使用现有故障分类作为评估目标 |
解析参数:提取 ml_app(第一个非标志参数)、--timeframe(默认值为 now-7d)、--trace-limit(默认值为 20)、--sdk-code、--data-only 和 --emit-dataset <path> 标志。按以下方式设置 output_mode(三个模式标志中最多只能设置一个;如果设置了多个则报错):
- 已设置
--emit-dataset <path>→output_mode = emit_dataset。跳过其余工作流入口模式逻辑,直接跳转到下方的 阶段 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}。将此映射带入阶段 2,以便去重。在
publish模式下,还要记录现有自定义评估器使用的模板变量惯例(以便新评估套件保持一致)。在线评估器模板依据完整的 span JSON解析,而不是依据EvaluatorContext。支持的语法({{span_input}}、{{span_output}}、点路径、数组选择器、筛选器访问器)请参阅“Publishing Conventions”下的“Online Template Variables”部分。 -
检测 Notebook 上下文:扫描当前对话,查找由
/eval-trace-rca生成的 Datadog Notebook URL(模式:https://app.datadoghq.com/notebook/{numeric-id})。如果找到,将其存储为rca_notebook_url,并提取数字 ID 作为rca_notebook_id。阶段 3 后会使用此信息,询问是否将评估器套件追加到该 Notebook,而不是创建新的 Notebook。
阶段 1:探索 Trace 并确定评估目标
目标:抽样生产 Trace,了解应用的功能,并识别值得衡量的质量维度。
冷启动路径
-
抽样应用:
search_llmobs_spans(query="@ml_app:\"<ml_app>\" @status:ok", root_spans_only=true, limit=50, from=<timeframe>)。按@status:ok筛选——错误 Span 没有可供评估的输出。 会话探查 (用于门控会话范围提案;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中——这种跨轨迹跨度才是真正的信号,表明会话携带了值得采用会话范围评估器的上下文。(如果某个session_id只对应一个轨迹,那么相比轨迹范围并无额外价值。) - 0 条结果 →
sessions_present = false。不要提议任何会话范围评估器;在提案中记录一行“已跳过会话范围 — 抽样跨度上没有session_id”说明。
- ≥ 1 条结果 → 设置
-
分析应用并识别评估目标跨度:按
trace_id分组调用get_llmobs_span_details,传入span_ids。检查content_info并进行分类:信号 应用类型 content_info包含messagesLLM/聊天应用 content_info包含documentsRAG 应用 跨度包含 agent类型Agent 应用 content_info包含metadata含自定义元数据 单个轨迹中包含多种跨度类型( agent+tool/get_llmobs_trace中的retrieval+llm)多步骤应用 — 套件中可能至少需要一个轨迹范围评估器( publish模式)同一 session_id出现在多个trace_id中(来自会话探查)多轨迹会话 — 套件中可能至少需要一个会话范围评估器( publish模式,且以sessions_present为门控条件)对于 Agent/多步骤应用,还需对 2-3 个轨迹调用
get_llmobs_trace,查看完整的跨度层级结构。比较根跨度及其子跨度的content_info。然后按以下顺序,对每个候选质量维度提出两个问题:- 判定是否依赖多个跨度?(例如,忠实度取决于
retrieval跨度中的文档和llm跨度中的回答;目标完成度取决于一系列tool调用和最终回复。)如果是 → 在publish模式下使用轨迹范围。不要尝试将其压缩为单个跨度。 - 仅当第 (1) 题的答案为否时:选取该维度信号最丰富的单个跨度(根跨度包含摘要;LLM 子跨度包含完整的系统提示词、工具调用结果和推理链)。
记录跨度类型直方图(agent + tool + llm + retrieval)——单个根跨度下包含多种类型,是强烈信号,表明套件中至少需要一个轨迹范围评估器。关于规范性轨迹范围用例(以及在
sessions_present为真时的规范性会话范围用例),请参阅 Phase 2 的“跨度范围与轨迹范围及会话范围分类”中必须完成的演练。 - 判定是否依赖多个跨度?(例如,忠实度取决于
-
提取内容并识别目标:对代表性跨度调用
get_llmobs_span_content。根据应用类型获取字段:应用类型 要获取的字段 LLM/聊天 messages(系统提示词使用path=$.messages[0])、outputRAG documents、input、outputAgent 对 Agent 跨度调用 get_llmobs_agent_loop,然后获取messages以查看详情含任意元数据的应用 metadata在同一条消息中发出所有调用。阅读时,捕捉两类信号:
通用质量信号 — “成功”是什么样的?不同输出之间存在何种差异?每个观察到的质量维度都是候选评估器,并以刚刚阅读的轨迹作为依据。同时留意安全信号(范围违规、输出中的敏感数据、偏离角色的回复);如果发现此类信号,则添加安全评估器。 领域信号 — 这些信号将在第 2 阶段归入 领域专属评估器 类别(杠杆效应最高的类别)。每查看 5–10 条轨迹,记录以下内容:
- 反复出现的意图/问题类别 — 此应用处理哪些类型的请求?(
申请福利 X、比较航班选项、总结政策、创建小部件) - 应用在输出中生成的实体 — URL、机构/公司名称、代码标识符、金额、日期、ID、文件路径、电话号码。注明用户会在后续操作中据此采取行动的实体(这些实体适合用正确性评估器评估),以及仅作为引用出现的实体。
- 工具参数结构(适用于 Agent 应用)— 列出 Agent 调用的每个工具,以及其输入的大致架构。架构较复杂的工具(≥ 3 个字段、包含结构化类型)适合作为参数正确性评估器的评估对象。
- 角色设定/语气规则 — 应用是否总是引用来源、总是拒绝某些话题(医疗、法律、财务建议),或始终使用特定语气?提取其在已观察到的输出中隐含遵循的规则。
- 领域专属故障模式 — 虚构标识符、过时的政策引用、货币/区域设置不匹配、ID 差一错误、单位错误。只需观察到一个实例,就足以将其列为候选评估器。
阅读轨迹前,不要试图穷尽所有领域信号;随着阅读,让模式自然显现。此步骤的目标是让最终提案涵盖面广,而不是在探索阶段做到面面俱到。
- 反复出现的意图/问题类别 — 此应用处理哪些类型的请求?(
来自 RCA 路径
-
从 RCA 报告中提取故障分类。每个严重程度为 High 或 Medium 的故障模式都应成为评估目标。还要运行第 1 阶段的 会话探测(
query="session_id:*"),以设置sessions_present— 仅在多轨迹对话中出现的故障(上下文丢失、重复出错、挫败感加剧)属于会话级评估目标。 -
检查根因类别中是否存在基础设施故障。 提出评估器前,检查故障分类表的 Root Cause 列中是否包含以下任何类别:
Instrumentation Deficiency、Harness Deficiency、Runtime Error、Upstream Data Issue,或任何其他指向基础设施/环境而非模型行为的根因。如果存在,请暂停并询问:"有些故障模式被诊断为基础设施或监测问题,而非模型行为问题(例如,
{list the infra root causes})。评估器可以通过两种方式设计:- 针对行为(推荐用于持续质量监控):衡量模型是否生成正确且具体的输出 — 基础设施修复后,可用于跟踪实际质量
- 针对产物(适合作为回归防护):检测观察到的特定错误输出(例如通用占位回复)— 如果基础设施再次出现故障,可捕获回归
你希望采用哪种方式,还是两种都采用?"
- 如果选择针对行为:根据正确输出应具备的特征设计评估器,而不是针对错误输出的特征。使用 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 范围内,每个评估器都会评判一个数据点:单条记录/跨度的输入和输出,而不是完整跟踪或批次。在 publish 模式下,eval_scope: trace 会评判完整跟踪,eval_scope: session 会评判包含多个跟踪的完整会话——请针对跟踪/会话负载设计这些评估器(参见下文“跨度、跟踪与会话范围分类”)。请根据评估器的范围进行相应设计。
目标取决于 output_mode:
sdk_code/data_only→ 离线实验。模板变量使用EvaluatorContext字段({{input_data}}、{{output_data}})。实际数据结构取决于用户的数据集和任务函数(参见 SDK Reference 中的 EvaluatorContext 说明)。publish→ 对生产环境中的跨度进行在线评估。模板变量通过点路径解析完整跨度 JSON({{meta.input.value}}、{{meta.output.messages[*].content}}、……),或使用内置的、感知跨度类型的别名({{span_input}}、{{span_output}})。对于eval_scope: trace和eval_scope: session,模板会分别根据跟踪负载({{spans[...]}})或会话负载({{traces[*].spans[...]}})解析。完整语法请参见 Publishing Conventions 中的“在线模板变量”。每个评估器还需要eval_scope、sampling_percentage和(可选的)filter——请在提案表格中列出这些内容,以便用户在发布前确认。仅当 Phase 1 探测集设置了sessions_present时,才使用会话范围。
提案按信号覆盖面从广到细排序。广泛提出,交由用户筛选——参见下文“应提出多少个评估器”。
-
领域专用评估器——对于这个特定应用,“好”意味着什么?这些提案最具杠杆效应,因为它们能捕捉通用评估器容易遗漏的质量标准。根据 Phase 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)。 - 跟踪中出现的领域专用失败模式(伪造标识符、过时的政策引用、单位不匹配、货币/区域设置不匹配)。每种反复出现的失败模式提出一个评估器。
每个评估器都应以用户关心的问题命名,而不是以技术检查命名(用
agency_url_is_real,而非regex_url_match)。使用 Phase 1 中读取的跟踪 ID 作为证据——如果同时观察到通过和失败案例,每个评估器至少各提供一个。 - 应用处理的常见意图/问题类别(例如,“申请联邦福利”“比较航班选项”“解释政策”)。针对主要意图提出
-
结果评估器——这个跨度/跟踪是否为该请求带来了良好结果?
- 示例:
task_completion、answer_correctness、response_groundedness
- 示例:
-
格式评估器——输出是否符合结构要求?
- 示例:
valid_json_output、response_length、citation_format
- 示例:
-
安全评估器——输出是否遵守了适当的边界?
- 示例:
no_pii_leakage、scope_adherence、no_hallucination
- 示例:
应提出多少个评估器
旧版技能默认的 4-6 上限太严格了——这会让技能倾向于只提出通用评估器,忽略领域信号。更新后的指导如下:
- 建议在提案中提出 8–15 个评估器,分布在全部四个类别中(通常领域特定评估器最多,其次是结果评估器,格式和安全评估器较少)。对于非常简单的单次大模型调用应用,数量可以更少;对于具有丰富领域信号的 Agent/RAG 应用,应倾向于选择较多的数量。
- 质量优于通用性:每个领域特定评估器提案都应至少有一个在抽样轨迹中观察到的模式作为依据。没有证据时,不要凭空提出通用领域评估器(如 "
response_quality")。 - 让用户筛选:下方的 MANDATORY CHECKPOINT 明确要求用户移除不适用的内容,而不只是批准。应将提案视为一组候选项,让用户进行删选。
与现有覆盖范围去重
在 data_only 模式下:完全跳过此部分(第 0 阶段未建立覆盖范围映射)。直接进入提案表格。
建立提案前,应用第 0 阶段的覆盖范围映射。覆盖范围以 (dimension, scope) 为键——而不仅仅是维度:每个 OOTB 评估器都在 span 范围运行,启用 OOTB 评估器并不妨碍针对同一维度提出 trace 范围或 session 范围的评估器。这三个范围回答的是不同问题。
-
已启用的 span 范围评估器(OOTB 或自定义),用于维度 D:
- 不要为 D 提出新的 span 范围评估器——该维度在 span 范围内已经有覆盖。
- 如果轨迹或会话结构需要,则应为 D 提出 trace 范围或 session 范围评估器(多步骤应用,或包含多条轨迹的会话——具体取决于是否需要跨 span 或跨轨迹的上下文)。在理由中说明它们之间的关系,例如:“OOTB
Goal Completeness会单独评估每个 LLM span;此 trace 范围的goal_completion会检查 Agent 的完整步骤序列是否实现了用户的请求,而 session 范围的session_goal_completion会在整个对话范围内进行检查——这是三个不同的问题。”
-
已启用的 trace 范围自定义评估器,用于维度 D:不要再为同一维度提出另一个 trace 范围评估器;这是真正的重复。如果数据也适用于单个 span,则同一维度的 span 范围评估器仍可提出;如果该维度还需要跨轨迹上下文,则 session 范围评估器也可提出。同样,已启用的 session 范围自定义评估器 D 只会阻止再为 D 提出另一个 session 范围评估器——span 和 trace 范围仍可提出。
-
已禁用的 OOTB 评估器:不要为该维度提出新的自定义 span 范围评估器。应在提案中用简短说明指出这一点,并建议在 Datadog UI 中启用,而不是创建重复项。例如:
hallucination(ootb,已禁用)——建议在 Datadog UI(Evaluations → Configure)中启用,而不是创建自定义 span 范围评估器。(trace 范围的rag_faithfulness仍可提出,因为它检查的是不同问题。) -
识别覆盖缺口:在提案开头用一行总结覆盖情况:“现有覆盖:已配置 N 个评估器({names},除非另有说明,均为 span 范围)。现为尚未覆盖的维度和范围提出评估器。”
-
所有维度均已覆盖:只有在相关范围均已覆盖时(span,以及应用结构所需的 trace 和/或 session),某个维度才算“完全覆盖”。如果覆盖范围映射已在适当范围内涵盖所有已识别的质量维度,应明确指出这一点,并询问用户希望采取哪种方式:(a)审查/改进现有评估提示词;(b)为其他维度添加覆盖;或(c)无论如何继续。
对于每个拟议的评估器:
- 名称:必须匹配
^[a-zA-Z0-9_-]+$(仅限字母数字、下划线和连字符) - 类型:
LLMJudge(布尔值/评分/分类/自定义 JSON 架构)、内置类型(JSONEvaluator、RegexMatchEvaluator等)或BaseEvaluator子类。在publish模式下,MCP 工具仅支持 LLM 评审型评估器——不得静默丢弃基于代码的检查。将它们列在同一份提案表中,并将Type设为基于代码的类;在提案的“此模式下无法发布”小节中标明,并告知用户可按需获取离线代码(--sdk-code,或发布运行后再提出请求),也可获取--data-only规范。计数和覆盖率计算时,基于代码的提案也应视为套件的一部分。 - 衡量内容:用通俗语言写出 1-2 句话的描述
- 目标跨度:评估器针对哪个跨度的数据设计(例如,“根 Agent 跨度”、“LLM 子跨度
anthropic.request”、“所有llm跨度”)。如果根跨度的输入/输出对该质量维度的信息过于失真(例如,无法看到工具调用结果),请注明这一点,并指出哪个子跨度包含相关信号。在publish模式下,这对应于eval_scope(span/trace/session)、root_spans_only和 EVPfilter查询(例如@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>}}等) - 证据:至少提供一条能发现失败(或确认行为正确)的跟踪记录
- 仅发布模式字段(仅限
publish模式):integration_provider(默认值openai)、model_name(默认值gpt-5.4-mini)、sampling_percentage(默认值10)、eval_scope(默认值span),以及为限定正确跨度范围所需的任何filter查询。在提案中列出默认值,方便用户发布前覆盖。 integration_account_id(仅限publish模式):调用评审大模型所使用的集成账户。根据同一 ml_app 中已有的评估器自动检测(第 0 阶段覆盖率映射)。绝不向用户索要原始 UUID。如果没有现有评估器设置此字段,则省略该字段,并由用户在激活前于 UI 中选择账户。无论如何,所有评估器发布时的enabled均为false——完整激活流程请参阅第 3C 阶段的“始终以草稿形式发布”。
跨度、跟踪与会话范围分类(publish 模式)
不要询问用户;为每个评估器分类,并允许用户在检查点覆盖分类。
必须:先逐项检查四种规范跟踪范围用例
如果第 1 阶段发现了多步骤跟踪(≥ 2 种跨度类型,或 agent 根下存在任何 tool / retrieval / workflow 跨度),则在最终确定套件之前,必须逐项检查以下四种规范跟踪范围用例。对每种用例都必须明确判断:适用(纳入并设为 eval_scope: trace)或不适用(在提案的“已跳过的跟踪范围候选项”小节中记录一行原因)。如果跳过全部四种用例却没有逐项说明理由,说明你可能过度偏重跨度范围——请重新检查。
| 规范用例 | 触发条件 |
|---|---|
goal_completion — Agent 是否完成了用户的请求? | 任何 Agent / 多步骤应用。几乎总是适用。 |
tool_use_correctness — 是否使用了正确的工具和参数? | 跟踪中包含 tool 类型跨度。 |
rag_faithfulness — 回答是否以检索到的文档为依据? | 跟踪中包含 retrieval 类型跨度。 |
conversation_quality — 多轮 LLM 调用之间是否连贯? | 跟踪中包含 ≥ 2 个 llm 跨度,或应用对多轮会话进行了埋点。 |
必须:遍历规范的会话范围用例(仅当 sessions_present 时)
**门槛:**仅当 Phase 1 会话探测将 sessions_present 设为 true 时,才执行此演练。如果不存在会话,则完全跳过会话范围,并在提案中注明“session scope skipped — no session_id on sampled spans”。
当 sessions_present 为真时,你必须遍历以下四个规范的会话范围用例。对于每个用例,明确判断:适用(纳入并设置 eval_scope: session)或不适用(在“Skipped session-scope candidates”小节中用一行说明原因)。会话范围用于回答跨越同一 session_id 下多个 trace的问题——单个 trace 无法查看之前或之后的轮次。
| 规范会话用例 | 触发条件 |
|---|---|
session_goal_completion — 用户的目标是否在整个会话中得到满足? | 一个 session_id 跨越 ≥ 2 个 trace。对于多 trace 会话,几乎总是适用。 |
multi_turn_conversation_quality — 跨轮次的连贯性、记忆和一致的语气 | 多 trace 聊天/助手会话。 |
user_frustration_signals — 会话期间的沮丧、困惑、重复或放弃信号 | 任何多轮会话(跨 trace 重复或改述请求)。 |
agent_consistency_across_session — Agent 是否始终保持一致,并在跨 trace 发生错误后恢复? | 会话跨越 ≥ 2 个 trace 的 Agent 应用。 |
对于其他拟议的评估器(例如语气、格式、安全性),请按顺序应用此范围判断:
- 能否仅根据一个 span 的
meta.input+meta.output正确作出判断?这里的“正确”是指:即使考虑 trace 中的其他 span,判断结果也不会改变。→eval_scope: span。 - 否则,如果能根据单个 trace 中的 span 作出判断 →
eval_scope: trace。如果评估器名称包含 grounding、faithfulness、hallucination、completeness、correctness across steps、consistency 或 workflow,默认使用 trace 范围——这些评估器几乎总是需要跨 span 的上下文。 - 否则,如果判断需要同一
session_id中多个 trace 的上下文(总体满意度、随时间推移的行为、多轮连贯性),且sessions_present为真 →eval_scope: session。如果名称包含 session、conversation、across turns、over time、satisfaction、frustration 或 abandonment,默认使用 session 范围。如果sessions_present为假,则回退到 trace 范围,并注明此限制。
权衡因素(不要让它们左右选择)
Trace 范围的成本高于 span 范围:每个已完成的 trace 执行一次判断(而非每个匹配的 span),提示词负载更大,触发延迟为 3 分钟(Datadog 会等待 3 分钟无活动后才将 trace 视为已完成;之后到达的 span 将被排除)。Session 范围的成本最高:每个已完成的 session 执行一次判断(session_id 在30 分钟无活动后视为已完成——trace 的时限为 3 分钟——且前一个 span 到达 30 分钟后才到达的 span 将被排除),负载也最大(会话中每个 trace 的所有 span,最多 10,000 个 span)。这些是成本控制手段——应通过 sampling_percentage 和 filter 进行处理,而不是降低范围级别。选择范围的依据应是评估的正确性。
明确分类
在提案表中添加 Scope 列,并为每个评估器提供一句理由。如果你跳过了某个规范的跟踪范围 或会话范围 用例,请将其及原因列在对应的“已跳过的……范围候选项”小节中——用户会看到,并可覆盖你的决定。
理由示例:
tone_check— span。判断“这条回复是否礼貌”只需一个 LLM span 的meta.output.messages[*].content;跟踪中的其他 span 不会改变这一判断。goal_completion— trace。判断 Agent 是否完成了用户的请求,取决于工具调用序列和最终 LLM 回复;任一单独 span 的meta.output只能显示该步骤的输出。tool_use_correctness— trace。将工具输入与请求及最终回复进行比较,需要关联 ≥ 3 个 span(根 span、工具 span、最终 LLM span)。rag_faithfulness— trace。事实依据核验需要将retrievalspan 的文档与 LLM span 的回答配对。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 规范”),不要重新运行阶段 1–2。复用已确认的评估器套件,直接跳转到阶段 3A(sdk_code)或阶段 3B(data_only),将每个已发布的在线评估器转换为离线形式:
- 在线提示词模板(span/trace/session 占位符)转换为使用通用
{{input_data}}/{{output_data}}占位符的离线 LLMJudge(离线数据来自用户的数据集/任务函数,而不是 spans——参见 EvaluatorContext 说明),并保留评分标准和通过条件。 - 无法在线发布的代码检查(“此模式下无法发布”集合)在此处作为实际的
BaseEvaluator/ 内置评估器生成。
反过来也适用:从 --sdk-code 开始的用户可以要求在线发布已确认的套件(阶段 3C)。emit_dataset 路径与评估器工作流分开——它从来没有可供转换的“已确认套件”。
阶段 3A:生成并写入评估器代码
目标:生成最终的 .py 文件并写入磁盘。
对于每个已确认的评估器,按照上述 SDK Reference 模式生成生产级 Python 代码。
代码生成规则
-
提示词应基于 trace:LLMJudge 系统提示词和用户提示词必须引用在生产 trace 中实际观察到的模式。不要编写“评估响应是否良好”之类的通用提示词——应以应用领域、观察到的失败模式和成功标准为依据。
-
模板变量保持通用,并添加上下文注释:在提示词中使用
{{input_data}}和{{output_data}}作为顶层占位符——不要引用诸如{{input_data.messages[-1].content}}这样的嵌套 span 路径。评估器的数据来自用户的数据集和任务函数,而不是直接来自 spans。相反,应在每个评估器上方添加注释,说明其适用的数据以及用户应如何调整:# 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
笔记本导出(摘要之后)
显示摘要后,提供笔记本导出选项。
-
如果在阶段 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 调用。
- 其他任何错误(绑定、序列化、意外响应)→ 回退到 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
阶段 3B:生成并写入评估规范 JSON
目标:将已确认的评估器套件和代表性追踪样本序列化为单个独立 JSON 文件——不依赖任何 SDK。
输出路径:./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:来自阶段 1 的 10–20 条代表性追踪记录。suggested_labels是 Claude 根据追踪检查得出的最佳判断,并非真实标签。该字段名称已明确传达这一点。- PII 规则:写入前,从所有
input、output和evidence[].observation字段中删除电子邮件、姓名和敏感数据(与阶段 3A 相同)。
写入说明
- 按照上述架构在内存中组装 JSON 对象。
- 使用阶段 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
笔记本导出(摘要之后)
逻辑与阶段 3A 相同——如果检测到 rca_notebook_url,则提供追加到 RCA 笔记本的选项;否则创建新的独立笔记本。使用与阶段 3A 相同的笔记本单元格格式,将 output_path 替换为 JSON 规范文件路径。在 pup 模式下,按照阶段 3A 所述使用 pup notebooks create / pup notebooks edit。
阶段 3C:将在线评估器发布到 Datadog(作为禁用的草稿)
仅在用户于阶段 2 检查点确认套件后才会进入此阶段——在此之前,不会向 Datadog 写入以下任何内容。
目标:对于每个已确认的评估器,通过 create_or_update_llmobs_evaluator 向 Datadog 写入 LLM 评审配置,作为禁用的草稿(enabled: false)。在用户于 UI 中审核并启用之前,它不会为任何 span 评分;启用后,它会自动对匹配的生产 span 运行。
发布前检查(单条消息——并行处理)
对于每个拟定的 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 | span 范围为 10,trace 范围为 5,session 范围为 5(开销最大——可考虑调低) |
eval_scope | span(根据阶段 2 的分类规则,自动提升为 trace 或 session) |
提示词模板:将 LLMJudge 提示词转换为 MCP 格式——由 {role, content} 消息组成的有序数组。系统提示词对应 {role: "system"},用户提示词对应 {role: "user"}。使用span 数据占位符(见下文),不要使用离线格式 {{input_data}} / {{output_data}},该格式仅存在于 EvaluatorContext 中。
在线模板变量
在线评估器提示词通过 dd-source template 库(domains/ml-observability/shared/libs/template)运行。缺失路径 → 空字符串。模板解析所依据的数据结构取决于 eval_scope:
eval_scope: span(默认) — 占位符针对单个 span 的 JSON(序列化为 map 的llmobs.SpanJSON)进行解析。直接使用下方的 span 别名 / 点路径。eval_scope: trace— 占位符针对trace 负载{ spans: [...] }进行解析。使用{{spans[N]...}}、{{spans[*]...}}或{{spans[field.path:value]...}}选择 span,然后应用字段路径。{{span_input}}/{{span_output}}别名不可用于 trace 作用域 — 请改为通过spans数组引用 span 数据。eval_scope: session— 占位符针对session 负载{ session_id, traces: [ { trace_id, root_span_id, spans: [...] }, … ] }进行解析。通过{{traces[*].spans[*]...}}(比 trace 作用域多嵌套一层)以及下方的数组选择器访问 spans。{{span_input}}/{{span_output}}别名以及 trace 作用域的顶层{{spans[...]}}均不可用 — spans 位于traces[*].spans下。仅在sessions_present时使用(Phase 1 探测)。
Span 作用域(eval_scope: span)
内置的、根据 span 类型区分的别名(评估器需适用于多种 span 类型时优先使用):
| 别名 | LLM span(meta.span.kind = "llm") | 其他 span(agent、workflow、task,…) |
|---|---|---|
{{span_input}} | meta.input.messages[*].content | meta.input.value |
{{span_output}} | meta.output.messages[*].content | meta.output.value |
常用的显式点路径(评估器专为某一种 span 类型设计时使用):
| 路径 | 获取内容 |
|---|---|
{{meta.input.value}} / {{meta.output.value}} | agent / workflow / task / tool span 上的纯字符串 I/O |
{{meta.input.messages[*].content}} | LLM span 上所有输入消息的内容(以换行符连接) |
{{meta.input.messages[0].content}} | 第一条消息(通常是系统提示词) |
{{meta.output.messages[*].content}} | 助手回复 |
{{meta.input.documents}} | 检索到的文档(RAG)— JSON 序列化 |
{{meta.metadata.<key>}} | 自定义元数据字段 |
{{meta.tool_definitions}} | 可用工具 — JSON 数组 |
{{*}} | 整个 span 的紧凑 JSON(调试 / 兜底匹配所有内容) |
Trace 作用域(eval_scope: trace)
| 模式 | 获取内容 |
|---|---|
{{spans}} | trace 中每个 span 的 JSON |
{{spans[N].meta.input.value}} | 按索引选择单个 span — spans[0] 是 trace 根 span |
{{spans[*].name}} | 按顺序列出所有 span 名称,以换行符连接 |
{{spans[*].meta.output.value}} | 所有 span 的输出,以换行符连接(适用于“最终答案 = 最后一条输出”) |
{{spans[name:my-span].meta.input.value}} | 按 span 名称筛选 |
{{spans[meta.span.kind:llm].meta.output.value}} | 所有 LLM 类型 span 的输出 |
{{spans[meta.span.kind:tool]}} | 整个 tool span 的 JSON,包含成对的输入 / 输出 — 适用于工具使用正确性评估 |
{{spans[meta.span.kind:retrieval].meta.output.documents[*].text}} | 每篇检索文档的文本 — 适用于评估 RAG 忠实度 |
{{*}} | 整个 trace 负载的 JSON(调试兜底) |
会话范围(eval_scope: session)
| 模式 | 获取内容 |
|---|---|
{{traces}} | 会话中每条 trace 的 JSON,每条包含 { trace_id, root_span_id, spans: [...] } |
{{session_id}} | 会话 ID |
{{traces[*].trace_id}} | 会话中的所有 trace ID,以换行符连接 |
{{traces[0].spans[0].meta.input.value}} | 第一条 trace 的第一个 span |
{{traces[*].spans[*].name}} | 整个会话中所有 span 的名称 |
{{traces[*].spans[*].meta.output.value}} | 整个会话中每个 span 的输出 |
{{traces[*].spans[meta.span.kind:llm].meta.input.messages[*].content}} | 整个会话中所有 LLM span 的输入消息——用户轮次 |
{{traces[*].spans[meta.span.kind:llm].meta.output.messages[*].content}} | 整个会话中的所有助手回复 |
{{traces[*].spans[meta.span.kind:tool]}} | 整个会话中的所有工具 span(成对的输入/输出) |
{{traces[*].spans[meta.span.kind:retrieval].meta.output.documents[*].text}} | 整个会话中所有检索文档的文本(RAG) |
{{*}} | 整个会话载荷的 JSON(调试备用方案) |
注意:筛选选择器应用于 spans 数组(traces[*].spans[meta.span.kind:llm]),而不是 traces——trace 对象仅包含 trace_id、root_span_id 和 spans,因此 trace 层级没有可供筛选的内容。
数组选择器语法(适用于所有范围)
[N]— 索引(从 0 开始)[START,END]— 包含首尾的范围,END会限制在切片长度以内[*]— 通配符(展开所有元素)[field.path:value]— 按嵌套字段相等条件筛选数组元素,例如messages[role:user]或spans[meta.span.kind:tool]
编写提示词时需要牢记的解析规则:
- 字符串数组 → 以换行符连接
- 对象数组 / 混合值数组 → 紧凑 JSON
- 单个空切片 → 空字符串
- 隐式展开:
messages.content的行为与messages[*].content相同 - 不支持负索引(会产生解析错误)——使用已知索引的
[N],或使用[*]表示“最后一个助手轮次”
如何选择形式:
- 通用 span 评估器(例如
tone_check、output_format)→ 使用{{span_input}}/{{span_output}},以便适用于各种 span 类型。 - 特定于 LLM span 的评估器(例如
system_prompt_adherence)→ 使用显式的meta.input.messages[*].content/meta.output.messages[*].content,以便区分 system、user 和 assistant 轮次。 - Span 范围的 RAG 评估器(单个检索 + 生成 span)→ 将
{{meta.input.documents}}与{{span_output}}结合使用。 - Trace 范围的评估器 → 参见下方的“Trace 范围评估器示例”,了解四种典型模式(目标完成度、工具使用正确性、RAG 忠实度、对话质量)。
- 会话范围的评估器 → 参见下方的“会话范围评估器示例”,了解四种典型模式(会话目标完成度、多轮对话质量、用户挫败信号、Agent 在整个会话中的一致性)。
- 元数据感知评估器 → 直接引用
{{meta.metadata.<key>}}。
如果用户在同一个 ml_app 中已有自定义评估器(Phase 0 覆盖范围图),且没有充分理由偏离,则应遵循其惯例。
Trace 范围评估器示例
四种规范 Trace 范围用例的具体用户提示词正文,摘自公开文档(Trace-Level Evaluations)。每个示例都搭配一个描述评分标准的静态 System 提示词(不含占位符)。
| 用例 | filter | 用户提示词正文 |
|---|---|---|
| 目标完成度 — Agent 完成了用户的请求 | @parent_id:undefined @meta.span.kind:agent | User goal:\n{{spans[0].meta.input.value}}\n\nAgent steps:\n{{spans}} |
| 工具使用正确性 — 使用了正确的工具并提供正确的参数 | @parent_id:undefined @meta.span.kind:agent | User question:\n{{spans[0].meta.input.value}}\n\nTool calls:\n{{spans[meta.span.kind:tool].meta.input.parameters}}\n\nFinal response:\n{{spans[*].meta.output.value}} |
| RAG 忠实度 — 答案以检索到的文档为依据 | @parent_id:undefined | Retrieved context:\n{{spans[meta.span.kind:retrieval].meta.output.documents[*].text}}\n\nFinal answer:\n{{spans[meta.span.kind:llm].meta.output.value}} |
| 对话质量 — 多轮对话的连贯性与一致性 | @parent_id:undefined | Conversation:\n{{spans[meta.span.kind:llm].meta.input.messages[*].content}}\n\nAssistant responses:\n{{spans[meta.span.kind:llm].meta.output.messages[*].content}} |
请将这些示例用作起点。根据应用实际发出的 Span 名称 / 类型(在第 1 阶段观察到的情况),调整 filter 和 Span 路径。
Session 范围评估器示例
四种规范 Session 范围用例的具体用户提示词正文(改编自公开的 Session-Level Evaluations 文档)。每个示例都搭配一个描述评分标准的静态 System 提示词(不含占位符)。filter 会针对 Session 的根 Span(而非每个 Span)进行匹配,因此应使用根 Span 属性来限定范围;除此之外,无需满足 @parent_id:undefined 要求。
| 用例 | filter | 用户提示词正文 |
|---|---|---|
| Session 目标完成度 — 用户的目标在整个 Session 中均已达成 | @meta.span.kind:agent(或应用的根 Span 类型) | Session traces (chronological):\n{{traces}} |
| 多轮对话质量 — 多轮对话的连贯性与记忆能力 | @meta.span.kind:agent | User messages:\n{{traces[*].spans[meta.span.kind:llm].meta.input.messages[*].content}}\n\nAssistant responses:\n{{traces[*].spans[meta.span.kind:llm].meta.output.messages[*].content}} |
| 用户受挫信号 — 用户在整个 Session 中的受挫 / 放弃迹象 | @meta.span.kind:agent | Full session:\n{{traces}} |
| Agent 在整个 Session 中的一致性 — 在各条 Trace 中保持一致并能够恢复 | @meta.span.kind:agent | Session traces (chronological):\n{{traces}} |
根据应用实际发出的 Span 类型(在第 1 阶段观察到的情况),调整 filter 和 Span 路径。
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"] — 任何其他值都会被拒绝。务必包含 "reasoning": {"type": "string"},以便在 UI 中显示。
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}
}
如果设置了 max_threshold,请将其添加到 assessment_criteria。
分类(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"]}
}
注意:分类结构会在 anyOf 旁使用 "type": "string"(每个 const 都是一个字符串值),而离线 SDK 会在属性根节点直接使用 anyOf。
自定义/多维:固定名称的 schema 不直接支持此类评估。尽可能将其实现为评分或分类评估器,或者拆分为多个评估器。name 必须是上述三个固定值之一。
筛选器作用范围:如果提案针对特定类型的跨度(例如 LLM 子跨度),请将其转换为 EVP filter 查询,例如 @meta.span.kind:llm、service:checkout-agent 或更具体的标签。仅当目标是跟踪根跨度时,才与 root_spans_only:true 结合使用。
对于 eval_scope: trace:
- 评估器会在每条跟踪完成后触发一次,完成条件为经过 3 分钟无活动窗口。在同一跟踪的前一个跨度之后超过 3 分钟才到达的跨度,将不纳入评估。请在提案中说明这一点,让用户了解延迟,以及活动稀疏的 Agent(步骤之间间隔超过 3 分钟的长时间运行 Agent)可能被遗漏的情况。
filter查询必须仅匹配跟踪的根跨度——始终包含@parent_id:undefined(或root_spans_only: true),以避免在后代跨度上重复触发。可结合@meta.span.kind:agent(或 Phase 1 中观察到的应用根跨度类型)进行筛选。- 跟踪级采样比跨度级采样更重(在评估器一侧,一条跟踪包含多个跨度)。跟踪级评估器的
sampling_percentage默认设为5(而非跨度级默认值10);用户可以在人工审核后提高该值。
对于 eval_scope: session:
- 仅当
sessions_present(Phase 1 探测)为真时,才发布会话级评估器。否则评估器永远不会触发——没有跨度携带session_id。 - 评估器会在每个已完成的会话上触发一次。
session_id在经过 30 分钟无活动后视为完成(跟踪为 3 分钟);在前一个跨度之后超过 30 分钟才到达的跨度将不纳入评估。请说明延迟和跨度排除情况——运行时间长或活动稀疏的会话可能被拆分或截断,且超大会话的上限为 10,000 个跨度。 filter会针对会话的根跨度进行匹配(与跟踪作用范围使用相同的预筛选),因此请使用根跨度属性进行限定,例如@meta.span.kind:agent(或 Phase 1 中观察到的应用根跨度类型)。无需包含@parent_id:undefined:会话根本身已经是根跨度。- 会话级采样的开销最高(在评估器一侧,一个会话包含多条跟踪,而每条跟踪又包含多个跨度)。
sampling_percentage默认设为5(与跟踪级相同),并标明这是开销最高的作用范围;用户可以在人工审核后进一步降低该值。
始终以草稿形式发布(enabled: false)
创建或更新评估器时,始终将其设为 enabled: false——无论 integration_account_id 是否从现有评估器中自动检测得出。激活状态以 UI 为准;此技能绝不能代表用户自动启用评估器。用户会在 UI 中审核每份草稿,确认集成账户正确(自动检测到的 ID 可能对应另一个与用户希望用于此应用的评估器 LLM 不同的判断器 LLM),并在确认满意后切换开关。
这样一来,工作流默认就是安全的:错误的 integration_account_id、调校不当的提示词或范围过宽的筛选器,都不会未经人工审核就生效。自动检测账户 ID 仍然有帮助,因为草稿会预先选中正确账户,从而加快审核。
integration_account_id 的解析
integration_account_id 是一个不透明的 UUID,UI 会将其与组织的集成账号列表进行匹配,以填充账号部分的下拉菜单。用户通常不知道这个值,因此绝不要要求用户提供原始 UUID。
解析顺序:
-
从现有评估器继承——在 Phase 0 中,你已为每个现有的自定义评估器调用
get_llmobs_evaluator。检查这些响应中的llm_provider.integration_account_id字段。如果其中任何一个有值,就在已发布的草稿中使用相同的 ID。如果多个不同的 ID 出现在现有评估器中,选择出现次数最多的那个,并说明你选择了哪个,以便用户在 UI 审核阶段进行更正。 -
如果没有现有评估器带有该值,则省略——如果 ml_app 中没有任何自定义评估器带有
integration_account_id,则在发布负载中省略该字段。草稿渲染时不会预先选定账号;用户可在激活前的 UI 审核阶段选择一个账号。
无论哪种情况,评估器都会以 enabled: false 发布。由用户把关——参见上文的“始终以草稿形式发布”。
发布(单条消息——并行处理)
在单条消息中发出所有 create_or_update_llmobs_evaluator 调用(每个评估器调用一次)。将 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.
Notebook 导出(摘要之后)
遵循与 Phase 3A 相同的逻辑——如果检测到 rca_notebook_url,则提供将内容追加到 RCA notebook 的选项;否则创建一个新的独立 notebook。notebook 单元格应列出已发布评估器及其 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字段。每一页的所有调用都应在单条消息中发出(并行处理)。
记录提取
对于每个采样的根 span,构建一个 DatasetRecordRaw 条目:
| 字段 | 来源 | 备注 |
|---|---|---|
input_data | 根 span 的 meta.input | 如果 meta.input.value 是普通字符串,则包装为 {"input": "<value>"}。如果存在 meta.input.messages,则使用 {"messages": [...]}(保留 role/content 结构)。对于也包含 documents 的 RAG 根 span,加入 {"documents": [...]}。 |
expected_output | 根 span 的 meta.output.value(或 LLM 类型根 span 的 meta.output.messages[-1].content) | 这是生产行为基线,不是 ground truth。在摘要中明确说明这一点,以免用户过度信任它。 |
metadata | {"source": "production", "trace_id": "<id>", "span_id": "<id>", "ml_app": "<ml_app>", "extracted_at": "<ISO 8601 UTC>"} | Trace/span ID 可让用户点击并跳转回 Datadog 中的源 trace。 |
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 技能集中其他位置采用的同一套清理方式:将所有字符串值中匹配 email / phone / SSN / API-key 正则表达式的内容替换为 <REDACTED:pii-type>。在摘要中用一行警告列出受影响的记录索引。
标签规范化(写入前的强制步骤)。 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")→ 包装为"tag:<full-original-string>",这样既能保留格式错误的值,也不会导致 SDK 崩溃。
跟踪规范化次数,并在摘要中用一行说明(Tag normalizations applied: N (bare strings auto-prefixed with 'tag:'))。规范化后,如果任何记录的 tags 列表仍包含非 key:value 字符串,则不要写入数据集——这是此步骤中的错误。
跳过以下记录:
meta.output为空或缺失(没有可用的基线)。- 根 span 的 I/O 纯粹是结构性的(例如只有工具调用,没有自然语言输出)。记录跳过的记录数,并在摘要中说明。
写入文件
- 使用 Write 工具将 JSON 数组(缩进 2 个空格)写入
<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 结果是未经包装的字典或列表。