主题
使用 Temporal 进行追踪
学习如何使用 OpenTelemetry 在 LangSmith 中追踪 Temporal 工作流和活动。
Temporal 是一个持久执行平台,它让开发者能够构建有韧性的分布式应用。本指南展示如何使用 OpenTelemetry 在 LangSmith 中追踪 Temporal 工作流和活动。
LangSmith 支持 OpenTelemetry(OTEL)追踪摄取,它与 Temporal 原生的 OpenTelemetry 拦截器无缝集成。这能够跨你的工作流执行、活动以及其中的任何 LLM 调用实现完整的分布式追踪。
前置条件
- 一个 LangSmith 账户和 API 密钥
- 正在运行的 Temporal 服务器(本地或云)
- 适用于你所用语言的 OpenTelemetry SDK
环境变量
为所有实现设置以下环境变量:
| 变量 | 必需 | 描述 |
|---|---|---|
LANGSMITH_API_KEY | 是 | 你从 Settings 获得的 LangSmith API 密钥。 |
LANGSMITH_PROJECT | 否 | 项目名称(默认为 "default")。 |
NOTE
对于区域 SaaS 或自托管 LangSmith 安装,还要将 LANGCHAIN_BASE_URL 设为你的 LangSmith 实例 URL。
设置追踪
Go 使用 langsmith-go SDK 配合 Temporal 的 OpenTelemetry 拦截器,自动追踪工作流和活动。
Install
安装 LangSmith Go SDK、Temporal SDK 和 OpenTelemetry 拦截器:
bash
go get github.com/langchain-ai/langsmith-go@v0.1.0-alpha.7
go get go.temporal.io/sdk
go get go.temporal.io/sdk/contrib/opentelemetryInitialize tracer
初始化 LangSmith tracer,创建 Temporal 的 OpenTelemetry 拦截器,并将其注册到 Temporal 客户端和 worker:
go
package main
import (
"context"
"log"
"github.com/langchain-ai/langsmith-go"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/contrib/opentelemetry"
"go.temporal.io/sdk/interceptor"
"go.temporal.io/sdk/worker"
)
func main() {
ctx := context.Background()
// Initialize LangSmith tracer (reads LANGSMITH_API_KEY and LANGSMITH_PROJECT)
ls, err := langsmith.NewTracer(
langsmith.WithServiceName("temporal-worker"),
)
if err != nil {
log.Fatal("Failed to initialize LangSmith tracer:", err)
}
defer ls.Shutdown(ctx)
// Create Temporal tracing interceptor
tracer := ls.Tracer("temporal-app")
tracingInterceptor, err := opentelemetry.NewTracingInterceptor(
opentelemetry.TracerOptions{Tracer: tracer},
)
if err != nil {
log.Fatal("Failed to create tracing interceptor:", err)
}
// Create Temporal client with tracing
c, err := client.Dial(client.Options{
Interceptors: []interceptor.ClientInterceptor{tracingInterceptor},
})
if err != nil {
log.Fatal("Failed to create Temporal client:", err)
}
defer c.Close()
// Create worker with tracing (uses same client)
w := worker.New(c, "my-task-queue", worker.Options{})
w.RegisterWorkflow(MyWorkflow)
w.RegisterActivity(MyActivity)
// Start worker
if err := w.Run(worker.InterruptCh()); err != nil {
log.Fatal("Worker failed:", err)
}
}Define workflow and activity
定义一个执行活动的工作流。该活动演示如何为 LangSmith 可见性添加自定义 span 属性:
go
package main
import (
"context"
"fmt"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.temporal.io/sdk/activity"
"go.temporal.io/sdk/workflow"
)
// MyWorkflow executes an activity
func MyWorkflow(ctx workflow.Context, input string) (string, error) {
ao := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Second,
}
ctx = workflow.WithActivityOptions(ctx, ao)
var result string
err := workflow.ExecuteActivity(ctx, MyActivity, input).Get(ctx, &result)
return result, err
}
// MyActivity processes input with custom span attributes
func MyActivity(ctx context.Context, input string) (string, error) {
logger := activity.GetLogger(ctx)
logger.Info("Processing", "input", input)
// Get the span created by Temporal's interceptor
span := trace.SpanFromContext(ctx)
// Add Gen AI attributes for LangSmith visibility
span.SetAttributes(
attribute.String("gen_ai.prompt", input),
attribute.String("gen_ai.operation.name", "chat"),
)
result := fmt.Sprintf("Processed: %s", input)
// Set completion attribute
span.SetAttributes(
attribute.String("gen_ai.completion", result),
)
return result, nil
}Execute workflow
在一个单独的客户端应用中,初始化 tracer 并执行工作流:
go
// In a separate function or client application
func executeWorkflow() {
ctx := context.Background()
// Initialize tracer for client
ls, err := langsmith.NewTracer(
langsmith.WithServiceName("temporal-client"),
)
if err != nil {
log.Fatal(err)
}
defer ls.Shutdown(ctx)
// Create client with tracing
tracer := ls.Tracer("temporal-app")
tracingInterceptor, err := opentelemetry.NewTracingInterceptor(
opentelemetry.TracerOptions{Tracer: tracer},
)
if err != nil {
log.Fatal(err)
}
c, err := client.Dial(client.Options{
Interceptors: []interceptor.ClientInterceptor{tracingInterceptor},
})
if err != nil {
log.Fatal(err)
}
defer c.Close()
// Execute workflow
workflowOptions := client.StartWorkflowOptions{
ID: "my-workflow-1",
TaskQueue: "my-task-queue",
}
we, err := c.ExecuteWorkflow(ctx, workflowOptions, MyWorkflow, "Hello World")
if err != nil {
log.Fatal(err)
}
var result string
if err := we.Get(ctx, &result); err != nil {
log.Fatal(err)
}
log.Printf("Workflow result: %s", result)
}在 LangSmith 中查看追踪
一旦配置完成,追踪就会出现在你的 LangSmith 项目中:
- 导航到你的 LangSmith 实例。
- 选择你的项目。
- 在 Tracing 标签中查看追踪。
- 点击各个追踪以查看完整的 span 层级。
配置选项
设置自定义服务名
设置一个自定义服务名以区分不同的 Temporal worker 或服务:
go
ls, err := langsmith.NewTracer(
langsmith.WithServiceName("my-temporal-worker"),
)python
resource = Resource.create({
SERVICE_NAME: "my-temporal-worker",
})typescript
const provider = new NodeTracerProvider({
resource: new Resource({
[ATTR_SERVICE_NAME]: 'my-temporal-worker',
}),
});添加自定义 span 属性
添加自定义属性以丰富你的追踪:
go
import "go.opentelemetry.io/otel/attribute"
span := trace.SpanFromContext(ctx)
span.SetAttributes(
attribute.String("user.id", userID),
attribute.String("workflow.version", "v2"),
)python
from opentelemetry import trace
span = trace.get_current_span()
span.set_attribute("user.id", user_id)
span.set_attribute("workflow.version", "v2")typescript
import { trace } from '@opentelemetry/api';
const span = trace.getActiveSpan();
span?.setAttribute('user.id', userId);
span?.setAttribute('workflow.version', 'v2');配置采样
对于高吞吐量的工作流,配置采样以减少追踪量:
go
// Note: langsmith.NewTracer() uses default sampling
// For custom sampling, use the TracerProvider directly
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)), // 10% sampling
)python
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
provider = TracerProvider(
resource=resource,
sampler=TraceIdRatioBased(0.1), # 10% sampling
)typescript
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
const provider = new NodeTracerProvider({
resource: resource,
sampler: new TraceIdRatioBasedSampler(0.1), // 10% sampling
});故障排查
追踪未出现
- 核实 API 密钥:确保
LANGSMITH_API_KEY设置正确 - 检查端点:确认你使用的是
https://api.smith.langchain.com/otel/v1/traces - 关闭时刷新:调用
provider.shutdown()以在应用退出前刷新待处理的 span - 检查项目:核实追踪被发送到正确的项目(默认为
"default")
活动 span 缺失
确保追踪拦截器同时在客户端和 worker 上都已配置:
- 客户端:需要用于启动工作流的拦截器
- worker:需要用于执行活动的拦截器
上下文传播问题
核实传播器(propagator)配置正确:
- Go:
langsmith.NewTracer()会自动配置传播器 - Python/TypeScript:确保 OpenTelemetry SDK 已用追踪传播器正确初始化
worker 关闭挂起
如果追踪没有刷新,请确保你用合适的超时调用了关闭方法:
go
defer ls.Shutdown(context.Background())python
finally:
provider.shutdown()typescript
finally {
await provider.shutdown();
}后续步骤
其他资源
- Temporal 文档
- Temporal Go SDK
- Temporal Python SDK
- Temporal TypeScript SDK
- OpenTelemetry 文档
- LangSmith Go SDK