如果你正在开发 LLM 应用,可能已经习惯了记录文本输入输出。但现实世界中的 AI 应用远不止文字。用户可能会上传一张图片让模型分析,或者你的 agent 会生成一份图表报告,又或者语音助手的对话需要保存音频。这些多模态数据如果不能被追踪,调试和评估就会留下巨大的盲区。Opik 支持多模态追踪,让你不仅能记录文本,还能记录图像、视频、音频以及任何其他媒体文件。这篇文章会详细介绍如何在 Opik 中记录和管理这些附件,包括大小限制、代码示例、最佳实践和程序化管理。

为什么需要记录媒体附件?
想象一下,你做了一个图像问答应用。用户上传一张照片,问"这是什么植物?"模型回答后,用户反馈说答案不对。如果你只记录了文本的输入输出,你根本不知道用户上传的是哪张照片,也无法复现问题。同样,如果你的 agent 生成了一个 PDF 报告,或者调用了外部 API 返回了一张图表,这些内容如果不被保存,后续的分析和评估就无从谈起。
Opik 的附件功能就是为了解决这个问题。它允许你把图片、视频、音频、PDF、JSON 等文件关联到 trace 或 span 上。这些附件会被上传到 Opik 平台,你可以在 UI 中预览和下载它们。更重要的是,Opik 会自动处理一些常见格式,比如 base64 编码的图片和图片 URL,让它们直接在追踪视图中显示出来,而不需要你手动解码。
大小限制:为什么要关心?
在深入代码之前,有必要先了解 Opik 对内容大小的限制。这些限制的存在是为了保证数据摄入的速度和可靠性。如果你不小心把一个大文件直接塞进 input 或 output 字段,可能会导致请求被拒绝,或者追踪数据不完整。
Opik 对内联内容 (也就是直接放在 span 或 trace 的 input、output 或 metadata 里的内容)有两层限制:
- 每个字段约 20 MB :每个内联的非媒体
input/output字段应保持在 20 MB 以下。这个上限同样适用于input和output合并后的总大小。较新版本的 Opik SDK 会在客户端侧截断过大的input/output,替换为截断标记并记录警告,以确保单个 span 和 trace 不超限。metadata不会被截断,但也要保持小巧,因为它仍然计入每请求的限制。 - 每请求限制:单次摄入批次被限制在约 50 MB 压缩后和约 256 MB 未压缩。更大的请求会被拒绝,返回 413 状态码。
不过,Base64 编码的内容是单独处理的,不受上述两个限制约束 。当你把大型 base64 数据块嵌入某个字段时------比如图片、音频、视频、PDF 或其他可识别格式(如 JSON)------SDK 会自动提取大于约 250 KB 的内容,并将其作为附件上传。这是一个独立的上传过程,不属于 JSON 摄入请求的一部分。因此,单个嵌入值可以大到 100 MB。关于这一点,后面"嵌入附件"部分会详细说明。
附件是保留大型内容而不触及这些限制的推荐方式。 与其把大载荷内联在 input/output 里,不如把它记录为附件。完整载荷通过对象存储保存,而你的 trace 保持轻量。一个经验法则是:摘要、top-K 结果或 ID 可以内联记录,任何大型内容都附加为附件。
如何记录附件
在 Python SDK 中,你可以使用 Attachment 类型来给 trace 添加文件。附件可以是图片、视频、音频文件或任何其他你想记录到 Opik 的文件。
每个附件由以下字段组成:
data:文件路径、原始字节或文件的 base64 编码字符串。file_name:附件的可选名称(当使用原始字节且没有文件路径时必需)。content_type:文件的 MIME 类型。
这些附件可以通过 opik_context.update_current_span 和 opik_context.update_current_trace 方法记录到 trace 和 span 上。
使用文件路径
最常见的方式是提供文件路径:
python
from opik import opik_context, track, Attachment
@track
def my_llm_agent(input):
# LLM chain code
# ...
# Update the trace with a file path
opik_context.update_current_trace(
attachments=[
Attachment(
data="<path to the image>",
content_type="image/png",
)
]
)
return "World!"
print(my_llm_agent("Hello!"))
这段代码会在当前 trace 上附加一个 PNG 图片。你只需要指定文件路径和内容类型,Opik 会负责上传。
使用原始字节
你也可以直接传递原始字节。当你手头有文件内容在内存中(比如来自 API 响应、生成的内容或流式数据),不想先写到磁盘时,这很有用:
python
from opik import opik_context, track, Attachment
@track
def process_image(image_bytes: bytes):
# Process the image
# ...
# Log the raw bytes as an attachment
opik_context.update_current_trace(
attachments=[
Attachment(
data=image_bytes, # Raw bytes
file_name="processed_image.png", # Required for bytes
content_type="image/png",
)
]
)
return "Image processed!"
# Example: Reading a file into memory and logging it
with open("image.png", "rb") as f:
image_data = f.read()
print(process_image(image_data))
注意:当使用原始字节时,Opik 会自动创建一个临时文件用于上传,并在附件上传完成后清理它。如果你没有指定 content_type,Opik 会尝试从 file_name 推断,或者默认使用 application/octet-stream。
记录来自 HTTP 响应的图片
一个常见的用例是记录从外部 API 或 URL 获取的图片:
python
import httpx
from opik import opik_context, track, Attachment
@track
def analyze_remote_image(image_url: str):
# Fetch image from URL
response = httpx.get(image_url)
image_bytes = response.content
content_type = response.headers.get("content-type", "image/jpeg")
# Log the fetched image as an attachment
opik_context.update_current_trace(
attachments=[
Attachment(
data=image_bytes,
file_name="remote_image.jpg",
content_type=content_type,
)
]
)
# Process the image...
return "Image analyzed!"
# Analyze an image from a URL
result = analyze_remote_image("https://example.com/image.jpg")
这样,即使图片是动态获取的,也能被完整记录下来。
记录生成的内容
你也可以记录动态生成的内容,比如图表或报告:
python
from opik import opik_context, track, Attachment
import json
@track
def generate_report(data: dict):
# Generate a JSON report
report_bytes = json.dumps(data, indent=2).encode("utf-8")
opik_context.update_current_trace(
attachments=[
Attachment(
data=report_bytes,
file_name="report.json",
content_type="application/json",
)
]
)
return "Report generated!"
直接使用 Opik 客户端
你还可以直接使用 Opik 客户端记录附件,支持文件路径和原始字节:
python
import opik
from opik import Attachment
client = opik.Opik()
# Create a trace
trace = client.trace(
name="my-trace",
input={"query": "Process this data"},
project_name="my-project",
)
# Log attachment with file path
span_with_file = client.span(
trace_id=trace.id,
name="file-attachment-span",
attachments=[
Attachment(
data="/path/to/document.pdf",
content_type="application/pdf",
)
],
)
# Log attachment with raw bytes
binary_data = b"Hello, this is binary content!"
span_with_bytes = client.span(
trace_id=trace.id,
name="bytes-attachment-span",
attachments=[
Attachment(
data=binary_data,
file_name="data.bin",
content_type="application/octet-stream",
)
],
)
client.flush()
附件会被上传到 Opik 平台,并且可以在 UI 中预览和下载。
支持的内容类型
为了在 UI 中预览附件,你需要提供受支持的内容类型。Opik 支持以下类型:
- 图片 :
image/jpeg、image/png、image/gif、image/svg+xml - 视频 :
video/mp4、video/webm - 音频 :
audio/wav、audio/vorbis、audio/x-wav - 文本 :
text/plain、text/markdown - PDF :
application/pdf - 其他 :
application/json、application/octet-stream
如果你希望支持更多图片格式,可以在 GitHub 上反馈。
程序化管理附件
你还可以使用 AttachmentClient 以编程方式管理附件:
python
import opik
opik_client = opik.Opik()
attachment_client = opik_client.get_attachment_client()
# Get list of attachments
attachments_details = attachment_client.get_attachment_list(
project_name="my-project",
entity_id="some-trace-uuid-7",
entity_type="trace"
)
# Download an attachment
attachment_data = attachment_client.download_attachment(
project_name="my-project",
entity_type="trace",
entity_id="some-trace-uuid-7",
file_name="report.pdf",
mime_type="application/pdf"
)
# Upload a new attachment
attachment_client.upload_attachment(
project_name="my-project",
entity_type="trace",
entity_id="some-trace-uuid-7",
file_path="/path/to/document.pdf"
)
这让你可以在自动化流程中获取、下载或上传附件,比如在批量分析或数据迁移时。
自动预览 base64 图片和图片 URL
Opik 会自动检测记录到平台的 base64 编码图片和 URL。一旦检测到图片,它会隐藏字符串以提高可读性,并在 UI 中显示图片。这支持追踪视图、数据集视图和实验视图。
例如,如果你使用 OpenAI SDK,并且以 URL 的形式传递图片给模型,Opik 会自动检测并显示图片:
python
from opik.integrations.openai import track_openai
from openai import OpenAI
# Make sure to wrap the OpenAI client to enable Opik tracing
client = track_openai(OpenAI())
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",
},
},
],
}
],
max_tokens=300,
)
print(response.choices[0])
这样,你在 Opik UI 里就能直接看到图片,而不需要手动复制 URL 去浏览器打开。

嵌入附件
当你把 base64 编码的媒体直接嵌入到 trace/span 的 input、output 或 metadata 字段时,Opik 会自动优化存储和检索性能。
工作原理
对于大于 250 KB 的 base64 编码内容,Opik 会自动提取并单独存储。这个过程是透明的,你不需要修改代码。
当你之后检索 trace 或 span 时,附件默认会自动包含。如果你不需要附件数据,想要更快的查询,可以使用 strip_attachments=true 参数。
嵌入 base64 大小限制
Opik Cloud 支持每个字段最多 100 MB 的嵌入附件。这个限制适用于 input、output 或 metadata 字段中的单个 base64 字符串值,并且与前面的内联字段/请求限制是分开的------因为大于约 250 KB 的 base64 媒体会被提取并作为附件上传,而不是内联发送。
需要注意的是,Base64 编码会使文件大小增加约 33%。例如,一个 75 MB 的视频在 base64 编码后会变成约 100 MB。
如果你需要处理更大的文件:
- 使用 Attachment API :通过
AttachmentClient单独上传文件(推荐用于大于 50 MB 的文件)。 - 联系我们:如果你需要更高的限制,可以联系 Opik 团队。
- 自托管 Opik:配置你自己的限制。
最佳实践
- 较小的文件直接嵌入------Opik 会高效处理。
- 对于大于 50 MB 的文件,使用 Attachment API 以获得更好的性能。
- 查询时如果不需要附件数据,使用
strip_attachments=true。
下载附件
你可以通过两种方式下载附件:
- 从 UI:将鼠标悬停在附件上,点击下载图标。
- 以编程方式 :使用
AttachmentClient,如前面的示例所示。
总结
多模态追踪是 LLM 应用可观测性的重要组成部分。Opik 通过附件功能,让你可以轻松记录图像、视频、音频、PDF、JSON 等文件。理解大小限制和嵌入附件的工作机制,能帮助你避免常见的陷阱。无论是通过文件路径、原始字节、HTTP 响应还是生成内容,Opik 都提供了灵活的 API。程序化管理附件和自动预览功能,进一步简化了工作流程。如果你正在构建涉及多模态数据的 AI 应用,不妨从今天开始,把重要的媒体文件附加到你的 trace 上。这样,当问题出现时,你拥有的不仅是文本日志,而是完整的上下文。