mcpField
O
opik-compare

opik-compare

github.com/comet-ml测试与质量856 次安装6 周前更新
代码仓库

关于 opik-compare

opik-compare 用于比较同一 Opik 测试套件上的基线与候选实验。它读取逐项结果,报告指标变化、回归、修复、可比性及并排比较链接,且不修改应用代码。

主要功能

  1. 01运行或选择基线与候选实验
  2. 02逐项比较实验结果
  3. 03报告指标差值和通过率变化
  4. 04识别回归、修复项和最差结果
  5. 05检查实验是否可比
  6. 06生成 Opik 并排比较链接

适用场景

  1. 01将代码变更与先前运行结果比较
  2. 02不重新运行而检查两个现有实验
  3. 03找出测试用例的退步或改善
  4. 04检查评估运行是否采用一致设置

安装

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

$npx skills add https://github.com/comet-ml/opik-skills --skill opik-compare

安装后在 Claude Code 中输入/opik-compare确认已加载。

详细介绍

对比 — 套件中的候选版本与基线

完成定义: 在同一套件上运行两个实验——一个基线和一个候选版本——逐项读取,列出每项指标的变化值、从通过 → 未通过的案例(回归)和从未通过 → 通过的案例(修复)、表现最差的条目、说明两次运行是否可比,以及 Opik 对比视图链接。如果只有一次运行,完成状态为“已创建基线——更改后重新运行”。如果套件无法运行或读取,请在遇到第一个真实阻碍时停止,并且只返回一个下一步操作。仅有汇总分数不算对比;给出结论也不属于此技能的工作。

操作:**以与基线相同的方式运行候选版本,从 Opik 而非运行控制台输出中读取两次运行,指出发生变化的具体案例——不要更改任何应用代码。**此技能唯一写入的文件,是仓库外的临时运行器。

输入

入口为 /opik-compare <suite>(立即运行该套件作为候选版本,并与最近一次先前运行对比)、/opik-compare <experiment-A> <experiment-B>(读取两个已有运行,不启动新运行),或在 /opik-test 后立即运行 /opik-compare(使用该套件)。推断其余信息;以下均为可选覆盖项:

  • 套件(默认:<project>-regressions,或 /opik-test 刚写入的套件) · 基线(默认:该套件中最近一次较早的实验) · 候选版本名称(默认:candidate-<short git sha>) · 断言评审模型(默认:套件的模型/基线的模型) · 每项运行次数(默认:套件策略)。

只有遇到真实且无法推断的阻碍时才提问(见阻碍)。

激活 — 唯一范围内的工作

1. 确定套件和基线
import opik
client = opik.Opik()

suite = client.get_test_suite(name="<suite>", project_name="<project>")
prior = client.get_test_suite_experiments(name="<suite>", project_name="<project>")  # newest first is not guaranteed — sort by created_at yourself

基线 = 该套件中最近一次先前的实验,除非用户指定其他实验。没有先前的实验 → 本次运行即为基线(仍执行第 3 步;状态为 baseline_created)。明确指定了两个实验 → 跳过第 3 步,转到第 4 步。

确认 Opik 可访问:如果 ~/.opik.config 存在或已设置 OPIK_API_KEY,则使用它。否则 → 阻碍。

2. 构建任务适配器(代码候选版本)

套件中的条目说明了要调用什么:/opik-test 写入的每个条目的 description 都以 Entrypoint: <root span name> 结尾。在仓库中搜索该函数,导入并封装:

def task(item: dict) -> dict:
    return {"input": item["input"], "output": str(entrypoint(item["input"]))}

将运行器写入仓库外的临时文件(或用户指定的临时路径)——绝不写入代码库,也绝不提交。运行器需要应用的提供方凭据;如果缺少 → 阻碍(应用无法响应这些条目)。绝不要让运行器指向会写入数据、发送内容或产生费用的生产入口。

提示词候选版本(变更的是提示词版本,而非代码):无需适配器——在服务端运行:

client.rest_client.experiments.execute_experiment(
    dataset_name=suite.name, dataset_id=suite.id,
    prompts=[{"model": "<model>", "messages": [...], "configs": {}, "prompt_versions": [{"id": "<prompt_version_id>"}]}],
    project_name="<project>",
)  # 202 Accepted; one experiment per prompt variant, processed asynchronously — poll get_experiment_by_id until items fill
3. 运行候选版本

套件、版本、评审模型和每项运行次数均与基线相同——仅改变受测对象。

result = opik.run_tests(
    test_suite=suite,                         # or suite.get_version_view("<baseline's version>") to pin
    task=task,
    experiment_name="candidate-<sha>",
    experiment_tags=["compare", "<sha>"],
    model="<same judge model as baseline>",
    generate_report=False,                    # default True writes opik_test_suite_reports/ into cwd — the repo stays untouched
)
candidate_id = result.experiment_id          # result.experiment_url is the single-run link

# GUARD: a missing judge credential does NOT raise — every item comes back failed with
# scoring_failed=True and a "Missing credentials" reason, and the experiment is still created.
# Treat that as a Blocker, not as a regression; do not compare against that run.
judge_failed = [
    r for ir in result.item_results.values() for t in ir.test_results
    for r in t.score_results if getattr(r, "scoring_failed", False)
]

不要读取 result 中的分数就停止——第 4 步会从 Opik 读取两次运行,确保基线和候选版本经过相同流程。

4. 读取两次运行(优先使用 SDK;连接 MCP 时使用 MCP)
base = client.get_experiment_by_id("<baseline_id>")
cand = client.get_experiment_by_id(candidate_id)
b_items = {i.dataset_item_id: i for i in base.get_items()}
c_items = {i.dataset_item_id: i for i in cand.get_items()}
# each item: dataset_item_data, evaluation_task_output, feedback_scores [{name, value, reason}],
#            assertion_results [{value, passed, reason}], trace_id

# Side by side, one call, both experiments:
rows = client.get_experiments_client().find_experiment_items_for_dataset(
    suite.name, experiment_ids=["<baseline_id>", candidate_id], project_name="<project>"
)

get_experiment_by_name 已弃用——请使用 get_experiments_by_name 按名称查找,并按 id 选取。连接托管 MCP 后,list('experiment', name=…) 会直接显示每项指标的平均值,而 read('experiment', id) 会提供一次运行的摘要——使用它们查找并验证两次运行;上述逐项关联仍通过 SDK 完成。

5. 按顺序回答问题
  1. 是否可比? 套件版本(dataset_version/条目数)、评审模型、每个条目的运行次数是否相同。如果不同,请先说明——此后的差值只能作为参考,并非实测结果。
  2. 哪些案例失败了——基线中通过、候选中未通过的案例。每个案例都要提供其输入(一行)、失败的断言、reason,以及候选追踪链接。
  3. 哪些案例修复了——反过来比较。
  4. 各项指标的净变化——两次运行中每个 feedback_scores 名称的均值,以及通过率;报告“基线 → 候选(差值)”。
  5. 最差的条目——候选中得分最低的条目;它们是否与基线中最差的条目相同?
  6. 是应用还是评审? 如果某次结果翻转时,reason 引用了输出,那就是应用问题;如果输出未变,但 reason 摇摆不定,那就是评审问题——请标记出来,并建议该条目使用 runs_per_item: 3,而不要只信任一次运行。
  7. 成本与质量——候选追踪与基线的 total_estimated_cost/时长对比(追踪中包含这些信息时)。
  8. 不稳定的案例——同一代码的重复运行中结果会翻转的条目(套件的执行策略会公开 runs_passed/runs_total)。
  9. 趋势——存在两次以上运行时,报告最近几次运行的通过率,为单步差值提供背景。

如实报告数字所示的结果。不要决定是否发布——那是 /opik-verify 的职责,它会根据明确的发布策略评估这些数字。

6. 链接并交接

使用两个 ID 构建比较链接——它是一个经过 URL 编码的 JSON 数组,方便用户直接进入并排视图:

import json, urllib.parse
ids = urllib.parse.quote(json.dumps(["<baseline_id>", candidate_id]))
compare_url = f"{ui_base}/{workspace}/experiments/{suite.id}/compare?experiments={ids}"

ui_base 是 Opik UI 的源地址(配置的 URL 去掉 /api;result.experiment_url 会显示可复用的确切主机和工作区)。然后给出一个后续步骤(见输出)——如果用户问的是是否发布,该步骤就是 /opik-verify。

7. 记录(仅在用户提出要求时)

如果用户希望将发现记录在数据旁边:可通过 client.rest_client.traces.add_trace_comment(trace_id, text=…) 在回归案例的追踪上添加评论,或通过 client.log_traces_feedback_scores([...]) 在评审分数旁边记录人工评分。默认情况下不要记录。

阻塞项

遇到阻塞时,在最早的阻塞项处停止,并且只返回一个后续步骤:

  • “运行 opik configure,然后重新运行 /opik-compare <suite>。”
  • “项目 <name> 中没有名为 <suite> 的测试套件——运行 /opik-test <trace-id> 来创建一个,或指定套件名称。”
  • “套件条目没有说明要调用哪个函数——传入入口点(/opik-compare <suite> --entrypoint answer),我会构建运行器。”
  • “运行器需要提供商凭据——设置 OPENAI_API_KEY(或相应的密钥),然后重新运行。”
  • “断言评审器没有凭据(每个条目都报告 scoring_failed,‘Missing credentials’)——设置评审器的提供商密钥,然后重新运行;运行 <name> 不是可比较的候选。”
  • “实验 <id> 还没有条目(服务端运行仍在处理中)——一分钟后重新运行。”

输出

**面向用户:**简短的人类可读消息——可比性说明(如有)、先列回归项(案例、断言、原因),再列修复项,然后是逐项指标表(baseline → candidate (delta))、比较链接,以及唯一的下一步。不提供结论。不要原样倾倒所有条目。

底层(用于内容组织/评估)采用以下一种结构:

  • status:compared | baseline_created | blocked
  • suite:name、id、version
  • baseline / candidate:experiment_id、name、url、items、pass_rate
  • comparable:true|false、note
  • deltas:{metric, baseline, candidate, delta} 列表(包含通过率)
  • regressions:{dataset_item_id, input, assertion, reason, trace_url} 列表
  • fixes:相同结构
  • attribution:可能归因于某一方时,使用 {app: [...ids], judge: [...ids]}
  • compare_url
  • next_step:恰好一个
  • verdict:绝不可出现

不变量:compared 必须包含两个实验、非空的 deltas,以及包含两个 id 的 compare_url;baseline_created 只包含 baseline,且 next_step 为 "rerun after the change";blocked 必须且只能包含一个 next_step;任何路径都不得写入代码库;任何路径都不得给出发布/暂缓决定。

示例

已验证修复。 修复 retrieve() 后运行 /opik-compare support-bot-regressions。基线 = 昨天的运行(3/5 通过)。根据 Entrypoint: answer 构建运行器;候选版本 5/5。回读结果:修复 2 项(退款和配送案例),回归 0 项,pass_rate 0.60 → 1.00 (+0.40),套件版本相同,评审模型相同。→ compared;下一步 = "run /opik-online-eval to watch the refund assertion in production" 或 "merge — the decision is yours"。

发现回归。 候选版本修复了退款案例,但“不得提供法律建议”项变为失败;其 reason 引用了新输出中提供法律步骤的内容。归因:应用。→ compared,先列出一项回归,再列出差值表;下一步 = "/opik-explain <candidate trace> for the legal-advice item"。

首次运行。 该套件没有先前的实验。运行一次;报告 3/5,并列出失败项。→ baseline_created:"This is the baseline — rerun /opik-compare after your change."

两个已有运行,无代码。 /opik-compare nightly-2026-09-14 nightly-2026-09-15。通过 get_experiments_by_name 解析、关联条目并报告。→ compared(未构建运行器)。

反模式

从运行器的控制台输出读取分数,而不是从 Opik 读取;比较不同套件版本或评审模型的运行却不说明;只报告汇总数据(“准确率 0.82 → 0.85”),却不指出哪些案例发生了变化;将运行器写入仓库或提交它;更改应用代码以使套件通过;对不稳定项只信任一次运行;给出发布/不发布的结论(按设计不在范围内);使用已弃用的 get_experiment_by_name;只用一个 id 构建比较链接。

参考资料

测试套件、实验和指标的详情见安装在此工具旁边的 opik 技能。直接读取文件——路径相对于此文件:../opik/references/evaluation-test-suites.md(run_tests、TestSuiteResult、执行策略、版本、get_test_suite_experiments)、../opik/references/evaluation-datasets.md(evaluate()、实验、OQL)、../opik/references/production.md(跟踪记录中的成本和延迟字段)。如果宿主以不同方式组织技能,请找到 opik 技能的 references/ 目录。

如果未安装 opik 技能,请在报告中说明,并参考 https://www.comet.com/docs/opik/,不要依赖记忆作答。

评论

支持 Markdown