上周三凌晨两点,客服告警群炸了:用户投诉AI客服乱回复,明明问退款流程,模型却在推荐商品。排查半天,发现是RAG检索把老文档混进去了,但根本不知道是哪次调用出了问题。
从那之后我就给团队立了一条规矩:所有LLM调用必须带trace,不带trace的PR不让过。
LangSmith是什么
LangChain团队出品的AI应用观测平台。记录每次LLM调用的完整链路:输入prompt、检索的文档、工具调用参数、最终输出。类似APM工具但专为AI应用设计。
核心价值就三个字:可追溯。模型出了问题,不用猜,直接看链路日志就行。
接入方式
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "your-key"
os.environ["LANGCHAIN_PROJECT"] = "my-project"
# 之后的所有LangChain调用自动记录
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
resp = llm.invoke("你好") # 这条调用自动上报到LangSmith
不需要修改业务代码,环境变量开启后自动采集。每条trace记录完整的调用链:输入→prompt拼装→LLM调用→输出,甚至包括中间的工具调用和RAG检索结果。
如果你不用LangChain,也可以用LangSmith SDK手动埋点:
from langsmith import Client
client = Client()
# 手动创建trace
with client.trace("my-trace", project_name="my-project") as tracer:
with tracer.child("llm-call") as child:
child.inputs = {"prompt": "用户的问题"}
child.outputs = {"response": "模型的回答"}
核心功能
Tracing:记录完整调用链,可可视化每个步骤的输入输出。支持嵌套trace,RAG→Agent→工具调用的层级关系一目了然。
评估:批量运行测试集,自动评分。支持自定义评估函数,可以衡量准确性、相关性、幻觉率等指标:
from langsmith.evaluation import evaluate
def accuracy(run, example):
return {"score": 1.0 if run.output == example.output else 0.0}
results = evaluate(
accuracy,
data="my-test-dataset",
llm_or_chain_factory=my_chain
)
数据集管理:管理标注数据和测试数据,支持从生产trace中直接采样生成测试集。这招很实用——线上出过的case直接拿来当回归测试。
Prompt Hub:版本化管理prompt模板,支持团队协作。prompt改了什么、效果如何,都有记录。
本地替代方案
如果不想用SaaS,可以用LangFuse(开源替代品)本地部署。功能类似,数据完全自主可控:
# docker-compose.yml
services:
langfuse:
image: langfuse/langfuse:2
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/langfuse
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: your-secret
接入方式几乎一样,改个环境变量就行:
export LANGFUSE_HOST="http://localhost:3000"
export LANGFUSE_PUBLIC_KEY="pk-..."
export LANGFUSE_SECRET_KEY="sk-..."
适用场景
开发阶段:排查为什么模型给出了意外回答。上周帮同事查一个问题,调用链显示Agent在第三步调了一个过时的API,两分钟定位。
测试阶段:评估不同prompt版本的效果。我们有一个prompt改了三次,每次都跑同一组测试,对比score变化一目了然。
生产阶段:监控调用量、延迟、错误率。配合告警规则,异常波动第一时间通知。
团队协作:新来的工程师看trace就能理解业务逻辑,不用翻代码。PM也可以直接看trace理解模型行为。
实际踩坑经验
别在trace里记录用户敏感信息。我们第一次接入的时候把用户的手机号和邮箱都记录进去了,被安全审计点名批评。建议用脱敏处理:
import re
def sanitize(text):
text = re.sub(r"1[3-9]\d{9}", "[PHONE]", text)
text = re.sub(r"[\w.-]+@[\w.-]+", "[EMAIL]", text)
return text
另外,高频调用场景注意采样。全量记录会拖慢响应速度,建议只记录error和采样10%的成功请求。生产环境保持采样率在5-20%比较合理,既能看到足够样本,又不会产生太多存储成本。