==一次请求 = 一棵 Run 树==。最外层那个 Run 同时也是这棵 Trace 的根(root run),它的 trace_id 等于自己的 id;子 Run 通过 parent_run_id 指回父节点、共享同一个 trace_id。决定一个 Run 填什么 run_type 的唯一标准:这一步在做什么?调大模型→llm;查知识库/向量库→retriever;调外部工具/函数→tool;其它组合编排步骤→chain

三层骨架:Project / Trace / Run

概念是什么关键字段类比
ProjectRun 的归集容器,按项目 / 环境隔离数据项目名(env: LANGSMITH_PROJECT)一个看板 / 一个命名空间
Trace一次端到端请求展开成的 Run 树,整棵树共享一个 trace_idtrace_id(= 根 Run 的 id)一次分布式追踪 / 一棵调用树
Run树里的单个执行单元,是真正落库的记录id、name、run_type、inputs、outputs、parent_run_id、trace_id、tags、metadata、start_time、end_time、error树上的一个节点 / 一段 span
run_typeRun 的类型标签,决定 UI 渲染方式llm / chain / tool / retriever / prompt / parser节点的「种类」
口诀Project 装一堆 Trace,一个 Trace 是一棵 Run 树,run_type 决定每个 Run 长什么样。

接入方式一:LangChain / LangGraph 零代码自动接入

如果你的应用是用 LangChain 或 LangGraph 写的,接入 LangSmith 不需要改任何业务代码。LangChain 内部的每一个 Runnable(模型、prompt、chain、retriever、tool)都内建了回调(callback),只要进程里设了对应环境变量,这些回调就会把每一步执行上报成对应 run_type 的 Run,并自动按调用关系拼成 Trace 树。你要做的只是设三个环境变量。

# 安装:把 langsmith 和 langchain 一起装上(langchain 内建上报回调)
# pip install -U langsmith langchain langchain-openai

import os

# === 仅靠这几个环境变量即可开启自动追踪,下面是零代码接入的全部「配置」 ===
os.environ["LANGSMITH_TRACING"] = "true"            # 总开关:true 才上报
os.environ["LANGSMITH_API_KEY"] = "lsv2_pt_xxx"     # 你的 API key(见上一章)
os.environ["LANGSMITH_PROJECT"] = "my-rag-app"      # Run 归集到哪个 Project(不设则进 default)
# os.environ["LANGSMITH_ENDPOINT"] = "https://api.smith.langchain.com"  # 默认值,自托管才改
os.environ["OPENAI_API_KEY"] = "sk-xxx"

# === 下面是「纯 LangChain 业务代码」,没有一行是为追踪写的 ===
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是简洁的技术助手,一句话回答。"),
    ("human", "{question}"),
])
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 这条链 = prompt | model | parser,是一个组合 Runnable
chain = prompt | model | StrOutputParser()

# 只要执行它,LangSmith 就自动收到一棵 Trace:
#   chain(run_type=chain)
#     ├─ ChatPromptTemplate (run_type=prompt)
#     ├─ ChatOpenAI         (run_type=llm)   ← 自动带 token / 成本
#     └─ StrOutputParser    (run_type=parser)
answer = chain.invoke({"question": "LangSmith 的 Trace 是什么?"})
print(answer)
# 打开 https://smith.langchain.com 的 my-rag-app 项目,就能看到这棵树

接入方式二:@traceable 手动追踪普通 Python 函数

如果你的代码不是 LangChain(自己写的 RAG、自己拼的 Agent、调裸 OpenAI SDK),就用 langsmith@traceable 装饰器:给函数加一行装饰器,每次调用就上报成一个 Run,run_type 由你指定,函数的入参和返回值自动成为 inputsoutputs关键点:嵌套调用会自动串成树——被装饰的函数 A 里调用了被装饰的函数 B,B 的 Run 自动认 A 为父,靠的是 SDK 用 contextvars 维护的「当前父 Run」上下文,你不用手传任何 id。

# 仅需 langsmith,无需 langchain
# pip install -U langsmith openai

import os
from langsmith import traceable
from openai import OpenAI

os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "lsv2_pt_xxx"
os.environ["LANGSMITH_PROJECT"] = "manual-rag"

oai = OpenAI()  # 需要 OPENAI_API_KEY

# 1) 检索步骤 —— 标成 retriever,UI 会把返回值当文档列表渲染
@traceable(run_type="retriever", name="retrieve_docs")
def retrieve(query: str) -> list[dict]:
    # 这里假装查了向量库;真实场景换成你的检索逻辑
    return [
        {"page_content": "LangSmith 把一次请求记成一棵 Run 树。", "metadata": {"id": "d1"}},
        {"page_content": "run_type 决定节点在 UI 里的渲染方式。", "metadata": {"id": "d2"}},
    ]

# 2) 生成步骤 —— 标成 llm,UI 会统计 token / 成本
@traceable(run_type="llm", name="generate_answer")
def generate(question: str, context: str) -> str:
    resp = oai.chat.completions.create(
        model="gpt-4o-mini",
        temperature=0,
        messages=[
            {"role": "system", "content": f"只依据以下资料回答:\n{context}"},
            {"role": "user", "content": question},
        ],
    )
    return resp.choices[0].message.content

# 3) 顶层编排 —— 标成 chain,它是这棵 Trace 的根 Run
@traceable(run_type="chain", name="rag_pipeline")
def rag_pipeline(question: str) -> str:
    docs = retrieve(question)                       # ← 子 Run,自动认 rag_pipeline 为父
    context = "\n".join(d["page_content"] for d in docs)
    return generate(question, context)             # ← 另一个子 Run,同样自动挂上

answer = rag_pipeline("LangSmith 怎么组织追踪数据?")
print(answer)

# 上报到 manual-rag 项目,结构是一棵自动串好的树:
#   rag_pipeline (chain)   ← 根 Run,trace_id == 自己的 id
#     ├─ retrieve_docs   (retriever)   parent_run_id = rag_pipeline
#     └─ generate_answer (llm)         parent_run_id = rag_pipeline

用 tags 与 metadata 标注 Run

光有树还不够,生产里你要能按维度筛选和回溯。tags 是一个字符串列表,用来给 Run 打分类标签(环境、实验组、功能模块),LangSmith UI 里可以按 tag 过滤;metadata 是结构化键值,用来挂任意业务上下文(用户 id、版本号、检索条数)。两者都可以在 @traceable 里静态声明,也可以在运行时动态注入。

# tags / metadata 的两种写法:装饰器静态声明 + 运行时动态注入
import os
from langsmith import traceable, get_current_run_tree

os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "lsv2_pt_xxx"
os.environ["LANGSMITH_PROJECT"] = "tagging-demo"

# 写法 A:在装饰器里静态声明(每次调用都带上)
@traceable(
    run_type="chain",
    name="answer_question",
    tags=["prod", "rag"],                       # ← 字符串列表,用于筛选
    metadata={"pipeline_version": "v2.3"},      # ← 结构化键值,挂业务上下文
)
def answer_question(question: str, user_id: str) -> str:
    # 写法 B:运行时动态注入(值依赖入参时用)
    rt = get_current_run_tree()                 # 拿到当前 Run 句柄
    if rt is not None:
        rt.tags = (rt.tags or []) + [f"user:{user_id}"]
        rt.metadata["region"] = "cn"            # 动态补一个 metadata 字段
    return f"已回答 {user_id} 的问题:{question}"

# 写法 C:调用点临时覆盖(不改函数定义,单次调用追加标注)
answer_question(
    "什么是 Trace?",
    user_id="u-42",
    langsmith_extra={                            # ← traceable 注入的特殊参数,不进 inputs
        "tags": ["experiment-A"],
        "metadata": {"ab_group": "A"},
    },
)
# 该 Run 最终带的 tags = prod, rag, user:u-42, experiment-A
#              metadata = pipeline_version=v2.3, region=cn, ab_group=A
# 之后在 UI / SDK 里可按 tag "prod" 或 metadata.ab_group="A" 过滤检索
维度类型用途怎么筛
tags字符串列表粗粒度分类:环境 / 实验组 / 功能开关UI 按 tag 点选过滤;SDK list_runs(filter='has(tags, "prod")')
metadata结构化键值(dict)细粒度业务上下文:user_id、version、ab_groupUI / SDK 按 metadata 字段精确匹配过滤
inputsRun 的输入函数入参,UI 里展示与回放按内容全文搜索
outputsRun 的输出函数返回值,做正确性回溯的基础按内容全文搜索
口诀要「分类筛选」用 tags,要「挂结构化上下文」用 metadata;inputs/outputs 是天然记录的执行数据。
推荐做法
  • LangChain / LangGraph 代码:只设 LANGSMITH_TRACING=true + API key + PROJECT,零改业务代码
  • 自研普通函数:用 @traceable 并显式指定 run_type,让 UI 渲染和统计对得上
  • 顶层编排函数标 chain(它是根 Run),检索标 retriever,模型调用标 llm,工具标 tool
  • 环境 / 实验组用 tags 标,user_id / version 等结构化上下文用 metadata 标
  • 两种接入可共存:@traceable 的根函数里调 LangChain 链,会自然挂进同一棵 Trace
不推荐
  • 不要把 LLM 调用的 run_type 标成 chain——会丢掉 token 与成本统计
  • 不要手写 parent_run_id 去拼同步代码的树,contextvars 已经替你做了
  • 不要混设 LANGSMITH_ 与 LANGCHAIN_ 两套同义环境变量,容易互相覆盖
  • 不要把大段文本塞进 metadata——结构化小字段才进 metadata,长文本应是 inputs/outputs
常见误区
  • 设了 API key 但忘了 LANGSMITH_TRACING=true:什么都不会上报,且不报错
  • 异步任务 / 新线程里直接调被装饰函数:上下文断裂,子 Run 会断成另一棵孤树
  • 短脚本跑完进程立刻退出:langsmith 异步上报可能还没发完,CI 里建议结束前确保 flush

在 LangSmith UI 打开一条 Trace,能看到完整嵌套树、llm 节点有 token 数、retriever 节点显示文档列表、Run 上带着你设的 tags 与 metadata,即说明数据模型与接入都用对了。

先想清楚每个 Run 该是什么 run_type、该挂在树的哪一层,再写埋点——类型标错,UI 里的 token 曲线和文档视图就全是空的。

— 本章小结