Observability using Langfuse


title: “使用 Langfuse 实现可观测性” weight: 300


在本模块中,我们将把 agent 连接到 Langfuse ,以追踪每一次 LLM 调用、工具调用和 agent 决策。Langfuse 已经在我们的集群上运行,因此我们只需要接入即可。

什么是 Langfuse?

Langfuse 是一个开源的 LLM 可观测性平台。它摄取 OpenTelemetry span 并将其呈现为结构化的追踪:即单个请求中每一次 LLM 调用、工具调用和 agent 决策的层次化视图。可以把它想象成 Jaeger 或 Datadog APM,但它是专为 LLM 应用打造的:它开箱即用地理解 token 计数、prompt/completion 配对、工具调用边界以及成本归因。

为什么在本次研讨会中使用 Langfuse?

  • 完全运行在 EKS 上:无需外部 SaaS 依赖。Helm chart 会在 langfuse 命名空间中以 pod 的形式部署 Langfuse web、worker、PostgreSQL、ClickHouse 和 Redis。我们的追踪数据永远不会离开集群。
  • 原生 OTel 摄取:Strands 通过 [otel] extra 发出 OpenTelemetry span。Langfuse 通过其兼容 OTel 的端点消费这些数据,无需任何自定义埋点。
  • LiteLLM 集成:proxy 将其自身的 span 转发到同一个 Langfuse 项目中,因此我们可以在一个追踪树中同时看到 agent 级别和 proxy 级别的延迟。
  • 跨轨道共享:自管理和集成的 agent 都写入同一个 AnyCompany Shop 项目。我们可以按模型标签(qwen2-5-3b-neuronnova-lite)进行筛选,从而并排比较后端。

为什么可观测性对 agent 很重要

没有追踪,agent 就是一个黑盒。当客户得到错误答案,或者 agent 需要 10 秒才能响应时,我们想知道:

  • 单个查询涉及了多少次 LLM 调用?
  • agent 是调用了正确的工具,还是幻觉出了一个不存在的工具?
  • 延迟出在哪里:模型、工具还是网络?
  • 我们到底向 LLM 发送了什么?

Langfuse 会捕获追踪(traces):即 agent 所做的一切的层次化视图。

架构

  1. Agent 通过 LiteLLM → vLLM 向 Qwen2.5-3B 发送查询
  2. Strands SDK 为每一次 LLM 调用和工具调用发出 OpenTelemetry span
  3. LiteLLM 也将其自身的 proxy 级别 span 转发到同一个 Langfuse 项目中
  4. Langfuse 摄取 OTel span 并将其呈现为结构化的追踪
  5. 我们在 Langfuse UI 中探索追踪、延迟和 token 使用情况

Langfuse 如何与 Strands 集成

使用 [otel] extra,strands-agents 会自动为 agent 循环、LLM 调用和工具执行发出 OTel span。Langfuse 通过其兼容 OTel 的端点摄取这些数据,无需手动埋点。

步骤 1:验证 Langfuse 正在运行

kubectl get pods -n langfuse

所有 pod 都应处于 Running 状态。web pod 可能有 1-2 次重启,这在初次数据库迁移期间是正常的。

步骤 2:访问 Langfuse UI

echo "Langfuse UI: http://$(kubectl get ingress -n langfuse langfuse -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
字段
Email admin@workshop.local
Password workshop2025

AnyCompany Shop 项目和 API 密钥(pk-lf-workshop / sk-lf-workshop)已经预先配置好了。

步骤 3:代码讲解

cd ~/environment/modules/20-self-managed/300-observability-langfuse/customer-agent

其结构与 使用 Strands 的 Agents 实验 相似,只是新增了两项:在文件顶部初始化的 Langfuse 客户端,以及在末尾调用的 flush() 以确保所有追踪都被导出。tools.py 保持不变。

langfuse = get_client()
if langfuse.auth_check():
    print("Langfuse connected successfully")
else:
    print("WARNING: Langfuse authentication failed - traces will not be captured")

# ... agent construction ...

langfuse.flush()
  • get_client() 从环境变量中获取 LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_BASE_URL。这三项都存放在 agent-config 中。
  • auth_check() 将静默的 OTel 认证失败转变为明显的错误提示。
  • 在每次 /chat 请求之后调用 flush(),会在回复返回之前推送待处理的 span。

strands-agents[openai,otel]
langfuse

otel extra 就是让 Strands 为 agent 循环、LLM 调用和工具执行发出 OpenTelemetry span 的关键。Langfuse 通过其 OTel 端点摄取这些数据,无需手动埋点。

步骤 4:部署(镜像已构建)

customer-agent:langfuse 镜像在研讨会预置期间已经预先构建并推送到 ECR,因此我们可以直接部署:

cd ~/environment/modules/20-self-managed/300-observability-langfuse/customer-agent
envsubst < k8s.yaml | kubectl apply -f -
kubectl rollout status deployment/customer-agent --timeout=120s

:langfuse 是本模块的标签。Apply 会获取镜像引用并自行滚动更新 Deployment。

步骤 5:聊天与追踪

打开聊天 UI,选择 Customer Agent (Self-managed GenAI),询问某个订单。

Where is my order ORD-12345?
Trace: "Where is my order ORD-12345?"
├── Agent Loop
│   ├── LLM Call (qwen2-5-3b-neuron) — tool call decision
│   ├── Tool: lookup_order — ~2ms
│   └── LLM Call (qwen2-5-3b-neuron) — final response
└── Total: ~2s, 2 LLM calls, 1 tool call

然后切换到 Langfuse 控制台,从左侧导航面板中选择 Tracing 并选择该追踪。

我们构建了什么

每一次 LLM 调用、工具调用和决策都被捕获了。所有这一切都运行在集群上,无需外部服务。

下一步

agent 目前还没有产品知识,它只能查询订单。接下来我们将接入 Milvus 来提供产品目录和 FAQ。