引言
可观测性是可靠 LLM 应用的核心组成部分。我们已经看到,Langfuse 提供了一个专门构建的平台,用于深入了解 prompt、模型和响应在生产环境中的行为。本章介绍 Langfuse 的可观测性能力,解释如何收集、分析并基于延迟、token 使用量、成本等指标采取行动。本章也将为后续的 prompt 管理和评估实践奠定基础,并包含多个入门代码示例。
结构
本章将覆盖以下主题:
- 理解 Langfuse 中的可观测性数据模型
- 使用 Python SDK 向 Langfuse 发送第一条 Trace
- Langfuse 用户界面
- Token 使用量与成本追踪
- Langfuse 中的核心可观测性功能
- Sessions
- 用户追踪
- Environments
- Tags 与 Metadata
- Langfuse 中的高级可观测性功能
- Observation 类型
- 日志级别
- 版本与发布
- Trace URLs
- Sampling
- 分布式追踪
- 在 Trace 中屏蔽敏感数据
理解 Langfuse 中的可观测性数据模型
在开始向 Langfuse 发送数据之前,让我们先快速回顾一下 Langfuse 语境下可观测性的基础。可观测性数据模型是 Langfuse 用来表示和组织 LLM 应用中所有可追踪事件与工作流的核心结构。它受到 OpenTelemetry 启发,但被扩展为支持 LLM 特有的结构和用例。
Langfuse SDK 基于 OpenTelemetry。OpenTelemetry 是一个开源可观测性框架,提供了一种标准化方式,用于从软件系统中收集和导出遥测数据,例如 trace、metrics 和 logs。它通过提供通用数据模型和 API,帮助开发者对代码进行埋点,并分析分布式系统中的性能,从而理解应用如何运行。
下面是该数据模型中的主要组件,以及它们之间的关系:
| 组件 | 描述 | 角色 / 目的 |
|---|---|---|
| Trace | 表示单个操作或请求的顶层记录。 | 捕获该操作的完整生命周期,从输入 → 内部步骤 → 输出 + 元数据。 |
| Observation | Trace 内部更细粒度的记录,表示某个步骤、事件或子操作,包括调用工具、调用 LLM、检索等。在传统可观测性中,这对应 span。一个 Trace 内部可以嵌套多个 observation。 | 让我们能够把 trace 内部“发生了什么”拆解为有意义的片段。 |
| Sessions | 可选的更高层级分组,可将多个 trace 聚合在一起,例如一个对话 session。 | 对多轮聊天或工作流很有用,使我们可以在一个地方看到某次用户交互相关的所有 trace。 |
| Scores | 可以附着在 trace、session 或 dataset run 上的灵活 “score” 对象。 | 用于捕获评估结果,包括数值型、类别型、布尔型,并可选择性链接到特定 observation。 |
表 5.1:Langfuse 可观测性数据模型
除了通用 span / event 之外,Langfuse 还支持专门类型的 observation。例如 generation、agent、tool、chain、retriever、embedding、guardrail 等等。这些类型允许捕获 LLM 工作流中的领域特定数据,例如 prompt、token、cost。我们会在本章后面看到,向 Langfuse 发送 trace 时如何指定 observation 类型。
Langfuse 通过计算 Billable Units 来追踪使用量,也就是计费使用量。
Billable Units = Traces + Observations + Scores
如果我们使用 Langfuse Cloud,套餐使用量很大程度上取决于我们摄入了多少 trace、observation 和 score。
使用 Python SDK 向 Langfuse 发送第一条 Trace
现在我们已经准备好开始向 Langfuse 发送第一条 trace。作为接下来代码示例以及本书后续大部分内容的前置条件,请确保完成以下步骤:
设置 Langfuse Cloud 账号,从 Hobby 账号开始,或者自托管 Langfuse。可以参考第 4 章《Langfuse 导论》中 “Langfuse 入门” 一节。
设置 Python 环境。可以参考第 1 章《大语言模型与监控导论》中 “探索本书使用的 LLM 工具集” 一节,了解如何完成。
在 Langfuse 上创建一个新项目。可以参考第 4 章《Langfuse 导论》中 “Langfuse 入门” 一节。无论使用 Cloud 还是自托管版本,这一步都是一样的。在 .env 文件中添加以下环境变量:
LANGFUSE_SECRET_KEY = "sk-lf-…."
LANGFUSE_PUBLIC_KEY = "pk-lf-…."
LANGFUSE_HOST = "https://cloud.langfuse.com" # 如果使用 EU 区域
# LANGFUSE_HOST = "https://us.cloud.langfuse.com" # 如果使用 US 区域
# LANGFUSE_HOST = "http://localhost:3000" # 如果本地托管
我们还应该连接到以下某个 LLM 提供商:
- OpenAI,本书大多数代码示例使用它
- Azure OpenAI
- Anthropic
- Google Vertex
- Amazon Bedrock
根据所使用的 LLM 提供商,我们可能还需要把必要变量添加到 .env 文件中。
现在,让我们使用 Langfuse 发送第一条 trace。我们先使用一个非常简单的 hello world 示例,最开始不使用任何 LLM。
# 先安装最新版 langfuse SDK 和 python-dotenv,如果尚未安装
pip install langfuse python-dotenv
# 我们需要从 .env 文件中加载环境变量
from dotenv import load_dotenv
load_dotenv()
from langfuse import observe
@observe
def langfuse_hello_world():
return "This is my first trace in Langfuse"
langfuse_hello_world()
向 Langfuse 发送 trace 最直接的方式,是使用它的 @observe 装饰器。这会自动创建一个 span,并默认捕获方法的输入和输出。
如果设置正确,我们就可以在 Langfuse UI 中看到这条 trace。访问 LANGFUSE_HOST 中指定的 host URL,然后点击侧边栏中的 Tracing 选项。
完整 URL 大概如下:
http://localhost:3000/project/cmgje7s5h0006p207p5rjnhlr/traces
或者:
http://cloud.langfuse.com/project/cmgje7s5h0006p207p5rjnhlr/traces
具体取决于我们选择了哪种 Langfuse 设置。
图 5.1:Langfuse 中的第一条 Trace
点击这条 trace 后,我们可以获得更多信息,还能看到额外选项和完整 metadata。
图 5.2:第一条 Trace 的详细信息
使用 @observe 装饰器时,trace 总是在函数被调用时开始,并在函数返回时结束。如果我们希望更精细地控制 span 何时开始和结束,应该使用 context manager 方式。
下面是一个使用 context manager 创建包含多个 span 的 trace 的更详细示例:
from langfuse import get_client
import time
import random
from dotenv import load_dotenv
load_dotenv()
langfuse = get_client()
def fetch_user_query():
time.sleep(0.2)
return "Summarize the latest Langfuse documentation"
def call_llm_api(prompt):
time.sleep(0.5)
# 模拟 LLM 输出
if random.random() < 0.1:
raise RuntimeError("LLM timeout")
return f"Summary of '{prompt}'"
def store_result(summary):
time.sleep(0.1)
return "Saved successfully"
# 使用 context managers 进行结构化 tracing
with langfuse.start_as_current_span(name="handle-user-request") as request_span:
try:
# Step 1: 获取用户查询
with langfuse.start_as_current_span(name="fetch-query") as fetch_span:
query = fetch_user_query()
fetch_span.update(output=query)
request_span.update(input=query)
# Step 2: 使用 LLM 生成响应
with langfuse.start_as_current_observation(
as_type="generation",
name="generate-summary",
model="gpt-4.1",
input=query
) as generation:
summary = call_llm_api(query)
generation.update(output=summary)
# Step 3: 保存结果
with langfuse.start_as_current_span(name="store-results") as store_span:
status = store_result(summary)
store_span.update(output=status)
request_span.update(output="Request processed successfully")
except Exception as e:
# 错误会被自动记录到这条 trace 下
request_span.update(output=f"Failed: {e}")
raise
图 5.3:捕获包含多个自定义 Span 的 Trace
这个示例展示了一个真实感更强的 LLM 工作流:获取查询、生成响应并存储结果,其中每一步都被包装在自己的 span 中。这种结构使执行流程可视化、性能测量和错误捕获都变得更容易。注意,在这条 trace 中,Langfuse 也会根据我们提供的 LLM 名称,也就是 GPT 4.1,捕获延迟、成本和 token 使用量。
最后,让我们重新运行第 1 章中的代码示例,这一次集成 Langfuse。
# OPENAI_API_KEY 应该放在项目根目录下的 .env 文件中
# 不再从官方包中导入 openai,而是从 langfuse.openai 中导入它
from langfuse.openai import openai
from dotenv import load_dotenv
load_dotenv()
completion = openai.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{
"role": "system",
"content": "You are an expert in Langfuse, the LLM Engineering Platform",
},
{
"role": "user",
"content": "What are the top 3 features of Langfuse?",
},
]
)
这里我们唯一需要改变的是:不再从官方 OpenAI Python 包中导入 openai,而是使用 Langfuse SDK 提供的替代版本。
图 5.4:在 Langfuse 中追踪 OpenAI 调用
我们可以看到,Langfuse UI 非常清晰地呈现了这条 trace。这一次,我们捕获了真实的延迟、token 使用量和成本,同时也捕获了 trace 的输入和 LLM 输出。
这些简单示例应该能够帮助我们开始使用 Langfuse 和 OpenAI。不过,开发 LLM 应用还有无数其他方式,LLM 提供商和框架列表也在持续增长。如果我们打算使用不同提供商,一定要查看 langfuse.com/integration… 来确认所选方案。不管我们选择哪种框架或提供商,Langfuse UI 都会提供一致体验,并始终在 traces 页面展示 trace 中最重要的信息。下一节中,我们将看看 Langfuse UI 的主要功能,以及可用的不同导航选项。
Langfuse 用户界面
在上一节中,我们已经看到 Langfuse UI 中的 Traces 标签页。Langfuse UI 非常直观,并且设计目标是让各种用户都能使用,而不仅仅是技术或工程团队。
图 5.5:Langfuse UI 中的 Traces 页面
上图展示了 Langfuse Tracing UI,其中显示了所有已记录 trace 及其详细信息。
顶部导航栏:
用于在 organization 和 project 之间导航。
左侧边栏:
这是主导航面板。它包含 Observability 下的关键部分,例如 Tracing、Sessions、Users;Prompt Management 下的 Prompts、Playground;以及 Evaluation 下的 Scores、LLM-as-a-Judge、Human Annotation、Datasets。同时还包含 Settings 和 Support。
顶部筛选栏:
提供过滤和搜索控件,可按名称、时间范围、environment 或其他属性查找特定 trace。
列选择器:
选择要展示哪些列或指标,例如 latency、cost、model、token count 等等。
从这个页面,我们还可以创建 SavedView,用于预先选择 trace 的列、筛选器和排序顺序。
这里也有下载 / 导出 trace 的选项,支持 CSV、JSON 或 JSONL 格式。要让该选项生效,需要通过设置环境变量启用 batch exports:
LANGFUSE_S3_BATCH_EXPORT_ENABLED
LANGFUSE_S3_BATCH_EXPORT_BUCKET
中心区域的主 Traces 表格会列出每条已记录 trace,包括:
- 名称,例如 OpenAI-generation、handle-user-request;
- 输入和输出数据,便于快速检查;
- Observation levels,即每条 trace 包含多少嵌套操作;
- Latency,即执行持续时间;
- Token counts,即使用量摘要。
这个视图提供了所有 traced events 的统一概览,使我们可以直接在 UI 中轻松监控延迟、检查模型输出,并调试多步骤 LLM 工作流。
该页面最近还引入了一个基于 AI 的 trace 搜索过滤功能,可以基于自然语言查询帮助查找 trace。这个功能可以通过简单描述我们想要过滤的内容,自动构造 trace 过滤器。
图 5.6:基于 AI 的 Trace 搜索过滤器
在写作时,该功能仅适用于 Langfuse Cloud。AI 功能可以由 organization owner 和 admin 在 Organization Settings → General → AI Features 中启用。
从这里,我们还可以导航到 Observations 标签页,它会显示每个 Observation、其类型,以及模型名称、prompt、scores、tags 等额外细节。点击 observation 后,可以查看详细视图。
图 5.7:Langfuse UI 中的 Observations 页面
页面右下方有用于跨页面快速访问的导航。
整体来看,这个页面提供了项目中所有 traced operations 的清晰概览,并让输入、输出和性能指标等关键细节一目了然。
Token 使用量与成本追踪
追踪每次调用使用了多少 token,以及花费了多少成本,是 LLM 应用最基础的可观测性参数之一。正如我们在示例中已经看到的,这些信息会自动按 observation 记录,前提是 observation 类型为 generation 或 embedding。简单定义如下:
Token Usage 是被消耗的单位数量,也就是 token 数量;
Cost 是以美元($)计量的成本。
在 Langfuse 中,token 使用量和成本数据可能来自两个来源:
自动推断:
如果我们使用受支持模型,例如 OpenAI、Anthropic、Google,并传入正确的模型标识符,Langfuse 可以根据预定义定价模型估算 token 使用量并计算成本。Langfuse 会在较新模型可用时,定期添加这些模型的成本信息。
显式摄入:
如果 LLM 提供商返回 usage 或 cost 数据,例如通过 API 返回,我们可以通过 SDK 或集成直接把这些信息传给 Langfuse。这通常比单纯推断更准确。
如果我们自行摄入 usage 信息,它会覆盖 Langfuse 推断出的 usage / cost。因此,建议谨慎使用手动摄入。
图 5.8:成本与使用量摄入流程图
Langfuse 也支持自定义模型定义。因此,如果我们使用内置目录中不存在的模型,例如微调模型、自托管模型或新的提供商模型,我们可以添加 tokenizer、定价信息和 usage 类型,例如 input / output、cache reads、audio / image tokens 等等,以确保成本追踪能够正确运行。
在两种情况下,我们都能获得每次调用的 token 数量、按 usage 类型拆分的成本,以及聚合指标,用于随时间监控趋势、检测异常并优化成本。
下面是一个简单示例,展示如何在 observation 中使用 OpenAI 提供的 usage details 手动摄入使用量详情。
from langfuse import get_client
import openai
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
with langfuse_client.start_as_current_observation(
as_type="generation",
name="openai-style-generation",
model="gpt-4.1-mini"
) as generation:
messages = [
{
"role": "system",
"content": "You are an expert in Langfuse, the LLM Engineering Platform",
},
{
"role": "user",
"content": "Summarize how cost and usage ingestion works in Langfuse",
},
]
completion = openai.chat.completions.create(
model="gpt-4.1-mini",
messages=messages
)
# 从 completion 中获取 usage details
usage_details = completion.usage
# 使用 usage details 更新 generation
generation.update(
usage_details=usage_details.model_dump,
input=messages,
output=completion.choices[0].message.content
)
图 5.9:带有手动摄入 Usage Details 的 Observation
如果我们省略用 usage details 更新 generation 的部分,Langfuse 将使用自己的数据库中预定义的 usage details。这是基于 start_as_current_observation 方法中 model 参数提供的名称完成的。这里也就是 gpt-4.1-mini。要查看可用模型列表,可以进入项目的 Settings 页面,然后选择 Models。
图 5.10:项目中的 Model Settings
我们可以点击 Add model definition 按钮,向 Langfuse 添加自己的模型定义。对于不包含在标准提供商特定模型列表中的自托管模型或微调模型,这一步非常重要。
图 5.11:创建自定义模型定义
创建自定义模型定义时,需要提供模型名称并设置定价信息。Langfuse 会使用 Match pattern 字段中的正则表达式,将模型名称映射到 usage definitions。用户定义的模型优先级高于 Langfuse 维护的模型。创建自定义模型定义时还需要记住以下几点:
- 模型定义变更不会应用到历史 trace。更新后的成本只会应用到新的 generation。
- Token Usage 和 Cost 只适用于 generation 或 embedding observations。
- 如果缺少标准模型定义,可以通过在 Langfuse GitHub 页面创建 issue 请求添加:langfuse.com/issue
Langfuse 中的核心可观测性功能
到目前为止,本章已经展示了开始向 Langfuse 发送 trace 的最常见方式。这是实现应用完整可观测性所需的最基础起点。然而,仅仅发送原始 trace 本身价值有限,尤其是在真实应用中,我们可能需要把相关 trace 放在一起查看,或者在用户级别追踪活动。通过对发送 trace 的方式做一些小改动,我们就可以轻松从中获得更多洞察。Langfuse 提供了多个内置功能,将简单的 trace logging 扩展为完整的监控和分析工具包。
Sessions
列表中的第一个功能是创建 Sessions 的能力。Langfuse 中的 Sessions 是一组可以一起可视化的 trace,用于覆盖与应用的一整次交互。在 Langfuse 中创建 session 非常直接,我们只需要在 trace 中包含一个 sessionId。所有具有相同 id 的 trace 都会被分组到一起。
有几种方式可以向 trace 传递 session ID。
from dotenv import load_dotenv
from langfuse import observe, get_client
load_dotenv()
langfuse_client = get_client()
@observe()
def add_session_to_trace():
# 使用 @observe context manager 时,为正在运行的 trace 添加 session id
langfuse_client.update_current_trace(session_id="your-unique-session-id")
def update_session_in_span():
"""
向当前 span 添加 session id
"""
with langfuse_client.start_as_current_span(name="another-operation"):
# 添加到当前 trace
langfuse_client.update_current_trace(session_id="another-session-id")
对于聊天机器人这样的多轮对话,session 的使用更加突出。
from langfuse import get_client
from langfuse.openai import openai
import uuid
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
# 为这次聊天创建一个新的 session
session_id = str(uuid.uuid4())
print(f"New chat session started: {session_id}\n(Type 'bye' to exit)\n")
def chat_with_bot(user_message: str):
# 每条用户消息都会作为该 session 下的一条 trace 被记录
with langfuse_client.start_as_current_span(name="chat-turn") as span:
span.update_trace(input=user_message, session_id=session_id)
try:
# 记录 LLM generation
completion = openai.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{
"role": "system",
"content": "You are a helpful and friendly support chatbot for Langfuse",
},
{"role": "user", "content": user_message},
],
)
reply = completion.choices[0].message.content
print(f"Bot: {reply}")
span.update_trace(output=reply)
except Exception as e:
span.update_trace(output=f"Error: {e}")
# 运行聊天机器人,直到用户输入 "bye"、"exit" 或 "quit"
while True:
user_input = input("You: ")
if user_input.lower().strip() in ["bye", "exit", "quit"]:
print("Bot: Goodbye! Have a great day!")
break
chat_with_bot(user_input)
要浏览所有 session,我们可以从左侧导航栏进入 Sessions 标签页。
图 5.12:Langfuse 中的 Sessions
我们也可以点击 session id 旁边的 share 按钮来共享 session。这会生成一个唯一的可共享链接,可以分享给其他用户。请注意,在 cloud 版本中,该链接对任何人都是公开可访问的。因此,请小心不要共享机密或专有信息。这个选项也让用户可以在浏览器中将其加入书签。
我们会在后续章节中看到,如何对整个 session 进行评分,甚至将其发送到 Annotation Queues 中进行人工评估。
用户追踪
与 session 类似,基于系统用户追踪活动也非常常见。这有助于分析用户行为,并针对某个用户或一组用户过滤交互。在 Langfuse 中,用户的定义是开放式的,可以根据应用需求决定。一个用户可以基于 userId 被识别。添加 user id 的代码与添加 sessionId 完全相同。
from dotenv import load_dotenv
from langfuse import observe, get_client
load_dotenv()
langfuse_client = get_client()
@observe()
def add_user_in_trace():
langfuse_client.update_current_trace(user_id="your-unique-user-id")
def update_user_in_span():
"""
向当前 span 添加 user id
"""
with langfuse_client.start_as_current_span(name="another-operation"):
# 添加到当前 trace
langfuse_client.update_current_trace(user_id="another-user-id")
要查看用户列表,可以导航到 Users 标签页。
图 5.13:Langfuse 中的 Users
这个视图展示了应用中的所有用户,以及每个用户的事件数量、token 使用量和成本。要查看某个用户的更多细节,可以点击 user ID。
userId 应该是某种唯一值,例如 email 或任何其他唯一标识符,以确保 trace 不会被错误链接到其他用户。如果我们的应用使用某种 id 来识别用户,例如数字 id,那么最好在 Langfuse 中也使用同一个 id 作为用户标识。这样我们就可以把 Langfuse 中按用户关联的 trace,与应用中的其他组件链接起来。
截至目前,Langfuse 不支持为用户添加姓名、群组或其他识别参数等额外属性。
Environments
在软件应用中,environment 是代表开发和部署流程不同阶段的独立设置。我们可以有 development、staging、beta、production 等环境。通常,我们会为所有阶段和环境使用同一个 Langfuse 实例。为了避免数据污染,可以为不同 Langfuse 对象指定 environment,并把它们组织到适当上下文中。
Environment 可以出现在 Traces、Observations、Scores 和 Sessions 上。它应该是一个字符串,不能以 “langfuse” 开头,并且长度小于或等于 40 个字符。除此之外,它只能包含小写字母、数字、连字符和下划线。
import os
# 为所有对象全局设置 environment
# 也可以添加到 .env 文件中
os.environ["LANGFUSE_TRACING_ENVIRONMENT"] = "production"
# 在 client 初始化时设置 environment
# 这会覆盖通过环境变量设置的 environment
from langfuse import Langfuse
from langfuse.openai import openai
from dotenv import load_dotenv
load_dotenv()
langfuse_client = Langfuse(environment="production")
completion = openai.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant for Langfuse"},
{
"role": "user",
"content": "What is the benefit of setting environments in Langfuse?",
},
],
)
打开 trace 时,我们可以在详情页面看到 Environment。
图 5.14:Langfuse 中的 Environment
在 UI 中,我们也可以使用导航栏中的 environment filter 按 environment 过滤事件。
Langfuse 中的 Environments 是不可变的。它们会在第一条 trace 被摄入时创建,并且我们不能从 Langfuse 中删除 environment。
Tags 与 Metadata
我们要看的最后一个核心可观测性功能,是向 trace 添加 tags 和 metadata 的选项。这些是可以选择性添加到发送给 Langfuse 的 trace 上的补充信息。
Tags 是简单字符串标签,可以在创建或更新 trace 时附着到 trace 上。一条 trace 可以有多个 tag。和 Environments 一样,Tags 一旦添加后就不能被移除,只能追加。Tags 用于 trace 的分类和过滤。下表展示了一些可以在应用中使用的 tag 示例。
| 类别 | 示例 Tags | 描述 |
|---|---|---|
| Use Case / Workflow | content-summarization、translation、sentiment-analysis、SQL-generation、image-search | 按功能用例或流水线类型标记 trace。 |
| Feature / Product Area | onboarding-flow、support-chat、recommendation-engine、data-ingestion | 识别触发 trace 的产品模块或工作流。 |
| Experiment or Model Variant | exp-A、exp-B、gpt-4o-mini、mistral-7b、custom-rag-v2 | 按实验变体或模型版本区分 trace。 |
| Response Outcome | success、timeout、failed | 标记 trace 如何完成,便于快速可视化过滤。 |
| Priority or Severity | high、low、critical | 对内部流水线或错误处理流程进行 trace 时很有用。 |
| Pipeline Stage | preprocessing、inference、postprocessing | 标记 trace,以查看不同流水线片段的性能。 |
| Business Logic | free-plan、enterprise-plan、trial-period | 按业务规则或账号层级对 trace 分类。 |
表 5.2:Langfuse 中的 Tags 示例
另一方面,Metadata 是任意 JSON key / value 数据,可以附着到 trace 或 trace 内的 observation / span 上。它可以承载结构化上下文,类似:
{"request_id": "req-123", "user_role": "admin"}
也可以承载模型参数、环境信息、版本等等。Metadata 更新会在顶层 key 上合并,也就是说,添加新 key 会与已有 metadata 合并,但我们应该避免多次重写同一个 key。我们可以在 trace 层级,也可以在单个 span / observation 层级附加 metadata。
下面是一个小代码示例,展示如何使用 tags 和 metadata 来增强 trace / observation。通常,我们会在 trace 创建时或应用工作流更新中提供 tags 或 metadata。
from langfuse import get_client
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
with langfuse_client.start_as_current_span(
name="chat-about-langfuse",
) as span:
# 向 span 添加 tags
span.update_trace(tags=["chatbot", "observability", "gpt-4.1-mini"])
with span.start_as_current_observation(
name="llm-generation",
as_type="generation",
# 向 observation 添加 metadata
metadata={"temperature": 0.7, "max_tokens": 1000},
) as observation:
# 更新 observation 的 metadata
observation.update(
metadata={"user_id": "user_456", "topic": "langfuse-tags-vs-metadata"},
)
# 添加最终 tag 来标记 session 结果
observation.update(tags=["completed"])
什么时候使用 Tags
当我们想用经常过滤或分组的类别标签标记 trace 时,例如 env:prod、experiment-A、use-case-cart、fallback-path。
当我们希望在 UI filter 中快速可视化切换时。Tags 默认在 Traces 页面可见。
当我们不需要细粒度信息时。Tags 是粗粒度的,并且是全局作用于 trace 的。
什么时候使用 Metadata
当我们需要结构化上下文时,例如 user_id、request_id、version、model_params,以便进行更深入查询。
当我们需要存储多个相关属性或嵌套数据时。
当我们希望将数据关联到特定 span / observation,而不只是整条 trace 时。
当我们可能需要更丰富的过滤条件时,例如 “all traces where response_time > 500ms” 或 “where user_plan = ‘premium’”。
Langfuse 中的高级可观测性功能
在上一节中,我们看到了 Langfuse 在发送 trace 时提供的基础功能。对于 LLM 应用来说,我们可能需要更多选项,使监控更加全面。下面我们将看到最重要的高级功能,以及如何在 trace 中使用它们。
Observation 类型
Langfuse 中的 observation 定义了一条 trace 内部的操作类型。在现代 LLM 应用中,一个端到端 LLM 操作内部可能发生许多不同步骤。到目前为止,我们主要看到的是 “generation” 类型的 observation,它代表调用 LLM 以获得响应的主要步骤。然而,这并不是 trace 内唯一可能发生的事件类型。Observation 类型可以在使用 SDK 集成时自动检测,也可以在创建 trace / span 时显式指定。使用 SDK 时,Langfuse 支持以下 observation 类型:
Event 是根 observation 类型,所有其他 observation 都嵌套在它下面。每条 trace 都会自动创建 event。
Span 是 event 内部一个个按时间切分的区块。
Generation 表示生成输出的 observation,例如一次 LLM 调用。
Agent observation 可用于在 trace / span 内调用 AI Agent 的场景。
Tool 表示工具调用,通常与 agent 关联使用。
Chain 基本上与 LangChain 同义,可以包含 LangChain 调用中组成整个 chain 的不同步骤。
Retriever 主要用于 RAG 应用,表示数据检索步骤,例如调用向量存储或数据库。
Evaluator 可用于 trace 内返回 metric 或 score 的步骤。
Embedding 用于生成 embedding 的调用。
Guardrail 用于 guardrailing 或安全步骤。
下面是一个如何使用 observation 类型的简单示例:
from langfuse import get_client
from langfuse.openai import openai
from dotenv import load_dotenv
import requests
load_dotenv()
langfuse_client = get_client()
# 第一个 span 是类型为 "event" 的 root span
with langfuse_client.start_as_current_span(name="weather-app") as span:
user_message = "What's the weather in Berlin today?"
span.update(input=user_message)
# Observation 类型:tool
with langfuse_client.start_as_current_observation(
as_type="tool", name="weather-api", input={"city": "Berlin"}
) as tool_call:
tool_response = requests.get("https://wttr.in/Berlin?format=3")
tool_call.update(output=tool_response.text)
# 对 LLM 的调用会自动创建类型为 "generation" 的 observations
completion = openai.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "user", "content": tool_response.text},
{"role": "user", "content": user_message},
],
)
span.update(output=completion.choices[0].message.content)
正如我们可以看到的,Langfuse 会用适当的 observation 类型展示 trace 步骤,甚至还会为流程添加一个漂亮的图形表示。
图 5.15:Langfuse 中的 Observation 类型
添加 observation 类型,会让 trace 对操作过程的表达更强,也更适合可视化展示。更重要的是,我们还可以按类型过滤 observation,然后查看不同类型的指标或 score。
下面是另一个基于 RAG 应用使用 observation 类型的示例:
from langfuse import get_client
from langfuse.openai import openai
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
with langfuse_client.start_as_current_span(name="rag-question-answering") as span:
# Observation 类型:retriever
span.update(input="What is Langfuse?")
with langfuse_client.start_as_current_observation(
as_type="retriever",
name="knowledge-base-search",
metadata={"query": "What is Langfuse?"},
) as r:
docs = ["Langfuse is an observability platform for LLM apps."]
r.update(output=docs)
completion = openai.chat.completions.create(
model="gpt-4.1-mini",
messages=[
{
"role": "system",
"content": "Answer based on the provided context.",
},
{
"role": "user",
"content": f"Context: {docs}\n\nQuestion: What is Langfuse?",
},
],
)
answer = completion.choices[0].message.content.strip()
# Evaluator:评估响应质量
with langfuse_client.start_as_current_observation(
as_type="evaluator",
name="relevance-check",
metadata={"criteria": "factual-correctness"},
) as eval:
eval.update(output="relevant and correct")
span.update(output=answer)
图 5.16:Langfuse 中更多 Observation 类型
日志级别
和应用日志类似,observation 也可以有日志级别,因为它们与传统软件系统中的日志非常相似。Langfuse 支持四种最常见的日志级别:DEBUG、DEFAULT、WARNING、ERROR。我们还可以向 observation 添加日志消息,在 Langfuse 中称为 statusMessage,以提供更多信息。
默认情况下,Langfuse 的 observation 不关联任何 level。当我们添加日志级别后,可以在 UI 中按 level 过滤 Observations。
from langfuse import get_client
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
with langfuse_client.start_as_current_span(name="data-processing") as span:
span.update(output="Step 1 complete")
with langfuse_client.start_as_current_observation(
name="risky-step",
level="WARNING",
status_message="This step might fail under edge cases",
) as risky:
# 模拟可能出错的情况
result = None
if result is None:
risky.update(level="ERROR", status_message="Result is unexpectedly None")
# 在运行时更新当前 span
span.update(
level="DEBUG", status_message="Finishing cleanup operations"
)
版本与发布
应用显然会经历许多不同版本和发布周期。Langfuse 提供了向 trace 或 observation 添加版本信息的选项,使我们能够追踪指标随时间产生的影响。无论是 prompt 逻辑变更、模型版本、feature flags,还是后端服务变更,将可观测性数据绑定回这些部署都很重要。如果我们计划对应用做 A/B 测试,release version 可以帮助比较不同版本。
Release version 可以在初始化 client 时为应用全局设置。
from langfuse import get_client, Langfuse
from dotenv import load_dotenv
load_dotenv()
langfuse = Langfuse(release="v1.2.3")
langfuse_client = get_client()
with langfuse_client.start_as_current_span(
name="process-user-request", version="v1.0.0"
) as span:
span.update(input="This is a versioned span")
with langfuse.start_as_current_observation(
name="my-versioned-observation", as_type="generation", version="2.0.0"
) as observation:
observation.update(output="This is a versioned observation")
span.update(output="Done")
Releases 和 versions 可以在 trace 详情视图中看到。
图 5.17:Release 与 Version
在 Vercel、Heroku、Netlify 等一些平台上,release version 也可以基于可用环境变量自动填充。支持的完整变量列表可在这里查看:
Trace URLs
在 SDK 中,我们可以通过编程方式生成 trace 的完整 URL。这个 URL 可以被共享,也可以用于其他系统中直接链接到某条 trace。使用 UI 时,trace 也可以被公开分享给其他用户。和 session 一样,trace 也可能包含敏感信息,因此应谨慎分享。
from langfuse import observe, get_client
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
@observe()
def get_trace_url():
# 获取 trace 的 URL
trace_url = langfuse_client.get_trace_url()
print(f"View trace at: {trace_url}")
trace_id = langfuse_client.get_current_trace_id()
trace_url = langfuse_client.get_trace_url(trace_id=trace_id)
print(f"View trace at: {trace_url}")
运行这个方法会产生如下输出:
View trace at: http://localhost:3000/project/cmgje7s5h0006p207p5rjnhlr/traces/55615a3037823cf18e1800a973fd7198
View trace at: http://localhost:3000/project/cmgje7s5h0006p207p5rjnhlr/traces/55615a3037823cf18e1800a973fd7198
Sampling
如果我们的应用生成大量 trace,例如许多用户请求或内部工作流,摄入每一条 trace 都可能变得昂贵,包括带宽、存储和成本。Sampling 允许我们通过只收集 trace 子集来控制数据量。Sampling 可以通过以下两种方式强制执行:
- 设置环境变量
LANGFUSE_SAMPLE_RATE。 - 在初始化 Langfuse 时作为构造函数参数传入。
from langfuse import Langfuse
import os
os.environ["LANGFUSE_SAMPLE_RATE"] = "0.5"
langfuse = Langfuse(sample_rate=0.5)
Sampling rate 应在 0 到 1 之间。在上面的示例中,只有 50% 的 trace 会被摄入 Langfuse。
分布式追踪
在基于 LLM 的系统中,我们通常有许多不同组件,例如 API、日志系统、数据库、Web 应用等等。一次用户请求可能会跨越不同服务器或云函数。在这种情况下,理想状态是追踪整个工作流,而不只是单个服务内部。Langfuse 允许我们提供自己的 trace ID,也可以选择提供 parent span ID / observation ID,而不总是依赖自动生成的标识符。这使得跨服务关联相关操作成为可能。
当我们不提供它们时,Langfuse 会生成随机 trace ID,格式为 32 个小写十六进制字符;对于基于 OTEL 的 SDK,则会生成 16 个十六进制字符的 observation ID。要生成自己的 trace ID,该 trace ID 必须在项目内唯一。
什么时候在 LLM 应用中使用分布式 Trace IDs
当我们的工作流跨多个微服务、函数或进程时,例如检索服务 + embedding 服务 + generation 服务。
当我们集成工具和外部 API,也就是 tool calls,并希望把它们纳入同一条 trace 中时。
当我们希望将外部系统中的事件,例如用户请求或 job trigger,与 Langfuse 中的相关 trace 关联起来时。
当我们希望跨服务边界进行更深入的性能或成本分析时,例如哪个服务消耗了最多 token 或时间。
当我们构建基于 agent 的系统或编排工作流,例如多步骤 chain,并需要完整流程视图时。
from langfuse import get_client
from dotenv import load_dotenv
load_dotenv()
langfuse_client = get_client()
# trace ID 可以通过 trace_context 参数传入
with langfuse_client.start_as_current_span(
name="my-operation",
trace_context={
"trace_id": "abcdef1234567890abcdef1234567890" # 必须是 32 个十六进制字符,并且唯一
},
) as span:
# 访问当前 span 的 trace ID
current_trace_id = langfuse_client.get_current_trace_id()
current_span_id = langfuse_client.get_current_observation_id()
print(
f"The current trace ID is {current_trace_id} and the current span id is {current_span_id}"
)
在真实场景中,trace ID 可以来自上游服务,也可以传递给下游服务,以便即使在 Langfuse 外部也保留可追踪性。
使用分布式追踪时,有几件事需要记住。开发者需要确保每个相关进程都正确维护并附加相同 trace ID。我们必须确保这些 ID 在分布式系统中保持唯一且一致。当与 OpenTelemetry 或外部 API 等工具集成时,我们可能需要根据其他系统的要求生成 Langfuse trace ID。
在 Trace 中屏蔽敏感数据
作为最后一个高级功能,我们来看 Masking trace。Masking 允许我们在把 trace 发送到 Langfuse 之前隐藏敏感信息。这是保护用户隐私或遵守监管法律的关键步骤。如果你的应用处理这类数据,请确保在添加到 Langfuse 之前进行脱敏。Langfuse 维护了强数据加密能力,传输中数据使用 TLS 1.2+,静态数据使用 AES-256。该平台遵循公认合规框架,包括 SOC 2 Type II 和 ISO 27001,并支持 GDPR。然而,屏蔽敏感信息和个人身份信息,也就是 PII,是发送 trace 到 Langfuse 的系统自身的责任。
要启用 masking,我们需要定义一个 masking_function,然后将它传给 Langfuse 构造函数。在这个函数中,我们定义自己的 masking 逻辑。
下面是一个简单示例,展示如何从 trace 中屏蔽 email 和电话号码。
from langfuse import Langfuse
from dotenv import load_dotenv
import re
load_dotenv()
def mask_pii(data: any, **kwargs) -> any:
if isinstance(data, str):
# 屏蔽 email 地址
data = re.sub(r"\b[\w.-]+?@\w+?.\w+?\b", "[REDACTED_EMAIL]", data)
# 屏蔽美国风格电话号码
data = re.sub(r"\b\d{3}[-. ]?\d{3}[-. ]?\d{4}\b", "[REDACTED_PHONE]", data)
return data
langfuse = Langfuse(mask=mask_pii)
with langfuse.start_as_current_span(
name="contact-customer", input="Email: jane.doe@example.com, Phone: 555-123-4567"
) as span:
span.update(output="Reached out to jane.doe@example.com via 555-123-4567")
Langfuse 会屏蔽 trace 的输入和输出中的敏感数据。Metadata 中的敏感数据默认也会被屏蔽。
图 5.18:在 Trace 中屏蔽敏感信息
对于信用卡号、姓名和 / 或地址等高级用例,我们可能需要在 masking function 中引入专门的库来完成验证。
结论
本章围绕 LLM 应用中的 Langfuse 可观测性做了一次实践性概览。本章概述了用于监控的关键工具,包括 Python SDK 和 dashboard,并描述了如何管理 token 使用量、成本追踪、sessions、environments 和 metadata。本章还介绍了 tagging、用户追踪、版本管理和分布式追踪等高级能力,并解释了如何屏蔽 email 和信用卡号等敏感信息。下一章中,我们将覆盖 Langfuse 最有意思的功能之一:Prompt Management。之后我们会进一步逐步看到,如何使用 Langfuse 覆盖 prompt 的整个生命周期,包括版本控制。
参考资料
Langfuse Usage and Cost Tracking - langfuse.com/docs/observ…
Langfuse Sessions - langfuse.com/docs/observ…
Langfuse Users - langfuse.com/docs/observ…
Langfuse Environments - langfuse.com/docs/observ…
Langfuse Tags - langfuse.com/docs/observ…
Langfuse Metadata - langfuse.com/docs/observ…
Masking in Langfuse - langfuse.com/docs/observ…
Security in Langfuse - langfuse.com/security
Trace IDs - langfuse.com/docs/observ…