主题
自定义埋点
直接在你的代码中进行埋点,以控制哪些函数会被追踪以及它们在 LangSmith 中的呈现方式。
直接向你的代码添加埋点,可以让你精确控制应用追踪哪些函数、记录哪些输入和输出,以及你的追踪层级结构如何组织。三种核心埋点方式是:
@traceable装饰器:推荐用于大多数场景trace上下文管理器:仅限 PythonRunTreeAPI:显式、低层级的控制
本页还涵盖:
- 指定自定义运行 ID,这对于在运行后立即附加反馈或与外部系统关联非常有用。
- 在你的进程退出前确保所有追踪都已提交。
对于 LangChain(Python 或 JS/TS),请参阅针对 LangChain 的说明。
INFO
如果你使用的 LLM 服务商或智能体框架带有内置的 LangSmith 集成,请改为参阅集成概览
前置条件
在开始追踪之前,请设置以下环境变量:
LANGSMITH_TRACING=true:启用追踪。设置此项即可在不更改代码的情况下开启或关闭追踪。
NOTE
LANGSMITH_TRACING 控制 @traceable 装饰器和 trace 上下文管理器。要在不更改环境变量的情况下在运行时为 @traceable 覆盖此设置,请使用 tracing_context(enabled=True/False)(Python),或直接将 tracingEnabled 传给 traceable(JS/TS)。RunTree 对象不受上述任何控制的影响;它们在提交时始终会将数据发送到 LangSmith。
LANGSMITH_API_KEY:你的 LangSmith API key。默认情况下,LangSmith 会将追踪记录到名为
default的项目中。要记录到其他项目,请设置LANGSMITH_PROJECT。更多详情请参阅将追踪记录到特定项目。
使用 @traceable / traceable
将 @traceable(Python)、traceable(TypeScript)、traceable(Kotlin)或 Tracing.traceFunction(Java)应用于任意函数,即可使其成为可被追踪的运行。LangSmith 会自动处理嵌套调用之间的上下文传播。
以下示例追踪一个简单的流水线:run_pipeline 调用 format_prompt 来构建消息,调用 invoke_llm 来调用模型,并调用 parse_output 来提取结果。
每个函数都会被单独追踪,并且由于它们是从 run_pipeline(同样被追踪)内部调用的,LangSmith 会自动将它们嵌套为子运行。invoke_llm 使用 run_type="llm" 将其标记为 LLM 调用,以便 LangSmith 能正确渲染 token 数和延迟:
python
from langsmith import traceable
from openai import Client
openai = Client()
@traceable
def format_prompt(subject):
return [
{
"role": "system",
"content": "You are a helpful assistant.",
},
{
"role": "user",
"content": f"What's a good name for a store that sells {subject}?"
}
]
@traceable(run_type="llm")
def invoke_llm(messages):
return openai.chat.completions.create(
messages=messages, model="gpt-5.4-mini", temperature=0
)
@traceable
def parse_output(response):
return response.choices[0].message.content
@traceable
def run_pipeline():
messages = format_prompt("colorful socks")
response = invoke_llm(messages)
return parse_output(response)
run_pipeline()typescript
import { traceable } from "langsmith/traceable";
import OpenAI from "openai";
const openai = new OpenAI();
const formatPrompt = traceable((subject: string) => {
return [
{
role: "system" as const,
content: "You are a helpful assistant.",
},
{
role: "user" as const,
content: `What's a good name for a store that sells ${subject}?`,
},
];
},{ name: "formatPrompt" });
const invokeLLM = traceable(
async ({ messages }: { messages: { role: string; content: string }[] }) => {
return openai.chat.completions.create({
model: "gpt-5.4-mini",
messages: messages,
temperature: 0,
});
},
{ run_type: "llm", name: "invokeLLM" }
);
const parseOutput = traceable(
(response: any) => {
return response.choices[0].message.content;
},
{ name: "parseOutput" }
);
const runPipeline = traceable(
async () => {
const messages = await formatPrompt("colorful socks");
const response = await invokeLLM({ messages });
return parseOutput(response);
},
{ name: "runPipeline" }
);
await runPipeline();java
import com.langchain.smith.tracing.RunType;
import com.langchain.smith.tracing.TraceConfig;
import com.langchain.smith.tracing.Tracing;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.ChatModel;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionMessageParam;
import com.openai.models.chat.completions.ChatCompletionSystemMessageParam;
import com.openai.models.chat.completions.ChatCompletionUserMessageParam;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class TraceablePipeline {
public static void main(String[] args) {
new TraceablePipelineRunner().run();
}
private static final class TraceablePipelineRunner {
private final OpenAIClient openai = OpenAIOkHttpClient.fromEnv();
private final Function<String, List<ChatCompletionMessageParam>> formatPrompt =
Tracing.traceFunction(
subject ->
Arrays.asList(
ChatCompletionMessageParam.ofSystem(
ChatCompletionSystemMessageParam.builder()
.content("You are a helpful assistant.")
.build()),
ChatCompletionMessageParam.ofUser(
ChatCompletionUserMessageParam.builder()
.content("What's a good name for a store that sells " + subject + "?")
.build())),
TraceConfig.builder().name("format_prompt").build());
private final Function<List<ChatCompletionMessageParam>, ChatCompletion> invokeLlm =
Tracing.traceFunction(
messages ->
openai.chat()
.completions()
.create(
ChatCompletionCreateParams.builder()
.model(ChatModel.GPT_5_5)
.messages(messages)
.build()),
TraceConfig.builder().name("invoke_llm").runType(RunType.LLM).build());
private final Function<ChatCompletion, String> parseOutput =
Tracing.traceFunction(
response -> response.choices().get(0).message().content().orElse(""),
TraceConfig.builder().name("parse_output").build());
private final Function<String, String> runPipeline =
Tracing.traceFunction(
subject -> parseOutput.apply(invokeLlm.apply(formatPrompt.apply(subject))),
TraceConfig.builder().name("run_pipeline").build());
void run() {
runPipeline.apply("colorful socks");
}
}
}View example trace
打开本示例的一个公开 LangSmith 运行记录。
kotlin
import com.langchain.smith.tracing.RunType
import com.langchain.smith.tracing.TraceConfig
import com.langchain.smith.tracing.traceable
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatModel
import com.openai.models.chat.completions.ChatCompletion
import com.openai.models.chat.completions.ChatCompletionCreateParams
import com.openai.models.chat.completions.ChatCompletionMessageParam
import com.openai.models.chat.completions.ChatCompletionSystemMessageParam
import com.openai.models.chat.completions.ChatCompletionUserMessageParam
import kotlin.jvm.optionals.getOrNull
val openai = OpenAIOkHttpClient.fromEnv()
val formatPrompt =
traceable(
{ subject: String ->
listOf(
ChatCompletionMessageParam.ofSystem(
ChatCompletionSystemMessageParam.builder()
.content("You are a helpful assistant.")
.build(),
),
ChatCompletionMessageParam.ofUser(
ChatCompletionUserMessageParam.builder()
.content("What's a good name for a store that sells $subject?")
.build(),
),
)
},
TraceConfig.builder().name("format_prompt").build(),
)
val invokeLlm =
traceable(
{ messages: List<ChatCompletionMessageParam> ->
openai.chat().completions().create(
ChatCompletionCreateParams.builder()
.model(ChatModel.GPT_5_5)
.messages(messages)
.build(),
)
},
TraceConfig.builder().name("invoke_llm").runType(RunType.LLM).build(),
)
val parseOutput =
traceable(
{ response: ChatCompletion ->
response.choices()[0].message().content().getOrNull().orEmpty()
},
TraceConfig.builder().name("parse_output").build(),
)
val runPipeline =
traceable(
{ subject: String -> parseOutput(invokeLlm(formatPrompt(subject))) },
TraceConfig.builder().name("run_pipeline").build(),
)
println(runPipeline("colorful socks"))View example trace
打开本示例的一个公开 LangSmith 运行记录。
在 UI 中,你会看到一个 run_pipeline 追踪,其中 format_prompt、invoke_llm 和 parse_output 作为嵌套的子运行出现。
NOTE
当你用 traceable 包装一个同步函数时(例如前一个示例中的 formatPrompt),调用它时请使用 await 关键字,以确保追踪被正确记录。
使用 trace 上下文管理器(仅限 Python)
在 Python 中,你可以使用 trace 上下文管理器将追踪记录到 LangSmith。这在以下情况下很有用:
- 你想为特定的代码块记录追踪。
- 你想控制追踪的输入、输出和其他属性。
- 无法使用装饰器或包装器。
- 以上任意一项或全部。
该上下文管理器可与 traceable 装饰器和 wrap_openai 包装器无缝集成,因此你可以在同一个应用中一起使用它们。
以下示例展示了三者一起使用的情况。wrap_openai 包装 OpenAI 客户端,使其调用被自动追踪。my_tool 使用 @traceable 并设置 run_type="tool" 和自定义 name,以便在追踪中正确显示。chat_pipeline 本身没有装饰;而是由 ls.trace 包装调用,让你可以显式传入项目名和输入,并通过 rt.end() 手动设置输出:
python
import openai
import langsmith as ls
from langsmith.wrappers import wrap_openai
client = wrap_openai(openai.Client())
@ls.traceable(run_type="tool", name="Retrieve Context")
def my_tool(question: str) -> str:
return "During this morning's meeting, we solved all world conflict."
def chat_pipeline(question: str):
context = my_tool(question)
messages = [
{ "role": "system", "content": "You are a helpful assistant. Please respond to the user's request only based on the given context." },
{ "role": "user", "content": f"Question: {question}\nContext: {context}"}
]
chat_completion = client.chat.completions.create(
model="gpt-5.4-mini", messages=messages
)
return chat_completion.choices[0].message.content
app_inputs = {"input": "Can you summarize this morning's meetings?"}
with ls.trace("Chat Pipeline", "chain", project_name="my_test", inputs=app_inputs) as rt:
output = chat_pipeline("Can you summarize this morning's meetings?")
rt.end(outputs={"output": output})使用 RunTree API
另一种更显式地将追踪记录到 LangSmith 的方式是通过 RunTree API。该 API 让你对追踪拥有更多控制。你可以手动创建运行和子运行来组装你的追踪。你仍然需要设置 LANGSMITH_API_KEY,但此方法不需要 LANGSMITH_TRACING。
大多数用例不推荐使用此方法;与自动处理上下文传播的 @traceable 相比,手动管理追踪上下文容易出错。
python
import openai
from langsmith.run_trees import RunTree
# This can be a user input to your app
question = "Can you summarize this morning's meetings?"
# Create a top-level run
pipeline = RunTree(
name="Chat Pipeline",
run_type="chain",
inputs={"question": question}
)
pipeline.post()
# This can be retrieved in a retrieval step
context = "During this morning's meeting, we solved all world conflict."
messages = [
{ "role": "system", "content": "You are a helpful assistant. Please respond to the user's request only based on the given context." },
{ "role": "user", "content": f"Question: {question}\nContext: {context}"}
]
# Create a child run
child_llm_run = pipeline.create_child(
name="OpenAI Call",
run_type="llm",
inputs={"messages": messages},
)
child_llm_run.post()
# Generate a completion
client = openai.Client()
chat_completion = client.chat.completions.create(
model="gpt-5.4-mini", messages=messages
)
# End the runs and log them
child_llm_run.end(outputs=chat_completion)
child_llm_run.patch()
pipeline.end(outputs={"answer": chat_completion.choices[0].message.content})
pipeline.patch()typescript
import OpenAI from "openai";
import { RunTree } from "langsmith";
// This can be a user input to your app
const question = "Can you summarize this morning's meetings?";
const pipeline = new RunTree({
name: "Chat Pipeline",
run_type: "chain",
inputs: { question }
});
await pipeline.postRun();
// This can be retrieved in a retrieval step
const context = "During this morning's meeting, we solved all world conflict.";
const messages = [
{ role: "system", content: "You are a helpful assistant. Please respond to the user's request only based on the given context." },
{ role: "user", content: `Question: ${question}Context: ${context}` }
];
// Create a child run
const childRun = await pipeline.createChild({
name: "OpenAI Call",
run_type: "llm",
inputs: { messages },
});
await childRun.postRun();
// Generate a completion
const client = new OpenAI();
const chatCompletion = await client.chat.completions.create({
model: "gpt-5.4-mini",
messages: messages,
});
// End the runs and log them
childRun.end(chatCompletion);
await childRun.patchRun();
pipeline.end({ outputs: { answer: chatCompletion.choices[0].message.content } });
await pipeline.patchRun();java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import com.langchain.smith.tracing.RunTree;
import com.langchain.smith.tracing.RunType;
import com.langchain.smith.tracing.TraceConfig;
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.ChatModel;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionMessageParam;
import com.openai.models.chat.completions.ChatCompletionSystemMessageParam;
import com.openai.models.chat.completions.ChatCompletionUserMessageParam;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class RunTreeExample {
public static void main(String[] args) throws InterruptedException {
LangsmithClient langsmith = LangsmithOkHttpClient.fromEnv();
OpenAIClient openai = OpenAIOkHttpClient.fromEnv();
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
String question = "Can you summarize this morning's meetings?";
String runId = "01990f3e-7f97-74c5-a9b6-8d3f7e8e2f11";
RunTree pipeline = RunTree.builder()
.id(runId)
.name("Chat Pipeline")
.runType(RunType.CHAIN)
.inputs(Collections.singletonMap("question", question))
.client(langsmith)
.executor(executor)
.build();
pipeline.postRun();
String context = "During this morning's meeting, we solved all world conflict.";
List<ChatCompletionMessageParam> messages = Arrays.asList(
ChatCompletionMessageParam.ofSystem(
ChatCompletionSystemMessageParam.builder()
.content(
"You are a helpful assistant. Please respond to the user's " +
"request only based on the given context.")
.build()),
ChatCompletionMessageParam.ofUser(
ChatCompletionUserMessageParam.builder()
.content("Question: " + question + "\nContext: " + context)
.build()));
RunTree childRun = pipeline.createChild(
TraceConfig.builder().name("OpenAI Call").runType(RunType.LLM).build());
childRun.setInputs(Collections.singletonMap("messages", messages));
childRun.postRun();
ChatCompletion chatCompletion = openai.chat().completions().create(
ChatCompletionCreateParams.builder()
.model(ChatModel.GPT_5_5)
.messages(messages)
.build());
String answer = chatCompletion.choices().get(0).message().content().orElse("");
System.out.println(answer);
childRun.setOutputs(Collections.singletonMap("response", chatCompletion.toString()));
childRun.setEndTime(Instant.now().toString());
childRun.patchRun();
pipeline.setOutputs(Collections.singletonMap(
"answer", answer));
pipeline.setEndTime(Instant.now().toString());
pipeline.patchRun();
} finally {
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
throw new IllegalStateException(
"Timed out waiting for LangSmith traces to submit");
}
}
}
}kotlin
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient
import com.langchain.smith.tracing.RunTree
import com.langchain.smith.tracing.RunType
import com.langchain.smith.tracing.TraceConfig
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatModel
import com.openai.models.chat.completions.ChatCompletionCreateParams
import com.openai.models.chat.completions.ChatCompletionMessageParam
import com.openai.models.chat.completions.ChatCompletionSystemMessageParam
import com.openai.models.chat.completions.ChatCompletionUserMessageParam
import java.time.Instant
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
val langsmith = LangsmithOkHttpClient.fromEnv()
val openai = OpenAIOkHttpClient.fromEnv()
val executor = Executors.newSingleThreadExecutor()
try {
val question = "Can you summarize this morning's meetings?"
val runId = "01990f3e-7f97-74c5-a9b6-8d3f7e8e2f11"
val pipeline =
RunTree.builder()
.id(runId)
.name("Chat Pipeline")
.runType(RunType.CHAIN)
.inputs(mapOf("question" to question))
.client(langsmith)
.executor(executor)
.build()
println("[run-tree-example] Posting parent run to LangSmith…")
pipeline.postRun()
val context = "During this morning's meeting, we solved all world conflict."
val messages =
listOf(
ChatCompletionMessageParam.ofSystem(
ChatCompletionSystemMessageParam.builder()
.content(
"You are a helpful assistant. Please respond to the user's " +
"request only based on the given context.",
)
.build(),
),
ChatCompletionMessageParam.ofUser(
ChatCompletionUserMessageParam.builder()
.content("Question: $question\nContext: $context")
.build(),
),
)
val childRun =
pipeline.createChild(
TraceConfig.builder().name("OpenAI Call").runType(RunType.LLM).build(),
)
childRun.inputs = mapOf("messages" to messages)
println("[run-tree-example] Posting child run to LangSmith…")
childRun.postRun()
val chatCompletion =
openai.chat().completions().create(
ChatCompletionCreateParams.builder()
.model(ChatModel.GPT_5_5)
.messages(messages)
.build(),
)
val answer = chatCompletion.choices()[0].message().content().orElse("")
println("[run-tree-example] Answer:")
println(answer)
childRun.outputs = mapOf("response" to chatCompletion.toString())
childRun.endTime = Instant.now().toString()
childRun.patchRun()
pipeline.outputs =
mapOf(
"answer" to answer,
)
pipeline.endTime = Instant.now().toString()
pipeline.patchRun()
} finally {
executor.shutdown()
check(executor.awaitTermination(10, TimeUnit.SECONDS)) {
"Timed out waiting for LangSmith traces to submit"
}
}Java 和 Kotlin 示例使用了自定义根运行 ID 和专用执行器。关闭执行器并等待其终止,可确保后台的运行提交在进程退出前完成。
用法示例
你可以扩展上一节中讲解的工具类来追踪任意代码。以下代码展示了一些扩展示例。
追踪类中的任意公共方法:
python
from typing import Any, Callable, Type, TypeVar
T = TypeVar("T")
def traceable_cls(cls: Type[T]) -> Type[T]:
"""Instrument all public methods in a class."""
def wrap_method(name: str, method: Any) -> Any:
if callable(method) and not name.startswith("__"):
return traceable(name=f"{cls.__name__}.{name}")(method)
return method
# Handle __dict__ case
for name in dir(cls):
if not name.startswith("_"):
try:
method = getattr(cls, name)
setattr(cls, name, wrap_method(name, method))
except AttributeError:
# Skip attributes that can't be set (e.g., some descriptors)
pass
# Handle __slots__ case
if hasattr(cls, "__slots__"):
for slot in cls.__slots__: # type: ignore[attr-defined]
if not slot.startswith("__"):
try:
method = getattr(cls, slot)
setattr(cls, slot, wrap_method(slot, method))
except AttributeError:
# Skip slots that don't have a value yet
pass
return cls
@traceable_cls
class MyClass:
def __init__(self, some_val: int):
self.some_val = some_val
def combine(self, other_val: int):
return self.some_val + other_val
# See trace: https://smith.langchain.com/public/882f9ecf-5057-426a-ae98-0edf84fdcaf9/r
MyClass(13).combine(29)指定自定义运行 ID
默认情况下,LangSmith 会为每次运行分配一个随机 ID。当你需要提前知道运行 ID(例如,在运行后立即附加反馈)、将 LangSmith 运行与外部系统的 ID 关联,或使用确定性 ID 让运行具有幂等性时,你可以覆盖此 ID。
NOTE
为自定义运行 ID 使用 UUID v7。UUIDv7 内嵌时间戳,可保留追踪中运行的正确时间顺序。LangSmith SDK 导出一个 uuid7 辅助函数(Python v0.4.43+,JS v0.3.80+):
- Python:
from langsmith import uuid7 - JS/TS:
import { uuid7 } from 'langsmith'
接受任何 UUID v7 字符串——你可以使用 SDK 辅助函数,如果你的系统已经使用 UUID v7 标识符,也可以使用你自己的。
使用以下方式之一:
@traceable:调用@traceable函数时在langsmith_extra中传入run_id(Python),或在传给traceable的配置对象中传入id(TypeScript):
python
from langsmith import traceable, uuid7
@traceable
def my_pipeline(question: str) -> str:
return "answer"
run_id = uuid7()
my_pipeline("What is the capital of France?", langsmith_extra={"run_id": run_id})
# run_id can now be used to attach feedback, query the run, etc.typescript
import { traceable } from "langsmith/traceable";
import { uuid7 } from "langsmith";
const runId = uuid7();
const myPipeline = traceable(
async (question: string) => {
return "answer";
},
{ name: "my-pipeline", id: runId }
);
await myPipeline("What is the capital of France?");
// runId can now be used to attach feedback, query the run, etc.trace上下文管理器(仅限 Python):直接将run_id传给 trace 上下文管理器构造函数:pythonfrom langsmith import trace, uuid7 run_id = uuid7() with trace("my-pipeline", run_id=run_id) as run: result = "answer" run.end(outputs={"result": result}) # run_id can now be used to attach feedback, query the run, etc.
在退出前确保所有追踪都已提交
LangSmith 在后台线程中执行追踪,以避免妨碍你的生产应用。这意味着你的进程可能会在所有追踪成功提交到 LangSmith 之前就结束。请参考以下选项:
- 如果你使用的是 LangChain,请参阅 LangChain 追踪指南。
- 如果你单独使用 LangSmith SDK,可以在退出前使用
flush方法:
python
from langsmith import Client
client = Client()
@traceable(client=client)
async def my_traced_func():
# Your code here...
pass
try:
await my_traced_func()
finally:
await client.flush()typescript
import { Client } from "langsmith";
const langsmithClient = new Client({});
const myTracedFunc = traceable(async () => {
// Your code here...
},{ client: langsmithClient });
try {
await myTracedFunc();
} finally {
await langsmithClient.flush();
}相关
- 可观测性概念:关于运行、追踪和 LangSmith 数据模型的背景知识
- 运行(跨度)数据格式:运行字段的 schema 参考,包括
dotted_order、trace_id和parent_run_id - 使用 SDK 记录用户反馈:预先指定运行 ID 的常见用例
- 在被追踪函数中访问当前运行(跨度):从追踪内部读取或修改当前活动运行
- 将追踪记录到特定项目:将追踪路由到指定名称的项目,而不是
default - 使用 API 进行追踪:SDK 的低层级 REST API 替代方案
- 追踪基础视频,来自 Introduction to LangSmith 课程