给 LLM 应用加上追踪:Opik 日志记录实战指南

如果你正在开发基于大语言模型的应用,可能已经发现一个现象:模型调用本身只是整个系统的一小部分。一个典型的 LLM 应用往往包含检索、预处理、后处理、工具调用等多个步骤。当用户反馈"回答不对"时,你很难一眼看出是检索环节没找到相关文档,还是提示词组装出了问题,又或者是模型本身在胡编。这时候,追踪就成了必不可少的工具。

Opik 提供了一套完整的追踪方案,不仅能记录所有的 LLM 调用,还能追踪应用中的其他步骤。它支持 TypeScript SDK、Python SDK、OpenTelemetry 以及 REST API。这篇文章会从零开始,带你了解如何给应用加上 Opik 追踪,并深入一些高级用法。如果你刚开始接触 Opik,建议先看看快速入门指南,跑通第一个 LLM 调用。本文不会涉及聊天对话的追踪,那部分有单独的指南。

为什么需要追踪?

LLM 应用和传统软件有一个很大的不同:它的行为往往不是确定性的。同样的输入可能产生不同的输出,而且内部步骤多、嵌套深。如果没有追踪,你就像在一个黑盒里调试,只能靠猜。追踪能帮你理解应用的流程,并定位到具体是哪个环节出了问题。

Opik 的追踪功能可以记录每一次 LLM 调用,也可以记录检索、数据转换、外部 API 调用等步骤。每个追踪(trace)代表一次完整的交互,内部的每个步骤称为跨度(span)。跨度可以嵌套,形成一棵调用树。这样你就能一层层往下钻,直到看清最细粒度的操作。

安装和配置 SDK

在给应用添加可观测性之前,需要先安装并配置 Opik SDK。根据你使用的语言和框架,有几种选择。

TypeScript SDK

如果你用 TypeScript,可以通过 npm 安装:

bash 复制代码
npm install opik

然后在 .env 文件里设置环境变量:

bash 复制代码
OPIK_API_KEY=your_api_key_here
OPIK_WORKSPACE=your_workspace_name
OPIK_URL_OVERRIDE=https://www.comet.com/opik/api

OPIK_URL_OVERRIDE 是可选的,如果你用的是 Opik Cloud,可以设置成上面的地址。如果自托管,就换成你自己的实例地址。

Python SDK

Python 用户可以通过 pip 安装:

bash 复制代码
pip install opik

安装完成后,可以用 opik configure 命令行工具进行配置,也可以在 Jupyter Notebook 里调用 opik.configure。配置过程会提示你输入 API key 或 Opik 服务器地址。

OpenTelemetry

如果你已经在使用 OpenTelemetry,可以直接把数据导出到 Opik。需要设置两个环境变量:

bash 复制代码
export OTEL_EXPORTER_OTLP_ENDPOINT=https://www.comet.com/opik/api/v1/private/otel
export OTEL_EXPORTER_OTLP_HEADERS='Authorization=<your-api-key>,Comet-Workspace=default'

如果自托管,把 endpoint 换成 http://localhost:5173/api/v1/private/otel

Opik 是开源的,可以用 Docker 在本地托管。官方也提供了托管平台,注册 Comet 账号即可使用。

选择集成方式

安装并配置好 SDK 后,就可以开始追踪 agent 调用了。Opik 提供了 40 多个集成,覆盖大多数流行的框架和库。你可以从集成总览页面找到完整列表。下面挑几个常见的例子说明。

OpenAI(TypeScript)

如果你用 OpenAI 的 TypeScript SDK,先安装 Opik 的 OpenAI 集成包:

bash 复制代码
npm install opik-openai

配置环境变量:

bash 复制代码
export OPIK_API_KEY="<your-api-key>"
export OPIK_URL_OVERRIDE="https://www.comet.com/opik/api"

然后用 trackOpenAI 包装你的 OpenAI 客户端:

typescript 复制代码
import OpenAI from "openai";
import { trackOpenAI } from "opik-openai";

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const trackedOpenAI = trackOpenAI(openai);

const completion = await trackedOpenAI.chat.completions.create({
  model: "gpt-4",
  messages: [{ role: "user", content: "Hello, how can you help me today?" }],
});
console.log(completion.choices[0].message.content);

await trackedOpenAI.flush();

所有通过 trackedOpenAI 发起的调用都会自动记录到 Opik。记得在程序结束前调用 flush(),确保数据发送完毕。

OpenAI(Python)

Python 的用法类似。先安装 Opik:

bash 复制代码
pip install opik

然后运行 opik configure 进行配置。接着用 track_openai 包装客户端:

python 复制代码
from opik.integrations.openai import track_openai
from openai import OpenAI

openai_client = OpenAI()
openai_client = track_openai(openai_client)

之后所有通过 openai_client 的调用都会被记录。

AI Vercel SDK

如果你用 Vercel 的 AI SDK,可以安装 opik-vercel

bash 复制代码
npm install opik-vercel

配置环境变量后,初始化 OpikExporter

typescript 复制代码
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OpikExporter } from "opik-vercel";

const sdk = new NodeSDK({
  traceExporter: new OpikExporter(),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

const result = await generateText({
  model: openai("gpt-4o"),
  prompt: "What is love?",
  experimental_telemetry: { isEnabled: true },
});

console.log(result.text);

所有启用了 experimental_telemetry 的调用都会被记录。

ADK

如果你用 ADK,先安装 Opik 并配置,然后用 OpikTracer 包装 agent:

python 复制代码
from opik.integrations.adk import OpikTracer, track_adk_agent_recursive

opik_tracer = OpikTracer()
track_adk_agent_recursive(agent, opik_tracer)

LangGraph

LangGraph 的集成也很直接:

python 复制代码
from opik.integrations.langchain import OpikTracer

graph = ...
app = graph.compile(...)

opik_tracer = OpikTracer(graph=app.get_graph(xray=True))

result = app.invoke(
    {"messages": [HumanMessage(content="How to use LangGraph ?")]},
    config={"callbacks": [opik_tracer]}
)

函数装饰器

如果你不想依赖特定框架,可以用 @track 装饰器。Python 里这样写:

python 复制代码
from opik import track

@track
def my_function(input: str) -> str:
    return input

所有对 my_function 的调用都会被记录。它支持嵌套函数,也兼容大多数集成。只要把父函数用 @track 装饰一下,内部的调用会自动形成嵌套跨度。

AI Wizard

Opik 还提供了一个预构建的提示,可以配合 Cursor 等编辑器快速完成集成。点击按钮会打开 Cursor,并引导你完成 SDK 安装和代码插桩。它支持 Python 和 TypeScript,其他语言也可以联系官方获得帮助。集成完成后,运行应用就能在 Opik 仪表板里看到追踪数据。

其他框架

Opik 支持 30 多个集成,比如 Dify、Agno、Ollama 等。如果你的框架不在列表里,仍然可以用函数装饰器或低级 SDK 来记录追踪。

在 Opik 中分析 agent

启用可观测性后,你就可以在 Opik UI 里查看每次 agent 调用。可以看到 agent 图、所有工具调用、每个跨度的输入输出和耗时。这些数据对于调试和优化非常有价值。

高级用法

函数装饰器详解

函数装饰器是给现有应用添加 Opik 日志的好方法。给函数加上 @track 后,Opik 会为这次调用创建一个跨度,并记录输入参数和输出。如果被装饰的函数在另一个被装饰的函数内部调用,Opik 会自动创建嵌套跨度。

TypeScript 也支持装饰器,不过目前还是实验性的。下面是一个例子:

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

class TranslationService {
    @track({ type: "llm" })
    async generateText() {
        return "Generated text";
    }

    @track({ name: "translate" })
    async translate(text: string) {
        return `Translated: ${text}`;
    }

    @track({ name: "process", projectName: "translation-service" })
    async process() {
        const text = await this.generateText();
        return this.translate(text);
    }
}

Python 的例子更常见。你可以追踪 LLM 调用,也可以追踪检索、后处理等步骤:

python 复制代码
import opik
import openai

client = openai.OpenAI()

@opik.track
def retrieve_context(input_text):
    context = [
        "What specific information are you looking for?",
        "How can I assist you with your interests today?",
        "Are there any topics you'd like to explore?",
    ]
    return context

@opik.track
def generate_response(input_text, context):
    full_prompt = (
        f" If the user asks a non-specific question, use the context to provide a relevant response.\n"
        f"Context: {', '.join(context)}\n"
        f"User: {input_text}\n"
        f"AI:"
    )
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": full_prompt}]
    )
    return response.choices[0].message.content

@opik.track(name="my_llm_application")
def llm_chain(input_text):
    context = retrieve_context(input_text)
    response = generate_response(input_text, context)
    return response

result = llm_chain("Hello, how are you?")
print(result)

使用装饰器时,可以通过 opik_args 参数或 opik_context 模块自定义 trace 和 span 的数据。比如设置标签、元数据、线程 ID、反馈分数。opik_args 不会传递给函数本身,只用于 Opik 配置。如果指定了 opik_args,配置会传播到嵌套函数。

低级 SDK

如果你需要完全控制日志记录过程,可以使用低级 SDK。TypeScript 里这样写:

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

const client = new Opik({
    apiUrl: "https://www.comet.com/opik/api",
    apiKey: "your-api-key",
    projectName: "your-project-name",
    workspaceName: "your-workspace-name",
});

const trace = client.trace({
    name: `Trace`,
    input: { prompt: `Hello!` },
    output: { response: `Hello, world!` },
});

const span = trace.span({
    name: `Span`,
    type: "llm",
    input: { prompt: `Hello, world!` },
    output: { response: `Hello, world!` },
});

await client.flush();

Python 的用法:

python 复制代码
from opik import Opik

client = Opik(project_name="Opik client demo")

trace = client.trace(
    name="my_trace",
    input={"user_question": "Hello, how are you?"},
    output={"response": "Comment ça va?"}
)

trace.span(
    name="Add prompt template",
    input={"text": "Hello, how are you?", "prompt_template": "Translate the following text to French: {text}"},
    output={"text": "Translate the following text to French: hello, how are you?"}
)

trace.span(
    name="llm_call",
    type="llm",
    input={"prompt": "Translate the following text to French: hello, how are you?"},
    output={"response": "Comment ça va?"}
)

trace.end()

建议在完成 trace 和 span 后调用 end(),确保结束时间正确记录。Opik 的日志功能设计用于生产环境,所有操作在后台线程执行。如果需要确保程序退出前所有数据都发送完毕,可以调用 flush()

上下文管理器

Python 还提供了上下文管理器,让代码更简洁:

python 复制代码
import opik

with opik.start_as_current_trace("my-trace", project_name="my-project") as trace:
    trace.input = {"user_query": "What is the weather?"}
    trace.output = {"response": "It's sunny today!"}
    trace.tags = ["weather", "api-call"]
    trace.metadata = {"model": "gpt-4", "temperature": 0.7}

start_as_current_span 类似:

python 复制代码
with opik.start_as_current_span("llm-call", type="llm", project_name="my-project") as span:
    span.input = {"prompt": "Explain quantum computing"}
    span.output = {"response": "Quantum computing is..."}
    span.model = "gpt-4"
    span.provider = "openai"
    span.usage = {
        "prompt_tokens": 10,
        "completion_tokens": 50,
        "total_tokens": 60
    }

上下文管理器可以嵌套,形成层次结构。它们也会自动处理错误,确保 trace 被正确关闭和记录。你可以动态更新参数,控制 flush 时机。最佳实践包括:使用描述性名称、设置合适的类型、添加相关元数据、优雅处理错误、按项目组织、考虑性能(只在需要时使用 flush=True)。

项目和环境管理

默认情况下,trace 会记录到 Default Project。你可以通过几种方式指定项目。

TypeScript 里可以用 OPIK_PROJECT_NAME 环境变量,或者在 Opik 客户端构造函数里传 projectName

Python 里可以用环境变量,或者在 @track(project_name="my_project") 里指定,或者在 Opik(project_name="my_project") 里指定。

项目名称的解析顺序分两种情况。如果没有活动项目上下文,顺序是:显式参数、客户端配置、默认值 Default Project。一旦某个 @track(project_name=...)opik.project_context(...) 建立了活动项目上下文,所有嵌套操作都会使用这个项目名。嵌套函数即使指定了不同的项目名,也会被忽略,并记录警告。opik.project_context() 可以为代码块设置项目名。

有一个警告:当脚本同时使用 @track 和其他 Opik API 调用(如 evaluate()get_or_create_dataset()Prompt())时,如果项目名不一致,trace 和 API 对象可能落到不同项目。确保 opik.configure(project_name=...) 的值与每个 API 调用显式传递的 project_name 一致。

环境让你给 trace 打上生命周期标签,比如 developmentstagingproduction。TypeScript 里可以在 client.trace(environment: ...) 里指定,或用 OPIK_ENVIRONMENT 环境变量。Python 里可以用 @track(environment="production")client.trace(environment="production")。你还可以用 createEnvironmentgetEnvironmentsupdateEnvironmentdeleteEnvironment 管理环境。过滤时可以用 filter_string='environment = "production"',支持 =!=innot_in

刷新和禁用追踪

对于短生命周期的脚本,可能需要手动刷新。TypeScript 用 client.flush(),Python 用 client.flush()@track(flush=True)

如果想全局禁用日志,TypeScript 可以用 OPIK_TRACK_DISABLE 环境变量、trackDisable 配置或 setTracingActive(false)。Python 可以用 OPIK_TRACK_DISABLEopik.set_tracing_active(False)。禁用后,装饰器、集成和手动 client.trace() 调用都会停止发送数据。

下一步

设置好可观测性后,你还可以进一步记录聊天对话、用户反馈,以及设置在线评估指标。这些功能能帮你更全面地理解和优化 LLM 应用。

追踪不是可有可无的装饰品,而是 LLM 应用开发的基础设施。Opik 把这套东西做得足够简单,从安装到集成,再到高级用法,都有清晰的路径。花点时间跑通第一条追踪记录,你会发现后续的开发和调试效率会有质的提升。

相关推荐
oscar9996 天前
Ollie:Opik 内置的 AI 助手,让 Agent 调试从“看”变成“修”
人工智能·opik·ollie