主题
记录 LLM 调用
当你直接在 LangChain 或 LangSmith 支持的集成之外调用 LLM 时,需要提供特定元数据,以便 LangSmith 显示 token 计数、计算成本,并让你以正确的提供程序和模型打开运行,并在 Playground 中查看。
完全可用的 LLM 追踪有四项要求:
| 要求 | 要做什么 | 启用的功能 |
|---|---|---|
1. 设置 run_type="llm" | 将 run_type="llm" 传给 @traceable | LLM 专用渲染、token/成本显示 |
| 2. 格式化输入/输出 | 使用 OpenAI、Anthropic 或 LangChain 消息格式 | 结构化消息渲染、Playground 支持 |
3. 设置 ls_provider 和 ls_model_name | 在 metadata 中传入两者 | 成本跟踪、Playground 模型选择 |
| 4. 提供 token 计数 | 在运行上设置 usage_metadata | token 计数和成本计算 |
NOTE
如果你使用 LangChain OSS、OpenAI 包装器或 Anthropic 包装器,这些细节会自动处理。
本页示例使用 traceable 装饰器/包装器(Python 和 JS/TS 的推荐方式)。如果你直接使用 RunTree 或 API,也适用相同的要求。
消息格式
追踪自定义模型或自定义输入/输出格式时,它必须遵循 LangChain 格式、OpenAI completions 格式或 Anthropic messages 格式。有关更多细节,请参阅 OpenAI Chat Completions 或 Anthropic Messages 文档。LangChain 格式如下:
LangChain format
messagesarray必填
包含对话内容的消息列表。
rolestring必填
标识消息类型。以下之一:system | reasoning | user | assistant | tool
contentarray必填
消息内容。类型化字典的列表。
内容选项
typestring必填
以下之一:text | image | file | audio | video | tool_call | server_tool_call | server_tool_result。
text
typeliteral('text')必填
textstring必填
文本内容。
annotationsobject[]
文本的注解列表
extrasobject
其他提供程序特定数据。
reasoning
typeliteral('reasoning')必填
textstring必填
文本内容。
extrasobject
其他提供程序特定数据。
image
typeliteral('image')必填
urlstring
指向图像位置的 URL。
base64string必填
Base64 编码的图像数据。
idstring
指向外部存储图像(例如提供程序的文件系统或存储桶中)的引用 ID。
mime_typestring
图像 MIME 类型(例如 image/jpeg、image/png)。
文件(例如 PDF)
typeliteral('file')必填
urlstring
指向文件的 URL。
base64string必填
Base64 编码的文件数据。
idstring
指向外部存储文件(例如提供程序的文件系统或存储桶中)的引用 ID。
mime_typestring
文件 MIME 类型(例如 application/pdf)。
audio
typeliteral('audio')必填
urlstring
指向音频文件的 URL。
base64string必填
Base64 编码的音频数据。
idstring
指向外部存储音频文件(例如提供程序的文件系统或存储桶中)的引用 ID。
mime_typestring
音频 MIME 类型(例如 audio/mpeg、audio/wav)。
video
typeliteral('video')必填
urlstring
指向视频文件的 URL。
base64string必填
Base64 编码的视频数据。
idstring
指向外部存储视频文件(例如提供程序的文件系统或存储桶中)的引用 ID。
mime_typestring
视频 MIME 类型(例如 video/mp4、video/webm)。
tool_call
typeliteral('tool_call')必填
namestring
argsobject必填
要传递给工具的参数。
idstring
此工具调用的唯一标识符。
server_tool_call
typeliteral('server_tool_call')必填
idstring必填
此工具调用的唯一标识符。
namestring必填
要调用的工具名称。
argsobject必填
要传递给工具的参数。
server_tool_result
typeliteral('server_tool_result')必填
tool_call_idstring必填
对应服务器工具调用的标识符。
idstring
此工具调用的唯一标识符。
statusstring必填
服务器端工具的执行状态。以下之一:success | error。
output
已执行工具的输出。
tool_call_idstring
必须与先前 assistant 消息的 tool_calls[i] 条目中的 id 匹配。仅当 role 为 tool 时有效。
usage_metadataobject
使用此字段随模型输出发送 token 计数和/或成本。详情请参阅提供 token 和成本信息。
python
inputs = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hi, can you tell me the capital of France?"
}
]
}
]
}
outputs = {
"messages": [
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "The capital of France is Paris."
},
{
"type": "reasoning",
"text": "The user is asking about..."
}
]
}
]
}python
input = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What's the weather in San Francisco?"
}
]
}
]
}
outputs = {
"messages": [
{
"role": "assistant",
"content": [{"type": "tool_call", "name": "get_weather", "args": {"city": "San Francisco"}, "id": "call_1"}],
},
{
"role": "tool",
"tool_call_id": "call_1",
"content": [
{
"type": "text",
"text": "{\"temperature\": \"18°C\", \"condition\": \"Sunny\"}"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "The weather in San Francisco is 18°C and sunny."
}
]
}
]
}python
inputs = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What breed is this dog?"
},
{
"type": "image",
"url": "https://fastly.picsum.photos/id/237/200/300.jpg?hmac=TmmQSbShHz9CdQm0NkEjx1Dyh_Y984R9LpNrpvH2D_U",
# alternative to a url, you can provide a base64 encoded image
# "base64": "<base64 encoded image>",
"mime_type": "image/jpeg",
}
]
}
]
}
outputs = {
"messages": [
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "This looks like a Black Labrador."
}
]
}
]
}python
input = {
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "What is the price of AAPL?"
}
]
}
]
}
output = {
"messages": [
{
"role": "assistant",
"content": [
{
"type": "server_tool_call",
"name": "web_search",
"args": {
"query": "price of AAPL",
"type": "search"
},
"id": "call_1"
},
{
"type": "server_tool_result",
"tool_call_id": "call_1",
"status": "success"
},
{
"type": "text",
"text": "The price of AAPL is $150.00"
}
]
}
]
}将自定义 I/O 格式转换为 LangSmith 兼容格式
如果你使用自定义输入或输出格式,可以使用 @traceable 装饰器(Python)或 traceable 函数(TS)上的 process_inputs/processInputs 和 process_outputs/processOutputs 函数,将其转换为 LangSmith 兼容格式。
process_inputs/processInputs 和 process_outputs/processOutputs 接受函数,允许你在特定追踪的输入和输出记录到 LangSmith 之前对其进行转换。这些函数可以访问追踪的输入和输出,并可以返回包含处理后数据的新字典。
以下是一个样板示例,展示如何使用 process_inputs 和 process_outputs 将自定义 I/O 格式转换为 LangSmith 兼容格式:
python
class OriginalInputs(BaseModel):
"""Your app's custom request shape"""
class OriginalOutputs(BaseModel):
"""Your app's custom response shape."""
class LangSmithInputs(BaseModel):
"""The input format LangSmith expects."""
class LangSmithOutputs(BaseModel):
"""The output format LangSmith expects."""
def process_inputs(inputs: dict) -> dict:
"""Dict -> OriginalInputs -> LangSmithInputs -> dict"""
def process_outputs(output: Any) -> dict:
"""OriginalOutputs -> LangSmithOutputs -> dict"""
@traceable(run_type="llm", process_inputs=process_inputs, process_outputs=process_outputs)
def chat_model(inputs: dict) -> dict:
"""
Your app's model call. Keeps your custom I/O shape.
The decorators call process_* to log LangSmith-compatible format.
"""在追踪中标识自定义模型
使用自定义模型时,建议还提供以下 metadata 字段,以便在查看追踪和筛选时标识模型。
ls_provider:模型提供程序,例如"openai"、"anthropic"。ls_model_name:模型名称,例如"gpt-5.4-mini"、"claude-opus-4-8"。
python
from langsmith import traceable
inputs = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "I'd like to book a table for two."},
]
output = {
"choices": [
{
"message": {
"role": "assistant",
"content": "Sure, what time would you like to book the table for?"
}
}
]
}
@traceable(
run_type="llm",
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def chat_model(messages: list):
return output
chat_model(inputs)typescript
import { traceable } from "langsmith/traceable";
const messages = [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "I'd like to book a table for two." }
];
const output = {
choices: [
{
message: {
role: "assistant",
content: "Sure, what time would you like to book the table for?",
},
},
],
usage_metadata: {
input_tokens: 27,
output_tokens: 13,
total_tokens: 40,
},
};
// Can also use one of:
// const output = {
// message: {
// role: "assistant",
// content: "Sure, what time would you like to book the table for?"
// }
// };
//
// const output = {
// role: "assistant",
// content: "Sure, what time would you like to book the table for?"
// };
//
// const output = ["assistant", "Sure, what time would you like to book the table for?"];
const chatModel = traceable(
async ({ messages }: { messages: { role: string; content: string }[] }) => {
return output;
},
{
run_type: "llm",
name: "chat_model",
metadata: {
ls_provider: "my_provider",
ls_model_name: "my_model"
}
}
);
await chatModel({ messages });如果你实现自定义流式 chat_model,可以将输出“归约”为非流式版本相同的格式。这仅在 Python 中受支持:
python
def _reduce_chunks(chunks: list):
all_text = "".join([chunk["choices"][0]["message"]["content"] for chunk in chunks])
return {"choices": [{"message": {"content": all_text, "role": "assistant"}}]}
@traceable(
run_type="llm",
reduce_fn=_reduce_chunks,
metadata={"ls_provider": "my_provider", "ls_model_name": "my_model"}
)
def my_streaming_chat_model(messages: list):
for chunk in ["Hello, " + messages[1]["content"]]:
yield {
"choices": [
{
"message": {
"content": chunk,
"role": "assistant",
}
}
]
}
list(
my_streaming_chat_model(
[
{"role": "system", "content": "You are a helpful assistant. Please greet the user."},
{"role": "user", "content": "assistant"},
],
)
)TIP
对于 LangSmith 识别模型并计算自定义 LLM 追踪的成本,在 metadata 中设置 ls_model_name 是必需的。缺少它时,token 计数可能仍会被记录,但不会估算成本。
要详细了解如何使用 metadata 字段,请参阅添加元数据和标签指南。要自定义自定义 agent 运行在 Trajectory 视图中的显示方式,请参阅自定义 Trajectory 视图。
提供 token 和成本信息
token 计数支持成本计算,LangSmith 会在 Tracing Projects UI 中显示这些信息。有两种方式可以提供它们:
- 在运行树上设置
usage_metadata:在你的函数内调用get_current_run_tree()/getCurrentRunTree(),然后设置usage_metadata字段,该函数通过@traceable包装。这不会更改函数的返回值。 - 在输出中返回
usage_metadata:将usage_metadata作为函数返回字典中的顶级键。
支持的 usage_metadata 字段
| 字段 | 类型 | 描述 |
|---|---|---|
input_tokens | int | 输入/提示词 token 总数 |
output_tokens | int | 输出/补全 token 总数 |
total_tokens | int | 输入 + 输出之和(可选,可推断) |
input_token_details | object | 细分:cache_read、cache_creation、cache_read_over_200k、ephemeral_5m_input_tokens、ephemeral_1h_input_tokens、audio、text、image |
output_token_details | object | 细分:reasoning、audio、text、image |
要直接发送成本(用于非线性定价),还可以包含 input_cost、output_cost 和 total_cost 字段。有关配置模型定价和在 UI 中查看成本的详细信息,请参阅成本跟踪页面。
首 token 时间
如果你使用 traceable 或某个 SDK 包装器,LangSmith 会自动为流式 LLM 运行填充首 token 时间。但是,如果你直接使用 RunTree API,则需要向运行树添加 new_token 事件,以便正确填充首 token 时间。
以下是一个示例:
python
from langsmith.run_trees import RunTree
run_tree = RunTree(
name="CustomChatModel",
run_type="llm",
inputs={ ... }
)
run_tree.post()
llm_stream = ...
first_token = None
for token in llm_stream:
if first_token is None:
first_token = token
run_tree.add_event({
"name": "new_token"
})
run_tree.end(outputs={ ... })
run_tree.patch()typescript
import { RunTree } from "langsmith";
const runTree = new RunTree({
name: "CustomChatModel",
run_type: "llm",
inputs: { ... },
});
await runTree.postRun();
const llmStream = ...;
let firstToken;
for (const token of llmStream) {
if (firstToken == null) {
firstToken = token;
runTree.addEvent({ name: "new_token" });
}
}
await runTree.end({
outputs: { ... },
});
await runTree.patchRun();相关
- 自定义埋点:核心
@traceable和RunTree模式。 - 在被追踪函数中访问当前运行(span):使用
get_current_run_tree()在运行时设置usage_metadata和其他字段。 - 追踪 OpenAI 应用:使用 OpenAI 包装器时自动跟踪 token 和成本。
- 追踪 Anthropic 应用:使用 Anthropic 包装器时自动跟踪 token 和成本。
- 集成概览:内置 LangSmith 支持的提供程序和框架完整列表。