LangSmith 支持基于 OpenTelemetry 的追踪,允许您从任何兼容 OpenTelemetry 的应用程序发送追踪。本指南涵盖 LangChain 应用程序的自动仪表以及其他框架的手动仪表。 了解如何使用 LangSmith 和 OpenTelemetry 追踪您的 LLM 应用程序。对于自托管安装或欧盟地区组织,请在以下请求中适当地更新 LangSmith URL。对于欧盟地区,请使用 eu.api.smith.langchain.com。
追踪 LangChain 应用程序
如果您正在使用 LangChain 或 LangGraph,请使用内置集成来追踪您的应用程序
-
安装支持 OpenTelemetry 的 LangSmith 包
pip install "langsmith[otel]"
pip install langchain
需要 Python SDK 版本 langsmith>=0.3.18。我们推荐 langsmith>=0.4.25 以受益于重要的 OpenTelemetry 修复。
-
在您的 LangChain/LangGraph 应用程序中,通过设置
LANGSMITH_OTEL_ENABLED 环境变量来启用 OpenTelemetry 集成
LANGSMITH_OTEL_ENABLED=true
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=<your_langsmith_api_key>
# For LangSmith API keys linked to multiple workspaces, set the LANGSMITH_WORKSPACE_ID environment variable to specify which workspace to use.
-
创建一个带有追踪功能的 LangChain 应用程序。例如
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# Create a chain
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
model = ChatOpenAI()
chain = prompt | model
# Run the chain
result = chain.invoke({"topic": "programming"})
print(result.content)
-
应用程序运行后,在您的 LangSmith 仪表板中查看追踪(示例)。
追踪非 LangChain 应用程序
对于非 LangChain 应用程序或自定义仪表,您可以使用标准的 OpenTelemetry 客户端在 LangSmith 中追踪您的应用程序。(我们建议 langsmith ≥ 0.4.25。)
-
安装 OpenTelemetry SDK、OpenTelemetry 导出器包以及 OpenAI 包
pip install openai
pip install opentelemetry-sdk
pip install opentelemetry-exporter-otlp
-
设置端点环境变量,替换为您的特定值
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.smith.langchain.com/otel
OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<your langsmith api key>"
根据您的 otel 导出器配置方式,如果您只发送追踪,您可能需要在端点后附加 /v1/traces。
如果您是自托管 LangSmith,请将基本端点替换为您的 LangSmith api 端点并附加 /api/v1。例如:OTEL_EXPORTER_OTLP_ENDPOINT=https://ai-company.com/api/v1/otel
可选:指定一个自定义项目名称而不是“default”
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.smith.langchain.com/otel
OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<your langsmith api key>,Langsmith-Project=<project name>"
-
记录追踪。 此代码设置了一个 OTEL 追踪器和导出器,它将向 LangSmith 发送追踪。然后它调用 OpenAI 并发送所需的 OpenTelemetry 属性。
from openai import OpenAI
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
otlp_exporter = OTLPSpanExporter(
timeout=10,
)
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(otlp_exporter)
)
tracer = trace.get_tracer(__name__)
def call_openai():
model = "gpt-4o-mini"
with tracer.start_as_current_span("call_open_ai") as span:
span.set_attribute("langsmith.span.kind", "LLM")
span.set_attribute("langsmith.metadata.user_id", "user_123")
span.set_attribute("gen_ai.system", "OpenAI")
span.set_attribute("gen_ai.request.model", model)
span.set_attribute("llm.request.type", "chat")
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{
"role": "user",
"content": "Write a haiku about recursion in programming."
}
]
for i, message in enumerate(messages):
span.set_attribute(f"gen_ai.prompt.{i}.content", str(message["content"]))
span.set_attribute(f"gen_ai.prompt.{i}.role", str(message["role"]))
completion = client.chat.completions.create(
model=model,
messages=messages
)
span.set_attribute("gen_ai.response.model", completion.model)
span.set_attribute("gen_ai.completion.0.content", str(completion.choices[0].message.content))
span.set_attribute("gen_ai.completion.0.role", "assistant")
span.set_attribute("gen_ai.usage.prompt_tokens", completion.usage.prompt_tokens)
span.set_attribute("gen_ai.usage.completion_tokens", completion.usage.completion_tokens)
span.set_attribute("gen_ai.usage.total_tokens", completion.usage.total_tokens)
return completion.choices[0].message
if __name__ == "__main__":
call_openai()
-
在您的 LangSmith 仪表板中查看追踪(示例)。
将追踪发送到其他提供商
虽然 LangSmith 是 OpenTelemetry 追踪的默认目的地,但您也可以配置 OpenTelemetry 将追踪发送到其他可观测性平台。
适用于 LangSmith Python SDK ≥ 0.4.1。我们推荐 ≥ 0.4.25 以获取改进 OTEL 导出和混合扇出稳定性的修复。
使用环境变量进行全局配置
默认情况下,LangSmith OpenTelemetry 导出器会将数据发送到 LangSmith API OTEL 端点,但这可以通过设置标准 OTEL 环境变量进行自定义
OTEL_EXPORTER_OTLP_ENDPOINT: Override the endpoint URL
OTEL_EXPORTER_OTLP_HEADERS: Add custom headers (LangSmith API keys and Project are added automatically)
OTEL_SERVICE_NAME: Set a custom service name (defaults to "langsmith")
LangSmith 默认使用 HTTP 追踪导出器。如果您想使用自己的追踪提供商,您可以
- 如上所示设置 OTEL 环境变量,或
- 在初始化 LangChain 组件之前设置一个全局追踪提供商,LangSmith 将检测并使用它而不是创建自己的。
要将追踪发送到不同的提供商,请使用您的提供商的端点配置 OTLP 导出器
import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# Set environment variables for LangChain
os.environ["LANGSMITH_OTEL_ENABLED"] = "true"
os.environ["LANGSMITH_TRACING"] = "true"
# Configure the OTLP exporter for your custom endpoint
provider = TracerProvider()
otlp_exporter = OTLPSpanExporter(
# Change to your provider's endpoint
endpoint="https://otel.your-provider.com/v1/traces",
# Add any required headers for authentication
headers={"api-key": "your-api-key"}
)
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# Create and run a LangChain application
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
model = ChatOpenAI()
chain = prompt | model
result = chain.invoke({"topic": "programming"})
print(result.content)
混合追踪在版本 ≥ 0.4.1 中可用。要仅将追踪发送到您的 OTEL 端点,请设置:LANGSMITH_OTEL_ONLY="true" (建议:使用 langsmith ≥ 0.4.25。)
支持的 OpenTelemetry 属性和事件映射
通过 OpenTelemetry 将追踪发送到 LangSmith 时,以下属性将映射到 LangSmith 字段
核心 LangSmith 属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
langsmith.trace.name | 运行名称 | 覆盖运行的 span 名称 |
langsmith.span.kind | 运行类型 | 值:llm、chain、tool、retriever、embedding、prompt、parser |
langsmith.trace.session_id | 会话 ID | 相关追踪的会话标识符 |
langsmith.trace.session_name | 会话名称 | 会话的名称 |
langsmith.span.tags | 标签 | 附加到 span 的自定义标签(逗号分隔) |
langsmith.metadata.{key} | metadata.{key} | 带有 langsmith 前缀的自定义元数据 |
GenAI 标准属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
gen_ai.system | metadata.ls_provider | GenAI 系统(例如,“openai”,“anthropic”) |
gen_ai.operation.name | 运行类型 | 将“chat”/“completion”映射到“llm”,将“embedding”映射到“embedding” |
gen_ai.prompt | 输入 | 发送到模型的输入提示 |
gen_ai.completion | 输出 | 模型生成的输出 |
gen_ai.prompt.{n}.role | inputs.messages[n].role | 第 n 个输入消息的角色 |
gen_ai.prompt.{n}.content | inputs.messages[n].content | 第 n 个输入消息的内容 |
gen_ai.prompt.{n}.message.role | inputs.messages[n].role | 角色的替代格式 |
gen_ai.prompt.{n}.message.content | inputs.messages[n].content | 内容的替代格式 |
gen_ai.completion.{n}.role | outputs.messages[n].role | 第 n 个输出消息的角色 |
gen_ai.completion.{n}.content | outputs.messages[n].content | 第 n 个输出消息的内容 |
gen_ai.completion.{n}.message.role | outputs.messages[n].role | 角色的替代格式 |
gen_ai.completion.{n}.message.content | outputs.messages[n].content | 内容的替代格式 |
gen_ai.input.messages | inputs.messages | 输入消息数组 |
gen_ai.output.messages | outputs.messages | 输出消息数组 |
gen_ai.tool.name | invocation_params.tool_name | 工具名称,也设置运行类型为“tool” |
GenAI 请求参数
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
gen_ai.request.model | invocation_params.model | 用于请求的模型名称 |
gen_ai.response.model | invocation_params.model | 响应中返回的模型名称 |
gen_ai.request.temperature | invocation_params.temperature | 温度设置 |
gen_ai.request.top_p | invocation_params.top_p | Top-p 采样设置 |
gen_ai.request.max_tokens | invocation_params.max_tokens | 最大 token 设置 |
gen_ai.request.frequency_penalty | invocation_params.frequency_penalty | 频率惩罚设置 |
gen_ai.request.presence_penalty | invocation_params.presence_penalty | 存在惩罚设置 |
gen_ai.request.seed | invocation_params.seed | 用于生成的随机种子 |
gen_ai.request.stop_sequences | invocation_params.stop | 停止生成的序列 |
gen_ai.request.top_k | invocation_params.top_k | Top-k 采样参数 |
gen_ai.request.encoding_formats | invocation_params.encoding_formats | 输出编码格式 |
GenAI 使用指标
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
gen_ai.usage.input_tokens | usage_metadata.input_tokens | 使用的输入 token 数量 |
gen_ai.usage.output_tokens | usage_metadata.output_tokens | 使用的输出 token 数量 |
gen_ai.usage.total_tokens | usage_metadata.total_tokens | 使用的总 token 数量 |
gen_ai.usage.prompt_tokens | usage_metadata.input_tokens | 使用的输入 token 数量(已弃用) |
gen_ai.usage.completion_tokens | usage_metadata.output_tokens | 使用的输出 token 数量(已弃用) |
gen_ai.usage.details.reasoning_tokens | usage_metadata.reasoning_tokens | 使用的推理 token 数量 |
TraceLoop 属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
traceloop.entity.input | 输入 | TraceLoop 的完整输入值 |
traceloop.entity.output | 输出 | TraceLoop 的完整输出值 |
traceloop.entity.name | 运行名称 | TraceLoop 的实体名称 |
traceloop.span.kind | 运行类型 | 映射到 LangSmith 运行类型 |
traceloop.llm.request.type | 运行类型 | “embedding”映射到“embedding”,其他映射到“llm” |
traceloop.association.properties.{key} | metadata.{key} | 带有 traceloop 前缀的自定义元数据 |
OpenInference 属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
input.value | 输入 | 完整的输入值,可以是字符串或 JSON |
output.value | 输出 | 完整的输出值,可以是字符串或 JSON |
openinference.span.kind | 运行类型 | 将各种类型映射到 LangSmith 运行类型 |
llm.system | metadata.ls_provider | LLM 系统提供商 |
llm.model_name | metadata.ls_model_name | OpenInference 的模型名称 |
tool.name | 运行名称 | 当 span 种类为“TOOL”时,表示工具名称 |
metadata | metadata.* | 要合并的元数据的 JSON 字符串 |
LLM 属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
llm.input_messages | inputs.messages | 输入消息 |
llm.output_messages | outputs.messages | 输出消息 |
llm.token_count.prompt | usage_metadata.input_tokens | 提示 token 计数 |
llm.token_count.completion | usage_metadata.output_tokens | 完成 token 计数 |
llm.token_count.total | usage_metadata.total_tokens | 总 token 计数 |
llm.usage.total_tokens | usage_metadata.total_tokens | 替代的总 token 计数 |
llm.invocation_parameters | invocation_params.* | 调用参数的 JSON 字符串 |
llm.presence_penalty | invocation_params.presence_penalty | 存在惩罚 |
llm.frequency_penalty | invocation_params.frequency_penalty | 频率惩罚 |
llm.request.functions | invocation_params.functions | 函数定义 |
提示模板属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
llm.prompt_template.variables | 运行类型 | 将运行类型设置为“prompt”,与 input.value 一起使用 |
检索器属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
retrieval.documents.{n}.document.content | outputs.documents[n].page_content | 第 n 个检索文档的内容 |
retrieval.documents.{n}.document.metadata | outputs.documents[n].metadata | 第 n 个检索文档的元数据(JSON) |
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
tools | invocation_params.tools | 工具定义数组 |
tool_arguments | invocation_params.tool_arguments | 作为 JSON 或键值对的工具参数 |
Logfire 属性
| OpenTelemetry 属性 | LangSmith 字段 | 备注 |
|---|
prompt | 输入 | Logfire 提示输入 |
all_messages_events | 输出 | Logfire 消息事件输出 |
events | inputs/outputs | Logfire 事件数组,拆分输入/选择事件 |
OpenTelemetry 事件映射
| 事件名称 | LangSmith 字段 | 备注 |
|---|
gen_ai.content.prompt | 输入 | 从事件属性中提取提示内容 |
gen_ai.content.completion | 输出 | 从事件属性中提取完成内容 |
gen_ai.system.message | inputs.messages[] | 对话中的系统消息 |
gen_ai.user.message | inputs.messages[] | 对话中的用户消息 |
gen_ai.assistant.message | outputs.messages[] | 对话中的助手消息 |
gen_ai.tool.message | outputs.messages[] | 工具响应消息 |
gen_ai.choice | 输出 | 模型选择/响应,带结束原因 |
exception | status, error | 将状态设置为“error”并提取异常消息/堆栈追踪 |
对于消息事件,提取以下属性
content → 消息内容
role → 消息角色
id → tool_call_id (对于工具消息)
gen_ai.event.content → 完整消息 JSON
对于选择事件
finish_reason → 选择结束原因
message.content → 选择消息内容
message.role → 选择消息角色
tool_calls.{n}.id → 工具调用 ID
tool_calls.{n}.function.name → 工具函数名称
tool_calls.{n}.function.arguments → 工具函数参数
tool_calls.{n}.type → 工具调用类型
对于异常事件
exception.message → 错误消息
exception.stacktrace → 错误堆栈追踪(附加到消息)
实现示例
使用 LangSmith SDK 进行追踪
使用 LangSmith SDK 的 OpenTelemetry 帮助器配置导出
import asyncio
from langsmith.integrations.otel import configure
from google.adk import Runner
from google.adk.agents import LlmAgent
from google.adk.sessions import InMemorySessionService
from google.genai import types
# Configure LangSmith OpenTelemetry export (no OTEL env vars or headers needed)
configure(project_name="adk-otel-demo")
async def main():
agent = LlmAgent(
name="travel_assistant",
model="gemini-2.5-flash-lite",
instruction="You are a helpful travel assistant.",
)
session_service = InMemorySessionService()
runner = Runner(app_name="travel_app", agent=agent, session_service=session_service)
user_id = "user_123"
session_id = "session_abc"
await session_service.create_session(app_name="travel_app", user_id=user_id, session_id=session_id)
new_message = types.Content(parts=[types.Part(text="Hi! Recommend a weekend trip to Paris.")], role="user")
for event in runner.run(user_id=user_id, session_id=session_id, new_message=new_message):
print(event)
if __name__ == "__main__":
asyncio.run(main())
您不需要设置 OTEL 环境变量或导出器。configure() 会自动为 LangSmith 连接它们;仪表器(如 GoogleADKInstrumentor)会创建 span。
- 在您的 LangSmith 仪表板中查看追踪(示例)。
高级配置
使用 OpenTelemetry Collector 进行扇出
对于更高级的场景,您可以使用 OpenTelemetry Collector 将您的遥测数据扇出到多个目的地。这比在应用程序代码中配置多个导出器更具可扩展性。
-
为您的环境安装 OpenTelemetry Collector。
-
创建一个配置文件(例如,
otel-collector-config.yaml),该文件导出到多个目的地
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
otlphttp/langsmith:
endpoint: https://api.smith.langchain.com/otel/v1/traces
headers:
x-api-key: ${env:LANGSMITH_API_KEY}
Langsmith-Project: my_project
otlphttp/other_provider:
endpoint: https://otel.your-provider.com/v1/traces
headers:
api-key: ${env:OTHER_PROVIDER_API_KEY}
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp/langsmith, otlphttp/other_provider]
-
配置您的应用程序以发送到 Collector
import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# Point to your local OpenTelemetry Collector
otlp_exporter = OTLPSpanExporter(
endpoint="https://:4318/v1/traces"
)
provider = TracerProvider()
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# Set environment variables for LangChain
os.environ["LANGSMITH_OTEL_ENABLED"] = "true"
os.environ["LANGSMITH_TRACING"] = "true"
# Create and run a LangChain application
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
model = ChatOpenAI()
chain = prompt | model
result = chain.invoke({"topic": "programming"})
print(result.content)
这种方法有几个优点
- 所有遥测目的地的集中配置
- 减少应用程序代码中的开销
- 更好的可扩展性和弹性
- 无需更改应用程序代码即可添加或删除目的地
LangChain 和 OpenTelemetry 的分布式追踪
当您的 LLM 应用程序跨越多个服务或进程时,分布式追踪至关重要。OpenTelemetry 的上下文传播功能确保追踪在服务边界之间保持连接。
分布式追踪中的上下文传播
在分布式系统中,上下文传播在服务之间传递追踪元数据,以便相关的 span 链接到同一追踪
- 追踪 ID:整个追踪的唯一标识符
- Span ID:当前 span 的唯一标识符
- 采样决策:指示是否应采样此追踪
使用 LangChain 设置分布式追踪
要在多个服务中启用分布式追踪
import os
from opentelemetry import trace
from opentelemetry.propagate import inject, extract
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import requests
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# Set up OpenTelemetry trace provider
provider = TracerProvider()
otlp_exporter = OTLPSpanExporter(
endpoint="https://api.smith.langchain.com/otel/v1/traces",
headers={"x-api-key": os.getenv("LANGSMITH_API_KEY"), "Langsmith-Project": "my_project"}
)
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
# Service A: Create a span and propagate context to Service B
def service_a():
with tracer.start_as_current_span("service_a_operation") as span:
# Create a chain
prompt = ChatPromptTemplate.from_template("Summarize: {text}")
model = ChatOpenAI()
chain = prompt | model
# Run the chain
result = chain.invoke({"text": "OpenTelemetry is an observability framework"})
# Propagate context to Service B
headers = {}
inject(headers) # Inject trace context into headers
# Call Service B with the trace context
response = requests.post(
"http://service-b.example.com/process",
headers=headers,
json={"summary": result.content}
)
return response.json()
# Service B: Extract the context and continue the trace
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/process", methods=["POST"])
def service_b_endpoint():
# Extract the trace context from the request headers
context = extract(request.headers)
with tracer.start_as_current_span("service_b_operation", context=context) as span:
data = request.json
summary = data.get("summary", "")
# Process the summary with another LLM chain
prompt = ChatPromptTemplate.from_template("Analyze the sentiment of: {text}")
model = ChatOpenAI()
chain = prompt | model
result = chain.invoke({"text": summary})
return jsonify({"analysis": result.content})
if __name__ == "__main__":
app.run(port=5000)