Opik 数据导出实践:SDK、REST API、UI 与命令行怎么选

用 Opik 记录了一段时间的 trace、span 和反馈之后,很多团队会开始考虑一个问题:这些数据能不能拿出来?答案是可以的。不管是做离线分析、备份、迁移环境,还是把数据同步到另一个工作区,Opik 都提供了对应的导出方式。官方文档里列了四种主要途径:SDK、REST API、UI 和命令行工具。每种方式适合的场景不太一样,选对了能省不少事,选错了可能会被速率限制卡住,或者导出到一半发现数据不完整。

这篇文章会把这四种方式逐一讲清楚,重点放在实际使用时的注意事项上。尤其是大规模导出时,Opik 的读取接口有比较严格的速率限制,如果按照"一条 trace 一次请求"的直觉去写代码,很容易撞上 429。文档里给了正确的做法,下面会结合例子说明。

一、SDK 导出:最灵活的方式

Python 和 TypeScript 的 SDK 都支持通过代码搜索并导出 traces、spans 和 threads。对于需要把数据接入自己分析流程的团队,这是最推荐的方式。

导出 Traces

先看 Python 的写法:

python 复制代码
import opik

client = opik.Opik()

# Export all traces
traces = client.search_traces(project_name="Default project", max_results=1000000)

# Export filtered traces
traces = client.search_traces(
  project_name="Default project",
  filter_string='input contains "Opik"'
)

# Convert to dict if needed
traces = [trace.dict() for trace in traces]

TypeScript 的写法类似,只是参数形式变成了对象:

typescript 复制代码
import { Opik } from "opik";

const client = new Opik();

// Export all traces
const traces = await client.searchTraces({
  projectName: "Default project",
  maxResults: 1000000,
});

// Export filtered traces
const filtered = await client.searchTraces({
  projectName: "Default project",
  filterString: 'input contains "Opik"',
});

这里可以看到两个关键参数:project_namemax_results。如果不加过滤,默认会把项目里所有 trace 都拉出来。max_results 设得大一点,可以避免分页带来的额外请求。Python 里拿到的是对象,需要的话可以用 .dict() 转成字典,方便后续写入 CSV、JSON 或数据库。

导出 Spans

Spans 的导出可以按 trace ID 来查,也可以直接按条件过滤:

python 复制代码
import opik

client = opik.Opik()

# Export spans by trace ID
spans = client.search_spans(
  project_name="Default project",
  trace_id="067092dc-e639-73ff-8000-e1c40172450f"
)

# Export filtered spans
spans = client.search_spans(
  project_name="Default project",
  filter_string='input contains "Opik"'
)

按 trace ID 查适合你已经知道某条 trace 有问题,想单独看它下面的 span。按条件过滤则适合批量分析,比如找出所有输入里包含某个关键词的 span。

导出 Threads

Threads 代表一段对话线程,导出方式也差不多:

python 复制代码
import opik

client = opik.Opik()

# Export all threads
threads = client.search_threads(project_name="Default project", max_results=1000000)

# Export filtered threads
threads = client.search_threads(
  project_name="Default project",
  filter_string='number_of_messages >= 5'
)

这里用了一个比较实用的过滤条件:消息数大于等于 5。对于分析多轮对话、筛选长会话,这种过滤能直接减少传输量。

用 OQL 做过滤

上面几个例子里都出现了 filter_stringfilterString,它使用的是 Opik Query Language,简称 OQL。语法大致是这样的:

plain 复制代码
"<COLUMN> <OPERATOR> <VALUE> [AND <COLUMN> <OPERATOR> <VALUE>]*"

有几个规则需要记住:

  • 字符串值必须用双引号包起来;
  • 多个条件可以用 AND 组合,但不支持 OR
  • DateTime 字段需要 ISO 8601 格式,比如 "2024-01-01T00:00:00Z"
  • 嵌套字段用点表示法,比如 metadata.modelfeedback_scores.accuracy

文档里给了几个常见例子:

python 复制代码
client.search_traces(filter_string='start_time >= "2024-01-01T00:00:00Z"')
client.search_traces(filter_string='usage.total_tokens > 1000')
client.search_traces(filter_string='metadata.model = "gpt-4o"')
client.search_traces(filter_string='feedback_scores.user_rating is_not_empty')
client.search_traces(filter_string='tags contains "production"')

这些条件覆盖了时间范围、token 用量、模型名称、反馈评分、标签等常见维度。实际使用时,建议尽量把过滤放在服务端,也就是通过 filter_string 传给 Opik,而不是把全部数据拉到本地再筛。这样既省带宽,也省请求配额。

二、大规模导出:先理解速率限制,再决定怎么拉

SDK 虽然灵活,但读取操作是有速率限制的,而且是按工作区共享的。文档里明确提到,用于导出的搜索和列表接口------search_tracessearch_spans,以及底层的 GET /tracesGET /spans------每个工作区每分钟最多 30 次请求。超过限制会返回 429 Too Many Requests,并在 RateLimit-Reset 头里告诉你需要等多久。完整的限制列表可以看官方的 rate-limit FAQ。

30 次请求每分钟,这个预算不算大。所以怎么拉数据就变得很重要。文档给了三条建议:

  1. 批量获取,不要逐条获取。 一次 search_spans 调用底层单次请求最多可以流式返回 2000 行,并且会自动分页、在遇到 429 时退避。所以一次调用可能返回几千个 span 而不会报错。要避免"每条 trace 发一次请求"的做法。
  2. 在服务端过滤。filter_string 限定时间窗口或其他条件,只传你真正需要的数据。
  3. 控制并发。 限制是按工作区共享的,并发请求会互相竞争同一份预算,反而更容易提前撞到 429,并不会更快完成。

文档里给了一正一反两个例子。正确的做法是批量拉取,然后在客户端按 trace_id 分组:

python 复制代码
from collections import defaultdict

traces = client.search_traces(project_name="Default project", max_results=1000000)

# One call for all the spans, then group them by trace_id in memory.
spans = client.search_spans(project_name="Default project", max_results=1000000)
spans_by_trace = defaultdict(list)
for span in spans:
    spans_by_trace[str(span.trace_id)].append(span)

错误的做法是每条 trace 都发一次请求:

python 复制代码
traces = client.search_traces(project_name="Default project", max_results=1000000)

# A request per trace: slow, and quickly hits the read rate limit.
spans_by_trace = {}
for trace in traces:
    spans_by_trace[trace.id] = client.search_spans(
        project_name="Default project", trace_id=trace.id
    )

后一种写法看起来很自然,但请求数会随着 trace 数量线性增长,很快就会把每分钟 30 次的配额用完。如果你的项目有几千条 trace,这种方法基本不可行。所以导出之前,最好先想清楚:能不能一次性把需要的 span 全拉下来,然后在本地做关联。

三、REST API:能用,但更适合简单场景

如果你不想引入 SDK,也可以直接调用 REST API。导出数据主要用 /traces/spans 这两个端点,它们都支持分页。

但文档里给了一个警告:REST API 的 filter 参数灵活性有限,因为它最初是为 Opik UI 设计的。复杂查询还是用 SDK 更合适。另外,这些端点也有限流,而且不会自动退避。对于大规模导出,官方建议优先使用 SDK,或者自己实现重试逻辑,在收到 429 时遵守 RateLimit-Reset 头。

换句话说,REST API 适合轻量级集成,比如从其他语言或平台拉一小批数据。如果你要导出整个项目,SDK 会更省心。

四、UI 导出 CSV:适合小批量快速查看

如果只是想把几条 trace 或 span 拿出来看看,Opik 仪表盘提供了直接的导出功能。在界面上选中想要的 trace 或 span,然后点击 Actions 下拉菜单里的 Export CSV 就行。

不过要注意,UI 一次最多只能导出 100 条 trace 或 span。超过这个数量,就得用 SDK 或命令行工具。所以 UI 导出更适合抽查、演示、或者把少量样本发给同事,不太适合做完整备份。

五、命令行工具:迁移、备份、跨环境同步的好帮手

opik exportopik import 这两个命令,可以把某个项目的 traces、spans、datasets、prompts 和 experiments 导出成本地 JSON 或 CSV 文件,然后再导入回来。官方给它的定位很明确:迁移、备份、跨环境同步。

每个命令都限定在单个项目上,项目名紧跟在 workspace 后面。

在磁盘上,文件夹和文件是按 ID 组织的。数据会落在 <path>/<workspace>/projects/<project_id>/ 下面,里面有一个 project.json(内容包含 {"id", "name"}),以及以 ID 命名的条目文件,比如 dataset_<id>.jsonprompt_<id>.jsonexperiment_<id>.jsontrace_<id>.json。人类可读的名称作为数据存在文件内部。这样做的好处是路径里不会出现 /: 和空格。你在命令行里传的仍然是项目名和条目名,CLI 会帮你做名称和 ID 之间的解析。

导出命令

bash 复制代码
opik export WORKSPACE PROJECT ITEM [NAME] [OPTIONS]

ITEM 可以是:alldatasettracesexperimentprompt

几个例子:

bash 复制代码
# Export everything in a project
opik export my-workspace my-project all

# Export the project's traces
opik export my-workspace my-project traces

# Export a specific dataset
opik export my-workspace my-project dataset "my-test-dataset"

# Export with a date filter
opik export my-workspace my-project traces \
  --filter 'created_at >= "2024-01-01T00:00:00Z"'

# Export as CSV for analysis
opik export my-workspace my-project traces --format csv --path ./csv_data

可以看到,导出支持 --filter 做时间筛选,也支持 --format csv--path 指定输出位置。对于做数据分析的同事,CSV 格式通常比 JSON 更友好。

导入命令

bash 复制代码
opik import WORKSPACE PROJECT ITEM [NAME] [OPTIONS]

这里的 WORKSPACE 是源工作区,用来定位 <path>/WORKSPACE/projects/ 下面已经导出的文件。如果想写到不同的目标工作区,可以用 --to-workspace

几个例子:

bash 复制代码
# Import a dataset
opik import my-workspace my-project dataset "my-dataset"

# Import the project's traces
opik import my-workspace my-project traces

# Preview what would be imported
opik import my-workspace my-project all --dry-run

# Import into a different destination project
opik import my-workspace my-project all --to-project my-restore

# Import into a different destination workspace
opik import src-workspace my-project all --to-workspace dest-workspace

# Import into a different workspace and project
opik import src-workspace my-project all --to-workspace dest-workspace --to-project new-project

项目名会和每个导出的 project.json 里记录的 name 做匹配。导入使用和导出相同的 --path,两边都会解析 <path>/<workspace>/projects/<id>/,所以不需要自己调整路径。用 --to-project <NAME> 可以导入到不同的目标项目,用 --to-workspace <NAME> 可以导入到不同的工作区(源 WORKSPACE 参数仍然用来定位磁盘上的文件)。

还有一个很实用的特性:导入是自动可恢复的。如果中途被打断,重新运行同样的命令,它会通过本地的 migration_manifest.db 从断点继续,而不是从头再来。对于大数据量的迁移,这一点能省很多时间。

跨环境迁移示例

文档里给了一个完整的迁移流程。假设你要从源环境迁移到目标环境:

bash 复制代码
# Step 1: Export from source (use source credentials)
# Writes to ./migration_data/my-workspace/projects/<project_id>/
OPIK_API_KEY=<source_key> OPIK_URL_OVERRIDE=https://source.opik.example.com \
  opik export my-workspace my-project all --path ./migration_data

# Step 2: Import to destination --- same workspace (use destination credentials)
# Same --path as export --- import resolves <path>/my-workspace/projects/<id>/.
OPIK_API_KEY=<dest_key> OPIK_URL_OVERRIDE=https://dest.opik.example.com \
  opik import my-workspace my-project all --path ./migration_data

# Step 2 (alternative): Import into a different destination workspace
# WORKSPACE (my-workspace) still locates the files; --to-workspace sets the API target.
OPIK_API_KEY=<dest_key> OPIK_URL_OVERRIDE=https://dest.opik.example.com \
  opik import my-workspace my-project all --path ./migration_data --to-workspace dest-workspace

这里用到了两个环境变量:OPIK_API_KEYOPIK_URL_OVERRIDE。导出时用源环境的凭证,导入时用目标环境的凭证。路径保持一致,导入命令会自动找到对应的文件。如果想导入到不同的目标工作区,加上 --to-workspace 就行。

更多选项和故障排查,可以查看 CLI 帮助:opik export --helpopik import --help

六、怎么选:一张简单的决策表

把上面几种方式放在一起,选择逻辑其实不复杂:

  • 只是看几条数据,导出 CSV 发给同事:用 UI,但记得最多 100 条。
  • 要在代码里做分析、接入自己的流水线:用 SDK,Python 或 TypeScript 都行。
  • 数据量很大,想一次性拉下来:还是用 SDK,但一定要批量获取,不要逐条请求,并且尽量在服务端用 OQL 过滤。
  • 需要跨环境迁移、备份、恢复 :用命令行工具 opik exportopik import,支持断点续传,适合大工程。
  • 只是简单集成,数据量很小:REST API 也能用,但复杂查询和大规模导出还是交给 SDK。

不管选哪种方式,有一个原则是通用的:先想清楚你要哪些数据,再用过滤条件缩小范围。Opik 的读取配额是有限的,盲目全量拉取不仅慢,还可能影响同一工作区里其他人的正常使用。把过滤做好、批量拉取、控制并发,导出这件事就会顺畅很多。

数据导出的价值,往往在需要复盘、迁移或做深度分析的时候才体现出来。提前把导出路径跑通,等到真正要用的时候,就不会手忙脚乱。Opik 提供的这些方式,基本覆盖了从"随手导出几条"到"整项目跨环境迁移"的常见需求,剩下的就是根据团队的工作流挑一个合适的入口。

相关推荐
oscar9992 天前
用 Opik 追踪 LLM 应用成本:从仪表盘到自动补算的完整思路
opik
oscar9992 天前
用好 Opik 记录用户反馈:从手动标注到在线评估的一套实践
opik
oscar9993 天前
用 Opik 可视化 Agent 执行图:让复杂流程一目了然
opik
oscar9994 天前
用 Opik 记录多模态追踪:图像、视频、音频附件的完整指南
多模态·opik
oscar9995 天前
给 LLM 应用加上追踪:Opik 日志记录实战指南
opik
oscar99911 天前
Ollie:Opik 内置的 AI 助手,让 Agent 调试从“看”变成“修”
人工智能·opik·ollie