langchain-ai/langchain#38989 · get_usage_metadata_callback 异常退出后泄漏——后续调用的 token 被静默累加进旧统计 · 2026-07-24
get_usage_metadata_callback 是 LangChain 官方推荐的
按操作计量 token 用量的上下文管理器。但它的清理逻辑写在 yield 之后、
没有 try/finally 保护——一旦 with 块内抛出任何异常,
回调仍然全局注册,此后所有模型调用的 token
都会继续累加进这个"已经退出"的统计对象。
没有报错、没有日志,只有悄悄错掉的计量数据。
影响范围
所有基于该回调做计量/计费/配额的系统
框架现状
⚠️ open · bug/core 标签
ARK 方案
✅ OutputValidator + OTel Bridge
usage_metadata_callback_var.set(None)
这行清理代码被跳过 →
ContextVar 里仍挂着旧回调 → with 块外的下一次模型调用(3 tokens)
继续累加进旧统计 →
读数 6,实际应为 3 → 按操作计费/配额判断全部失真
langchain_core/callbacks/usage.py 的
get_usage_metadata_callback:
contextmanager 的 yield 裸奔,
清理语句在 yield 之后无 finally 兜底。同文件邻居
langchain_core.tracers.context
的同类上下文管理器都正确使用了 try/finally + token reset——
本质是资源清理不变式(进入必退出)在异常路径上被破坏。
OutputValidator(不变式校验)+
OTel Bridge(独立计量信源)的守备范围。
📄 复现(issue 提供零依赖最小复现,节选)
try:
with get_usage_metadata_callback() as cb:
llm.invoke("in block") # tracked: 3 tokens
raise RuntimeError
except RuntimeError:
pass
llm.invoke("outside block") # still tracked! ❌
print(cb.usage_metadata["fake"]["total_tokens"])
# 输出 6 —— 期望 3。块外调用被累进已退出的统计
📄 根因代码(issue 定位,langchain_core/callbacks/usage.py)
usage_metadata_callback_var.set(cb)
yield cb # ← 块内 raise,直接跳出
usage_metadata_callback_var.set(None) # ❌ 被跳过,回调永久泄漏
# 正确写法(同库 tracers.context 已采用):
token = usage_metadata_callback_var.set(cb)
try:
yield cb
finally:
usage_metadata_callback_var.reset(token) # ✅ 异常路径也清理
失败可见性
静默(数值悄悄错)
触发条件
with 块内任意异常
影响版本
langchain-core ≤ 1.5.0
✅ 方案A(推荐):OutputValidator 守住计量不变式
from ark import OutputValidator
# 不变式:单次操作的 token 用量必须落在合理区间
schema = {
"type": "object",
"fields": {
"total_tokens": {"type": "number", "min": 1, "max": 8000},
"call_count": {"type": "number", "min": 1, "max": 1},
},
"required": ["total_tokens", "call_count"],
}
validator = OutputValidator(schema)
with get_usage_metadata_callback() as cb:
result = run_single_operation()
validator.validate({
"total_tokens": usage_total(cb),
"call_count": usage_call_count(cb), # 泄漏累加 → 超界 → 立即 ValidationError
})💡 "读数 6 而非 3"的污染在写入计费系统之前被拦下,而不是月底对账时才发现。
✅ 方案B:OTel Bridge 独立计量信源交叉验证
import ark ark.auto_init() # ARK 事件流独立于 LangChain 回调机制 # 每次 guard/validate 调用都 emit 独立事件 → Langfuse/Jaeger # 框架计量 vs ARK 事件计数 对不上 → 告警 # 计费数据从"单一信源盲信"升级为"双信源对账"
💡 计量泄漏这类"框架内伤",只有独立第二信源能发现。ARK 事件流不经过 LangChain ContextVar,天然免疫本 bug。
✅ 方案C:上游修复(issue 作者已给出 patch)
# 等待/推动上游合入 try/finally 修复:
token = usage_metadata_callback_var.set(cb)
try:
yield cb
finally:
usage_metadata_callback_var.reset(token)💡 上游修复解决泄漏本身,但方案A/B 的边界校验+双信源仍是长期防线——下一个类似 bug 出现时你第一时间知道。
58/100 · 功能路径正常,但异常路径污染计量数据且不可见
💡 健康得分 58/100。数据可信度仅 35——一旦业务代码在计量块内抛过一次异常,
此后所有 token 统计都不可信,而系统没有任何信号提示污染已发生。
对按 token 计费、做配额限流的产品,这是财务级风险。接入 ARK
OutputValidator(计量不变式)+
OTel Bridge(独立信源对账)后,污染发生 1 秒内即可见,可信度分可回到
90+。
本报告由 ARK 生成 · 智能体健康感知系统
langchain-ai/langchain#38989
· 锚定自真实 Issue(open · bug/core · 附零依赖最小复现 · 作者已给出 patch 待合入)