可观测的生产闭环不是「看日志」,而是「定义信号 → 设阈值 → 触发告警 → 回溯 Trace 根因 → 沉淀为回归用例」。监控负责发现异常,Trace 负责定位根因,CI 回归负责防止异常复发。三者缺一,可观测就退化成事后救火。
一、监控 Dashboard:四个生产核心信号
LangSmith 的 Monitor 视图按 project(也就是 tracing project)聚合 Run 级时序指标。打开任意 project 的 Monitor tab,默认就能看到随时间变化的折线/直方图。真正需要盯的生产信号收敛为四类,下表给出各信号的来源字段与典型告警动作。
| 信号 | 来源 | 为什么重要 | 典型阈值告警 |
|---|---|---|---|
| 延迟 (Latency) | Run 的 start_time / end_time,看 P50/P99 | 用户体感与 SLA 直接相关,长尾请求最伤体验 | P99 latency > 8s 持续 5 分钟 |
| Token 成本 | Run 的 prompt_tokens / completion_tokens / total_cost | 成本随流量线性增长,提示词回归会让成本悄悄翻倍 | 单小时 total cost > $X |
| 错误率 (Error rate) | Run 的 error 字段非空占比 | 上游 LLM 限流、超时、解析失败都体现在这里 | error rate > 2% 持续 10 分钟 |
| 反馈分布 (Feedback) | 通过 create_feedback 写入的 score/value 聚合 | 线上真实质量信号,用户点踩或自动 judge 评分 | negative feedback 占比 > 10% |
前三个信号 LangSmith 自动从 Run 数据计算,无需额外代码。第四个「反馈」需要你主动上报——线上既可以接用户的赞/踩,也可以挂一个在线 LLM-as-judge 周期性打分。下面是把反馈写回某条 Run 的真实 API:
# pip install -U langsmith
from langsmith import Client
client = Client() # 自动读取 LANGSMITH_API_KEY / LANGSMITH_ENDPOINT
# 假设你在主路径里拿到了某次调用的 run_id(见下文 @traceable / LangGraph 如何取得)
# 用户点了「踩」,或离线 judge 给出 0.3 分,都通过 create_feedback 写回
client.create_feedback(
run_id="<your-run-id>", # 目标 Run 的 UUID
key="user_thumbs", # 反馈维度名,Dashboard 按 key 聚合分布
score=0, # 数值分(0/1 或 0~1 连续分);Monitor 按 score 画分布
value="down", # 可选的分类值(如 up/down)
comment="答案与问题无关", # 可选文本说明
)
# 想批量给反馈,遍历某个 project 最近的 Run 即可
for run in client.list_runs(project_name="my-prod-app", limit=20):
# 这里可以接入你的在线 judge,对 run.outputs 打分后写回
pass
配置基于阈值的告警
在 project 的 Monitor 页面,每个图表(latency、error rate、feedback 等)右上角可以 Create rule / Add alert。规则三要素:选指标 → 设比较算子与阈值 → 配通知渠道(Webhook / PagerDuty / Email)。告警触发后,点开规则能直接跳转到触发时间窗内的 Run 列表,从 Dashboard 一键下钻到 Trace 做根因分析。
二、LangGraph 集成:零埋点的分层 Trace
LangGraph 构建的是有状态的多节点图。只要设置了 tracing 环境变量,编译后的图在 invoke 时会自动产生 一个父 Run + 每个节点一个子 Run 的嵌套结构,无需任何手工埋点。这让你能在 Trace 视图里逐节点下钻,看到每一步的 state 输入输出、耗时与 token。
# 安装依赖:langgraph 自带 tracing 集成,langchain-openai 提供 LLM 节点
pip install -U langgraph langchain-openai langsmith
# 三个环境变量即开启自动 tracing(核心是前两个)
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY="lsv2_pt_xxx"
export LANGSMITH_PROJECT="langgraph-demo" # 不设则归到 default project
export OPENAI_API_KEY="sk-xxx"
# 一个最小的两节点 LangGraph:分类节点 -> 回答节点
# 运行后去 LangSmith 的 langgraph-demo project,会看到分层 Trace
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 1) 定义在节点间流转的状态
class State(TypedDict):
question: str
category: str
answer: str
# 2) 节点一:给问题分类(每个节点会成为一个子 Run)
def classify(state: State) -> dict:
resp = llm.invoke(
f"用一个词分类这个问题(技术/闲聊/其他):{state['question']}"
)
return {"category": resp.content.strip()}
# 3) 节点二:根据分类作答
def answer(state: State) -> dict:
resp = llm.invoke(
f"这是一个[{state['category']}]问题,请回答:{state['question']}"
)
return {"answer": resp.content}
# 4) 编译图:START -> classify -> answer -> END
builder = StateGraph(State)
builder.add_node("classify", classify)
builder.add_node("answer", answer)
builder.add_edge(START, "classify")
builder.add_edge("classify", "answer")
builder.add_edge("answer", END)
graph = builder.compile()
# 5) invoke 会自动上报:1 个父 Run(整图)+ classify/answer 两个子 Run + 内部 LLM 调用
result = graph.invoke({"question": "LangGraph 和 LangChain 有什么区别?"})
print(result["answer"])
三、CI 自动化回归:用 evaluate 守住评分基线
回归测试的本质是「锁住质量下界」。每次改提示词、换模型、调链路,都跑同一个 dataset 过同一组 evaluator,把聚合分数和基线对比;只要低于基线就 fail build。这把「凭感觉觉得没变差」变成「有数字证明没变差」——这是 verifiability_first 在 LLM 工程里的落地。
下面是一个能直接放进 tests/ 目录、被 pytest 收集的回归测试。它调用 evaluate 在指定 dataset 上跑目标函数,用内置/自定义 evaluator 打分,聚合平均分后用 assert 守住基线。
# tests/test_regression.py
# pip install -U langsmith pytest
import statistics
from langsmith import Client, evaluate
client = Client()
DATASET_NAME = "qa-regression-set" # 事先在 LangSmith 创建好的评估数据集
SCORE_BASELINE = 0.8 # 质量下界:平均 correctness 不得低于此值
# 1) 被测系统:把它包成一个接收 inputs(dict) 返回 outputs(dict) 的函数
def target(inputs: dict) -> dict:
# 真实场景这里调用你的链路 / LangGraph 图
question = inputs["question"]
answer = f"针对「{question}」的回答" # 占位,替换为真实推理
return {"answer": answer}
# 2) 自定义 evaluator:返回 dict,含 key 与 score
# run = 目标函数的输出 Run;example = dataset 中的标准样本
def correctness(run, example) -> dict:
predicted = (run.outputs or {}).get("answer", "")
expected = (example.outputs or {}).get("answer", "")
score = 1.0 if expected and expected in predicted else 0.0
return {"key": "correctness", "score": score}
def test_qa_regression():
# 3) 在数据集上跑评估,evaluate 自动把每条结果上报到 LangSmith
results = evaluate(
target,
data=DATASET_NAME,
evaluators=[correctness],
experiment_prefix="ci-regression", # 实验前缀,便于在 UI 里区分每次 CI
max_concurrency=4,
)
# 4) 聚合分数:遍历每条结果,取 correctness 分
scores = []
for r in results:
for res in r["evaluation_results"]["results"]:
if res.key == "correctness":
scores.append(res.score)
assert scores, "没有产生任何评分,检查 dataset 与 evaluator"
avg = statistics.mean(scores)
print(f"平均 correctness = {avg:.3f} (baseline={SCORE_BASELINE})")
# 5) 守基线:低于下界直接 fail,CI 红灯
assert avg >= SCORE_BASELINE, (
f"质量回退!平均分 {avg:.3f} < 基线 {SCORE_BASELINE}"
)
# .github/workflows/langsmith-regression.yml
# 在 PR 与 main 推送时跑回归,评分跌破基线则 fail
name: LangSmith Regression
on:
pull_request:
push:
branches: [main]
jobs:
regression:
runs-on: ubuntu-latest
env:
# 关键:在 CI 里注入 LangSmith 与模型凭证(存到 repo Secrets)
LANGSMITH_TRACING: "true"
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
LANGSMITH_PROJECT: "ci-regression"
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install deps
run: pip install -U langsmith pytest langchain-openai
- name: Run regression eval
# pytest 收集 test_qa_regression;assert 失败 -> 退出码非 0 -> build 红灯
run: pytest tests/test_regression.py -s -v
四、生产踩坑汇总
追踪开销与异步上报
- 典型表现
- 担心 tracing 拖慢主请求,或高并发下 Run 上报丢失。
- 判断标准
- tracing 不应给 P99 latency 带来可感知的增量。
- 解决方向
- LangSmith SDK 默认在后台线程批量异步上报,几乎不阻塞主路径。短生命周期脚本/Serverless 在进程退出前调用
client.flush()(或wait_for_all_tracers())确保缓冲区刷出,否则最后几条 Run 会丢。
采样与限流
- 典型表现
- 全量上报触达 LangSmith 配额上限或产生不必要成本/噪音。
- 判断标准
- 生产高流量下既保留代表性样本,又不超配额。
- 解决方向
- 用
LANGSMITH_SAMPLING_RATE(0~1,如 0.1 表示采样 10%)按比例上报;对关键链路保持全量、对高频低价值路径降采样。客户端侧再叠加自有限流,避免突发流量打满。
PII 脱敏
- 典型表现
- 用户隐私/敏感数据随 inputs/outputs 进入 Trace,触发合规风险。
- 判断标准
- 上报到 LangSmith 的数据不含明文 PII。
- 解决方向
- 设
LANGSMITH_HIDE_INPUTS=true/LANGSMITH_HIDE_OUTPUTS=true完全不上报输入输出,或传入可调用的脱敏函数对字段做掩码后再上报。脱敏要在数据离开进程前完成,而非事后删除。
自托管 endpoint
- 典型表现
- 企业合规要求数据不出私网,无法用 SaaS 版。
- 判断标准
- Trace 数据只落在自有基础设施。
- 解决方向
- 部署 self-hosted LangSmith,把
LANGSMITH_ENDPOINT指向自托管地址(如https://langsmith.internal.company.com/api),API key 用自托管实例签发。代码无需改动,只换环境变量。
✓推荐做法
- 生产 project 与 CI/开发 project 用不同
LANGSMITH_PROJECT名隔离,避免回归实验污染线上指标 - 告警阈值基于历史 P99/均值设定,并保证能一键下钻到触发 Trace
- 回归测试把模型 temperature 锁 0 或用聚合分守基线,给随机性留余量
- Serverless / 短脚本结束前 flush tracer,防止尾部 Run 丢失
✗不推荐
- 不要在生产高流量下全量上报又不设采样,配额和噪音都会失控
- 不要把含 PII 的 inputs/outputs 原样上报后再指望事后删除
- 不要让回归断言钉死在某次幸运高分上,否则正常抖动天天误报
- 不要只配告警却没有从指标跳到 Trace 的回溯路径
⚠常见误区
- CI Secrets 里漏配 LANGSMITH_API_KEY 会导致 evaluate 静默跑空、断言拿不到分数
- LANGSMITH_TRACING 默认关闭,CI 与生产都要显式设为 true 才会上报
- 自托管换 endpoint 时 SaaS 的 key 不通用,需用自托管实例签发的 key
异常能被监控发现、能下钻到 Trace 定位根因、能沉淀为 CI 回归用例防复发——三环闭合即合格。
瓶颈是验证,不是生成。能被度量的问题才能被完成;能消失的分支胜过能工作的分支。
— 本 Wiki 的工程审美