关于 agent-observability-experiment-py-bootstrap
此技能使用公开的 ddtrace.llmobs SDK 生成独立的 Python 实验脚本或笔记本。面向从本地或 Datadog 数据集搭建评估的开发者,并支持配置评估器风格及发现应用任务入口。
主要功能
- 01生成 Python 脚本或笔记本
- 02支持本地和 Datadog 数据集
- 03提供函数、类和远程评估器
- 04发现应用任务入口
- 05配置实验项目和输出
适用场景
- 01从本地 JSON 或 CSV 数据集创建实验
- 02将现有 Datadog 数据集载入实验
- 03围绕应用任务搭建实验框架
- 04为生成代码选择评估器风格
MCP 相关
可使用 Datadog LLM-Obs MCP 工具发送启动连通性和使用情况信标。若工具不可用,则可使用 pup;代码生成不要求访问后端。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datadog-labs/ agent-skills --skill agent-observability-experiment-py-bootstrap安装后在 Claude Code 中输入/agent-observability-experiment-py-bootstrap确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/evaluator-styles/class.mdfunction.mdremote.mdreferences/providers/anthropic.mdbedrock.mdgemini.mdlangchain.mdlitellm.mdllamaindex.mdopenai.mdscripts/env_setup_template.py详细介绍
后端
检测 — 每次调用开始时,在执行任何操作之前,确定启动信标调用应使用哪个后端(如果有):
- 如果用户在调用中的任意位置传入了
--backend pup→ 信标使用 pup 模式。跳过步骤 2–3。 - 检查当前活动工具列表中是否存在 MCP 工具。判定的标准信号是:可用工具中是否出现
mcp__datadog-llmo-mcp__list_llmobs_evals。如果存在 → 使用 MCP 模式。 - 如果不存在 MCP 工具 → 通过
pup --version检查pup是否可执行。如果有响应 → 使用 pup 模式。 - 如果两者都不可用 → 静默跳过信标,继续代码生成。此技能主要用于本地操作(内省 + 文件生成),不需要访问后端;信标仅用于使用情况归因。
调用 ID: 每次调用开始时,必须在任何 MCP/pup 调用之前生成一个 8 字符的十六进制调用 ID(例如,3a9f1c2b)。在整个调用过程中保持不变。
意图标记: 每次 MCP 工具调用时,都要在 telemetry.intent 前加上 skill:agent-observability-experiment-py-bootstrap[<inv_id>] — ,后接调用原因说明。仅在第一次 MCP 工具调用(下方的启动信标)时,使用 skill:agent-observability-experiment-py-bootstrap:start[<inv_id>] — (注意 :start 后缀)。
启动信标: 解析参数后立即(工作流步骤 1,在步骤 2 解析数据集之前)发出且仅发出一次信标调用,以登记技能使用情况并验证后端连接。此操作为即发即弃——如有任何错误,向用户显示一行 Note:,但不要阻止代码生成。
- MCP 模式: 使用
telemetry.intent = "skill:agent-observability-experiment-py-bootstrap:start[<inv_id>] — Skill startup: register usage and verify Datadog connectivity"调用mcp__datadog-llmo-mcp__list_llmobs_evals。丢弃响应内容;此调用的目的是添加遥测标记。 - pup 模式: 通过 Bash 运行
pup llm-obs evals list --limit 1。pup 自带遥测,无需意图前缀。 - 无后端: 输出一行
(Telemetry beacon skipped — no Datadog backend detected; this is informational only and does not affect codegen.),然后继续。
信标不得导致技能失败。如果调用出错(身份验证、网络等),显示一行说明并继续。
Agent 可观测性实验(Python)引导 — 使用 ddtrace.llmobs 生成 Python 实验
生成一个使用官方 ddtrace.llmobs SDK 的独立 Python 实验。输出可以是 .py 脚本或 .ipynb 笔记本。生成的代码应遵循 DataDog 参考笔记本 https://github.com/DataDog/llm-observability/tree/main/experiments/notebooks 中展示的模式。
SDK 会代为处理惰性项目/实验创建、数据集推送差异、5 MB / 1000 条记录的批量阈值、评估指标流式传输,以及状态机。因此,此技能绝不能重新实现这些基础功能——只需导入 LLMObs 并信任它。
用法
/agent-observability-experiment-py-bootstrap [--purpose <free text>] [--format py|ipynb] [--dataset <path>] [--dataset-name <name>] [--dataset-version <int>] [--project-name <name>] [--evaluator-style function|class|remote] [--jobs <n>] [--output <path>] [--task-source <module:function>] [--placeholder-task] [--app-root <path>] [--env-file <path>]
参数:$ARGUMENTS
输入
所有输入均为可选项。如果用户省略某个标志,则回退到默认值 — 绝不要因提示用户提供 --jobs、--format 等参数而阻塞。
| 输入 | 默认值 | 说明 |
|---|---|---|
--format | py | py(单个 .py 文件)或 ipynb(每个部分对应一个单元格的 Jupyter 笔记本)。 |
--dataset | 无 — 内联生成包含 3 条记录的示例 records=[...],使文件可直接运行 | 本地 DatasetRecordRaw[] JSON 或 CSV 文件的路径。JSON → create_dataset(records=...);CSV → create_dataset_from_csv(...)。不能与 --dataset-name 同时使用。 |
--dataset-name | 无 | 运行时通过 LLMObs.pull_dataset(...) 获取的现有 Datadog 数据集名称。如果数据集已存在于 Datadog 中(例如在 UI 中创建或由先前运行创建),请使用此参数 — 无需本地文件。不能与 --dataset 同时使用。 |
--dataset-version | 无(最新版本) | 使用 --dataset-name 时,固定到指定的数据集版本。作为 pull_dataset(version=N) 传入。如果未设置 --dataset-name,则忽略。 |
--project-name | experiment-<service-name> — 根据代码库推导(参见工作流步骤 1);仅当无法解析时才回退到 experiment-sdk-default | Datadog 项目名称(在 LLM Experiments UI 中可见)。SDK 的 ml_app 标签会自动回退到此值 — 无需单独设置标志。 |
--evaluator-style | function | function(普通函数 — 笔记本默认值)、class(BaseEvaluator 子类)或 remote(RemoteEvaluator 实例)。 |
--jobs | 10 | 传递给 experiment.run(jobs=N)。 |
--output | ./experiments/experiment.<ext> | 文件扩展名根据 --format 确定:.py 或 .ipynb。 |
--task-source | 自动 — 通过应用内省发现(参见工作流步骤 2.5) | 显式覆盖项:用于包装为 task_fn 的函数,格式为 <dotted.module.path>:<function_name>。如果已知入口点且希望跳过内省扫描,请使用此参数。 |
--placeholder-task | 关闭 | 选择退出应用内省,并生成通用的 # TODO(user) 占位任务。在没有实际应用的情况下搭建脚手架、运行测试,或用户明确希望自行填写任务时使用。 |
--app-root | 从 pyproject.toml / setup.cfg / setup.py / cwd 解析 | 限制内省扫描范围的根目录。如果设置了 --placeholder-task 或 --task-source,则跳过。 |
--env-file | 无 — 生成的文件会在运行时自动发现 .env 文件(参见工作流步骤 4,第 1 节) | .env 格式文件的绝对路径。生成的代码会先预加载此路径,然后再自动遍历发现文件。如果凭据位于非标准位置(例如 ~/.config/dd/staging.env),请使用此参数。 |
--purpose | 自动 — 如果未设置或无法从调用消息中推断,则在工作流步骤 2.0 中通过 AskUserQuestion 提示输入 | 描述实验目的的自由格式字符串(例如 "test that the agent picks the right tool for ambiguous user requests"、"verify SQL output always parses"、"regression-test prompt v3 against prod baseline")。用作推理上下文 — 影响工作流步骤 2.5 中的候选项排序,决定步骤 2.5d 中的包装器返回类型,辅助步骤 3 中的评估器选择,并嵌入生成文件的头部注释中。不是固定分类体系 — Claude 会针对每次调用动态读取字符串并决定其影响。 |
SDK 接口(已引用)
这是生成的代码使用的公共符号。它们全部来自 ddtrace.llmobs(公共包,切勿从 ddtrace.llmobs._experiment 或其他以下划线开头的模块导入)。
| 导入项 | 来源 | 提供的功能 |
|---|---|---|
LLMObs | ddtrace/llmobs/__init__.py 重新导出 _llmobs.py | .enable()、.create_dataset()、.create_dataset_from_csv()、.pull_dataset(dataset_name, project_name, version)、.experiment()、.async_experiment() |
RemoteEvaluator、EvaluatorContext | ddtrace/llmobs/__init__.py | 在服务端运行的 LLM-as-Judge;优先于内联 LLMJudge 使用 |
BaseEvaluator、EvaluatorResult | ddtrace/llmobs/__init__.py | 基于类的评估器路径(高级用法) |
LLMJudge | ddtrace/llmobs/_evaluators/llm_judge.py(已重新导出) | 支持提示词模板的内联 LLM-as-Judge |
规范调用签名(必须与生成的代码完全一致):
LLMObs.enable(
api_key=os.getenv("DD_API_KEY"),
app_key=os.getenv("DD_APPLICATION_KEY"),
site=os.getenv("DD_SITE", "datadoghq.com"), # required for non-prod sites (e.g. datad0g.com, datadoghq.eu)
project_name="<project>",
agentless_enabled=True, # required when not running behind the dd-agent
)
# Note: ml_app is not a separate input. The SDK derives it from project_name
# when not supplied. If a user really wants to override it later, they can
# add `ml_app="..."` to enable() themselves.
dataset = LLMObs.create_dataset(
dataset_name="<name>",
description="<optional>",
records=[
# Per-record `tags` MUST be a list of "key:value" strings (e.g. "env:smoke"),
# never bare strings — the SDK rejects malformed tags with a ValueError on append.
{"input_data": {"<k>": "<v>"}, "expected_output": "<v>", "metadata": {}, "tags": ["env:<env>"]},
# ...
],
)
# OR
dataset = LLMObs.create_dataset_from_csv(
csv_path="<path>",
dataset_name="<name>",
input_data_columns=["<col1>", "<col2>"],
expected_output_columns=["<col>"],
)
# OR pull an existing Datadog dataset by name (no local file needed)
dataset = LLMObs.pull_dataset(
dataset_name="<name>",
project_name="<project>", # optional — defaults to the project on enable()
version=2, # optional — pin a version; omit for the latest
)
def task_fn(input_data: dict, config: dict):
# In real output, this is wired to the user's discovered LLM call site via
# Workflow step 2.5. Only emits as a generic placeholder (with # TODO(user))
# when --placeholder-task is set or introspection found nothing.
...
# Plain function evaluator (default style)
def exact_match(input_data, output_data, expected_output) -> bool:
return output_data == expected_output
experiment = LLMObs.experiment(
name="<experiment_name>",
dataset=dataset,
task=task_fn,
evaluators=[exact_match],
config={
"model": "gpt-4o-mini",
"temperature": 0.0,
# Provenance also lives in `config` so it renders in the
# experiment's Configuration view alongside model/temperature.
# `tags=` below only reaches metadata.tags, which the current UI
# does not surface as chips — config is what users actually see.
"generated_by": "claude-code",
"skill": "agent-observability-experiment-py-bootstrap",
"purpose": "<one-line purpose from step 2.4>",
},
description="<one-line purpose from step 2.4>",
tags={
# Same provenance, sent to experiment metadata.tags for any future
# tag-filter UI / API consumers. Always emitted alongside the
# config copy — never one without the other.
"generated_by": "claude-code",
"skill": "agent-observability-experiment-py-bootstrap",
"purpose": "<one-line purpose from step 2.4>",
},
)
experiment.run(jobs=10)
print(experiment.url)
评估器风格
生成的代码使用三种评估器接口中的一种,由 --evaluator-style 选择。各风格的详细信息位于 references/evaluator-styles/<style>.md,并按需加载——生成时只会参考所选风格。适用于三种风格的唯一通用指导是下面的 EvaluatorResult 规则。
返回 EvaluatorResult,而非裸值
普通函数可以返回 bool / float / dict,BaseEvaluator.evaluate() 可以返回原始 JSONType。SDK 两种形式都接受,但 EvaluatorResult 包含一些字段,能够以原始值无法实现的方式呈现在 Datadog UI 中:
| 字段 | 类型 | Datadog UI 中的用途 |
|---|---|---|
value | bool / float / str / dict(JSONType) | 分数本身——显示在实验指标上。必填。 |
reasoning | str | 在比较 UI 中显示每条记录的解释;让审阅者无需重新运行 LLM,就能了解评估器通过或失败的原因。 |
assessment | str(例如 "pass" / "fail" / "partial") | 确定指标趋势上升还是下降代表改进;UI 会据此为基线与候选结果的比较着色。 |
metadata | dict[str, JSONType] | 每条记录的自由格式上下文(例如 {"confidence": 0.95});显示在记录详细信息中。 |
tags | dict[str, JSONType] | 用于在 UI 中筛选实验结果(例如 {"category": "accuracy"})。 |
除单行相等性检查外,其他评估器默认使用 EvaluatorResult。exact_match 和 length_under_500 这类简单检查是仅有的可以接受裸 bool 的情况。
风格路由
解析 --evaluator-style(默认值为 function)后,从 <this-skill-dir>/references/evaluator-styles/ 读取相应的参考文件,并使用其中的代码模板生成文件第 5 节:
--evaluator-style 的值 | 读取文件 | 适用场景 |
|---|---|---|
function (默认值) | references/evaluator-styles/function.md | 大多数情况——符合规范的 Notebook 风格 |
class | references/evaluator-styles/class.md | 需要持久状态或异步客户端的评估器 |
remote | references/evaluator-styles/remote.md | 在 Datadog UI 中配置、可在多个实验中重复使用的服务端 LLM-as-Judge |
不要加载全部三种风格。每个参考文件都是独立的,包含代码模板、适用场景和不适用场景指导。
生成文件结构
两种格式的章节顺序相同。在 .py 中,这些章节会成为注释横幅;在 .ipynb 中,每个章节会成为一个 Markdown 单元格加一个代码单元格。
0. File header docstring — name, generated_at, purpose (from step 2.4), provider, wired task source.
1. Env setup — auto-discover .env files (cwd, app root, parent walk,
~/.datadog/credentials), then os.getenv reads + hard-assert
required keys. NO python-dotenv dependency. Shell env wins
over file-loaded values. Only the provider keys actually
needed by the wired task_fn are asserted (see step 2.5).
2. LLMObs.enable() — explicit api_key/app_key/project_name/agentless_enabled
3. Dataset — inline records OR create_dataset_from_csv
4. Task function — REAL task function imported from the user's app (via Workflow step 2.5
introspection) and adapted to the SDK signature. Only falls back to a
placeholder OpenAI call with # TODO(user) if --placeholder-task is set or
no LLM call site was found in the project.
5. Evaluators — 2-3 in the requested style, semantics seeded by the purpose (step 3)
6. Experiment — LLMObs.experiment(config={..., "generated_by": "claude-code", "purpose": "...", ...}, tags={...})
7. Run — experiment.run(jobs=N); print(experiment.url)
8. Results inspection — experiment.as_dataframe() if pandas, else print
工作流
-
解析参数。默认
--format py。根据--format确定--output的扩展名。 如果未提供--project-name,则按以下来源的顺序查找,取第一个匹配项,并解析出experiment-<service-name>形式的默认值:pyproject.toml→[project] name(PEP 621)或[tool.poetry] name。setup.cfg→[metadata] name。setup.py→setup(...)的第一个name="..."参数。package.json→"name"(适用于 LLM 应用位于 TS/JS monorepo Python 包中的情况)。- 当前工作目录的基本名称,转为小写并规范化为 slug(
/^[a-z0-9-]+$/— 将不匹配的字符替换为-)。
最终项目名称为
experiment-<service-name>。如果<service-name>已以experiment-开头,则去掉此前缀(因此,包名为experiment-foo时,结果是experiment-foo,而不是experiment-experiment-foo)。如果五个来源都无法解析出非空字符串,则回退到experiment-sdk-default,并在后续步骤输出中发出警告,提示用户应显式设置--project-name。将解析出的名称作为字符串字面量嵌入生成文件的
PROJECT_NAME = "..."行 — 不要生成运行时os.getcwd()查询,因为用户运行文件时所在的目录可能与技能解析名称时所在的目录不同。
1.5. 启动信标。 按照上文 Backend 部分的说明:生成一个 8 位十六进制调用 ID,然后发出单次信标调用(添加标签 skill:agent-observability-experiment-py-bootstrap:start[<inv_id>] — Skill startup: ... 的 MCP list_llmobs_evals 调用,或等效的 pup 调用;如果没有后端则跳过)。如有错误,以单行 Note: 提示,并继续执行 — 信标仅用于使用情况归因和连接验证,绝不阻止代码生成。
-
解析数据集来源。 如果同时传入
--dataset和--dataset-name,则报错 — 两者互斥。-
--dataset <path>(本地文件 → 内联记录或 CSV 加载器):- 读取文件。如果是 JSON,则验证顶层数组是否符合
DatasetRecordRaw结构(input_data,可选的expected_output、metadata、tags)。如果是 CSV,则解析表头,并使用dataset-bootstrap启发式规则自动检测列:prompt|input|query|question→ 输入,expected|gold|truth|answer→ 预期结果。 - 对所有字符串值执行 PII 清理(电子邮件/电话/SSN/API 密钥正则表达式);将匹配项替换为
<REDACTED:pii-type>,并发出警告,列出受影响的索引。 - 对于 JSON 数据集,将记录内联到生成文件中(
records=[...]),使用户只需使用一个自包含的文件。对于 CSV 数据集,生成LLMObs.create_dataset_from_csv(csv_path="<absolute path>", ...),并告知用户运行时必须提供该 CSV 文件。
- 读取文件。如果是 JSON,则验证顶层数组是否符合
-
--dataset-name <name>(现有 Datadog 数据集 → 运行时拉取):- 生成
LLMObs.pull_dataset(dataset_name="<name>", project_name="<project>"[, version=<n>]),以替代任何create_dataset*调用。数据会在运行生成的实验时拉取 — 技能本身不会调用 Datadog。 - 仅当设置了
--dataset-version时才传递version=<n>;否则省略该参数,让 SDK 解析最新版本。 - 在调用上方添加单行注释,说明拉取的内容,例如:
# Pulled from Datadog: dataset_name="qa_v3", version=latest。 - 跳过 PII 清理和内联记录生成 — 没有需要清理的本地记录。
- 生成
-
未提供任何一个标志:
- 回退到
--dataset默认值中所述的 3 条内联示例记录,确保生成的文件开箱即可运行。
- 回退到
关于数据集 ID 的说明。 公共 SDK 的
LLMObs.pull_dataset(...)接受名称而非 ID — 因此没有--dataset-id标志。如果用户只有 Datadog 界面 URL(/llm/datasets/<id>)中的数据集 ID,操作流程为:在界面中打开该 URL,复制数据集名称,然后通过--dataset-name传入。技能不得导入ddtrace.llmobs._experiment或任何其他下划线模块来规避这一限制。 2.4. 确定实验目的。 用一句话说明实验要验证什么。这将成为一个推理输入,影响所有后续步骤(2.5 中的自省排序、2.5d 中的封装形式、第 3 步中的评估器选择、第 4 步中的文件头)。它不是固定的分类体系——用户可以输入任何能描述其目标的内容,Claude 会在每次调用时阅读并作出判断。确定顺序:
- 如果传入了
--purpose "<text>"→ 原样使用。跳至 2.5。 - 如果用户最初的调用消息中包含明确的目的陈述(例如 “设置一个实验来测试我的工具选择逻辑”、“验证 SQL 输出是否始终可解析”、“对提示词 v3 进行回归测试”),提取该陈述,并通过
AskUserQuestion将其呈现给用户确认:把提取的文本作为一个标为“使用我描述的目的”的选项,另加“选择其他目的”和“其他”。如果用户确认,则使用提取的文本。跳至 2.5。 - 否则,通过
AskUserQuestion发起询问。选项是种子提示词,而非受限的分类体系——它们只是为了给用户提供参考。每个“标签”都对应一个可供用户修改的起始句;选择某个选项后,除非用户提供备注或自由文本,否则就将其起始句用作purpose。
AskUserQuestion载荷(请原样使用以下 5 个选项——它们涵盖常见情况,但不会限制用户):question: "What is this experiment meant to validate?" header: "Purpose" options: - label: "Output accuracy / answer quality" description: "Verify the model produces correct answers compared to expected outputs. Most common starting point." - label: "Tool call correctness" description: "For agent apps — validate the agent picks the right tool with the right arguments. Useful when tool routing is the failure surface." - label: "Structured output / schema validity" description: "Verify the output always conforms to a required shape (JSON, SQL, citation format, etc.)." - label: "Retrieval / RAG faithfulness" description: "Verify the answer is grounded in retrieved documents — no hallucinations beyond the retrieved context." - label: "Refactor / regression test" description: "Check whether a prompt or code change preserves observed behavior. Uses the dataset's expected_output as the ground-truth baseline."用户可以选择某个选项(该选项将成为起始句),也可以选择“其他”并自行填写。无论哪种情况,最终生成的字符串都会作为
purpose传递到后续步骤。后续步骤中如何使用目的——Claude 会阅读目的并推断其影响,不使用静态映射:
- 在第 2.5 步(自省排序)中:阅读目的;如果提到工具 / Agent → 提升使用
@LLMObs.agent、@workflow、LangChainReActAgent或工具调用形式的候选项。如果提到检索 / RAG / 基于上下文 → 提升导入向量存储或调用retrieve/query_engine的候选项。如果提到 schema / JSON / SQL → 提升使用response_format、pydantic 或结构化输出 API 的候选项。请自行判断——没有硬编码映射。 - 在第 2.5d 步(生成封装)中:如果目的需要比最终输出更丰富的信号(工具调用、检索到的文档、中间状态),且用户的函数可以提供这些信息,则生成能够捕获这些信息的封装。否则,添加一条
# Note:注释,说明封装理想情况下应返回什么,以及更丰富的返回形式有何帮助——不要重构用户的实际函数。 - 在第 3 步(评估器模板)中:阅读目的,并据此预置评估器列表。准确性 → 标准的
exact_match+ LLMJudge。工具调用 → 使用具有工具正确性评分标准的 LLMJudge,或使用用户已配置的RemoteEvaluator。Schema →JSONEvaluator(required_keys=[...])或RegexMatchEvaluator。检索 → 用于评估依据充分性的 LLMJudge。回归 →exact_match+ 近似匹配检查。--evaluator-style标志(function / class / remote)仍决定表面形式;目的决定语义。 - 在第 4 步(文件生成)中:在文件的文档字符串头部中添加
# Purpose:注释,写明目的,让用户和审阅者了解该实验的搭建目的。 - 在输出(后续步骤)中:在后续步骤部分显示已确定的目的,使其成为运行摘要记录的一部分。
如果用户未作说明 / 选择默认选项且未提供备注,则按原样使用起始句。此步骤绝不能阻塞流程——后续始终应有一个
purpose字符串,即使它只是通用的“输出准确性 / 答案质量”。 -
2.5. 从用户的应用代码中发现任务函数。 此步骤取代旧的“占位符 task_fn”行为——入门级实验需要实际运行用户真正的 LLM 逻辑。技能必须完成这项工作,而不是把任务推给用户。
如果设置了 --placeholder-task,或设置了 --task-source <module>:<function>(此时直接使用提供的源——跳转到子步骤 2.5d),则跳过整个步骤。否则:
2.5a — 确定应用根目录。 如果提供了 --app-root <path>,则使用该路径。否则,使用步骤 1 中解析出的以下文件所在目录(按顺序):pyproject.toml、setup.cfg、setup.py、package.json。如果这些文件都未解析出,则使用当前工作目录。扫描范围严格限制在该目录树内;不得遍历 node_modules、.venv、venv、__pycache__、.git、dist、build、target、vendor、third_party,或 .gitignore(如果存在)匹配的任何目录。如果解析出的根目录为 / 或 ~,则拒绝扫描——这表示解析失败;按未找到候选项处理。
2.5b — 发现候选项。 使用 Grep / Bash 在应用根目录中查找 Python 文件,并识别以下 LLM SDK 的调用点(常见 LLM 客户端的并集):
| 模块 / 调用模式 | 信号 |
|---|---|
openai.ChatCompletion.create、openai.chat.completions.create、client.chat.completions.create | OpenAI 聊天 |
openai.completions.create、client.completions.create | OpenAI 文本 |
anthropic.messages.create、client.messages.create、Anthropic(...).messages.create | Anthropic |
litellm.completion、litellm.acompletion | LiteLLM(路由器) |
langchain.*.invoke、ChatOpenAI(...)、ChatAnthropic(...)、LLMChain(...) | LangChain |
from llama_index、as_query_engine、as_chat_engine | LlamaIndex |
google.generativeai.GenerativeModel(...).generate_content | Vertex/Gemini |
boto3.client("bedrock-runtime").invoke_model | AWS Bedrock |
@LLMObs.llm、@LLMObs.agent、@LLMObs.workflow、@LLMObs.task、@workflow、@agent | 已插桩 |
对于每个匹配项,向上查找其所在的函数(def / async def)——称其为调用点函数。记录 {file, line, function_name, is_async, signature, enclosing_class}。
跳过匹配 test_*.py、*_test.py、tests/**、conftest.py、*_fixtures.py 的文件。跳过私有辅助函数(如果 def _foo 是文件中唯一的函数,且看起来是实用工具——根据它是否具有带类型的字符串输入来判断)。
2.5c — 为候选项评分并排序。 为每个候选项评分;分数越高,越可能是“核心”入口点:
| 信号 | 分数变化 |
|---|---|
函数名属于 {generate、chat、complete、respond、answer、handle_request、process_query、process_message、run、predict、infer、call_llm、query、agent_loop、main} | +5 |
函数恰好有一个或两个参数,且至少一个参数为 str 或 dict | +3 |
函数使用了任意 @LLMObs.* / @workflow / @agent 装饰器 | +5(这是预期的插桩点) |
| 函数位于模块顶部(第一个非导入代码块) | +2 |
模块路径匹配 **/{main,app,api,handlers,server,routes,agent,bot,chat}.py | +3 |
函数是类方法(第一个参数为 self),且类名匹配 *Agent、*Bot、*Handler、*Service | +2 |
| 函数名以下划线开头,且模块中有其他非下划线候选项 | −3(很可能是辅助函数) |
函数文件位于 examples/、scripts/、notebooks/ 下 | −2 |
| 函数在多行中使用 LLM SDK(看起来是多步骤编排,而非单次调用) | +1 |
| 在这些分数的基础上,将第 2.4 步中的实验目的作为额外的软偏向。 阅读目的字符串,并提高其形态与用户测试目标相符的候选项的排名——如果目的是测试工具正确性,则优先考虑 Agent/使用工具的函数;如果目的是 RAG/接地,则优先考虑检索/向量存储用户;如果目的是测试架构,则优先考虑结构化输出用户;等等。请酌情判断,而不要使用硬编码映射。与实验目的相符的候选项通常应胜过名称更通用、但整体更优的候选项(合理的有效偏向幅度为 +3 到 +5)。示例请参见第 2.4 步“如何在后续步骤中使用实验目的”。 |
选择排名前 3 的候选项,并通过一条简短提示一次性呈现给用户(不要使用 AskUserQuestion——只需在聊天中设置一个确认检查点):
## Task function discovery
Scanned `<app_root>` and found <N> LLM-calling functions. Top candidates:
1. `<module.path>:<function_name>` score <S> (<file>:<line>, args=<sig>)
2. ...
3. ...
I'll wire candidate **1** as the experiment's task function unless you say otherwise.
Reply with the number to pick a different candidate, or "placeholder" to emit a
generic placeholder task instead.
等待确认。如果用户没有回应/说“继续”/说“1”,则使用 #1。如果他们选择其他编号,则使用对应项。如果他们说“占位符”,则设置 --placeholder-task 语义并跳到第 3 步。如果用户提供了其他 module:function,则验证其存在,然后使用它。
如果未找到任何候选项,不要阻止用户——打印一行提示(“在 <app_root> 下未检测到 LLM 调用点。将生成通用占位任务——运行前请替换它。”),然后使用占位符语义继续执行。
2.5d — 生成任务函数包装器。 一旦确定目标 <module>:<function>,就生成一个真实的 task_fn,导入并调用用户的函数。这是生成文件的第 4 节(“任务函数”);如果内省成功,则绝不能包含 # TODO(user) 标记——整个目的就是免去这项负担。
包装器构造规则:
-
导入:在第 4 节顶部生成
from <module> import <function_name>(不要放在文件顶部——将其保留在本节内,使本节自包含且易于替换)。如果该函数是类方法,也要导入类,并在task_fn内延迟实例化(使用默认的__init__();仅当构造函数需要必填参数时,才添加# TODO: pass constructor args if needed注释)。 -
签名适配:SDK 的任务函数签名为
task_fn(input_data: dict, config: dict) -> Any。用户的函数很可能具有不同的签名。请构建一个小型适配器:- 如果用户的函数接收一个参数,且数据集的
input_data形如{"<key>": <value>},其中只有一个键,则调用<function_name>(input_data["<key>"])。 - 如果用户的函数接收多个参数,则根据参数名称从
input_data的键中映射。如果名称不匹配,则按input_data.values()的顺序回退到位置传参,并添加一行注释说明这一假设。 - 如果用户的函数接收
**kwargs或一个字典,则不做修改,直接传入input_data。 - 如果用户的函数是
async,则在同步task_fn中使用asyncio.run(...)包装调用(最简单的做法;这样LLMObs.experiment(...).run()仍可保持同步)。另一种做法是生成async def task_fn并使用LLMObs.async_experiment(...)——仅当实验的其余部分已经是异步时才这样做。
- 如果用户的函数接收一个参数,且数据集的
-
配置透传:绝不能悄悄丢弃
config参数。如果用户的函数接收config/model/temperature参数,则将config.get("...")传入。 -
注释头:在
task_fn上方生成一个注释块,注明源函数、文件、行号、实验目的(来自第 2.4 步)以及所有适配选择,例如:# Task function wired to: <module.path>:<function_name> # source: <file>:<line> # purpose: <one-line purpose from step 2.4> # adapter: input_data["<key>"] -> <function_name>(<key>=...) # # To experiment with prompt / model variants without editing your app, inline # the call here instead of importing. -
在目的需要时采用更丰富的返回结构:阅读步骤 2.4 中的目的。如果仅验证最终输出字符串会丢失用户测试所需的信息(例如,用户在检查工具调用的选择——最终字符串说“我来帮你预订”,但真正有意义的信号是调用了哪个工具以及传入了哪些参数),则输出一个能捕获更丰富信号的封装,但前提是用户的函数能提供这些信息。分两种情况:
- 函数已经返回更丰富的数据(例如
{"output": str, "tool_calls": [...]},或返回带有intermediate_steps的 LangChainAgentExecutor.invoke()结果):让task_fn直接返回该结构。步骤 3 中的评估器会将其作为output_data接收。 - 函数只返回字符串,但目的需要更多信息:在
task_fn上方输出一条# Note:注释,说明需要什么更丰富的结构,以及用户可以如何将其提供出来(例如 "To capture tool calls for evaluation, refactorrespond(query)to also return theintermediate_stepsfrom your AgentExecutor.")。不要重构用户的函数——输出说明,提供简单的封装,由用户自行决定。
- 函数已经返回更丰富的数据(例如
切勿凭空捏造用户函数实际上并未提供的更丰富返回结构。封装只是接线,不是手术。
-
副作用警告:检查所选函数(以及同一模块中直接调用的函数)是否存在以下模式。如果存在任何一种,请在后续步骤输出中打印一行
WARNING:,但不要修改导入:模式 警告 os.environ[...]读取 LLM API 密钥以外的环境变量"Task reads env vars beyond LLM credentials — make sure they're set when running the experiment." requests.,httpx.,aiohttp.调用非 LLM 提供商的 URL"Task makes external HTTP calls — running the experiment will hit those endpoints." 数据库驱动( psycopg2、sqlalchemy、pymongo、redis、boto3≠ bedrock)"Task hits a database — point at a non-prod instance before running." 文件 I/O 写入( open(..., 'w')、Path.write_*)"Task writes to disk — make sure the path is safe in your experiment env." -
回退到占位符:如果设置了
--placeholder-task,或者内省未找到任何内容,则输出原始占位符代码块(通用 OpenAI 调用,带有# TODO(user))。这是任务部分中唯一允许使用TODO(user)的情况。
2.6. 确定所需凭证并输出环境变量设置部分。 只要标准位置存在可发现的 .env 文件,生成的实验就必须能够在用户无需预先导出任何 shell 环境变量的情况下运行。不要把文件本身可以完成的设置工作推给用户。
所需的 Datadog 密钥(始终需要):DD_API_KEY 和(DD_APPLICATION_KEY 或 DD_APP_KEY)。DD_SITE 为可选项(默认值为 datadoghq.com)。
所需的提供商密钥——取决于步骤 2.5 中的选择。检查导入的调用位置以确定提供商,然后读取匹配的参考文件,获取准确的断言代码行、适配器说明和注意事项。只加载适用的那个文件;不要全部加载。
| 任务中检测到的 SDK | 阅读 |
|---|---|
OpenAI(openai.*、client.chat.completions.create)——也用作占位符回退方案 | references/providers/openai.md |
Azure OpenAI(AzureOpenAI(...)、azure_endpoint=) | references/providers/openai.md(Azure 部分) |
Anthropic(anthropic.*、Anthropic().messages.create) | references/providers/anthropic.md |
LiteLLM(litellm.completion、litellm.acompletion) | references/providers/litellm.md |
LangChain(ChatOpenAI / ChatAnthropic / 等) | references/providers/langchain.md(向下深入一层,查看底层提供方) |
| LlamaIndex | references/providers/llamaindex.md |
Google Gemini(google.generativeai)/ Vertex AI | references/providers/gemini.md |
AWS Bedrock(boto3.client("bedrock-runtime")) | references/providers/bedrock.md |
| 自定义 / 无法识别的 SDK | 无参考文件——改为输出 # TODO(user): set the API key(s) your task_fn needs in your .env or shell. 注释,不要输出断言。不要臆造提供方密钥。 |
读取 <this-skill-dir>/scripts/env_setup_template.py 中附带的模板,并替换两个占位符,以生成第 1 节。 该模板与此 SKILL.md 一同提供(通过 cp -r 安装时会一并复制),是加载器和断言格式的规范来源——不要在此处自行推导。读取文件,执行以下替换,并将结果原样输出为生成的实验文件第 1 节。
| 占位符 | 替换内容 | 示例 |
|---|---|---|
{{ENV_FILE_OVERRIDE_LIST}} | 来自 --env-file 的绝对路径 Python 列表字面量(可重复指定;若未传入则为空列表) | [] 或 ["/Users/me/.config/dd/staging.env"] |
{{PROVIDER_ASSERTS}} | 根据第 2.5 步接入的内容生成零条或多条 assert os.getenv(...) 行——依据上方的 SDK 到密钥对照表。每个必需的提供方密钥占一行。若任务使用 LiteLLM(会自动路由)或自定义/无法识别的 SDK,则为空字符串。 | assert os.getenv("ANTHROPIC_API_KEY"), "ANTHROPIC_API_KEY is required for the wired task_fn." |
要替换进 {{PROVIDER_ASSERTS}} 的确切断言行位于各提供方参考文件的 ## {{PROVIDER_ASSERTS}} substitution 标题下。只读取上方对照表中与检测到的 SDK 相匹配的文件。
模板中的查找顺序(环境文件 → 脚本目录 → 当前工作目录 → 父目录 → ~/.datadog/credentials)以及 shell 覆盖文件的约定均稳定不变——它们定义在模板中,而非此技能正文中。若需更改查找语义,请直接编辑 scripts/env_setup_template.py;不要在此处用文字重新推导。
-
根据
--evaluator-style和第 2.4 步中的用途选择评估器模板。--evaluator-style决定形式(函数 / 类 / 远程——语法格式见references/evaluator-styles/<style>.md)。用途决定语义——每个评估器实际衡量什么。两项输入都要纳入考虑。默认形式(用途:准确性 / 未指定):
function:3 个普通函数——一个简单的布尔函数(exact_match风格,直接返回bool即可)、一个较丰富的基于规则的检查函数(返回包含reasoning和assessment的EvaluatorResult),以及一个 LLM-as-Judge 替代方案。若--dataset中包含结构化的expected_output,则添加 JSON 结构检查(同样返回EvaluatorResult)。class:2 个BaseEvaluator子类,带有evaluate(self, context: EvaluatorContext) -> EvaluatorResult方法。始终返回EvaluatorResult(绝不直接返回值)——带状态的评估器能提供更丰富的信息。remote:1–2 个RemoteEvaluator(eval_name=...)实例,并附带注释,提示用户先在 Datadog UI 中创建评审器。
按用途调整(读取第 2.4 步中的用途描述,并据此调整默认方案——采用自然语言判断,不要使用硬编码查找):
- 如果目的中提到 工具调用 / Agent 路由 / 工具选择:将 LLM-as-Judge 替代方案替换为
tool_calls_correctLLMJudge,其评分标准应检查工具和参数是否正确。如果 2.5d 中的包装器捕获了output_data["tool_calls"],评分标准应引用该字段;否则,应检查文本输出中的工具名称。 - 如果目的中提到 groundedness / 忠实性 / RAG:添加一个
groundednessLLMJudge,接收input_data(问题 + 检索到的文档)和output_data(答案),并检查答案中的每项陈述是否都能追溯到文档。评分标准应明确指出,出现幻觉即判定失败。 - 如果目的中提到 结构化输出 / JSON / SQL / schema:优先使用内置集合中的
JSONEvaluator(required_keys=[...])或RegexMatchEvaluator(pattern=r"...")。如果用户的结构化输出除格式外还有约束(例如枚举值、数值范围),则添加更完善的EvaluatorResult检查,以验证语义上的 schema 适配情况。 - 如果目的中提到 回归 / 重构 / 保持行为:优先使用
exact_match作为主要信号,并添加近似匹配检查(difflib.SequenceMatcher.ratio()≥ 0.95;在 0.7 到 0.95 之间时,返回assessment="partial"的EvaluatorResult)。删除 LLMJudge 替代方案——回归测试需要确定性,而不是模型判断。 - 对于 SKILL.md 无法逐字识别的目的:阅读用户提供的目的字符串,判断最适合衡量它的评估器形式,并从头编写评估器。在评估器上方的注释中引用该目的,让用户了解两者的关联。切勿为用户的 Datadog 组织中可能不存在的名称编造
RemoteEvaluator(eval_name=...)——如有疑问,请使用带内联评分标准的 LLMJudge。
无论采用何种形式,评估器数量应保持在 2–3 个,以精简生成的文件。用户可以在首次运行后添加更多评估器。
所有形式均须遵守:任何包含非简单逻辑的评估器都必须返回
EvaluatorResult,至少填充value、reasoning和assessment(参见“返回EvaluatorResult,而非裸值”一节)。比较界面会使用reasoning展示逐条记录的详细信息,并使用assessment判断指标趋势是否有所改善。 -
生成文件。
对于
.py——单个文件,各部分之间空一行,使用如下横幅注释:# ─── 3. Dataset ───────────────────────────────────────────────────────────────在文件顶部使用
from __future__ import annotations和from typing import Any, Dict。为任务函数和评估器函数签名添加类型注解。对于
.ipynb——有效的 Jupyter notebook JSON。结构如下:{ "cells": [ {"cell_type": "markdown", "metadata": {}, "source": ["## 1. Env setup\n", "..."]}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": ["..."]}, ... ], "metadata": { "kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.10"} }, "nbformat": 4, "nbformat_minor": 5 }每个部分包含一个 Markdown 单元格和一个代码单元格。确保每个代码单元格都足够独立,可以单独重新运行。
-
尽力进行语法检查,使用 Bash。如果缺少工具链,不要导致技能失败——只需报告这一情况。
.py:python -m py_compile <path>.ipynb:python -c "import json; nb = json.load(open('<path>')); assert nb.get('cells'); print(f'cells={len(nb[\"cells\"])}')"
-
打印后续步骤(参见“输出”部分)。
生成的代码绝不能执行的操作
审核者应能针对生成的文件运行以下 grep 检查,并且不匹配任何内容:
grep 模式 | 错误原因 |
|---|---|
uuid4, uuid.uuid4 | record_id 由 SDK 在 dataset.append() 时生成;切勿在客户端生成。 |
PATCH , batch_update, records/upload | 状态机和数据集差异均由 SDK 负责。 |
from ddtrace.llmobs._ | 私有导入路径。始终使用 from ddtrace.llmobs import ...。 |
"record_id", "canonical_id"(作为记录中的字典键) | 这些字段由 SDK 管理。 |
DD_API_KEY = "<actual key>" | 始终从 os.environ 读取。 |
requests.post, httpx.post | 此技能只生成 SDK 代码。直接发起 HTTP 调用会绕过 SDK 的延迟创建、推送差异和批量阈值处理。 |
如果输出中包含上述任何内容,说明技能有误——请重新生成。
输出
写入后,打印:
Generated SDK experiment: <format>
Path: <path>
Lines: <count> (or Cells: <count> for .ipynb)
Purpose:
"<resolved purpose string from step 2.4>"
(sourced via: --purpose flag | extracted from invocation message | AskUserQuestion default | AskUserQuestion + free text)
SDK calls used:
✓ LLMObs.enable(...) (line/cell ~<N>)
✓ LLMObs.<create_dataset|create_dataset_from_csv|pull_dataset>(...) (line/cell ~<N>)
✓ task_fn(input_data, config) (line/cell ~<N>)
✓ <N> evaluators (style: <function|class|remote>, semantics seeded by purpose)
✓ LLMObs.experiment(...).run(jobs=<N>) (line/cell ~<N>)
✓ Provenance (in config + tags): generated_by=claude-code, skill=agent-observability-experiment-py-bootstrap, purpose=...
Task function source:
✓ Wired to: <module.path>:<function_name> (source: <file>:<line>)
✓ Adapter: <one line describing the input_data → call shape mapping>
✓ Return shape: <plain string | {output, tool_calls} | {answer, retrieved_docs} | etc.>
✓ Sync/async: <sync | async (wrapped with asyncio.run)>
[WARNING lines from the side-effect scan in Workflow step 2.5d, if any]
[Note line if the purpose requested richer return shape but the function only exposes a string]
(If --placeholder-task was used or introspection found nothing:)
Task function source:
⚠ Placeholder task emitted (no real LLM call site found / opted out).
Replace `task_fn` with your actual LLM call before running.
Syntax check: <pass | skipped: toolchain missing | fail with details>
Install:
pip install "ddtrace>=4.7" <provider-sdk-if-needed>
# python-dotenv is NOT required — the generated file ships its own .env loader.
Credentials:
The generated file auto-discovers .env files at runtime. Discovery order
(first non-empty value wins per key; shell env always overrides files):
1. --env-file path baked in as ENV_FILE_OVERRIDE (if --env-file was passed)
2. <output-file's-directory>/.env
3. <output-file's-directory>/.env.local
4. <cwd>/.env and <cwd>/.env.local
5. parent-walk from cwd up to /
6. ~/.datadog/credentials
Drop a .env at any of those locations with at minimum:
DD_API_KEY=...
DD_APPLICATION_KEY=...
DD_SITE=datadoghq.com # only if not the US1 prod site
<PROVIDER>_API_KEY=... # the provider key the wired task needs
Or override on a per-run basis by exporting them in your shell — the loader
never overwrites a value that is already in os.environ.
Run:
python <path> # for --format py
jupyter notebook <path> # for --format ipynb
Next steps:
1. Confirm the wired task_fn matches the entry point you want to evaluate (see "Task function source" above).
Edit the import in section 4 of the generated file if you'd rather inline the call or pick a different function.
2. Confirm the purpose ("<purpose string>") matches what you actually want to measure — section 0 of the
file documents it, section 5's evaluators were seeded against it, and section 6's experiment carries
it as a config tag. Re-run this skill with `--purpose "<new text>"` to regenerate against a different
target without changing your code.
3. Adjust the evaluators if needed (or wire up RemoteEvaluator names you created in the Datadog UI).
4. Run it. The script prints experiment.url at the end.
5. Watch the experiment: https://app.datadoghq.com/llm/experiments
参考 Notebook 模式(用作模板)
权威示例集位于 https://github.com/DataDog/llm-observability/tree/main/experiments/notebooks,可作为风格参考——生成的代码应当看起来像是出自此示例集。
| Notebook | 展示的模式 |
|---|---|
00-basic-datasets.ipynb | 数据集创建/追加/推送生命周期 |
01-basic-experiments.ipynb | 最小可行实验——内联记录、OpenAI 任务、2 个布尔评估器 |
02-extra-data.ipynb | 从 CSV 加载的数据集、多值任务输出、基于置信度的评估器 |
04-multi-span-experiments.ipynb | 在单个 task_fn 中运行两步 LLM 流水线 |
07-remote-evaluators.ipynb | 使用自定义 transform_fn 的 RemoteEvaluator |
当 --evaluator-style remote 时,优先采用 07 风格。当 --dataset 是 CSV 时,优先采用 02 风格。默认情况下(无 --dataset,且 --evaluator-style function),采用 01 风格。
Datadog 文档
以下是 https://docs.datadoghq.com/ 上的权威参考页面。请以此为依据回答有关 Agent Observability 功能的问题,并查找本技能未涵盖的详细信息。
研究技能未涵盖的功能
如果用户询问技能正文未涉及的 Agent Observability 功能(例如,特定的 span 类型、数据集版本控制语义,或上文未涵盖的评估器类型),请从 docs.datadoghq.com 获取相关页面,不要猜测:
- 从上表中选择最具体的 URL。 大多数 Agent Observability 问题都可以在
/llm_observability/{experiments,evaluations,instrumentation,monitoring}/下找到答案。 - 对该 URL 使用
WebFetch,并提出有针对性的查询(例如,"How does Dataset.push() handle the 5 MB threshold?")。优先使用WebFetch,而不是通用网页搜索——规范页面几乎总是在docs.datadoghq.com/llm_observability/下。 - 使用
WebSearch作为后备方案,并添加site:docs.datadoghq.com/llm_observability,以查找相关主题所属的子页面。 - 在回答中引用页面并附上其 URL,方便用户核实和收藏。
切勿编造此技能正文或上述文档中未提及的符号或行为。如果文档也未涵盖相关问题,请明确说明,并建议在 DataDog/llm-observability 提交 issue,而不是编造变通方案。
操作规则
- 仅使用 SDK。 不使用
requests.post,不手动构造 JSON:API 信封,也不手动生成 ID。如果某项功能看起来需要这些操作,那说明你解决错了问题——SDK 已经支持该功能。 - 仅使用公开导入。
from ddtrace.llmobs import ...。绝不使用_experiment、_llmobs或任何以下划线开头的模块。 - 使用环境变量,不使用字面值。 凭证始终从
os.environ读取。生成的main()(或环境设置单元)必须使用assert检查是否已设置,并提供清晰的提示信息。 - 自动发现,不要把设置工作推给用户。 第 1 节始终输出
_load_env_files辅助函数(不依赖python-dotenv)。它会按工作流步骤 2.6 中记录的发现顺序遍历,并打印已加载的文件。绝不能用第三方python-dotenv包中的load_dotenv()替代——内联辅助函数零依赖,行为完全相同。Shell 环境变量始终优先于从文件加载的值,因此用户可以在重新运行前通过export <KEY>=...覆盖任何自动发现的值。 - 提供方密钥断言必须与接入的任务相符。 仅当任务导入或调用 OpenAI 时,才生成
OPENAI_API_KEY的断言;Anthropic / Gemini / Bedrock 等也同理。请遵循工作流步骤 2.6 中的表格。绝不要为任务实际不需要的提供方密钥生成断言——这会造成困惑,并引发无谓的“缺少密钥”错误。 - 始终向
LLMObs.enable()传入site=。 从os.getenv("DD_SITE", "datadoghq.com")读取。省略site=会静默地默认使用 US1 生产环境,导致所有非生产环境组织无法正常使用(例如 stagingdatad0g.com、datadoghq.eu)。规范签名已包含该参数——绝不要省略。 - 每条记录的
tags都必须是"key:value"字符串。 内联记录时(无论来自--datasetJSON、CSV 还是默认样例),每条记录的"tags"列表中的条目都必须是"key:value"字符串,例如"env:prod"、"source:traces"、"category:geography"。裸字符串("smoke"、"baseline")会在调用Dataset.append()时触发ValueError: Tag '<name>' is malformed.。如果源数据包含裸字符串标签,请为其添加命名空间——例如,将"smoke"包装为"tag:smoke",而不是将其丢弃。 - 生成之前始终先确定用途。 步骤 2.4 必须在步骤 2.5 / 3 / 4 运行前生成非空的
purpose字符串。如果用户通过--purpose或调用参数提供了用途,就使用该值。否则,通过AskUserQuestion提供 5 个预设选项 +“其他”。绝不要跳过询问,也绝不要在用途为空时继续。用途描述不够具体(“测试一下”)也比没有用途好——通用的准确性语义至少能提供合理的默认值。 - 将用途视为推理输入,而不是 switch 语句。 不存在将用途字符串硬编码映射到评估器代码或包装器形态的规则。阅读用途,推理要测量什么,并据此生成合适内容。相同的用途字符串在两个不同应用中可能生成不同输出(例如,LangChain 应用中的工具调用用途会生成与原生 OpenAI 应用不同的包装器代码)——这是预期行为。
- 先内省,最后才用占位符。 默认行为是遵循工作流步骤 2.5——扫描用户的应用,找到 LLM 入口点,并将
task_fn接入该入口点。仅当内省确实没有找到任何内容,或用户传入--placeholder-task时,才可以在任务部分使用# TODO(user)标记。只要项目中存在真实候选项,绝不要生成占位任务——这正是此技能要解决的问题。 - 至少在一个评估器上添加
# TODO(user)标记,以免审阅者不小心发布未经自定义的评估器。(即使任务已自动接入,评估器仍由用户负责。) - 限定内省范围。 工作流步骤 2.5 中的扫描必须遵循
--app-root(或其解析出的默认值)、(如存在的).gitignore,以及目录屏蔽列表(node_modules、.venv、__pycache__等)。拒绝扫描/或~。如果扫描会触及超过约 10k 个 Python 文件,请缩小根目录范围,或询问用户要扫描哪个相关子目录。 - 遵循 Notebook 约定。 默认使用普通函数评估器;仅当用户选择启用时才使用基于类的评估器。每个生成文件的末尾都要打印
experiment.url。 - 在
config和tags中都为每个实验标记来源和用途。 每个LLMObs.experiment(...)调用都必须在config={...}字典(以便在实验的 Configuration 视图中显示,这是用户实际查看的位置)和tags={...}字典(SDK 会将其序列化到metadata.tags,供未来的标签筛选使用)中同时包含"generated_by": "claude-code"、"skill": "agent-observability-experiment-py-bootstrap"和"purpose": "<step 2.4 string>"。仅使用tags=不够:当前的 LLM Experiments UI 不会将metadata.tags显示为可筛选标签,因此除非同时写入config,否则用户看不到这些值。purpose字段能让用户按意图找到同一实验的后续运行——没有它,用户看到的将是十个名称晦涩的实验,却不知道每个实验测试的内容。此外,还要在实验上设置description="<purpose>",以便在 UI 列表视图中显示。 - 在入口处清除 PII。 如果提供了
--dataset,请先清理,再将内容内联到生成的文件中。绝不要嵌入包含未遮盖电子邮件、电话号码、SSN 或 API 密钥模式的记录。 - 不要生成
requirements.txt或pyproject.toml。 请在后续步骤消息中打印pip install命令——大多数用户已经有虚拟环境。 - 不要静默回退。 如果
--format不受支持,请报错并列出有效选项。 - 仅支持 Python。 如果用户传入
--language typescript(或任何非 Python 语言标志),请报错——此技能仅生成 Pythonddtrace.llmobsSDK 代码。 - 先查阅资料,不要臆造。 如果用户询问本技能正文中未记录的 Agent Observability 功能、跨度类型、评估器类型或 SDK 符号,请在回答前使用
WebFetch获取相关的docs.datadoghq.com/llm_observability/*页面(规范 URL 请参阅上方的 Datadog 文档表格)。在回复中引用页面 URL。如果文档未涵盖相关主题,请明确说明——绝不要捏造符号、标志或行为。