本页面介绍如何控制 LangSmith 发送追踪的位置:
- - 静态设置目标项目
- - 动态设置目标项目
- - 动态设置目标工作区
- - 使用副本将追踪写入多个目标
静态设置目标项目
LangSmith 使用 项目 的概念来对追踪进行分组。如果未指定,项目将设置为 default.
您可以设置 LANGSMITH_PROJECT 环境变量来为整个应用程序运行配置自定义项目名称。在运行应用程序前设置此变量:
如果指定的项目不存在,LangSmith 将在首次摄入追踪时自动创建该项目。
动态设置目标项目
您还可以通过多种方式在程序运行时设置项目名称,具体取决于您 对代码进行追踪注解的方式。当您想要将追踪记录到同一应用程序中的不同项目时,这非常有用:
- - 在装饰或配置时传递项目名称。
- - 在每个单独调用时覆盖它。
- - 在直接构造运行时设置它。
from langsmith import traceable
from langsmith.run_trees import RunTree
client = openai.Client()
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
# Use the @traceable decorator with the 'project_name' parameter to log traces to LangSmith
# Ensure that the LANGSMITH_TRACING environment variables is set for @traceable to work
@traceable(
run_type="llm",
name="OpenAI Call Decorator",
project_name="My Project"
)
def call_openai(
messages: list[dict], model: str = "gpt-5.4-mini"
) -> str:
return client.chat.completions.create(
model=model,
messages=messages,
).choices[0].message.content
# Call the decorated function
call_openai(messages)
# You can also specify the Project via the project_name parameter
# This will override the project_name specified in the @traceable decorator
call_openai(
messages,
langsmith_extra={"project_name": "My Overridden Project"},
)
# The wrapped OpenAI client accepts all the same langsmith_extra parameters
# as @traceable decorated functions, and logs traces to LangSmith automatically.
# Ensure that the LANGSMITH_TRACING environment variables is set for the wrapper to work.
from langsmith import wrappers
wrapped_client = wrappers.wrap_openai(client)
wrapped_client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
langsmith_extra={"project_name": "My Project"},
)
# Alternatively, create a RunTree object
# You can set the project name using the project_name parameter
rt = RunTree(
run_type="llm",
name="OpenAI Call RunTree",
inputs={"messages": messages},
project_name="My Project"
)
chat_completion = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
)
# End and submit the run
rt.end(outputs=chat_completion)
rt.post()
const client = new OpenAI();
const messages = [
{role: "system", content: "You are a helpful assistant."},
{role: "user", content: "Hello!"}
];
const traceableCallOpenAI = traceable(async (messages: {role: string, content: string}[], model: string) => {
const completion = await client.chat.completions.create({
model: model,
messages: messages,
});
return completion.choices[0].message.content;
},{
run_type: "llm",
name: "OpenAI Call Traceable",
project_name: "My Project"
});
// Call the traceable function
await traceableCallOpenAI(messages, "gpt-5.4-mini");
// Create and use a RunTree object
const rt = new RunTree({
run_type: "llm",
name: "OpenAI Call RunTree",
inputs: { messages },
project_name: "My Project"
});
await rt.postRun();
// Execute a chat completion and handle it within RunTree
rt.end({outputs: chatCompletion});
await rt.patchRun();
/**
* Simple example: Send a single OpenTelemetry trace to LangSmith.
*
* Usage:
* export LANGSMITH_API_KEY=your_api_key
* export LANGSMITH_PROJECT=your_project_name # Optional, defaults to "default"
*/
public class OtelLangSmithSimpleExample {
public static void main(String[] args) throws Exception {
// Get API key and project name
String apiKey = System.getenv("LANGSMITH_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
System.err.println("ERROR: LANGSMITH_API_KEY environment variable is required!");
return;
}
String projectName = System.getenv("LANGSMITH_PROJECT");
if (projectName == null || projectName.isEmpty()) {
projectName = "default";
}
// Configure exporter
Map<String, String> headers = new HashMap<>();
headers.put("x-api-key", apiKey);
headers.put("Langsmith-Project", projectName);
OtelConfig config = OtelConfig.builder()
.enabled(true)
.endpoint("https://api.smith.langchain.com/otel/v1/traces")
.headers(headers)
.timeout(Duration.ofSeconds(30))
.serviceName("langsmith-java-simple")
.build();
OtelTraceExporter exporter = OtelTraceExporter.fromConfig(config);
Tracer tracer = exporter.getTracer();
// Create a simple span
Span span = OtelSpanCreator.createLlmSpan(
tracer, "simple.llm.call", "openai", "gpt-4", projectName, null);
try {
OtelSpanCreator.setInput(span, "Hello, world!");
Thread.sleep(100); // Simulate processing
OtelSpanCreator.setOutput(span, "Hello! How can I help you?");
OtelSpanCreator.setTokenUsage(span, 5, 8);
span.setStatus(StatusCode.OK);
} finally {
span.end();
}
// Flush and shutdown
exporter.flush().join(5, java.util.concurrent.TimeUnit.SECONDS);
exporter.shutdown().join(2, java.util.concurrent.TimeUnit.SECONDS);
System.out.println("✓ Trace sent to LangSmith!");
}
}
动态设置目标工作区
如果您需要根据运行时配置动态地将追踪路由到不同的 LangSmith 工作区 (例如,将不同用户或租户路由到单独的工作区),方法因语言而异:
- Python:使用具有
tracing_context. - TypeScript:传递自定义客户端至
traceable,或使用LangChainTracer与回调。
此方法对于多租户应用程序非常有用,您希望在工作区级别按客户、环境或团队隔离追踪。它适用于任何 LangSmith 兼容的追踪,包括 LangChain、OpenAI 以及使用 @traceable.
前提条件
- - A LangSmith API 密钥 具有对多个工作区的访问权限。
- - 每个目标工作区的 工作区 ID 。
跨工作区通用追踪
当您希望根据运行时逻辑(例如客户 ID、租户或环境)动态地将跟踪路由到不同的工作空间时,可使用此方法处理通用应用程序。
关键组件:
- 为每个工作空间初始化单独的
Client实例,并使用各自的workspace_id. - 使用
tracing_context(Python)或传递工作空间特定的clienttotraceable(TypeScript)来路由跟踪。 - 通过应用程序的运行时配置传递工作空间配置。
- 在每个路由中覆盖工作空间和项目名称,以进一步组织每个工作空间内的跟踪。
from langsmith import Client, traceable, tracing_context
# API key with access to multiple workspaces
api_key = os.getenv("LS_CROSS_WORKSPACE_KEY")
# Initialize clients for different workspaces
workspace_a_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id="" # e.g., "abc123..."
)
workspace_b_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id="" # e.g., "def456..."
)
# Example: Route based on customer ID
def get_workspace_client(customer_id: str):
"""Route to appropriate workspace based on customer."""
if customer_id.startswith("premium_"):
return workspace_a_client, "premium-customer-traces"
else:
return workspace_b_client, "standard-customer-traces"
@traceable
def process_request(data: dict, customer_id: str):
"""Process a customer request with workspace-specific tracing."""
# Your business logic here
return {"status": "success", "data": data}
# Use tracing_context to route to the appropriate workspace
def handle_customer_request(customer_id: str, request_data: dict):
client, project_name = get_workspace_client(customer_id)
# Everything within this context will be traced to the selected workspace
with tracing_context(enabled=True, client=client, project_name=project_name):
result = process_request(request_data, customer_id)
return result
# Example usage
handle_customer_request("premium_user_123", {"query": "Hello"})
handle_customer_request("standard_user_456", {"query": "Hi"})
// API key with access to multiple workspaces
const apiKey = process.env.LS_CROSS_WORKSPACE_KEY;
// Initialize clients for different workspaces
const workspaceAClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "abc123..."
});
const workspaceBClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "def456..."
});
// Example: Route based on customer ID
function getWorkspaceClient(customerId: string): {
client: Client;
projectName: string;
} {
if (customerId.startsWith("premium_")) {
return {
client: workspaceAClient,
projectName: "premium-customer-traces",
};
} else {
return {
client: workspaceBClient,
projectName: "standard-customer-traces",
};
}
}
// Route traces to the appropriate workspace by passing the client to traceable
async function handleCustomerRequest(
customerId: string,
requestData: Record<string, any>
) {
const { client, projectName } = getWorkspaceClient(customerId);
// Create a traceable function with the workspace-specific client
const processRequest = traceable(
async (data: Record<string, any>, customerId: string) => {
// Your business logic here
return { status: "success", data };
},
{
name: "process_request",
client,
project_name: projectName,
}
);
return await processRequest(requestData, customerId);
}
// Example usage
await handleCustomerRequest("premium_user_123", { query: "Hello" });
await handleCustomerRequest("standard_user_456", { query: "Hi" });
覆盖 LangSmith 部署的默认工作空间
当 将代理部署到 LangSmith 时 ,您可以使用图形生命周期上下文管理器来覆盖跟踪的默认目标工作空间。当您希望根据通过 config parameter.
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.state import RunnableConfig
from langsmith import Client, tracing_context
# API key with access to multiple workspaces
api_key = os.getenv("LS_CROSS_WORKSPACE_KEY")
# Initialize clients for different workspaces
workspace_a_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id=""
)
workspace_b_client = Client(
api_key=api_key,
api_url="https://api.smith.langchain.com",
workspace_id=""
)
# Define configuration schema for workspace routing
class Configuration(TypedDict):
workspace_id: str
# Define the graph state
class State(TypedDict):
response: str
def greeting(state: State, config: RunnableConfig) -> State:
"""Generate a workspace-specific greeting."""
workspace_id = config.get("configurable", {}).get("workspace_id", "workspace_a")
if workspace_id == "workspace_a":
response = "Hello from Workspace A!"
elif workspace_id == "workspace_b":
response = "Hello from Workspace B!"
else:
response = "Hello from the default workspace!"
return {"response": response}
# Build the base graph
base_graph = (
StateGraph(state_schema=State, config_schema=Configuration)
.add_node("greeting", greeting)
.set_entry_point("greeting")
.set_finish_point("greeting")
.compile()
)
@contextlib.asynccontextmanager
async def graph(config):
"""Dynamically route traces to different workspaces based on configuration."""
# Extract workspace_id from the configuration
workspace_id = config.get("configurable", {}).get("workspace_id", "workspace_a")
# Route to the appropriate workspace
if workspace_id == "workspace_a":
client = workspace_a_client
project_name = "production-traces"
elif workspace_id == "workspace_b":
client = workspace_b_client
project_name = "development-traces"
else:
client = workspace_a_client
project_name = "default-traces"
# Apply the tracing context for the selected workspace
with tracing_context(enabled=True, client=client, project_name=project_name):
yield base_graph
# Usage: Invoke with different workspace configurations
# await graph({"configurable": {"workspace_id": "workspace_a"}})
# await graph({"configurable": {"workspace_id": "workspace_b"}})
// API key with access to multiple workspaces
const apiKey = process.env.LS_CROSS_WORKSPACE_KEY;
// Initialize clients for different workspaces
const workspaceAClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "abc123..."
});
const workspaceBClient = new Client({
apiKey: apiKey,
apiUrl: "https://api.smith.langchain.com",
workspaceId: "", // e.g., "def456..."
});
// Define the graph state
const StateAnnotation = Annotation.Root({
response: Annotation<string>(),
});
async function greeting(state: typeof StateAnnotation.State, config: any) {
const workspaceId = config?.configurable?.workspace_id || "workspace_a";
let response: string;
if (workspaceId === "workspace_a") {
response = "Hello from Workspace A!";
} else if (workspaceId === "workspace_b") {
response = "Hello from Workspace B!";
} else {
response = "Hello from the default workspace!";
}
return { response };
}
// Build the base graph
const baseGraph = new StateGraph(StateAnnotation)
.addNode("greeting", greeting)
.addEdge("__start__", "greeting")
.addEdge("greeting", "__end__")
.compile();
// Helper to get workspace-specific client and project
function getWorkspaceConfig(workspaceId: string): {
client: Client;
projectName: string;
} {
if (workspaceId === "workspace_a") {
return { client: workspaceAClient, projectName: "production-traces" };
} else if (workspaceId === "workspace_b") {
return { client: workspaceBClient, projectName: "development-traces" };
}
return { client: workspaceAClient, projectName: "default-traces" };
}
// Invoke the graph with workspace-specific tracing
async function invokeWithWorkspaceTracing(
workspaceId: string,
input: typeof StateAnnotation.State
) {
const { client, projectName } = getWorkspaceConfig(workspaceId);
// Create a LangChainTracer with the workspace-specific client
const tracer = new LangChainTracer({
client,
projectName,
});
// Invoke the graph with the tracer attached via callbacks
// All traces will be routed to the selected workspace
return await baseGraph.invoke(input, {
configurable: { workspace_id: workspaceId },
callbacks: [tracer],
});
}
// Example usage
await invokeWithWorkspaceTracing("workspace_a", { response: "" });
await invokeWithWorkspaceTracing("workspace_b", { response: "" });
使用副本将跟踪写入多个目标
副本允许您同时将每条跟踪发送到多个项目或工作空间 **。**与每条跟踪只去往一个目的地的动态路由模式不同,副本会将跟踪并行复制到所有配置的目的地。
副本可用于:
- - 将生产跟踪镜像到暂存或个人项目中进行调试。
- - 写入多个工作空间以实现多租户隔离,而无需更改任何应用程序代码。
- - 将跟踪发送到同一服务器下的不同项目,并带有按副本的元数据覆盖。
通过环境变量配置副本
将 LANGSMITH_RUNS_ENDPOINTS 环境变量设置为 JSON 值。支持两种格式:
- 对象格式:将每个端点 URL 映射到其 API 密钥:
"https://api.smith.langchain.com": "ls__key_workspace_a",
"https://api.smith.langchain.com": "ls__key_workspace_b"
}'
- 数组格式:副本对象列表,当您需要多个指向同一 URL 的副本或想要设置
project_name每个副本时很有用:
{"api_url": "https://api.smith.langchain.com", "api_key": "ls__key1", "project_name": "project-prod"},
{"api_url": "https://api.smith.langchain.com", "api_key": "ls__key2", "project_name": "project-staging"}
]'
在运行时配置副本
您也可以直接在代码中传递副本,这在每个请求或租户的目的地不同时非常有用。
from langsmith import traceable, tracing_context
from langsmith.run_trees import WriteReplica, ApiKeyAuth
@traceable
def my_pipeline(query: str) -> str:
# Your application logic here
return f"Answer to: {query}"
replicas = [
WriteReplica(
api_url="https://api.smith.langchain.com",
auth=ApiKeyAuth(api_key="ls__key_workspace_a"),
project_name="project-prod",
),
WriteReplica(
api_url="https://api.smith.langchain.com",
auth=ApiKeyAuth(api_key="ls__key_workspace_b"),
project_name="project-staging",
# Optionally override fields on the replicated run
updates={"metadata": {"environment": "staging"}},
),
]
with tracing_context(replicas=replicas):
my_pipeline("What is LangSmith?")
const myPipeline = traceable(
async (query: string): Promise<string> => {
// Your application logic here
return `Answer to: ${query}`;
},
{
name: "my_pipeline",
replicas: [
{
apiUrl: "https://api.smith.langchain.com",
apiKey: "ls__key_workspace_a",
projectName: "project-prod",
},
{
apiUrl: "https://api.smith.langchain.com",
apiKey: "ls__key_workspace_b",
projectName: "project-staging",
// Optionally override fields on the replicated run
updates: { metadata: { environment: "staging" } },
},
],
}
);
await myPipeline("What is LangSmith?");
您也可以使用 updates 字段将附加字段(如 元数据或标签)合并到特定副本的运行中——主跟踪保持不变。副本错误不会导致致命问题:如果副本端点不可用,LangSmith 会记录错误而不影响主跟踪。
在同一服务器内复制(仅限项目副本)
如果所有副本使用相同的 LangSmith 服务器,您可以省略 api_url 和 auth 并仅指定 project_name。SDK 重用默认客户端凭据:
from langsmith import traceable, tracing_context
from langsmith.run_trees import WriteReplica
@traceable
def my_pipeline(query: str) -> str:
return f"Answer to: {query}"
with tracing_context(
replicas=[
WriteReplica(project_name="project-prod"),
WriteReplica(project_name="project-staging", updates={"metadata": {"env": "staging"}}),
]
):
my_pipeline("What is LangSmith?")
const myPipeline = traceable(
async (query: string) => `Answer to: ${query}`,
{
name: "my_pipeline",
replicas: [
{ projectName: "project-prod" },
{ projectName: "project-staging", updates: { metadata: { env: "staging" } } },
],
}
);
await myPipeline("What is LangSmith?");
在 LangSmith 和 OpenTelemetry 目标之间路由
您可以在运行时决定给定的调用是将追踪发送到 LangSmith、OpenTelemetry (OTel) 后端还是同时发送到两者,而无需重新部署或修改应用程序逻辑。这在您想要在每个环境甚至每个请求之间切换可观测性后端时很有用,在运行时做出决策。
使用 tracing_mode 构造函数参数或 LANGSMITH_TRACING_MODE 环境变量设置追踪模式。两者接受相同的值;显式 tracing_mode 参数始终优先于环境变量:
- - **
"langsmith"(默认)**:原生发送追踪到 LangSmith。 - - **
"otel"**:将追踪导出为配置好的 OTel 后端的 OpenTelemetry 跨度。 - - **
"hybrid"(仅 Python)**:从单个副本同时发送到 LangSmith 和 OTel 后端。
传递一个已配置的 Client 直接传入副本以在运行时应用所需模式:
from langsmith import Client, traceable, tracing_context
from langsmith.run_trees import WriteReplica
from langsmith.wrappers import wrap_openai
# Create clients with different tracing modes
ls_client = Client() # tracing_mode="langsmith" (default)
otel_client = Client(tracing_mode="otel") # tracing_mode="otel"
hybrid_client = Client(tracing_mode="hybrid") # tracing_mode="hybrid" (both)
openai_client = wrap_openai(openai.Client())
@traceable()
def joke():
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Tell me a short joke."}],
)
return response.choices[0].message.content
# Mix tracing modes across replicas in a single invocation:
# one replica sends via LangSmith's native format, another as OTel spans.
with tracing_context(replicas=[
WriteReplica(client=ls_client), # tracing_mode="langsmith"
WriteReplica(client=otel_client), # tracing_mode="otel"
]):
joke()
# Alternatively, a single hybrid replica sends to both simultaneously.
with tracing_context(replicas=[WriteReplica(client=hybrid_client)]):
joke()
# Swap replica lists at runtime — e.g. based on a feature flag or environment.
def get_replicas(send_to_otel: bool):
replicas = [WriteReplica(client=ls_client)]
if send_to_otel:
replicas.append(WriteReplica(client=otel_client))
return replicas
with tracing_context(replicas=get_replicas(send_to_otel=True)): # LangSmith + OTel
joke()
with tracing_context(replicas=get_replicas(send_to_otel=False)): # LangSmith only
joke()
// Note: tracingMode: "otel" requires OTel SDK initialization
// (TracerProvider, SpanProcessor, etc.) before creating the client.
// See the OpenTelemetry integration guide for setup details.
// Create clients with different tracing modes
const lsClient = new Client(); // tracingMode: "langsmith" (default)
const otelClient = new Client({ tracingMode: "otel" }); // tracingMode: "otel"
const openaiClient = wrapOpenAI(new OpenAI());
async function jokeImpl() {
const response = await openaiClient.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Tell me a short joke." }],
});
return response.choices[0].message.content;
}
// Mix tracing modes across replicas in a single traceable call:
// the primary client sends via LangSmith, the replica sends as OTel spans.
const joke = traceable(jokeImpl, {
name: "joke",
client: lsClient, // tracingMode: "langsmith" (default)
replicas: [{ client: otelClient }], // tracingMode: "otel"
});
await joke();
// Build replicas dynamically for runtime switching — e.g. based on a feature flag.
function buildReplicas(sendToOtel: boolean) {
return sendToOtel ? [{ client: otelClient }] : [];
}
const sendToOtel = process.env.ROUTE_TO_OTEL === "true";
const jokeDynamic = traceable(jokeImpl, {
name: "joke",
client: lsClient,
replicas: buildReplicas(sendToOtel),
});
await jokeDynamic();
该 tracing_mode 在每个 Client 决定该副本的导出路径。在 Python 中, "hybrid" 模式在单个副本内处理两个目标。在 TypeScript 中,"同时发送"的情况使用两个独立的副本,每个客户端一个,因为没有 "hybrid" 模式。由于每个副本独立解析其自己的客户端,您也可以在单个 tracing_context中混合模式,例如保持一个副本发送到 LangSmith,同时通过第二个副本将同一跟踪转发到 OTel 收集器。