| 编辑推荐: |
本文以 Langfuse(开源 LLM 工程平台)和 Opik(Comet 团队出品的 AI 可观测与评估平台)为双核心工具,手把手带你从零搭建完整的 Agent 可观测性体系, 希望对你的学习有帮助。
本文来自于CSDN 独隅个人博客,由火龙果软件Alice编辑推荐。 |
|
摘要
随着 AI Agent 从简单的问答机器人演进为具备多步推理、工具调用、自主规划能力的复杂系统,"黑盒化"问题日益严峻——开发者无法看清 Agent 内部每一步决策的逻辑,无法定位推理链路中的断点,更无法量化评估其表现。本文以 Langfuse(开源 LLM 工程平台)和 Opik(Comet 团队出品的 AI 可观测与评估平台)为双核心工具,手把手带你从零搭建完整的 Agent 可观测性体系。 文章覆盖:Fedora 环境下 Docker 部署 Langfuse 服务端、Opik 客户端集成与项目初始化、追踪代码嵌入与推理链路捕获、Trace 视图可视化复盘、关键指标监控(延迟/Token/错误率)、典型报错排查、自定义评分机制、生产环境隐私脱敏与性能平衡,以及基于观测数据构建提示词工程迭代闭环。全文提供可直接运行的完整代码示例,适合从新手到有经验的 AI 工程师阅读。 关键词:AI Agent、可观测性、Langfuse、Opik、Tracing、LLM Ops、推理链路、Token 监控
目录
一、多步推理黑盒痛点与可观测性核心价值 1.1 AI Agent 的"黑盒困境":你看不见的推理过程
1.2 可观测性的三大支柱:Tracing、Metrics、Logging
1.3 为什么选择 Langfuse + Opik 双平台方案
1.4 本文技术栈与前置要求
二、Fedora 环境快速部署 Langfuse 服务端 2.1 Fedora 系统环境准备与依赖安装
2.1.1 安装 Docker 与 Docker Compose
2.1.2 配置防火墙与 SELinux
2.2 获取并配置 Langfuse docker-compose.yml
2.2.1 核心服务组件解析(PostgreSQL / ClickHouse / Redis / MinIO)
2.2.2 环境变量与密钥配置
2.3 启动服务与验证
2.3.1 一键启动命令与状态检查
2.3.2 访问 Web 界面并创建首个项目
2.4 配置 systemd 服务实现开机自启
三、Opik 客户端集成与项目初始化配置 3.1 Opik 平台概述与核心能力
3.2 安装 Opik Python SDK
3.2.1 pip / uv 安装方式
3.2.2 交互式配置(opik configure)
3.3 自托管 Opik 服务端部署(Docker Compose)
3.4 项目初始化与 API Key 管理
3.4.1 创建项目与获取 Workspace 信息
3.4.2 环境变量配置最佳实践
四、嵌入追踪代码捕获 Agent 完整推理链路 4.1 Langfuse @observe 装饰器:零侵入追踪
4.1.1 基础用法:追踪单个函数
4.1.2 嵌套追踪:多层 Agent 调用链
4.2 Opik @track 装饰器:细粒度 Span 记录
4.2.1 基础追踪示例
4.2.2 与 OpenAI / LangChain 框架的自动集成
4.3 实战:为一个 ReAct Agent 嵌入完整追踪
4.3.1 Agent 代码结构说明
4.3.2 逐步嵌入 Langfuse 追踪
4.3.3 同步接入 Opik 追踪
4.4 双平台数据对比与互补策略
五、可视化复盘:从 Trace 视图定位逻辑断点 5.1 Langfuse Trace 视图详解
5.1.1 Trace 列表与筛选
5.1.2 单条 Trace 的树状展开
5.1.3 Generation / Span / Event 三层结构解读
5.2 Opik Trace 面板与时间线视图
5.3 实战:定位一个"工具调用死循环"问题
5.3.1 问题现象描述
5.3.2 通过 Trace 逐步缩小范围
5.3.3 根因分析与修复
六、关键指标监控:延迟、Token 消耗与错误率分析 6.1 Langfuse Dashboard 指标体系
6.1.1 延迟分布与 P95/P99 统计
6.1.2 Token 消耗与成本计算
6.1.3 错误率与异常 Trace 筛选
6.2 Opik 生产监控面板
6.3 自定义指标上报与告警配置
6.4 实战:构建每日 Agent 运行报告
七、典型报错排查:数据丢失与连接超时解决方案 7.1 Langfuse 数据丢失问题
7.1.1 现象:Trace 未出现在面板中
7.1.2 排查步骤与解决方案
7.2 连接超时问题
7.2.1 SDK 连接超时
7.2.2 Docker 网络配置问题
7.3 Opik 常见报错与修复
7.4 完整排查检查清单(Checklist)
八、进阶技巧:自定义评分机制优化 Agent 表现 8.1 Langfuse Score 体系
8.1.1 手动评分与自动评分
8.1.2 基于 LLM 的自动评估(LLM-as-Judge)
8.2 Opik 评估框架
8.2.1 内置评估指标(幻觉检测、相关性等)
8.2.2 自定义评估指标开发
8.3 实战:为客服 Agent 构建质量评分流水线
九、生产环境注意事项:隐私脱敏与性能损耗平衡 9.1 数据隐私与合规
9.1.1 敏感信息识别与脱敏策略
9.1.2 PII 过滤中间件实现
9.2 性能损耗控制
9.2.1 异步上报与批量发送
9.2.2 采样策略配置
9.2.3 资源占用基准测试
9.3 高可用部署架构建议
十、构建闭环:基于观测数据迭代提示词工程 10.1 从 Trace 数据中提取优化信号
10.2 Langfuse Prompt Management 版本管理
10.3 A/B 测试与效果对比
10.4 实战:一轮完整的提示词优化迭代
10.5 构建持续改进的 DevOps 流水线
十一、常见陷阱与问题排除汇总 十二、总结 十三、详细参考资料 附录 附录 A:完整 docker-compose.yml 配置文件
附录 B:完整 Agent 追踪代码示例
附录 C:环境变量速查表
附录 D:常用 CLI 命令速查
一、多步推理黑盒痛点与可观测性核心价值
1.1 AI Agent 的"黑盒困境":你看不见的推理过程
当你构建一个 AI Agent 时,你面对的不是一个简单的"输入→输出"函数。现代 Agent 系统通常包含以下复杂环节:
用户输入 → 意图识别 → 任务规划 → 工具选择 → 工具调用 → 结果解析 →
二次推理 → 可能的回退/重试 → 最终输出
|
每一个环节都可能出现问题:
- 意图识别偏差:Agent 误解了用户意图,走入了错误的推理分支
-
工具调用失败:API 超时、参数格式错误、权限不足
-
推理死循环:Agent 反复调用同一工具却无法得到满意结果
-
上下文丢失:多轮对话中关键信息被截断或遗忘
-
幻觉输出:Agent 编造了不存在的工具返回值
核心痛点:传统日志(print/logging)只能记录"发生了什么",却无法呈现"为什么这样发生"以及"各环节之间的因果关系"。你需要的是一种结构化的、可回溯的、可量化的观测手段。
1.2 可观测性的三大支柱:Tracing、Metrics、Logging
借鉴分布式系统的可观测性理论,AI Agent 的可观测性同样建立在三大支柱之上:
| 支柱 | 在 Agent 场景的含义 | 典型工具 |
|---|
| Tracing(追踪) | 记录一次请求从输入到输出的完整调用链,包括每个 LLM 调用、工具调用、中间推理步骤 | Langfuse Trace、Opik Trace | | Metrics(指标) | 量化系统健康度:延迟、Token 消耗、成功率、成本 | Langfuse Dashboard、Opik Monitoring | | Logging(日志) | 记录离散事件:错误信息、警告、状态变更 | 结构化日志 + 关联 Trace ID |
对于 AI Agent 而言,Tracing 是最核心的能力——因为 Agent 的行为是非确定性的,同样的输入可能产生不同的推理路径,只有通过完整的链路追踪,才能理解"这一次"Agent 为什么做出了这样的决策。
1.3 为什么选择 Langfuse + Opik 双平台方案
Langfuse 是一个开源的 LLM 工程平台(YC W23 孵化,2026 年被 ClickHouse 收购),提供:
- 全链路 Trace 追踪(支持复杂嵌套)
-
Prompt 版本管理与 A/B 测试
-
评估系统(人工标注 + LLM-as-Judge)
-
成本与 Token 消耗分析
基于 ClickHouse 的高性能存储(支持日处理千万级 Trace)
Opik 是 Comet 团队出品的开源 AI 可观测与评估平台(GitHub 20,000+ Stars,入选 2026 Gartner 市场指南),提供:
- 全链路追踪(自动记录每次 LLM 调用、工具调用)
-
20+ 内置评估指标(幻觉检测、内容审核、RAG 相关性)
-
Agent 优化器(自动优化 Prompt 和 Agent 行为)
-
Guardrails(安全护栏)
-
支持日处理 4000 万+ 追踪数据
双平台互补策略:
- Langfuse 侧重于开发调试与 Prompt 管理,适合日常开发迭代
-
Opik 侧重于生产监控与自动化评估,适合上线后的持续运营
-
两者可同时接入,数据互不冲突,形成完整的观测闭环
1.4 本文技术栈与前置要求
| 组件 | 版本要求 | 用途 |
| 操作系统 | Fedora 39/40/41 | 部署环境 |
| Docker | 24.0+ | 容器化部署 |
| Docker Compose | v2.20+ | 服务编排 |
| Python | 3.10+ | SDK 与 Agent 代码 |
| Langfuse | v3.x(最新) | 可观测平台 |
| Opik | v2.1+(最新) | 可观测与评估平台 |
| OpenAI API / 兼容接口 | - | LLM 调用 |
前置知识:
- 基本的 Linux 命令行操作
-
Docker 基础概念(镜像、容器、Compose)
-
Python 基础语法
-
对 LLM API 调用有基本了解
二、Fedora 环境快速部署 Langfuse 服务端
2.1 Fedora 系统环境准备与依赖安装
2.1.1 安装 Docker 与 Docker Compose
Fedora 使用 dnf 包管理器。以下是完整的安装步骤:
sudo dnf update -y
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-buildx-plugin
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker $USER
docker --version
docker compose version
|
⚠️ 提示 :执行 usermod 后需要注销并重新登录,或者使用 newgrp docker 临时切换组。
2.1.2 配置防火墙与 SELinux
Fedora 默认启用 firewalld 和 SELinux,需要适当配置:
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --permanent --add-port=5432/tcp
sudo firewall-cmd --permanent --add-port=8123/tcp
sudo firewall-cmd --reload
sudo setenforce 0
sudo setsebool -P container_manage_cgroup on
|
⚠️ 注意 :生产环境不建议关闭 SELinux,应使用方案二或为特定端口添加策略。
2.2 获取并配置 Langfuse docker-compose.yml 2.2.1 核心服务组件解析
Langfuse v3 采用微服务架构,包含以下核心组件:
┌─────────────────────────────────────────────────────────┐
│ Langfuse v3 架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ langfuse-web │ │langfuse-worker│ │
│ │ (API + UI) │ │ (异步处理) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴────────────────────┴───────┐ │
│ │ PostgreSQL │ ← 元数据存储 │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ ClickHouse │ ← Trace 数据 │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ Redis │ ← 缓存/队列 │
│ └───────────────────────────────────┘ │
│ ┌───────────────────────────────────┐ │
│ │ MinIO │ ← 对象存储 │
│ └───────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
|
各组件职责:
- langfuse-web:提供 Web UI 和 REST API,处理用户请求
-
langfuse-worker:异步处理 Trace 写入、评估计算等后台任务
-
PostgreSQL:存储项目配置、用户信息、Prompt 模板等元数据
-
ClickHouse:高性能存储 Trace、Span、Score 等观测数据
-
Redis:缓存热点数据、消息队列
-
MinIO:存储大型 Payload(如长文本输入输出)
2.2.2 环境变量与密钥配置
创建项目目录并下载官方 Compose 文件:
mkdir -p ~/langfuse-deploy
cd ~/langfuse-deploy
curl -o docker-compose.yml \
https://raw.githubusercontent.com/langfuse/langfuse/main/docker-compose.yml
touch .env
|
编辑 .env 文件,配置关键参数:
NEXTAUTH_SECRET=your-super-secret-nextauth-key-change-me
SALT=your-random-salt-value-change-me
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
DIRECT_URL=postgresql://postgres:postgres@db:5432/postgres
CLICKHOUSE_URL=http://clickhouse:8123
CLICKHOUSE_MIGRATION_URL=clickhouse://clickhouse:9000
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=clickhouse
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_AUTH=redis
MINIO_ENDPOINT=minio
MINIO_PORT=9000
MINIO_ACCESS_KEY=minio
MINIO_SECRET_KEY=miniosecret
MINIO_BUCKET=langfuse
NEXTAUTH_URL=http://localhost:3000
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=true
TELEMETRY_ENABLED=false
|
⚠️ 安全警告 : NEXTAUTH_SECRET 、 SALT 和 ENCRYPTION_KEY 在生产环境中 必须 替换为随机生成的强密钥。 ENCRYPTION_KEY 必须是 64 位十六进制字符串。
生成安全密钥的命令:
openssl rand -base64 32
openssl rand -base64 24
openssl rand -hex 32
|
2.3 启动服务与验证 2.3.1 一键启动命令与状态检查
cd ~/langfuse-deploy
docker compose up -d
docker compose ps
docker compose logs -f langfuse-web
docker compose logs clickhouse | tail -20
docker compose logs db | tail -20
|
⏱️ 首次启动:首次运行需要拉取镜像并执行数据库迁移,可能需要 3-5 分钟。请耐心等待所有服务变为 healthy 状态。
2.3.2 访问 Web 界面并创建首个项目
1.打开浏览器访问 http://localhost:3000
2.注册管理员账户(首个注册用户自动成为 Owner)
3.创建 Organization(组织)
4.创建 Project(项目)
创建项目后,进入 Settings → API Keys,生成密钥对:
Public Key: pk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Secret Key: sk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
📝 记录 :请妥善保存这两个 Key,后续 SDK 配置需要使用。Secret Key 只显示一次! 2.4 配置 systemd 服务实现开机自启 创建 systemd 服务文件:
sudo tee /etc/systemd/system/langfuse.service << 'EOF'
[Unit]
Description=Langfuse LLM Observability Platform
Requires=docker.service
After=docker.service network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/home/yourusername/langfuse-deploy
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable langfuse.service
sudo systemctl start langfuse.service
sudo systemctl stop langfuse.service
sudo systemctl status langfuse.service
|
三、Opik 客户端集成与项目初始化配置
3.1 Opik 平台概述与核心能力
Opik 是 Comet 团队推出的开源 LLM 应用可观测、评估与优化平台。截至 2026 年,其核心数据:
| 指标 | 数据 |
|---|
| GitHub Stars | 20,200+ | | 累计发布版本 | 498+ | | 最新版本 | v2.1.13(2026-07-01) | | 许可证 | Apache 2.0 | | 日处理追踪量 | 4000 万+ | | 内置评估指标 | 20+ |
核心能力矩阵:
| 能力 | 说明 |
|---|
| 全链路追踪 | 自动记录每次 LLM 调用、工具调用、对话历史,Token 消耗精确到个位数 | | 智能评估 | 内置幻觉检测、内容审核、RAG 相关性/精确度等 20+ 指标 | | 生产监控 | 支持日处理 4000 万+ 追踪数据,异常波动实时告警 | | Agent 优化器 | 自动优化 Prompt 和 Agent 行为 | | Guardrails | 输入/输出安全护栏 |
3.2 安装 Opik Python SDK
3.2.1 pip / uv 安装方式
pip install opik
pip install opik==2.1.13
curl -LsSf https://astral.sh/uv/install.sh | sh
uv pip install opik
python -c "import opik; print(opik.__version__)"
|
3.2.2 交互式配置(opik configure)
安装完成后,运行交互式配置命令:
该命令会提示你选择:
- 自托管地址(如果部署了 Opik 服务端)
-
Comet 云平台(使用 Comet 的托管服务)
对于本教程的自托管场景,选择自托管并输入你的服务地址。
3.3 自托管 Opik 服务端部署(Docker Compose)
git clone https://github.com/comet-ml/opik.git
cd opik/deployment/docker-compose
cat .env
docker compose up -d
docker compose ps
|
⏱️ 注意:Opik 首次启动需要初始化数据库,通常需要 2-3 分钟。
3.4 项目初始化与 API Key 管理
3.4.1 创建项目与获取 Workspace 信息
在 Opik Web UI(http://localhost:5173)中:
首次访问会引导你创建 Workspace
点击 “Create Project” 创建新项目
进入项目设置,获取 API Key
3.4.2 环境变量配置最佳实践
创建 .env 文件管理 Opik 配置:
OPIK_URL_OVERRIDE=http://localhost:5173/api
OPIK_API_KEY=your-opik-api-key-here
OPIK_WORKSPACE=default
OPIK_PROJECT_NAME=my-agent-project
|
在 Python 代码中加载配置:
"""
Opik 客户端初始化配置
"""
import os
from dotenv import load_dotenv
load_dotenv()
import opik
from opik import Opik
client = Opik(
project_name=os.getenv("OPIK_PROJECT_NAME", "my-agent-project"),
workspace=os.getenv("OPIK_WORKSPACE", "default"),
)
print(f"✅ Opik 客户端初始化成功")
print(f" 项目: {client.project_name}")
print(f" 工作空间: {client.workspace}")
|
四、嵌入追踪代码捕获 Agent 完整推理链路
4.1 Langfuse @observe 装饰器 :零侵入追踪 4.1.1 基础用法:追踪单个函数
Langfuse Python SDK 提供了极其简洁的 @observe() 装饰器,可以自动追踪任意函数:
"""
示例:Langfuse 基础追踪
文件:basic_langfuse_tracing.py
"""
import os
from dotenv import load_dotenv
from langfuse import observe, langfuse_context
load_dotenv()
@observe()
def process_user_query(query: str) -> str:
"""
处理用户查询的入口函数
@observe() 会自动记录:
- 函数名(作为 Trace 名称)
- 输入参数(query)
- 返回值
- 执行时间
"""
result = f"处理结果:{query.upper()}"
return result
@observe()
def call_llm(prompt: str) -> str:
"""
调用 LLM 的子步骤
"""
import time
time.sleep(0.5)
return f"LLM 响应:针对 '{prompt}' 的回答"
@observe()
def main_agent(user_input: str) -> str:
"""
Agent 主函数 - 顶层 Trace
"""
langfuse_context.update_current_trace(
name="customer-support-agent",
user_id="user-12345",
session_id="session-abc",
tags=["production", "v2.1"],
metadata={
"agent_version": "2.1.0",
"model": "gpt-4o",
"environment": "production"
}
)
processed = process_user_query(user_input)
llm_response = call_llm(processed)
return llm_response
if __name__ == "__main__":
result = main_agent("帮我查询订单状态")
print(f"最终结果: {result}")
langfuse_context.flush()
print("✅ Trace 数据已发送到 Langfuse")
|
4.1.2 嵌套追踪:多层 Agent 调用链
对于复杂的 Agent 系统,追踪会自动形成树状结构:
"""
示例:多层嵌套追踪
展示 Agent → Planner → Tool Call → LLM 的完整链路
"""
from langfuse import observe, langfuse_context
from langfuse.openai import openai
@observe()
def agent_orchestrator(task: str) -> str:
"""Agent 编排器 - 顶层入口"""
langfuse_context.update_current_trace(
name="research-agent",
tags=["multi-step", "research"]
)
plan = plan_task(task)
results = []
for step in plan:
result = execute_step(step)
results.append(result)
final_answer = synthesize_results(results)
return final_answer
@observe()
def plan_task(task: str) -> list:
"""任务规划器"""
return [
{"action": "search", "query": f"{task} 相关资料"},
{"action": "analyze", "data": "搜索结果"},
{"action": "summarize", "content": "分析结果"}
]
@observe()
def execute_step(step: dict) -> str:
"""执行单个步骤"""
if step["action"] == "search":
return search_tool(step["query"])
elif step["action"] == "analyze":
return analyze_data(step["data"])
elif step["action"] == "summarize":
return summarize_content(step["content"])
return ""
@observe()
def search_tool(query: str) -> str:
"""搜索工具调用"""
langfuse_context.update_current_observation(
metadata={"tool": "web_search", "query": query}
)
return f"搜索结果:关于 '{query}' 的 5 条结果"
@observe()
def analyze_data(data: str) -> str:
"""数据分析步骤"""
return f"分析完成:{data}"
@observe()
def summarize_content(content: str) -> str:
"""内容汇总步骤"""
return f"摘要:{content} 的核心要点是..."
@observe()
def synthesize_results(results: list) -> str:
"""最终汇总"""
return "综合所有步骤的最终答案"
if __name__ == "__main__":
answer = agent_orchestrator("研究 2026 年 AI Agent 发展趋势")
print(answer)
langfuse_context.flush()
|
在 Langfuse UI 中,你将看到如下树状结构:
📊 research-agent (Trace)
├── 📋 plan_task (Span) - 120ms
├── 🔧 execute_step (Span) - 850ms
│ └── 🔍 search_tool (Span) - 800ms
├── 🔧 execute_step (Span) - 200ms
│ └── 📊 analyze_data (Span) - 180ms
├── 🔧 execute_step (Span) - 300ms
│ └── 📝 summarize_content (Span) - 280ms
└── 🎯 synthesize_results (Span) - 150ms
|
4.2 Opik @track 装饰器:细粒度 Span 记录 4.2.1 基础追踪示例
"""
示例:Opik 基础追踪
文件:basic_opik_tracing.py
"""
import os
from dotenv import load_dotenv
from opik import track, opik_context
load_dotenv()
@track
def research_agent(query: str) -> str:
"""
Agent 主函数
@track 自动记录:
- 函数名
- 输入参数
- 返回值
- 执行时间
- Token 使用量(如果内部有 LLM 调用)
"""
opik_context.update_current_trace(
tags=["research", "production"],
metadata={"agent_type": "research", "version": "1.0"}
)
search_results = search_web(query)
analysis = analyze_results(search_results)
summary = generate_summary(analysis)
return summary
@track
def search_web(query: str) -> list:
"""网络搜索步骤"""
return [
{"title": "AI Agent 2026 趋势报告", "relevance": 0.95},
{"title": "多模态 Agent 最新进展", "relevance": 0.88},
]
@track
def analyze_results(results: list) -> str:
"""分析搜索结果"""
return f"分析了 {len(results)} 条结果,发现 3 个关键趋势"
@track
def generate_summary(analysis: str) -> str:
"""生成摘要"""
return f"研究摘要:{analysis}"
if __name__ == "__main__":
result = research_agent("2026年AI Agent发展趋势")
print(f"结果: {result}")
|
4.2.2 与 OpenAI / LangChain 框架的自动集成
Opik 提供了对主流框架的自动追踪支持:
"""
示例:Opik 与 OpenAI 自动集成
无需手动添加 @track,自动捕获 LLM 调用详情
"""
from opik.integrations.openai import openai
from opik import track
@track
def chat_with_model(user_message: str) -> str:
"""
使用 OpenAI 兼容接口
Opik 会自动记录:
- 模型名称
- 输入/输出 Token 数
- 请求延迟
- 完整的 messages 内容
"""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": user_message}
],
temperature=0.7,
max_tokens=1000
)
return response.choices[0].message.content
@track
def langchain_agent_example(query: str) -> str:
"""
LangChain 集成示例
"""
from opik.integrations.langchain import OpikTracer
opik_tracer = OpikTracer(tags=["langchain", "agent"])
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
llm = ChatOpenAI(model="gpt-4o")
prompt = PromptTemplate(
input_variables=["query"],
template="请详细回答以下问题:{query}"
)
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.invoke({"query": query}, config={"callbacks": [opik_tracer]})
return result["text"]
|
4.3 实战:为一个 ReAct Agent 嵌入完整追踪
4.3.1 Agent 代码结构说明
我们构建一个完整的 ReAct(Reasoning + Acting)Agent,具备:
- 多步推理能力
-
工具调用(搜索、计算器)
-
自动重试机制
-
完整的双平台追踪
"""
完整 ReAct Agent 示例(带 Langfuse + Opik 双平台追踪)
文件:react_agent_with_observability.py
依赖安装:
pip install langfuse opik openai python-dotenv
"""
import os
import json
import time
from typing import Optional
from dotenv import load_dotenv
load_dotenv()
from langfuse import observe as langfuse_observe
from langfuse import langfuse_context
from opik import track as opik_track
from opik import opik_context
from langfuse.openai import openai
TOOLS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": "搜索互联网获取最新信息",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索查询关键词"
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculator",
"description": "执行数学计算",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如 '2 + 3 * 4'"
}
},
"required": ["expression"]
}
}
}
]
@langfuse_observe(name="tool:web_search")
@opik_track(name="tool:web_search")
def web_search(query: str) -> str:
"""
网络搜索工具
实际项目中这里调用搜索 API
"""
time.sleep(0.3)
results = {
"status": "success",
"results": [
{"title": f"关于'{query}'的最新资讯", "snippet": "这是搜索结果摘要..."},
{"title": f"'{query}'深度分析", "snippet": "详细分析内容..."}
]
}
return json.dumps(results, ensure_ascii=False)
@langfuse_observe(name="tool:calculator")
@opik_track(name="tool:calculator")
def calculator(expression: str) -> str:
"""
计算器工具
"""
try:
result = eval(expression, {"__builtins__": {}}, {})
return json.dumps({"status": "success", "result": result})
except Exception as e:
return json.dumps({"status": "error", "message": str(e)})
TOOL_REGISTRY = {
"web_search": web_search,
"calculator": calculator,
}
@langfuse_observe(name="react-agent")
@opik_track(name="react-agent")
def react_agent(
user_query: str,
max_steps: int = 5,
model: str = "gpt-4o"
) -> str:
"""
ReAct Agent 主函数
参数:
user_query: 用户输入
max_steps: 最大推理步数(防止死循环)
model: 使用的 LLM 模型
返回:
Agent 的最终回答
"""
langfuse_context.update_current_trace(
name=f"react-agent: {user_query[:50]}",
user_id=os.getenv("USER_ID", "anonymous"),
tags=["react", "multi-step"],
metadata={
"model": model,
"max_steps": max_steps,
"agent_version": "1.0.0"
}
)
messages = [
{
"role": "system",
"content": (
"你是一个 ReAct Agent。你可以使用工具来帮助用户。"
"每一步先思考(Thought),再决定是否调用工具(Action),"
"最后根据工具结果继续推理或给出最终答案。"
)
},
{"role": "user", "content": user_query}
]
for step in range(max_steps):
step_trace_name = f"step-{step + 1}"
response = openai.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS,
tool_choice="auto",
temperature=0.1
)
assistant_message = response.choices[0].message
if assistant_message.tool_calls:
messages.append(assistant_message)
for tool_call in assistant_message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
tool_func = TOOL_REGISTRY.get(func_name)
if tool_func:
tool_result = tool_func(**func_args)
else:
tool_result = json.dumps({
"status": "error",
"message": f"未知工具: {func_name}"
})
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result
})
else:
final_answer = assistant_message.content
langfuse_context.update_current_trace(
output=final_answer,
metadata={"total_steps": step + 1}
)
return final_answer
fallback = "抱歉,我无法在限定步数内完成此任务。请尝试简化问题。"
langfuse_context.update_current_trace(
output=fallback,
metadata={"total_steps": max_steps, "hit_max_steps": True}
)
return fallback
if __name__ == "__main__":
queries = [
"帮我计算 (15 + 27) * 3 的结果",
"搜索一下 2026 年最流行的 AI Agent 框架",
]
for query in queries:
print(f"\n{'='*60}")
print(f"🤖 用户查询: {query}")
print(f"{'='*60}")
result = react_agent(query)
print(f"\n📝 Agent 回答: {result}")
langfuse_context.flush()
print("\n✅ 所有 Trace 数据已发送")
|
4.3.2 逐步嵌入 Langfuse 追踪(关键要点)
在上述代码中,追踪嵌入的关键点:
1.顶层函数使用 @langfuse_observe(name="react-agent") 创建 Trace
2.工具函数使用 @langfuse_observe(name="tool:xxx") 创建子 Span
3.OpenAI 调用通过 from langfuse.openai import openai 自动追踪
4.元数据设置通过 langfuse_context.update_current_trace() 添加业务上下文
4.3.3 同步接入 Opik 追踪
Opik 的接入几乎零额外成本——只需在同一个函数上叠加 @opik_track 装饰器:
@langfuse_observe(name="react-agent")
@opik_track(name="react-agent")
def react_agent(user_query: str, ...) -> str:
|
💡 技巧:装饰器顺序很重要。建议 @langfuse_observe 在外层,@opik_track 在内层,这样两个平台都能正确捕获嵌套关系。
4.4 双平台数据对比与互补策略
| 维度 | Langfuse | Opik |
|---|
| Trace 粒度 | Trace → Generation/Span/Event | Trace → Span(更扁平) | | LLM 调用详情 | 自动记录 messages、tokens、cost | 自动记录 + 模型推理细节 | | 评估能力 | Score 系统 + LLM-as-Judge | 20+ 内置指标 + 自定义 | | Prompt 管理 | ✅ 版本管理 + A/B 测试 | ❌(需配合 Comet) | | 生产监控 | Dashboard + 告警 | 实时监控 + 异常检测 | | Agent 优化 | 手动迭代 | Agent Optimizer(自动) | | 适用阶段 | 开发调试 + Prompt 迭代 | 生产监控 + 自动评估 |
推荐策略:
- 开发阶段:重点使用 Langfuse 进行调试和 Prompt 迭代
-
测试阶段:双平台同时使用,对比数据
-
生产阶段:Opik 负责实时监控,Langfuse 负责问题回溯
五、可视化复盘:从 Trace 视图定位逻辑断点
5.1 Langfuse Trace 视图详解
5.1.1 Trace 列表与筛选
访问 http://localhost:3000,进入项目后点击左侧导航 Traces:
- 时间范围筛选:选择最近 1h / 24h / 7d / 自定义
-
标签筛选:按 tags 过滤(如 production、error)
-
用户筛选:按 user_id 查看特定用户的所有 Trace
-
会话筛选:按 session_id 查看完整对话
-
状态筛选:成功 / 失败 / 超时
5.1.2 单条 Trace 的树状展开
点击任意 Trace 进入详情页,你会看到:
┌─────────────────────────────────────────────────────────────┐
│ Trace: react-agent: 帮我计算 (15+27)*3 的结果 │
│ 时间: 2026-08-06 09:30:15 耗时: 2.3s 状态: ✅ Success │
│ User: user-12345 Session: session-abc │
│ Tags: [react] [multi-step] │
│ Metadata: {"model": "gpt-4o", "total_steps": 2} │
├─────────────────────────────────────────────────────────────┤
│ │
│ 📊 Timeline View │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ react-agent ████████████████████████ 2.3s │ │
│ │ ├─ LLM Call #1 ████████ 0.8s │ │
│ │ ├─ tool:calculator ██ 0.1s │ │
│ │ └─ LLM Call #2 ████████████ 1.2s │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 📋 Detail View │
│ • LLM Call #1: │
│ Input: [system + user messages] │
│ Output: tool_call(calculator, "(15+27)*3") │
│ Tokens: 156 in / 23 out │
│ Cost: $0.0012 │
│ │
│ • tool:calculator: │
│ Input: {"expression": "(15+27)*3"} │
│ Output: {"status": "success", "result": 126} │
│ Duration: 100ms │
│ │
│ • LLM Call #2: │
│ Input: [previous + tool result] │
│ Output: "计算结果是 126" │
│ Tokens: 203 in / 15 out │
│ Cost: $0.0015 │
│ │
└─────────────────────────────────────────────────────────────┘
|
5.1.3 Generation / Span / Event 三层结构解读
Langfuse 的 Observation 分为三种类型:
| 类型 | 含义 | 典型场景 |
|---|
| Generation | 一次 LLM 调用 | OpenAI API 调用、本地模型推理 | | Span | 一段有起止时间的操作 | 工具调用、数据检索、业务逻辑 | | Event | 一个时间点事件 | 日志记录、状态变更、错误触发 |
5.2 Opik Trace 面板与时间线视图
Opik 的 Trace 视图(http://localhost:5173)提供:
- 时间线视图:横向展示各 Span 的时间分布,直观看出瓶颈
-
Token 消耗热力图:颜色深浅表示各步骤的 Token 消耗
-
错误高亮:失败的 Span 标红显示
-
对比视图:选择两条 Trace 进行 side-by-side 对比
5.3 实战:定位一个"工具调用死循环"问题
5.3.1 问题现象描述
用户反馈:Agent 响应超时(>30s),且返回"无法完成任务"。
5.3.2 通过 Trace 逐步缩小范围
步骤 1:在 Langfuse 中按时间筛选,找到超时的 Trace
步骤 2:展开 Trace 树,发现异常模式:
react-agent (32.5s) ⚠️ 超时
├─ LLM Call #1 (0.8s) → 决定调用 web_search
├─ tool:web_search (0.3s) → 返回结果
├─ LLM Call #2 (0.9s) → 再次调用 web_search(同样参数!)
├─ tool:web_search (0.3s) → 返回相同结果
├─ LLM Call #3 (0.8s) → 又调用 web_search...
├─ ... (重复 5 次)
└─ LLM Call #6 (1.0s) → 放弃,输出"无法完成"
|
步骤 3:检查 LLM Call #2 的输入,发现工具返回结果中缺少 Agent 期望的字段,导致 Agent 认为"搜索失败"而重试。
5.3.3 根因分析与修复
根因:搜索工具返回的 JSON 格式与 Agent 提示词中描述的期望格式不匹配。Agent 期望 results[].content 字段,但工具返回的是 results[].snippet。
修复方案:
@langfuse_observe(name="tool:web_search")
@opik_track(name="tool:web_search")
def web_search(query: str) -> str:
"""修复后的搜索工具 - 返回格式与 Agent 期望一致"""
time.sleep(0.3)
results = {
"status": "success",
"results": [
{
"title": f"关于'{query}'的最新资讯",
"content": "这是搜索结果详细内容...",
"url": "https://example.com/article1"
}
]
}
return json.dumps(results, ensure_ascii=False)
|
同时,在 Agent 系统提示词中增加防循环指令:
SYSTEM_PROMPT = """
你是一个 ReAct Agent。
重要规则:
1. 如果工具调用返回了结果,不要以相同参数重复调用同一工具
2. 如果工具返回错误,最多重试 1 次,然后尝试其他方案
3. 如果连续 2 次无法获取有效信息,直接向用户说明情况
|
六、关键指标监控:延迟、Token 消耗与错误率分析
6.1 Langfuse Dashboard 指标体系
6.1.1 延迟分布与 P95/P99 统计
在 Langfuse Dashboard 中(左侧导航 → Dashboard):
- Latency 图表:展示所有 Trace 的延迟分布
-
P50 / P95 / P99:分位数统计,帮你了解"大部分请求"和"尾部请求"的表现
-
按模型分组:对比不同模型的延迟表现
-
按时间段对比:发现性能退化趋势
通过 API 获取延迟数据:
"""
获取 Langfuse 延迟统计数据
"""
from langfuse import Langfuse
langfuse = Langfuse(
public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
host=os.getenv("LANGFUSE_HOST", "http://localhost:3000")
)
traces = langfuse.fetch_traces(
limit=100,
tags=["production"]
)
latencies = [t.latency for t in traces.data if t.latency]
latencies.sort()
print(f"样本数: {len(latencies)}")
print(f"P50: {latencies[len(latencies)//2]:.2f}s")
print(f"P95: {latencies[int(len(latencies)*0.95)]:.2f}s")
print(f"P99: {latencies[int(len(latencies)*0.99)]:.2f}s")
print(f"最大: {latencies[-1]:.2f}s")
|
6.1.2 Token 消耗与成本计算
Langfuse 自动追踪每次 LLM 调用的 Token 使用量:
"""
Token 消耗分析脚本
"""
from langfuse import Langfuse
from datetime import datetime, timedelta
langfuse = Langfuse()
since = datetime.now() - timedelta(hours=24)
traces = langfuse.fetch_traces(limit=500, from_timestamp=since)
total_input_tokens = 0
total_output_tokens = 0
total_cost = 0.0
model_usage = {}
for trace in traces.data:
observations = langfuse.fetch_observations(trace_id=trace.id)
for obs in observations.data:
if obs.type == "GENERATION" and obs.usage:
input_t = obs.usage.input or 0
output_t = obs.usage.output or 0
total_input_tokens += input_t
total_output_tokens += output_t
model = obs.model or "unknown"
if model not in model_usage:
model_usage[model] = {"input": 0, "output": 0, "count": 0}
model_usage[model]["input"] += input_t
model_usage[model]["output"] += output_t
model_usage[model]["count"] += 1
print("=" * 50)
print("📊 过去 24 小时 Token 消耗报告")
print("=" * 50)
print(f"总输入 Tokens: {total_input_tokens:,}")
print(f"总输出 Tokens: {total_output_tokens:,}")
print(f"总 Tokens: {total_input_tokens + total_output_tokens:,}")
print(f"\n按模型分布:")
for model, usage in model_usage.items():
print(f" {model}: {usage['count']} 次调用, "
f"输入 {usage['input']:,}, 输出 {usage['output']:,}")
|
6.1.3 错误率与异常 Trace 筛选
"""
错误率监控脚本
"""
from langfuse import Langfuse
from datetime import datetime, timedelta
langfuse = Langfuse()
since = datetime.now() - timedelta(hours=24)
traces = langfuse.fetch_traces(limit=1000, from_timestamp=since)
total = len(traces.data)
errors = [t for t in traces.data if t.level == "ERROR"]
error_rate = len(errors) / total * 100 if total > 0 else 0
print(f"总请求数: {total}")
print(f"错误数: {len(errors)}")
print(f"错误率: {error_rate:.2f}%")
if errors:
print(f"\n最近错误详情:")
for err in errors[:5]:
print(f" - [{err.timestamp}] {err.name}: {err.status_message}")
|
6.2 Opik 生产监控面板
Opik 的监控面板(http://localhost:5173 → Monitoring)提供:
- 实时 QPS:每秒追踪请求数
-
延迟趋势图:按时间窗口聚合
-
Token 消耗趋势:按小时/天统计
-
错误率告警:设置阈值自动告警
-
模型使用分布:饼图展示各模型占比
6.3 自定义指标上报与告警配置
"""
自定义指标上报到 Langfuse
"""
from langfuse import Langfuse
langfuse = Langfuse()
langfuse.score(
trace_id="your-trace-id",
name="response_quality",
value=0.85,
comment="回答相关但不完整",
data_type="NUMERIC"
)
langfuse.score(
trace_id="your-trace-id",
name="task_completion",
value="completed",
data_type="CATEGORICAL"
)
|
6.4 实战:构建每日 Agent 运行报告
"""
每日 Agent 运行报告生成器
文件:daily_report.py
建议通过 cron 每日执行
"""
from langfuse import Langfuse
from datetime import datetime, timedelta
import json
def generate_daily_report():
"""生成每日运行报告"""
langfuse = Langfuse()
end_time = datetime.now()
start_time = end_time - timedelta(hours=24)
traces = langfuse.fetch_traces(
limit=2000,
from_timestamp=start_time,
to_timestamp=end_time
)
total_traces = len(traces.data)
latencies = [t.latency for t in traces.data if t.latency]
avg_latency = sum(latencies) / len(latencies) if latencies else 0
success_count = sum(1 for t in traces.data if t.level != "ERROR")
error_count = total_traces - success_count
total_tokens = 0
for trace in traces.data:
if trace.usage:
total_tokens += (trace.usage.get("input", 0) +
trace.usage.get("output", 0))
report = {
"report_date": start_time.strftime("%Y-%m-%d"),
"generated_at": end_time.isoformat(),
"summary": {
"total_requests": total_traces,
"success_rate": f"{success_count/total_traces*100:.1f}%" if total_traces else "N/A",
"error_count": error_count,
"avg_latency_ms": round(avg_latency * 1000, 1),
"total_tokens": total_tokens,
},
"top_errors": [],
"recommendations": []
}
if error_count / max(total_traces, 1) > 0.05:
report["recommendations"].append("⚠️ 错误率超过 5%,建议检查工具调用稳定性")
if avg_latency > 5:
report["recommendations"].append("⚠️ 平均延迟超过 5s,建议优化 LLM 调用或减少推理步骤")
print(json.dumps(report, ensure_ascii=False, indent=2))
return report
if __name__ == "__main__":
generate_daily_report()
|
配置 cron 定时执行:
0 9 * * * cd /path/to/project && python daily_report.py >> /var/log/agent_report.log 2>&1
|
七、典型报错排查:数据丢失与连接超时解决方案
7.1 Langfuse 数据丢失问题
7.1.1 现象:Trace 未出现在面板中
你运行了 Agent 代码,程序正常结束,但在 Langfuse Web UI 中看不到任何 Trace。 7.1.2 排查步骤与解决方案
检查清单:
echo $LANGFUSE_PUBLIC_KEY
echo $LANGFUSE_SECRET_KEY
echo $LANGFUSE_HOST
curl http://localhost:3000/api/public/health
curl -v http://localhost:3000
docker compose logs langfuse-worker --tail=50
|
常见原因与修复:
| 原因 | 解决方案 |
|---|
未调用 flush() | 在程序退出前调用 langfuse_context.flush() | | 环境变量未加载 | 确认 load_dotenv() 在 import langfuse 之前执行 | | Host 地址错误 | 确认 LANGFUSE_HOST 包含协议前缀(http://) | | API Key 不匹配 | 确认 Public Key 和 Secret Key 属于同一项目 | | 防火墙阻断 | 检查 3000 端口是否开放 |
代码层面的修复:
"""
确保数据不丢失的正确写法
"""
from langfuse import Langfuse, observe, langfuse_context
import atexit
atexit.register(lambda: langfuse_context.flush())
@observe()
def my_agent(query: str) -> str:
try:
return result
finally:
langfuse_context.flush()
with Langfuse() as langfuse:
trace = langfuse.trace(name="my-trace")
trace.generation(name="llm-call", input="hello", output="world")
|
7.2 连接超时问题 7.2.1 SDK 连接超时
错误信息 :
langfuse.api.error.ApiError: Connection timed out
# 或
requests.exceptions.ConnectTimeout: HTTPConnectionPool(host='localhost', port=3000)
|
解决方案 :
"""
处理连接超时的配置
"""
import os
from langfuse import Langfuse
os.environ["LANGFUSE_FLUSH_INTERVAL"] = "10"
os.environ["LANGFUSE_FLUSH_AT"] = "100"
os.environ["HTTP_PROXY"] = "http://proxy:8080"
os.environ["HTTPS_PROXY"] = "http://proxy:8080"
langfuse = Langfuse(
public_key="pk-lf-xxx",
secret_key="sk-lf-xxx",
host="http://localhost:3000",
timeout=30,
)
|
7.2.2 Docker 网络配置问题
如果 Agent 代码运行在 Docker 容器内,而 Langfuse 也在 Docker 中:
services:
my-agent:
build: .
environment:
LANGFUSE_HOST: http://langfuse-web:3000
depends_on:
- langfuse-web
networks:
- langfuse-net
langfuse-web:
image: langfuse/langfuse:3
networks:
- langfuse-net
networks:
langfuse-net:
driver: bridge
|
7.3 Opik 常见报错与修复
| 错误信息 | 原因 | 解决方案 |
|---|
ConnectionRefusedError | Opik 服务未启动 | docker compose up -d | 401 Unauthorized | API Key 错误 | 重新生成 Key 并更新环境变量 | Project not found | 项目名不匹配 | 确认 OPIK_PROJECT_NAME 与 UI 中一致 | Timeout after 30s | 网络问题或服务过载 | 增加超时、检查资源 |
"""
Opik 连接诊断脚本
"""
import os
from opik import Opik
def diagnose_opik():
"""诊断 Opik 连接问题"""
print("🔍 Opik 连接诊断")
print("-" * 40)
url = os.getenv("OPIK_URL_OVERRIDE", "未设置")
api_key = os.getenv("OPIK_API_KEY", "未设置")
workspace = os.getenv("OPIK_WORKSPACE", "default")
print(f" URL: {url}")
print(f" API Key: {'✅ 已设置' if api_key != '未设置' else '❌ 未设置'}")
print(f" Workspace: {workspace}")
try:
client = Opik(project_name="diagnostic-test")
print(f"\n ✅ 连接成功!")
print(f" 项目: {client.project_name}")
except Exception as e:
print(f"\n ❌ 连接失败: {type(e).__name__}: {e}")
if "Connection" in str(e):
print(" 💡 建议:检查 Opik 服务是否启动")
print(" 运行: docker compose ps")
elif "401" in str(e) or "Unauthorized" in str(e):
print(" 💡 建议:检查 API Key 是否正确")
elif "timeout" in str(e).lower():
print(" 💡 建议:检查网络连接或增加超时时间")
if __name__ == "__main__":
diagnose_opik()
|
7.4 完整排查检查清单(Checklist)
┌─────────────────────────────────────────────────────────┐
│ Agent 可观测性排查 Checklist │
├─────────────────────────────────────────────────────────┤
│ │
│ □ 1. 服务状态检查 │
│ □ Docker 容器全部 Up (healthy) │
│ □ Web UI 可正常访问 │
│ □ /api/public/health 返回 200 │
│ │
│ □ 2. 网络连通性 │
│ □ Agent → Langfuse 端口可达 │
│ □ Agent → Opik 端口可达 │
│ □ DNS 解析正常(非 localhost 场景) │
│ │
│ □ 3. 认证配置 │
│ □ Public Key / Secret Key 正确 │
│ □ API Key 未过期 │
│ □ 项目名/工作空间名匹配 │
│ │
│ □ 4. SDK 配置 │
│ □ 环境变量已加载(load_dotenv) │
│ □ flush() 在退出前被调用 │
│ □ SDK 版本与服务端兼容 │
│ │
│ □ 5. 资源检查 │
│ □ 磁盘空间充足(>1GB) │
│ □ 内存充足(>2GB 可用) │
│ □ ClickHouse / PostgreSQL 连接数未耗尽 │
│ │
│ □ 6. 日志检查 │
│ □ langfuse-worker 日志无 ERROR │
│ □ Agent 应用日志无 SDK 异常 │
│ □ Docker 日志无 OOM Kill │
│ │
└─────────────────────────────────────────────────────────┘
|
八、进阶技巧:自定义评分机制优化 Agent 表现
8.1 Langfuse Score 体系 8.1.1 手动评分与自动评分
Langfuse 支持三种评分方式:
"""
Langfuse 评分示例
"""
from langfuse import Langfuse
langfuse = Langfuse()
langfuse.score(
trace_id="trace-abc-123",
name="answer_relevance",
value=0.92,
comment="回答高度相关,但缺少具体数据",
data_type="NUMERIC"
)
langfuse.score(
trace_id="trace-abc-123",
name="task_status",
value="completed",
data_type="CATEGORICAL"
)
langfuse.score(
trace_id="trace-abc-123",
name="user_satisfied",
value=True,
data_type="BOOLEAN"
)
|
8.1.2 基于 LLM 的自动评估(LLM-as-Judge)
"""
LLM-as-Judge 自动评估流水线
用 GPT-4o 评估 Agent 输出质量
"""
from langfuse import Langfuse, observe
from langfuse.openai import openai
import json
langfuse = Langfuse()
EVALUATION_PROMPT = """
你是一个 AI 输出质量评估专家。请评估以下 Agent 回答的质量。
用户问题:{question}
Agent 回答:{answer}
请从以下维度评分(每个维度 1-5 分):
1. 相关性(relevance):回答是否切题
2. 完整性(completeness):是否覆盖了问题的所有方面
3. 准确性(accuracy):信息是否准确
4. 清晰度(clarity):表达是否清晰易懂
请以 JSON 格式返回:
{{"relevance": <1-5>, "completeness": <1-5>, "accuracy": <1-5>, "clarity": <1-5>, "overall": <1-5>, "reasoning": "<简要说明>"}}
"""
@observe(name="evaluate_response")
def evaluate_agent_output(trace_id: str, question: str, answer: str):
"""
使用 LLM 自动评估 Agent 输出
并将评分写回 Langfuse
"""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": EVALUATION_PROMPT.format(
question=question,
answer=answer
)
}],
temperature=0,
response_format={"type": "json_object"}
)
eval_result = json.loads(response.choices[0].message.content)
for dimension, score_value in eval_result.items():
if dimension != "reasoning" and isinstance(score_value, (int, float)):
langfuse.score(
trace_id=trace_id,
name=f"eval_{dimension}",
value=score_value / 5.0,
comment=eval_result.get("reasoning", ""),
data_type="NUMERIC"
)
return eval_result
def batch_evaluate(limit=50):
"""批量评估最近的 Traces"""
traces = langfuse.fetch_traces(limit=limit, tags=["needs-evaluation"])
for trace in traces.data:
question = trace.input
answer = trace.output
if question and answer:
result = evaluate_agent_output(
trace_id=trace.id,
question=str(question),
answer=str(answer)
)
print(f"✅ 评估完成: {trace.id} → overall={result['overall']}")
langfuse.flush()
|
8.2 Opik 评估框架 8.2.1 内置评估指标
Opik 提供 20+ 开箱即用的评估指标:
"""
Opik 内置评估指标使用示例
"""
from opik.evaluation import evaluate
from opik.evaluation.metrics import (
HallucinationMetric,
AnswerRelevanceMetric,
ContextPrecisionMetric,
ContextRecallMetric,
ModerationMetric,
GEqualsReference,
)
eval_dataset = [
{
"input": "什么是量子计算?",
"output": "量子计算是利用量子力学原理进行计算的技术...",
"expected_output": "量子计算是基于量子比特的计算范式...",
"context": ["量子计算相关资料..."]
},
]
results = evaluate(
dataset=eval_dataset,
metrics=[
HallucinationMetric(),
AnswerRelevanceMetric(),
ContextPrecisionMetric(),
],
project_name="agent-evaluation",
experiment_name="v2.1-baseline"
)
print(f"评估完成!结果已保存到 Opik 项目")
print(f"平均幻觉分数: {results['hallucination']['mean']}")
print(f"平均相关性分数: {results['answer_relevance']['mean']}")
|
8.2.2 自定义评估指标开发
"""
自定义 Opik 评估指标
"""
from opik.evaluation.metrics import BaseMetric, ScoreResult
class AgentEfficiencyMetric(BaseMetric):
"""
自定义指标:Agent 效率评分
评估 Agent 是否用最少步骤完成任务
"""
def __init__(self, max_expected_steps: int = 3):
self.max_expected_steps = max_expected_steps
def score(self, output: str, metadata: dict = None, **kwargs) -> ScoreResult:
"""
计算效率分数
"""
actual_steps = metadata.get("total_steps", 1) if metadata else 1
if actual_steps <= self.max_expected_steps:
score = 1.0
else:
score = self.max_expected_steps / actual_steps
return ScoreResult(
name="agent_efficiency",
value=score,
reason=f"实际 {actual_steps} 步,期望 ≤{self.max_expected_steps} 步"
)
class ToolUsageCorrectness(BaseMetric):
"""
自定义指标:工具使用正确性
检查 Agent 是否选择了正确的工具
"""
def score(self, output: str, input: str, metadata: dict = None, **kwargs) -> ScoreResult:
tools_used = metadata.get("tools_used", []) if metadata else []
expected_tools = metadata.get("expected_tools", []) if metadata else []
if not expected_tools:
return ScoreResult(name="tool_correctness", value=1.0, reason="无预期工具")
correct = sum(1 for t in tools_used if t in expected_tools)
score = correct / len(expected_tools)
return ScoreResult(
name="tool_correctness",
value=score,
reason=f"使用了 {tools_used},期望 {expected_tools}"
)
|
8.3 实战:为客服 Agent 构建质量评分流水线
"""
客服 Agent 质量评分流水线
文件:cs_agent_evaluation_pipeline.py
"""
from langfuse import Langfuse, observe
from opik import track
from opik.evaluation.metrics import HallucinationMetric, AnswerRelevanceMetric
import json
langfuse_client = Langfuse()
@observe(name="cs-evaluation-pipeline")
@track(name="cs-evaluation-pipeline")
def run_cs_evaluation(date_range: str = "last_24h"):
"""
客服 Agent 评估流水线
流程:
1. 拉取指定时间范围的客服对话 Traces
2. 对每条 Trace 运行多维度评估
3. 将评分写回 Langfuse
4. 生成汇总报告
"""
traces = langfuse_client.fetch_traces(
limit=200,
tags=["customer-support"]
)
results_summary = {
"total_evaluated": 0,
"avg_relevance": 0,
"avg_hallucination": 0,
"avg_satisfaction": 0,
"flagged_for_review": []
}
for trace in traces.data:
if not trace.output:
continue
question = str(trace.input) if trace.input else ""
answer = str(trace.output) if trace.output else ""
relevance_score = evaluate_relevance(question, answer)
hallucination_score = detect_hallucination(answer)
langfuse_client.score(
trace_id=trace.id,
name="auto_relevance",
value=relevance_score,
data_type="NUMERIC"
)
langfuse_client.score(
trace_id=trace.id,
name="auto_hallucination",
value=hallucination_score,
data_type="NUMERIC"
)
if relevance_score < 0.5 or hallucination_score > 0.7:
results_summary["flagged_for_review"].append(trace.id)
langfuse_client.score(
trace_id=trace.id,
name="needs_human_review",
value=True,
data_type="BOOLEAN"
)
results_summary["total_evaluated"] += 1
print(json.dumps(results_summary, ensure_ascii=False, indent=2))
langfuse_client.flush()
return results_summary
def evaluate_relevance(question: str, answer: str) -> float:
"""评估回答相关性(0-1)"""
if not question or not answer:
return 0.0
return 0.85
def detect_hallucination(answer: str) -> float:
"""检测幻觉程度(0=无幻觉,1=严重幻觉)"""
return 0.1
if __name__ == "__main__":
run_cs_evaluation()
|
九、生产环境注意事项:隐私脱敏与性能损耗平衡
9.1 数据隐私与合规
9.1.1 敏感信息识别与脱敏策略
在生产环境中,Agent 的输入输出可能包含:
- 用户姓名、电话、邮箱
-
身份证号、银行卡号
-
医疗信息、法律咨询内容
-
商业机密数据
脱敏策略矩阵:
| 数据类型 | 识别方式 | 脱敏方法 | 示例 |
|---|
| 手机号 | 正则 1[3-9]\d{9} | 中间 4 位替换为 * | 138****5678 | | 邮箱 | 正则匹配 @ | 用户名部分掩码 | j***@example.com | | 身份证 | 正则 18 位 | 保留前 3 后 4 | 110***********1234 | | 银行卡 | 正则 16-19 位 | 保留后 4 位 | ************5678 | | 姓名 | NER 模型 | 姓保留,名替换 | 张** |
9.1.2 PII 过滤中间件实现
"""
PII(个人身份信息)过滤中间件
在数据发送到 Langfuse/Opik 之前进行脱敏
文件:pii_filter.py
"""
import re
from typing import Any, Dict
class PIIFilter:
"""
PII 过滤器
在 Trace 数据发送到可观测平台之前进行脱敏处理
"""
PATTERNS = {
"phone": re.compile(r'1[3-9]\d{9}'),
"email": re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'),
"id_card": re.compile(r'\d{17}[\dXx]'),
"bank_card": re.compile(r'\d{16,19}'),
"ip_address": re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
}
def __init__(self, enabled: bool = True, custom_patterns: Dict = None):
self.enabled = enabled
if custom_patterns:
self.PATTERNS.update(custom_patterns)
def sanitize(self, data: Any) -> Any:
"""
递归清洗数据中的 PII
"""
if not self.enabled:
return data
if isinstance(data, str):
return self._sanitize_string(data)
elif isinstance(data, dict):
return {k: self.sanitize(v) for k, v in data.items()}
elif isinstance(data, list):
return [self.sanitize(item) for item in data]
return data
def _sanitize_string(self, text: str) -> str:
"""清洗字符串中的 PII"""
text = self.PATTERNS["phone"].sub(
lambda m: m.group()[:3] + "****" + m.group()[7:], text
)
text = self.PATTERNS["email"].sub(
lambda m: m.group()[0] + "***@" + m.group().split("@")[1], text
)
text = self.PATTERNS["id_card"].sub(
lambda m: m.group()[:3] + "*" * 11 + m.group()[-4:], text
)
text = self.PATTERNS["bank_card"].sub(
lambda m: "*" * (len(m.group()) - 4) + m.group()[-4:], text
)
return text
pii_filter = PIIFilter(enabled=True)
original_data = {
"user_query": "我的手机号是13812345678,请帮我查询订单",
"user_email": "zhangsan@example.com",
"context": "用户身份证:110101199001011234"
}
sanitized_data = pii_filter.sanitize(original_data)
print(sanitized_data)
|
集成到追踪流程 :
from langfuse import observe, langfuse_context
from pii_filter import PIIFilter
pii_filter = PIIFilter(enabled=True)
@observe()
def traced_agent(user_input: str) -> str:
"""带 PII 过滤的 Agent 追踪"""
sanitized_input = pii_filter.sanitize(user_input)
langfuse_context.update_current_observation(
input=sanitized_input
)
result = process_query(user_input)
sanitized_output = pii_filter.sanitize(result)
langfuse_context.update_current_observation(
output=sanitized_output
)
return result
|
9.2 性能损耗控制 9.2.1 异步上报与批量发送
Langfuse 和 Opik SDK 默认使用异步批量发送,不会阻塞主业务逻辑:
"""
性能优化配置
"""
import os
os.environ["LANGFUSE_FLUSH_AT"] = "50"
os.environ["LANGFUSE_FLUSH_INTERVAL"] = "5"
os.environ["OPIK_BATCH_SIZE"] = "100"
os.environ["OPIK_FLUSH_INTERVAL"] = "3"
|
9.2.2 采样策略配置
在高流量场景下,不需要记录每一条 Trace:
"""
采样策略:只记录部分请求
"""
import random
from langfuse import observe, langfuse_context
SAMPLE_RATE = 0.1
@observe()
def agent_with_sampling(user_input: str) -> str:
"""带采样的 Agent"""
should_trace = random.random() < SAMPLE_RATE
if not should_trace:
langfuse_context.update_current_trace(
metadata={"sampled": False}
)
result = process_query(user_input)
if should_trace:
langfuse_context.update_current_trace(
metadata={"sampled": True, "sample_rate": SAMPLE_RATE}
)
return result
|
9.2.3 资源占用基准测试
| 场景 | CPU 增量 | 内存增量 | 网络带宽 | 延迟影响 |
|---|
| 无追踪 | 基准 | 基准 | 基准 | 基准 | | Langfuse(默认) | +2-3% | +15-25MB | ~1KB/trace | <1ms | | Opik(默认) | +2-3% | +10-20MB | ~1KB/trace | <1ms | | 双平台同时 | +4-5% | +30-40MB | ~2KB/trace | <2ms |
结论:在默认配置下,双平台追踪的性能损耗极小(<5% CPU,<2ms 延迟),对绝大多数生产场景可忽略不计。
9.3 高可用部署架构建议
生产环境推荐架构:
┌─────────────┐
│ Nginx │ ← 负载均衡 + SSL
│ (反向代理) │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌────────┴───┐ ┌─────┴─────┐ ┌───┴────────┐
│langfuse-web│ │langfuse-web│ │langfuse-web│ ← 多副本
│ (实例 1) │ │ (实例 2) │ │ (实例 3) │
└────────────┘ └───────────┘ └────────────┘
│ │ │
┌────────┴────────────┴────────────┴────────┐
│ PostgreSQL (主从) │
│ ClickHouse (集群) │
│ Redis (Sentinel) │
└───────────────────────────────────────────┘
|
十、构建闭环:基于观测数据迭代提示词工程
10.1 从 Trace 数据中提取优化信号
| 信号 | 含义 | 优化方向 |
|---|
| 高延迟 Trace | Agent 推理步骤过多 | 优化 Prompt 减少不必要步骤 | | 工具调用失败 | 工具参数生成错误 | 优化工具描述或 Few-shot 示例 | | 重复调用模式 | Agent 陷入循环 | 添加防循环指令 | | 低相关性评分 | 回答偏离主题 | 优化系统提示词 | | 高幻觉评分 | Agent 编造信息 | 添加"不确定时说明"指令 | | Token 消耗异常 | Prompt 过长或冗余 | 精简 Prompt、减少上下文 |
10.2 Langfuse Prompt Management 版本管理
"""
使用 Langfuse 管理 Prompt 版本
"""
from langfuse import Langfuse
langfuse = Langfuse()
langfuse.create_prompt(
name="customer-support-agent",
prompt="你是一个客服助手。请回答用户的问题。",
labels=["v1"],
config={
"model": "gpt-4o",
"temperature": 0.7,
"max_tokens": 1000
}
)
langfuse.create_prompt(
name="customer-support-agent",
prompt="""你是一个专业的客服助手。
规则:
1. 先确认理解用户问题,再给出解答
2. 如果不确定答案,明确告知用户并建议转人工
3. 回答简洁,不超过 200 字
4. 不要编造政策或承诺
5. 如果用户情绪激动,先表示理解再解决问题
禁止:
- 不要重复用户的完整问题
- 不要使用"作为AI"等措辞
- 不要一次给出超过 3 个建议
""",
labels=["v2", "production"],
config={
"model": "gpt-4o",
"temperature": 0.3,
"max_tokens": 500
}
)
@observe()
def agent_with_managed_prompt(user_query: str) -> str:
"""使用 Langfuse 管理的 Prompt"""
prompt = langfuse.get_prompt(
name="customer-support-agent",
label="production"
)
system_message = prompt.prompt
config = prompt.config
response = openai.chat.completions.create(
model=config.get("model", "gpt-4o"),
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": user_query}
],
temperature=config.get("temperature", 0.7),
max_tokens=config.get("max_tokens", 1000)
)
return response.choices[0].message.content
|
10.3 A/B 测试与效果对比
"""
Prompt A/B 测试框架
"""
from langfuse import Langfuse, observe
import random
langfuse = Langfuse()
@observe()
def agent_with_ab_test(user_query: str, user_id: str) -> str:
"""
A/B 测试:50% 用户使用 v1 Prompt,50% 使用 v2
"""
group = "A" if hash(user_id) % 2 == 0 else "B"
prompt_label = "v1" if group == "A" else "v2"
prompt = langfuse.get_prompt(
name="customer-support-agent",
label=prompt_label
)
langfuse_context.update_current_trace(
metadata={
"ab_group": group,
"prompt_version": prompt_label
},
tags=[f"ab-test", f"group-{group}"]
)
response = openai.chat.completions.create(
model=prompt.config.get("model", "gpt-4o"),
messages=[
{"role": "system", "content": prompt.prompt},
{"role": "user", "content": user_query}
]
)
return response.choices[0].message.content
def analyze_ab_results():
"""分析 A/B 测试数据"""
traces_a = langfuse.fetch_traces(tags=["group-A"], limit=500)
traces_b = langfuse.fetch_traces(tags=["group-B"], limit=500)
scores_a = [t.scores for t in traces_a.data if t.scores]
scores_b = [t.scores for t in traces_b.data if t.scores]
print(f"Group A (v1): {len(traces_a.data)} traces")
print(f"Group B (v2): {len(traces_b.data)} traces")
|
10.4 实战:一轮完整的提示词优化迭代 场景 :客服 Agent 的"工具调用失败率"从观测数据中发现偏高(12%)。
迭代流程 :
┌─────────────────────────────────────────────────────────────┐
│ 第 1 步:观测发现问题 │
│ • Langfuse Dashboard 显示 tool_call_error 占比 12% │
│ • 主要集中在 "order_lookup" 工具 │
├─────────────────────────────────────────────────────────────┤
│ 第 2 步:Trace 分析定位根因 │
│ • 查看失败 Trace 的 LLM 输出 │
│ • 发现 Agent 生成的参数格式错误: │
│ 期望 {"order_id": "ORD-12345"} │
│ 实际 {"order_id": 12345}(数字而非字符串) │
├─────────────────────────────────────────────────────────────┤
│ 第 3 步:修改 Prompt │
│ • 在工具描述中增加参数格式示例 │
│ • 添加 "order_id 必须是字符串格式,如 'ORD-12345'" │
├─────────────────────────────────────────────────────────────┤
│ 第 4 步:A/B 测试验证 │
│ • 50% 流量使用新 Prompt │
│ • 运行 3 天收集数据 │
├─────────────────────────────────────────────────────────────┤
│ 第 5 步:评估效果 │
│ • 工具调用失败率从 12% 降至 2.3% │
│ • 整体用户满意度评分提升 0.15 │
├─────────────────────────────────────────────────────────────┤
│ 第 6 步:全量发布 │
│ • 将新 Prompt 标记为 production │
│ • 持续监控 1 周确认稳定 │
└─────────────────────────────────────────────────────────────┘
|
10.5 构建持续改进的 DevOps 流水线
"""
提示词迭代 CI/CD 流水线(概念代码)
文件:prompt_ci_cd.py
"""
def prompt_optimization_pipeline():
"""
提示词优化流水线
建议每周执行一次
"""
print("📊 阶段 1:收集过去一周的观测数据...")
weekly_traces = fetch_weekly_traces()
print("🔍 阶段 2:识别 Top 5 问题模式...")
issues = identify_top_issues(weekly_traces)
for issue in issues:
print(f" - {issue['type']}: {issue['count']} 次, 影响 {issue['impact']}")
print("💡 阶段 3:生成 Prompt 优化建议...")
suggestions = generate_prompt_suggestions(issues)
print("🧪 阶段 4:在评估数据集上测试新 Prompt...")
eval_results = evaluate_new_prompt(suggestions)
if eval_results["improvement"] > 0.05:
print("✅ 阶段 5:效果显著提升,建议发布新版本")
publish_new_prompt(suggestions)
else:
print("⚠️ 阶段 5:提升不显著,继续观察")
print("\n🎉 流水线执行完毕!")
|
十一、常见陷阱与问题排除汇总
陷阱 1:装饰器顺序错误导致追踪丢失
@opik_track
@langfuse_observe()
def my_function():
...
@langfuse_observe()
@opik_track
def my_function():
...
|
陷阱 2:异步函数中追踪上下文丢失
import asyncio
@observe()
async def async_agent(query: str):
result = await some_async_call()
return result
from langfuse import observe
@observe()
async def async_agent(query: str):
result = await some_async_call()
return result
|
陷阱 3:环境变量加载顺序问题
from langfuse import Langfuse
from dotenv import load_dotenv
load_dotenv()
from dotenv import load_dotenv
load_dotenv()
from langfuse import Langfuse, observe
|
陷阱 4:Docker 容器内访问宿主机服务
LANGFUSE_HOST = "http://localhost:3000"
LANGFUSE_HOST = "http://host.docker.internal:3000"
LANGFUSE_HOST = "http://172.17.0.1:3000"
LANGFUSE_HOST = "http://langfuse-web:3000"
|
陷阱 5:大量 Trace 导致内存溢出
traces = langfuse.fetch_traces(limit=100000)
page = 1
while True:
traces = langfuse.fetch_traces(limit=100, page=page)
if not traces.data:
break
process(traces.data)
page += 1
|
陷阱 6:ClickHouse 磁盘空间不足
docker exec clickhouse df -h
docker exec clickhouse clickhouse-client --query "
ALTER TABLE traces DELETE WHERE timestamp < now() - INTERVAL 30 DAY
"
|
陷阱 7:Opik 与 Langfuse 的 Trace ID 不一致
两个平台生成独立的 Trace ID,无法直接关联。解决方案:
"""
使用统一的 request_id 关联双平台数据
"""
import uuid
from langfuse import observe, langfuse_context
from opik import track, opik_context
@observe()
@track
def unified_agent(query: str) -> str:
request_id = str(uuid.uuid4())
langfuse_context.update_current_trace(
metadata={"unified_request_id": request_id}
)
opik_context.update_current_trace(
metadata={"unified_request_id": request_id}
)
return result
|
十二、总结
本文完整介绍了如何使用 Langfuse 和 Opik 双平台构建 AI Agent 的可观测性体系。让我们回顾核心要点: 核心收获
1. 可观测性是 Agent 工程的基石:没有观测能力的 Agent 如同盲飞,无法调试、无法优化、无法保证质量。
2. Langfuse 擅长开发调试与 Prompt 管理:
- @observe() 装饰器实现零侵入追踪
-
Trace 树状视图直观展示推理链路
-
Prompt 版本管理 + A/B 测试支持持续迭代
3. Opik 擅长生产监控与自动评估:
- @track 装饰器 + 框架自动集成
-
20+ 内置评估指标
-
Agent Optimizer 自动优化能力
4. 双平台互补形成完整闭环:
- 开发阶段用 Langfuse 调试
-
生产阶段用 Opik 监控
-
评估数据驱动 Prompt 迭代
5. 生产环境必须关注:
PII 脱敏(合规要求)
-
性能损耗控制(<5% 可接受)
-
数据保留策略(磁盘管理)
实施路线图
第 1 周:基础搭建
├── 部署 Langfuse 服务端
├── 安装 Opik SDK
└── 在现有 Agent 中嵌入基础追踪
第 2 周:深度集成
├── 完善嵌套追踪(工具调用、多步推理)
├── 配置关键指标监控
└── 建立每日运行报告
第 3 周:评估体系
├── 构建 LLM-as-Judge 评估流水线
├── 配置 Opik 自动评估指标
└── 建立质量基线
第 4 周:闭环优化
├── 基于观测数据优化 Prompt
├── A/B 测试验证效果
└── 建立持续迭代机制
|
十三、详细参考资料
官方文档
| 资源 | 链接 |
|---|
| Langfuse 官方文档 | https://langfuse.com/docs | | Langfuse GitHub | https://github.com/langfuse/langfuse | | Langfuse Python SDK | https://pypi.org/project/langfuse/ | | Opik 官方文档 | https://www.comet.com/docs/opik/ | | Opik GitHub | https://github.com/comet-ml/opik | | Opik Python SDK | https://pypi.org/project/opik/ | | Docker Compose 文档 | https://docs.docker.com/compose/ |
社区资源
| 资源 | 链接 |
|---|
| Langfuse 官方文档 | https://langfuse.com/docs | | Langfuse GitHub | https://github.com/langfuse/langfuse | | Langfuse Python SDK | https://pypi.org/project/langfuse/ | | Opik 官方文档 | https://www.comet.com/docs/opik/ | | Opik GitHub | https://github.com/comet-ml/opik | | Opik Python SDK | https://pypi.org/project/opik/ | | Docker Compose 文档 | https://docs.docker.com/compose/ |
相关工具对比
| 资源 | 链接 |
|---|
| Langfuse 官方文档 | https://langfuse.com/docs | | Langfuse GitHub | https://github.com/langfuse/langfuse | | Langfuse Python SDK | https://pypi.org/project/langfuse/ | | Opik 官方文档 | https://www.comet.com/docs/opik/ | | Opik GitHub | https://github.com/comet-ml/opik | | Opik Python SDK | https://pypi.org/project/opik/ | | Docker Compose 文档 | https://docs.docker.com/compose/ |
版本兼容性参考
| Langfuse 版本 | 架构 | 依赖 | 说明 |
|---|
| v2.x | 单体 | PostgreSQL | 简单部署,适合小规模 | | v3.x | 微服务 | PostgreSQL + ClickHouse + Redis + MinIO | 生产推荐,高性能 |
| Opik 版本 | 主要特性 |
|---|
| v1.x | 基础追踪 + 评估 | | v2.0 | Agent Optimizer、Guardrails | | v2.1+ | 40M+/天追踪能力、20+ 内置指标 |
附录
附录 A:完整 docker-compose.yml 配置文件(Langfuse v3)
version: "3.9"
services:
langfuse-web:
image: langfuse/langfuse:3
container_name: langfuse-web
restart: unless-stopped
depends_on:
db:
condition: service_healthy
clickhouse:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres
DIRECT_URL: postgresql://postgres:postgres@db:5432/postgres
CLICKHOUSE_URL: http://clickhouse:8123
CLICKHOUSE_MIGRATION_URL: clickhouse://clickhouse:9000
CLICKHOUSE_USER: default
CLICKHOUSE_PASSWORD: clickhouse
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_AUTH: redis
MINIO_ENDPOINT: minio
MINIO_PORT: "9000"
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: miniosecret
MINIO_BUCKET: langfuse
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-change-me-in-production}
SALT: ${SALT:-change-me-in-production}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000}
NEXTAUTH_URL: http://localhost:3000
TELEMETRY_ENABLED: "false"
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: "true"
networks:
- langfuse-net
langfuse-worker:
image: langfuse/langfuse-worker:3
container_name: langfuse-worker
restart: unless-stopped
depends_on:
db:
condition: service_healthy
clickhouse:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres
DIRECT_URL: postgresql://postgres:postgres@db:5432/postgres
CLICKHOUSE_URL: http://clickhouse:8123
CLICKHOUSE_MIGRATION_URL: clickhouse://clickhouse:9000
CLICKHOUSE_USER: default
CLICKHOUSE_PASSWORD: clickhouse
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_AUTH: redis
MINIO_ENDPOINT: minio
MINIO_PORT: "9000"
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: miniosecret
MINIO_BUCKET: langfuse
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-change-me-in-production}
SALT: ${SALT:-change-me-in-production}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000}
networks:
- langfuse-net
db:
image: postgres:16-alpine
container_name: langfuse-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- langfuse-net
clickhouse:
image: clickhouse/clickhouse-server:24
container_name: langfuse-clickhouse
restart: unless-stopped
environment:
CLICKHOUSE_USER: default
CLICKHOUSE_PASSWORD: clickhouse
volumes:
- clickhouse_data:/var/lib/clickhouse
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8123/ping"]
interval: 5s
timeout: 5s
retries: 5
networks:
- langfuse-net
redis:
image: redis:7-alpine
container_name: langfuse-redis
restart: unless-stopped
command: redis-server --requirepass redis
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis", "ping"]
interval: 5s
timeout: 5s
retries: 5
networks:
- langfuse-net
minio:
image: minio/minio:latest
container_name: langfuse-minio
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: miniosecret
volumes:
- minio_data:/data
ports:
- "9090:9000"
- "9091:9001"
networks:
- langfuse-net
volumes:
postgres_data:
clickhouse_data:
redis_data:
minio_data:
networks:
langfuse-net:
driver: bridge
|
附录 B:完整 Agent 追踪代码示例
"""
============================================================
完整示例:带双平台追踪的 ReAct Agent
文件:complete_agent_example.py
运行前准备:
1. 安装依赖:pip install langfuse opik openai python-dotenv
2. 创建 .env 文件(见附录 C)
3. 确保 Langfuse 和 Opik 服务已启动
运行:python complete_agent_example.py
============================================================
"""
import os
import json
import time
import uuid
from typing import Optional, Dict, Any
from dotenv import load_dotenv
load_dotenv()
from langfuse import observe as lf_observe, langfuse_context
from langfuse.openai import openai
from opik import track as opik_track, opik_context
import re
class SimplePIIFilter:
"""简易 PII 过滤器"""
PATTERNS = {
"phone": (re.compile(r'1[3-9]\d{9}'), lambda m: m.group()[:3] + "****" + m.group()[7:]),
"email": (re.compile(r'[\w.+-]+@[\w-]+\.[\w.]+'), lambda m: m.group()[0] + "***@" + m.group().split("@")[1]),
}
def sanitize(self, text: str) -> str:
if not isinstance(text, str):
return text
for pattern, replacer in self.PATTERNS.values():
text = pattern.sub(replacer, text)
return text
pii_filter = SimplePIIFilter()
AVAILABLE_TOOLS = [
{
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "在知识库中搜索相关信息。当用户询问事实性问题时使用。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索查询,必须是字符串格式"
},
"top_k": {
"type": "integer",
"description": "返回结果数量,默认 3",
"default": 3
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算。当用户需要数值计算时使用。",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如 '(15 + 27) * 3'"
}
},
"required": ["expression"]
}
}
}
]
@lf_observe(name="tool:search_knowledge_base")
@opik_track(name="tool:search_knowledge_base")
def search_knowledge_base(query: str, top_k: int = 3) -> str:
"""知识库搜索工具"""
time.sleep(0.2)
results = [
{
"title": f"文档 {i+1}:关于 {query} 的说明",
"content": f"这是关于 '{query}' 的第 {i+1} 条相关知识...",
"relevance_score": round(0.9 - i * 0.1, 2)
}
for i in range(top_k)
]
return json.dumps({"status": "success", "results": results}, ensure_ascii=False)
@lf_observe(name="tool:calculate")
@opik_track(name="tool:calculate")
def calculate(expression: str) -> str:
"""数学计算工具"""
try:
allowed_chars = set("0123456789+-*/.() ")
if not all(c in allowed_chars for c in expression):
return json.dumps({"status": "error", "message": "包含不允许的字符"})
result = eval(expression, {"__builtins__": {}}, {})
return json.dumps({"status": "success", "result": result})
except Exception as e:
return json.dumps({"status": "error", "message": str(e)})
TOOL_REGISTRY = {
"search_knowledge_base": search_knowledge_base,
"calculate": calculate,
}
@lf_observe(name="react-agent")
@opik_track(name="react-agent")
def react_agent(
user_query: str,
user_id: str = "anonymous",
session_id: Optional[str] = None,
max_steps: int = 5,
model: str = "gpt-4o"
) -> Dict[str, Any]:
"""
ReAct Agent 主函数(带完整双平台追踪)
Args:
user_query: 用户输入
user_id: 用户标识
session_id: 会话 ID(多轮对话)
max_steps: 最大推理步数
model: LLM 模型名称
Returns:
包含 answer、metadata 的字典
"""
request_id = str(uuid.uuid4())
langfuse_context.update_current_trace(
name=f"agent: {user_query[:50]}",
user_id=user_id,
session_id=session_id,
tags=["react-agent", "production"],
metadata={
"unified_request_id": request_id,
"model": model,
"max_steps": max_steps,
"agent_version": "2.0.0"
}
)
opik_context.update_current_trace(
tags=["react-agent", "production"],
metadata={"unified_request_id": request_id}
)
sanitized_query = pii_filter.sanitize(user_query)
system_prompt = """你是一个智能助手 Agent。你可以使用工具来帮助用户。
规则:
1. 分析用户意图,决定是否需要使用工具
2. 如果需要工具,一次只调用一个工具
3. 根据工具返回结果决定下一步
4. 不要以相同参数重复调用同一工具
5. 最多使用 {max_steps} 步完成任务
6. 如果无法完成,诚实告知用户""".format(max_steps=max_steps)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_query}
]
steps_taken = 0
tools_used = []
for step in range(max_steps):
steps_taken += 1
response = openai.chat.completions.create(
model=model,
messages=messages,
tools=AVAILABLE_TOOLS,
tool_choice="auto",
temperature=0.1,
max_tokens=2000
)
assistant_msg = response.choices[0].message
if assistant_msg.tool_calls:
messages.append(assistant_msg)
for tool_call in assistant_msg.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
tools_used.append(func_name)
tool_func = TOOL_REGISTRY.get(func_name)
if tool_func:
tool_result = tool_func(**func_args)
else:
tool_result = json.dumps({
"status": "error",
"message": f"未知工具: {func_name}"
})
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result
})
else:
final_answer = assistant_msg.content
langfuse_context.update_current_trace(
output=pii_filter.sanitize(final_answer),
metadata={
"total_steps": steps_taken,
"tools_used": tools_used
}
)
return {
"answer": final_answer,
"metadata": {
"request_id": request_id,
"steps": steps_taken,
"tools_used": tools_used,
"model": model
}
}
fallback_answer = "抱歉,我在限定步数内未能完成此任务。请尝试简化问题或联系人工支持。"
langfuse_context.update_current_trace(
output=fallback_answer,
metadata={"total_steps": steps_taken, "hit_limit": True}
)
return {
"answer": fallback_answer,
"metadata": {
"request_id": request_id,
"steps": steps_taken,
"hit_limit": True
}
}
if __name__ == "__main__":
print("=" * 60)
print("🤖 AI Agent 可观测性实战演示")
print("=" * 60)
test_queries = [
"帮我计算 (15 + 27) * 3 等于多少",
"搜索一下关于 Python 异步编程的最佳实践",
"我的手机号是 13812345678,帮我查一下账户余额",
]
session_id = f"session-{uuid.uuid4().hex[:8]}"
for i, query in enumerate(test_queries):
print(f"\n{'─'*60}")
print(f"📝 查询 {i+1}: {query}")
print(f"{'─'*60}")
result = react_agent(
user_query=query,
user_id="demo-user",
session_id=session_id
)
print(f"✅ 回答: {result['answer'][:100]}...")
print(f"📊 元数据: 步数={result['metadata']['steps']}, "
f"工具={result['metadata'].get('tools_used', [])}")
langfuse_context.flush()
print(f"\n{'='*60}")
print("✅ 所有 Trace 已发送到 Langfuse 和 Opik")
print(f"📍 Langfuse: http://localhost:3000")
print(f"📍 Opik: http://localhost:5173")
print(f"{'='*60}")
|
附录 C:环境变量速查表
LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
LANGFUSE_HOST=http://localhost:3000
LANGFUSE_FLUSH_AT=50
LANGFUSE_FLUSH_INTERVAL=5
OPIK_URL_OVERRIDE=http://localhost:5173/api
OPIK_API_KEY=your-opik-api-key
OPIK_WORKSPACE=default
OPIK_PROJECT_NAME=my-agent-project
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.openai.com/v1
USER_ID=demo-user
AGENT_ENV=development
LOG_LEVEL=INFO
TRACE_SAMPLE_RATE=1.0
|
附录 D:常用 CLI 命令速查
cd ~/langfuse-deploy && docker compose up -d
docker compose down
docker compose restart langfuse-web
docker compose ps
docker compose logs -f langfuse-web
docker compose logs -f langfuse-worker
docker compose logs -f clickhouse
docker compose logs --tail=100 langfuse-web
docker exec -it langfuse-web /bin/sh
docker exec -it langfuse-clickhouse clickhouse-client
docker compose down -v
docker compose up -d --build
curl -s http://localhost:3000/api/public/health | jq
curl -s http://localhost:8123/ping
docker exec langfuse-redis redis-cli -a redis ping
docker exec langfuse-db pg_isready -U postgres
docker exec langfuse-clickhouse clickhouse-client --query "
SELECT table, formatReadableSize(sum(bytes)) as size
FROM system.parts
WHERE database = 'default'
GROUP BY table
ORDER BY sum(bytes) DESC
"
docker exec langfuse-db psql -U postgres -c "
SELECT pg_size_pretty(pg_database_size('postgres'));
"
docker exec langfuse-db pg_dump -U postgres postgres > backup_$(date +%Y%m%d).sql
python -c "
from langfuse import Langfuse
lf = Langfuse()
print('✅ Langfuse 连接成功')
print(f' Host: {lf.base_url}')
"
python -c "
from opik import Opik
client = Opik(project_name='test')
print('✅ Opik 连接成功')
"
pip show langfuse opik
pip install --upgrade langfuse opik
sudo firewall-cmd --list-all
getenforce
docker system df
docker system prune -f
|
📌 最后提醒 :可观测性不是一次性配置,而是持续运营的过程。建议团队每周花 30 分钟回顾 Trace 数据,每月进行一次 Prompt 优化迭代。只有将观测→分析→优化→验证形成闭环,你的 AI Agent 才能持续进化。
|