langchain-ai/langchain#38892 · RunnableWithFallbacks 空流误判:9个PR全部卡关,监管场景下的静默数据污染 · 2026-07-24
RunnableWithFallbacks.stream() 用裸
next(stream) 偷窥第一个chunk来判断primary是否成功。
当primary合法地返回零个chunk(空结果集)时,
StopIteration 被
exceptions_to_handle
默认捕获为失败,导致两种静默错误:
① fallback静默替换了正确的空结果(调用方以为拿到了primary输出,实际是备胎内容);
② 无fallback时抛出 RuntimeError: generator raised StopIteration(把正确空流误报为代码bug)。
任何返回空结果的Filter/ModerationGate/SummarizerChain都会触发——这在生产Agent中极为常见。
fallback内容本身是合理的——所以调用方无法从内容判断"这条是primary的,还是备胎的?"——无任何信号。
合规审查/分析流水线中,"该步骤正确返回了空结果"本身就是重要发现(如"本协议无排除条款")。空结果被静默替换改变了流水线的语义。
当 primary 产生 0 个 chunk:
next(stream) → StopIteration(正常流结束)RuntimeError: generator raised StopIterationexcept Exception 捕获 → fallback触发9个PR全部因"未分配issue"被自动关闭——维护者的分配机制形成了死锁。
但更值得注意的是:@sergioperezcheco、@ErenAta16、@truongsontung、@PiedPiper911 四人独立研究,全部收敛到同一方案:
用哨兵值把"流正常结束返回空"和"流抛出异常"分开——StopIteration不再被当作异常处理。
场景:合规审查流水线
步骤1(非结构化):"总结此赔偿条款的风险" → 空结果(该条款无风险)
步骤2(结构化提取):从总结中提取JSON义务
→ 实际发生:步骤1被fallback的非结构化总结替换,步骤2提取的是fallback的JSON
→ 审查员看到的是"看起来合理的风险评估",实际上风险分析根本没跑。
问题本质
Agent的语义输出被静默替换,没有任何元数据标记这是来自primary还是fallback。调用方无法发现发生了替换,除非对比fallback的内容。
① OutputValidator — 强制空值语义不变式
② CircuitBreaker — 空流率异常熔断
③ IdempotencyGuard — 防止重放攻击掩盖空流问题
ARK OTel Bridge 可捕获 ark.fallback.trigger 事件,记录每次 fallback 触发的具体原因:
配合 Langfuse/Jaeger 可视化,Agent团队可以实时监控"空流→fallback"频率,在静默污染扩散前发现根因。
#38892 是 LangChain 最被低估的高危bug。它不crash、不报错,只静默污染Agent的输出语义。
在合规审查、数据分析、内容审核等"空结果本身就是重要发现"的场景中,这个bug把"我们发现没有问题"替换成"fallback说没有问题"——两者在内容层面几乎无法区分,却在语义上完全不等价。
9个PR的关闭揭示的不仅是维护瓶颈,更说明了独立研究者的共识强度——四人独立、同一方案,这是bug修复中最稀缺的信号。
ARK的幂等守护+输出验证组合,恰好填补了这个"无信号静默污染"的风险缺口:ARK让空流vs fallback变得可观测、可拦截、可证明。