LangSmith-LLM应用的全链路可观测性
一个 RAG 链给出了错误的回答,原因可能有十种:检索到的文档不相关、Prompt 指令被误解、模型在某个步骤幻觉、格式化出错……用 print 语句排查,要在代码里插入几十个断点,重跑十几次才能定位。这是 LLM 应用开发的特有痛点:传统的 logging 对文本输入输出有效,对"模型在哪一步开始偏离"无能为力。
LangSmith:LLM 应用的全链路可观测性
一个 RAG 链给出了错误的回答,原因可能有十种:检索到的文档不相关、Prompt 指令被误解、模型在某个步骤幻觉、格式化出错……用 print 语句排查,要在代码里插入几十个断点,重跑十几次才能定位。这是 LLM 应用开发的特有痛点:传统的 logging 对文本输入输出有效,对"模型在哪一步开始偏离"无能为力。
LangSmith 是 LangChain 官方推出的追踪和评估平台,专门解决 LLM 应用的可观测性问题。
1.1 为什么 print/logging 不够用
LangSmith全链路可观测性——Trace追踪、Run分析、Dataset评估、Feedback反馈形成持续改进闭环
传统应用的可观测性关注:请求耗时、错误率、资源占用。LLM 应用在这之上还需要:
中间状态可见性:一条 RAG 链有 5 个步骤,每步的输入输出都可能导致最终结果偏差。传统 logging 可以记录输入输出,但无法结构化地展示"步骤 3 的 Prompt 注入了哪些文档、模型实际看到的是什么"。
Token 成本归因(归因:找出谁消耗了多少):整体账单在增长,但哪条链、哪个 Prompt 消耗最多 Token?不追踪到链级别,成本优化无从下手。
版本对比:改了 Prompt 后,新版本在哪些案例上变好了、哪些变差了?没有历史记录就无法对比。
评估数据沉淀:线上标注"这个回答不好",要能自动沉淀成评估数据集,用于离线评估。这不是 logging 能做到的。
| 问题 | 传统 logging | LangSmith |
|---|---|---|
| 记录输入输出 | 能(手动写) | 自动(零代码) |
| 中间步骤可视化 | 不能 | Trace 树状图 |
| Token 用量统计 | 不能 | 自动统计,按步骤 |
| 版本 A/B 对比 | 不能 | Run 对比视图 |
| 在线标注 | 不能 | Feedback API |
| 构建评估数据集 | 不能 | 从 Trace 一键添加 |
1.2 LangSmith 是什么
LangSmith 是 LangChain 的官方可观测性平台,与 LangChain 和 LCEL 原生集成。核心能力:
- Tracing:自动追踪每个 Runnable 的输入、输出、耗时、Token 用量,以树状结构展示调用链路
- Datasets:从线上 Trace 收集数据,构建离线评估数据集
- Evaluations:批量在数据集上运行链,自动评估输出质量
- Prompt Hub:版本化管理 Prompt,支持 pull/push
1.3 5 分钟接入:零代码改动启用追踪
LangSmith 的接入成本极低,只需设置环境变量:
# 1. 注册 LangSmith(https://smith.langchain.com)并获取 API Key
# 2. 设置环境变量
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__your_api_key_here
export LANGCHAIN_PROJECT=my-rag-project # 可选,默认 "default"
或者在 Python 代码中设置(在任何 LangChain 代码之前):
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls__your_api_key_here"
os.environ["LANGCHAIN_PROJECT"] = "my-rag-project"
# 之后的所有 LangChain 调用都会自动上报到 LangSmith
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o")
chain = ChatPromptTemplate.from_template("解释{concept}") | llm
# 这次调用会自动出现在 LangSmith 的 Traces 页面
result = chain.invoke({"concept": "向量数据库"})
就这么简单。不需要修改任何业务代码,不需要添加任何 try/except,LangSmith 通过 LangChain 的回调系统自动捕获所有信息。
1.4 LangSmith UI 核心功能
1.4.1 Trace 详情
每次链调用都会生成一条 Trace,以树状结构展示完整的调用链路:
RAG Chain (3.2s | 1,847 tokens | $0.009)
├── Retriever (0.4s)
│ └── VectorSearch
│ Input: "什么是向量数据库?"
│ Output: [doc1, doc2, doc3, doc4]
├── PromptTemplate (0ms)
│ Input: {question: "...", context: "doc1\ndoc2..."}
│ Output: [SystemMessage, HumanMessage]
└── ChatOpenAI (2.8s | 1,847 tokens)
Input: [SystemMessage("你是..."), HumanMessage("...")]
Output: AIMessage("向量数据库是...")
Tokens: prompt=1,243, completion=604
Cost: $0.009
点击任意节点,可以看到完整的输入输出内容,以及该步骤的 Token 用量和耗时。
1.4.2 Run 对比
修改了 Prompt 版本后,可以选择两次运行进行 side-by-side 对比,清晰看到哪些地方输出不同。
1.4.3 Feedback:标注好坏回答
在 UI 或代码中标注某次运行的质量:
from langsmith import Client
client = Client()
# 给某次运行打分
client.create_feedback(
run_id="run_id_from_trace", # 从 Trace 页面复制
key="quality",
score=0.8, # 0-1
comment="回答准确,但语气稍显生硬"
)
# 标注为正面/负面
client.create_feedback(
run_id="run_id_from_trace",
key="user_rating",
value="positive" # 或 "negative"
)
被标注的运行可以直接添加到 Dataset,形成有标注的评估数据集。
1.5 Dataset 与评估:批量测试 RAG 质量
from langsmith import Client
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langsmith.evaluation import evaluate, LangChainStringEvaluator
client = Client()
# 1. 创建评估数据集
dataset = client.create_dataset(
dataset_name="rag-quality-v1",
description="RAG 问答质量评估数据集"
)
# 添加测试案例
examples = [
{
"inputs": {"question": "LangChain 的 LCEL 是什么?"},
"outputs": {"answer": "LCEL 是 LangChain Expression Language,用于声明式构建链..."}
},
{
"inputs": {"question": "如何给 LangChain 添加记忆?"},
"outputs": {"answer": "可以使用 ConversationBufferMemory 或 ChatMessageHistory..."}
},
]
client.create_examples(
inputs=[e["inputs"] for e in examples],
outputs=[e["outputs"] for e in examples],
dataset_id=dataset.id
)
# 2. 定义被评估的链
def rag_chain_fn(inputs: dict) -> dict:
"""待评估的 RAG 链"""
chain = (
ChatPromptTemplate.from_template("基于你的知识回答:{question}")
| ChatOpenAI(model="gpt-4o")
| StrOutputParser()
)
answer = chain.invoke(inputs)
return {"answer": answer}
# 3. 定义评估器
# 使用 LangChain 内置的 QA 评估器
qa_evaluator = LangChainStringEvaluator("qa")
# 自定义评估器:检查关键词
from langsmith.evaluation import run_evaluator
@run_evaluator
def keyword_evaluator(run, example) -> dict:
"""检查关键词是否在回答中"""
answer = run.outputs.get("answer", "")
reference = example.outputs.get("answer", "")
# 提取参考答案中的关键词(简化版)
keywords = [w for w in reference.split() if len(w) > 3][:5]
hits = sum(1 for kw in keywords if kw in answer)
score = hits / len(keywords) if keywords else 0.0
return {"key": "keyword_recall", "score": score}
# 4. 运行批量评估
results = evaluate(
rag_chain_fn,
data=dataset.name,
evaluators=[qa_evaluator, keyword_evaluator],
experiment_prefix="gpt-4o-baseline",
num_repetitions=1
)
print(results.to_pandas())
1.6 Prompt Hub:版本化管理 Prompt
LangSmith 提供 Prompt Hub,支持把 Prompt 模板推送到云端,在代码中 pull 使用,实现 Prompt 与代码的解耦:
from langchain import hub
# 从 Hub 拉取社区 Prompt(无需 LangSmith 账号)
rag_prompt = hub.pull("rlm/rag-prompt")
print(rag_prompt.messages[0].prompt.template)
# 从 Hub 拉取自己的 Prompt(需要登录)
my_prompt = hub.pull("my-username/customer-service-v2")
# 在链中使用
chain = my_prompt | llm | StrOutputParser()
# 推送 Prompt 到 Hub
from langchain_core.prompts import ChatPromptTemplate
new_prompt = ChatPromptTemplate.from_messages([
("system", "你是专业的客服助手,版本 3.0"),
("human", "{question}")
])
hub.push("my-username/customer-service-v3", new_prompt)
1.7 手动记录自定义 Span
对于不经过 LangChain 的自定义逻辑,可以手动创建 Span(追踪单元:在链路追踪中代表一段操作的记录,包含开始时间、结束时间和相关数据,多个 Span 组合成完整的 Trace 树):
from langsmith import traceable, Client
from langsmith.run_helpers import get_current_run_tree
# 方法 1:@traceable 装饰器
@traceable(name="custom-preprocessing", run_type="chain")
def preprocess_query(query: str) -> str:
"""自定义预处理逻辑"""
# 清理、规范化、扩展查询
cleaned = query.strip().lower()
expanded = f"{cleaned} 相关内容 技术文档" # 查询扩展
return expanded
@traceable(name="rerank-results", run_type="retriever")
def rerank_documents(query: str, docs: list) -> list:
"""文档重排序(自定义逻辑)"""
# 用关键词相关性重排
scored_docs = [
(doc, sum(1 for kw in query.split() if kw in doc.page_content))
for doc in docs
]
return [doc for doc, _ in sorted(scored_docs, key=lambda x: -x[1])]
# 方法 2:上下文管理器
from langsmith.run_helpers import langsmith_trace
def complex_pipeline(query: str) -> str:
with langsmith_trace(name="query-expansion") as run:
expanded_query = preprocess_query(query)
run.metadata["expansion_ratio"] = len(expanded_query) / len(query)
docs = retriever.invoke(expanded_query)
with langsmith_trace(name="reranking") as run:
reranked_docs = rerank_documents(query, docs)
run.metadata["docs_before"] = len(docs)
run.metadata["docs_after"] = len(reranked_docs)
return generate_answer(query, reranked_docs)
1.8 LangSmith 追踪链路完整图
LangSmith 把"黑盒"变成"白盒"——从线上 Trace 发现问题,到标注数据集,到离线评估验证修复,这条链路建起来,LLM 应用的质量就有了可追溯的依据,而不是靠猜。