mcpField
L
llm-obs-experiment-py-bootstrap

llm-obs-experiment-py-bootstrap

github.com/datadog-labs测试与质量180172 次安装6 天前更新
代码仓库

关于 llm-obs-experiment-py-bootstrap

使用公开的 ddtrace.llmobs SDK 生成 Datadog LLM Obs 实验的 Python 脚本或 Notebook。支持数据集配置、应用任务发现及多种评估器类型。

主要功能

  1. 01生成 Python 脚本或 Notebook
  2. 02配置本地或 Datadog 数据集
  3. 03扫描应用以发现任务函数
  4. 04支持函数、类和远程评估器
  5. 05配置 LLMObs 实验

适用场景

  1. 01搭建 LLM 实验文件
  2. 02用数据集评估任务
  3. 03创建实验 Notebook
  4. 04使用现有 Datadog 数据集

MCP 相关

可选地通过 Datadog LLM Obs MCP 工具发送启动遥测信标;即使没有 MCP 服务或 pup 命令行,也可继续生成代码。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/datadog-labs/agent-skills --skill llm-obs-experiment-py-bootstrap

安装后在 Claude Code 中输入/llm-obs-experiment-py-bootstrap确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/evaluator-styles/
class.md
function.md
remote.md
references/providers/
anthropic.md
bedrock.md
gemini.md
langchain.md
litellm.md
llamaindex.md
openai.md
scripts/
env_setup_template.py

详细介绍

后端

检测 — 每次调用开始时,在执行任何操作之前,确定启动信标调用要使用哪个后端(如果有):

  1. 如果用户在调用中的任何位置传入了 --backend pup → 信标使用 pup 模式。跳过步骤 2–3。
  2. 检查当前活动工具列表中是否存在 MCP 工具。判定信号是可用工具中是否出现 mcp__datadog-llmo-mcp__list_llmobs_evals。如果存在 → 使用 MCP 模式。
  3. 如果没有 MCP 工具 → 通过 pup --version 检查 pup 是否可执行。如果有响应 → 使用 pup 模式。
  4. 如果两者都不可用 → 静默跳过信标,并继续生成代码。此技能主要在本地执行(内省 + 文件生成),不需要访问后端;信标仅用于使用情况归因。

调用 ID: 每次调用开始时,在任何 MCP/pup 调用之前,生成一个 8 个字符的十六进制调用 ID(例如 3a9f1c2b)。整个调用过程中保持不变。

意图标记: 每次 MCP 工具调用时,在 telemetry.intent 前加上 skill:llm-obs-experiment-py-bootstrap[<inv_id>] — ,后接说明调用该工具的原因。仅在第一次 MCP 工具调用(下方的启动信标)时,使用 skill:llm-obs-experiment-py-bootstrap:start[<inv_id>] — (注意 :start 后缀)。

启动信标: 解析参数后立即(工作流步骤 1,在步骤 2 解析数据集之前)发起且仅发起一次信标调用,以登记技能使用情况并验证后端连接。此操作不等待结果——如有错误,向用户显示一行 Note:,但不要阻止代码生成。

  • MCP 模式: 调用 mcp__datadog-llmo-mcp__list_llmobs_evals,并设置 telemetry.intent = "skill:llm-obs-experiment-py-bootstrap:start[<inv_id>] — Skill startup: register usage and verify Datadog connectivity"。丢弃响应负载;此调用的目的是添加遥测标记。
  • 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.) 并继续。

信标不得导致技能失败。如果调用出错(身份验证、网络等),显示一行提示并继续。


LLM Obs Experiment(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 并信任它。

用法

/llm-obs-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 等参数而阻塞。

输入默认值说明
--formatpypy(单个 .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-nameexperiment-<service-name> — 根据代码库推导(参见工作流步骤 1);仅当无法解析时才回退到 experiment-sdk-defaultDatadog 项目名称(在 LLM Experiments UI 中可见)。SDK 的 ml_app 标签会自动回退到此值 — 无需单独设置标志。
--evaluator-stylefunctionfunction(普通函数 — 笔记本默认值)、class(BaseEvaluator 子类)或 remote(RemoteEvaluator 实例)。
--jobs10传递给 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 或其他以下划线开头的模块导入)。

导入项来源提供的功能
LLMObsddtrace/llmobs/__init__.py 重新导出 _llmobs.py.enable()、.create_dataset()、.create_dataset_from_csv()、.pull_dataset(dataset_name, project_name, version)、.experiment()、.async_experiment()
RemoteEvaluator、EvaluatorContextddtrace/llmobs/__init__.py在服务端运行的 LLM-as-Judge;优先于内联 LLMJudge 使用
BaseEvaluator、EvaluatorResultddtrace/llmobs/__init__.py基于类的评估器路径(高级用法)
LLMJudgeddtrace/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": "llm-obs-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": "llm-obs-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 中的用途
valuebool / float / str / dict(JSONType)分数本身——显示在实验指标上。必填。
reasoningstr在比较 UI 中显示每条记录的解释;让审阅者无需重新运行 LLM,就能了解评估器通过或失败的原因。
assessmentstr(例如 "pass" / "fail" / "partial")确定指标趋势上升还是下降代表改进;UI 会据此为基线与候选结果的比较着色。
metadatadict[str, JSONType]每条记录的自由格式上下文(例如 {"confidence": 0.95});显示在记录详细信息中。
tagsdict[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 风格
classreferences/evaluator-styles/class.md需要持久状态或异步客户端的评估器
remotereferences/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

工作流

  1. 解析参数。默认 --format py。根据 --format 确定 --output 的扩展名。 如果未提供 --project-name,则按顺序检查以下来源,并采用第一个匹配项,解析出 experiment-<service-name> 形式的默认值:

    1. pyproject.toml → [project] name(PEP 621)或 [tool.poetry] name。
    2. setup.cfg → [metadata] name。
    3. setup.py → setup(...) 中第一个 name="..." 参数。
    4. package.json → "name"(适用于 LLM 应用位于 TS/JS 单体仓库中的 Python 包内的情况)。
    5. 当前工作目录的基本名称,转为小写并规范化为 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:llm-obs-experiment-py-bootstrap:start[<inv_id>] — 技能启动:... 标签的 MCP list_llmobs_evals 调用,或对应的 pup 调用;如果没有后端则跳过)。将任何错误作为单行 Note: 显示,并继续执行 — 信标仅用于使用情况归因和连接验证,绝不会阻塞代码生成。

  1. 解析数据集来源。 如果同时传入 --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 必须在运行时可用。
    • --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 UI URL(/llm/datasets/<id>)中的数据集 ID,工作流如下:在 UI 中打开该 URL,复制数据集名称,并将其作为 --dataset-name 传入。技能不得导入 ddtrace.llmobs._experiment 或任何其他带下划线的模块来绕过此限制。 2.4. 确定实验目的。 用一句话说明实验要验证什么。这将成为一个推理输入,影响所有后续步骤(2.5 中的自省排序、2.5d 中的封装形式、第 3 步中的评估器选择、第 4 步中的文件头)。它不是固定的分类体系——用户可以输入任何能描述其目标的内容,Claude 会在每次调用时阅读并作出判断。

    确定顺序:

    1. 如果传入了 --purpose "<text>" → 原样使用。跳至 2.5。
    2. 如果用户最初的调用消息中包含明确的目的陈述(例如 “设置一个实验来测试我的工具选择逻辑”、“验证 SQL 输出是否始终可解析”、“对提示词 v3 进行回归测试”),提取该陈述,并通过 AskUserQuestion 将其呈现给用户确认:把提取的文本作为一个标为“使用我描述的目的”的选项,另加“选择其他目的”和“其他”。如果用户确认,则使用提取的文本。跳至 2.5。
    3. 否则,通过 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、LangChain ReActAgent 或工具调用形式的候选项。如果提到检索 / 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.createOpenAI 聊天
openai.completions.create、client.completions.createOpenAI 文本
anthropic.messages.create、client.messages.create、Anthropic(...).messages.createAnthropic
litellm.completion、litellm.acompletionLiteLLM(路由器)
langchain.*.invoke、ChatOpenAI(...)、ChatAnthropic(...)、LLMChain(...)LangChain
from llama_index、as_query_engine、as_chat_engineLlamaIndex
google.generativeai.GenerativeModel(...).generate_contentVertex/Gemini
boto3.client("bedrock-runtime").invoke_modelAWS 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 的 LangChain AgentExecutor.invoke() 结果):让 task_fn 直接返回该结构。步骤 3 中的评估器会将其作为 output_data 接收。
    • 函数只返回字符串,但目的需要更多信息:在 task_fn 上方输出一条 # Note: 注释,说明需要什么更丰富的结构,以及用户可以如何将其提供出来(例如 "To capture tool calls for evaluation, refactor respond(query) to also return the intermediate_steps from 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(向下深入一层,查看底层提供方)
LlamaIndexreferences/providers/llamaindex.md
Google Gemini(google.generativeai)/ Vertex AIreferences/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;不要在此处用文字重新推导。

  1. 根据 --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_correct LLMJudge,其评分标准应检查工具和参数是否正确。如果 2.5d 中的包装器捕获了 output_data["tool_calls"],评分标准应引用该字段;否则,应检查文本输出中的工具名称。
    • 如果目的中提到 groundedness / 忠实性 / RAG:添加一个 groundedness LLMJudge,接收 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 判断指标趋势是否有所改善。

  2. 生成文件。

    对于 .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 单元格和一个代码单元格。确保每个代码单元格都足够独立,可以单独重新运行。

  3. 尽力进行语法检查,使用 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\"])}')"
  4. 打印后续步骤(参见“输出”部分)。


生成的代码绝不能执行的操作

审核者应能针对生成的文件运行以下 grep 检查,并且不匹配任何内容:

grep 模式错误原因
uuid4, uuid.uuid4record_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=llm-obs-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.ipynbCSV 加载的数据集、多值任务输出、基于置信度的评估器
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/ 上的规范参考页面。请参考这些页面,回答有关 LLM Observability 功能的问题,并查找本技能未涵盖的详细信息。

主题URL适用场景
LLM Observability 概览https://docs.datadoghq.com/llm_observability/了解产品涵盖的内容和相关术语
设置https://docs.datadoghq.com/llm_observability/setup/创建 API/应用密钥、设置项目和 ml_app、选择区域/站点
仪表化概览https://docs.datadoghq.com/llm_observability/instrumentation/自动仪表化、手动使用 SDK、span 模型
Python SDK 参考https://docs.datadoghq.com/llm_observability/instrumentation/sdk/公共符号列表、装饰器语义、span 类型、annotate/enable 签名
实验https://docs.datadoghq.com/llm_observability/experiments/LLMObs.experiment(...)、数据集生命周期、评估流式处理、状态
评估https://docs.datadoghq.com/llm_observability/evaluations/评估器概念、托管评估器与自定义评估器
自定义 LLM-as-a-judge 评估https://docs.datadoghq.com/llm_observability/evaluations/custom_llm_as_a_judge_evaluations/RemoteEvaluator 负载结构和评分标准设计
托管评估https://docs.datadoghq.com/llm_observability/evaluations/managed_evaluations/预构建评判器(忠实度、毒性等)
监控https://docs.datadoghq.com/llm_observability/monitoring/警报、仪表板、span 级监控器
术语/词汇表https://docs.datadoghq.com/llm_observability/terms/Span 类型、会话、追踪、ml_app
评估开发者指南https://docs.datadoghq.com/llm_observability/guide/evaluation_developer_guide/编写离线评估器、验证策略
Claude Code 技能指南https://docs.datadoghq.com/llm_observability/guide/claude_code_skills/了解此技能如何与 dd-llmo 套件的其余部分配合使用
MCP 服务https://docs.datadoghq.com/llm_observability/mcp_server/将兼容 MCP 的客户端连接到 LLM Obs 数据
参考 Notebook(GitHub)https://github.com/DataDog/llm-observability/tree/main/experiments/notebooks生成的 .py/.ipynb 的风格示例
调研此技能未涵盖的功能

如果用户询问此技能正文未涉及的 LLM Observability 功能(例如特定的 span 类型、数据集版本管理语义,或上文未涵盖的评估器类型),请从 docs.datadoghq.com 获取相关页面,而不要猜测:

  1. 从上表中选择最具体的 URL。大多数 LLM Obs 问题都归属 /llm_observability/{experiments,evaluations,instrumentation,monitoring}/ 下的页面。
  2. 在该 URL 上使用 WebFetch,并附上聚焦的问题(例如:“Dataset.push() 如何处理 5 MB 阈值?”)。优先使用 WebFetch,而不是通用网页搜索——规范页面几乎总是在 docs.datadoghq.com/llm_observability/ 下。
  3. 如果不知道哪个子页面涉及该主题,则回退到 WebSearch,并使用 site:docs.datadoghq.com/llm_observability。
  4. 在回答中引用页面及其 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 生产环境默认值,导致所有非生产环境的组织无法正常工作(例如 staging datad0g.com、datadoghq.eu)。标准签名已包含此参数——绝不要省略。
  • 每条记录的 tags 必须是 "key:value" 字符串。 内联记录时(无论来自 --dataset JSON、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 个预设选项和 Other 选项,并提示用户选择。绝不要跳过提示,也绝不要在用途为空时继续执行。用途描述较弱(“测试一下”)也胜过没有用途——通用的准确性语义至少能提供合理的默认值。
  • 将用途视为推理输入,而非开关语句。 用途字符串与评估器代码或包装器形式之间不存在硬编码映射。读取用途,推理要衡量的内容,并据此生成代码。同一个用途字符串用于两个不同应用时,可能生成不同的输出(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": "llm-obs-experiment-py-bootstrap" 和 "purpose": "<step 2.4 string>"。仅使用 tags= 不够:当前的 LLM Experiments 界面不会将 metadata.tags 显示为可筛选标签,因此除非这些值也在 config 中,否则用户看不到它们。purpose 字段能让用户按意图发现同一实验的后续运行——没有它,用户会看到十个名称晦涩的实验,却不知道各自测试的是什么。还要在实验中设置 description="<purpose>",让它显示在界面列表视图中。
  • 在入口处清除 PII。 如果提供了 --dataset,请在将数据内联到生成文件之前先清除 PII。绝不要嵌入含有未掩码邮箱、电话、SSN 或 API 密钥模式的记录。
  • 不要生成 requirements.txt 或 pyproject.toml。 改为在后续步骤提示中打印 pip install 命令——大多数用户已经有 venv。
  • 不要静默回退。 如果不支持 --format,则报错并列出有效选项。
  • 仅支持 Python。 如果用户传入 --language typescript(或任何非 Python 语言标志),则报错——此技能仅生成 Python ddtrace.llmobs SDK 代码。
  • 先查证,不要臆造。 如果用户询问此技能正文未记载的 LLM Observability 功能、span kind、评估器类型或 SDK 符号,请使用 WebFetch 获取相关的 docs.datadoghq.com/llm_observability/* 页面(标准 URL 请参阅上方的 Datadog 文档表),然后再回答。在回复中引用页面 URL。如果文档未涵盖相关主题,请明确说明——绝不要编造符号、标志或行为。

评论

支持 Markdown