🛡️ ARK 智能体诊断报告

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

🔍

根因定位

🧬 现象链

with 块内调用模型(统计 3 tokens)→ 块内业务代码抛异常 → usage_metadata_callback_var.set(None) 这行清理代码被跳过 → ContextVar 里仍挂着旧回调 → with 块外的下一次模型调用(3 tokens) 继续累加进旧统计 → 读数 6,实际应为 3 → 按操作计费/配额判断全部失真

⚙️ 根因

langchain_core/callbacks/usage.pyget_usage_metadata_callback: contextmanager 的 yield 裸奔, 清理语句在 yield 之后无 finally 兜底。同文件邻居 langchain_core.tracers.context 的同类上下文管理器都正确使用了 try/finally + token reset—— 本质是资源清理不变式(进入必退出)在异常路径上被破坏
💡 本质:这是典型的 "计量数据无独立校验"问题——应用层完全信任框架内置计量器, 而计量器本身在异常路径上会静默污染。计量/计费这类 钱相关的数据,必须有独立于框架的第二信源交叉验证。 这正是 ARK 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

🔧

ARK 一键修复

✅ 方案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

58/100 · 功能路径正常,但异常路径污染计量数据且不可见

稳定性75
效率80
正确性(数据可信度)35

💡 健康得分 58/100。数据可信度仅 35——一旦业务代码在计量块内抛过一次异常, 此后所有 token 统计都不可信,而系统没有任何信号提示污染已发生。 对按 token 计费、做配额限流的产品,这是财务级风险。接入 ARK OutputValidator(计量不变式)+ OTel Bridge(独立信源对账)后,污染发生 1 秒内即可见,可信度分可回到 90+

本报告由 ARK 生成 · 智能体健康感知系统
langchain-ai/langchain#38989 · 锚定自真实 Issue(open · bug/core · 附零依赖最小复现 · 作者已给出 patch 待合入)