← 返回文章选择

MediAC 的一次回答,该从哪里往回查

回答偏题时按 query、召回、prompt 往回查;trace 缺席、字段测试和冒烟检查各管哪一层。

问答能显示以后,我接下来想知道两件事:改代码有没有弄坏接口,生成内容为什么是这样。这两件事要看的材料不同。前者可以给固定输入检查字段,后者得展开 query、召回和 prompt,沿这次请求往回找。

看一次实际回答时,往哪里翻

报告偏题时,我会按这个顺序查:

  1. 展开改写后的 query,确认它还在问原来的事;
  2. 看召回片段;
  3. 看生成节点实际拼进去的上下文。

依次检查 query、召回片段和 prompt 上下文,定位证据截断

这是排查顺序,不是某次故障记录。比如证据在 bundle 里,却被截断在 prompt 外,光看检索返回成功就会漏掉。截断发生在拼上下文这一步:检索拿到的是完整片段,到 prompt 里少了一块。两处看到的本来就不是同一份内容,检索那一步的记录说明不了模型最后看到了什么。

这种截断是常规操作带来的,并不罕见。LangChain 的短期记忆文档讲裁剪消息时,做法就是数历史里的 token、接近上限就截掉一段,用 strategy 决定留哪头,文档自己也写明这样“可能丢信息”,替代办法是改用摘要。所以“检索成功”和“模型看到”分属两次记录,要分开看。

在 LangSmith 的口径里,这个分法是现成的。官方概念页把一次操作记成一条 trace,里面每个工作单元是一个 run,例子里“格式化 prompt”和“检索文档”正是两种不同的 run;每条 trace 最多 25,000 个 run。上面三步因此可以写成具体动作:查询语法支持按 run_type(有 retriever、prompt、llm 等)过滤,filter 只看返回的 run,trace_filter 看根 run,tree_filter 查整棵树的任意位置。与其在一条长 trace 里肉眼翻,不如直接把 retriever 和 prompt 两类 run 拉出来对照。

trace 里没有,不等于没跑

LangSmith 可以记录这些调用。自定义追踪要接在实际经过的函数上,新接口或新异步路径也得重新确认覆盖。只装 SDK,保证不了后来新增的聊天入口都出现在预期的 trace 里。

新增路径要确认的,是那次调用有没有经过接过追踪的函数。trace 里没有记录,不等于这段代码没跑,可能只是没接上。

翻文档之后,我觉得“没接上”至少要拆成几种情况,按好查到难查排:

  1. 开关没开。 同一页文档说明,LANGSMITH_TRACING 环境变量同时控制 @traceable 装饰器和 trace 上下文管理器,不用改代码就能整体开关。
  2. 函数没装饰、客户端没包。 官方例子特意注明 chat_pipeline 本身没被装饰,是外面套了一层 ls.trace;OpenAI 客户端要用 wrap_openai 包住才会自动追踪。新入口没走这些位置,就不会出现。
  3. 上下文在异步或线程里断了。 嵌套追踪文档写明,SDK 靠 contextvars 隐式传递追踪信息,Python 3.8–3.10 的 asyncio task 支持不完整,可能产生断开的 trace;标准库的 ThreadPoolExecutor 默认就会打断追踪,官方另给了 ContextThreadPoolExecutor。这时子 run 其实记下了,只是落到了别的 trace 里。
  4. 父 span 没上报,子 span 被丢弃。 LangChain 文档的一次提交补充说明:父 span 一直没导出时,缓冲的子 span 到期后不会成为 run,而接口在处理前就已经返回 200,调用方看不到任何报错。

第 4 种最麻烦,因为从头到尾没有一条错误日志。如果新路径在 trace 里找不到,我会先查开关和装饰位置,再看是不是跨了线程或 task,最后才怀疑代码根本没执行。

固定输入的测试,管的是另一层

九月整理的 test_retrieve_structure.py 只验证召回结果的必要字段,judge 测试只验证 JSON 解析与回退。它们没调用真实检索与模型,也没覆盖完整 B 循环。实际输出的缺口,仍要沿上面的 trace 查。

这条分界在 LangSmith 的评测概念里也有名字:单元测试验证单个组件,代码评测器是确定性的规则函数,例子正是 JSON 结构、必备字段和 schema 合规。字段测试做的就是这一层,它本来就不承担端到端质量。

judge 的回退也不是边角情况。结构化输出文档直接承认模型用工具调用产出结构化结果时会出错,提供了默认开启的重试和 handle_errors,还有 StructuredOutputValidationError 这类具名异常。解析失败是预期分支,用固定输入盯住它是值得的。

要往上一层测“材料对不对”,代价马上变大。Ragas 的 context recall 明说计算时总要有参考答案;生成侧的 faithfulness 只比回答和召回内容是否一致,不需要参考答案。两层指标不能互相顶替,检索层要先有一批带标准答案的样本,这是那批字段测试补不上的。

judge 本身能不能判质量,MT-Bench 那篇论文给过一个参考:强模型裁判和人类偏好的一致率超过 80%,接近人与人之间的水平,但有位置、啰嗦和偏向自己三类偏置,推理能力也有限。我的看法是拿它做粗筛够用,现在让 judge 测试只管 JSON 解析与回退,是合理的分工。

文档和冒烟检查,各放各的位置

这一阶段整理了三份文档:

文档 放什么
README 启动步骤
DEMO_CHECKLIST 演示前检查
INTERVIEW 选型说明

各放各的位置,免得现场讲到一半才去找配置。检查 README 时,最好在一个新目录按顺序走:旧目录里的缓存和已生成文件,常常让漏写的步骤暂时不报错。

检查单这件事有老前辈。Google SRE 书里收录的发布协调检查单是他们约 2005 年的原版,里面把“监控内部状态”和“监控端到端行为”分开列,也把可重复构建列为发布流程的一项。在新目录重走一遍 README,算是一个人版本的可重复构建。

eval_smoke.py 留给快速演示检查,用几组典型输入看追问、报告结构与引用有没有明显异常。它不是带完整样本和指标的医学评测。Google 的 ML Test Score 列了 28 项测试和监控需求,同时承认模型的实际预测行为很难事先规定标准。冒烟检查只管“明显坏了没有”,正是因为更细的标准还给不出来。

某条输出不对,就带着这条输入找对应的过程记录:字段缺失先改代码,材料不对先查召回,报告漏读再看 prompt,不把所有失败都归给模型。

留下几句话 读者来信

读到这里,有什么想法?欢迎留下你的看法。