十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Bug已解决】Inconsistent `generations` shape in chat model streaming `on_llm_error` callbacks: emits `[…

【Bug已解决】Inconsistent `generations` shape in chat model streaming `on_llm_error` callbacks: emits `[… 【Bug已解决】Inconsistentgenerationsshape in chat model streamingon_llm_errorcallbacks: emits[[]]and[[chunk], []]where every other path emits[]一、现象长什么样在 chat model 的流式错误回调on_llm_error里传给回调的generations参数形状不一致大多数路径成功或普通错误回调收到的generations是[]空列表表示没有生成结果。但流式错误这条路径有时候传[[]]列表里套一个空列表有时候传[[chunk], []]一个含 chunk 的列表再套一个空列表。这就导致下游回调代码写if generations:或generations[0]时行为分裂在[]路径下无结果判断成立在[[]]/[[chunk],[]]路径下却看起来有结果于是错误处理逻辑如取generations[0].text做日志在某些错误场景下拿到错误形状抛IndexError/TypeError掩盖了原始错误。本质问题同一个回调的generations参数在不同错误分支下形状不统一破坏了对generations 形状的契约假设。二、背景LangChain 的回调on_llm_error签名大致是on_llm_error(self, error, *, run_id, parent_run_id, **kwargs)其中通常会附带generations本次 LLM 调用产生的生成列表外层是批次维度内层是candidate维度List[List[Generation]]。正常成功generations [[gen1, gen2]]。 正常无生成/错误多数实现generations []表示没有结果。 流式错误路径bug错误地构造成了[[]]或[[chunk], []]——多包了一层或把半成品 chunk 也塞了进去。下游消费方按要么[]要么[[...]]理解结果流式错误的怪形状让消费方崩。三、根因根因两点错误路径构造了错误嵌套流式错误时代码把空批次或半成品 chunk用错误的列表层级包了一下得到[[]]/[[chunk],[]]。缺形状归一化所有路径的generations没有统一成同一契约无生成时一律[]有生成时一律[[...]]导致分支间形状漂移。本质把generations 的形状当成各错误分支自由发挥忽略了它是对下游的契约必须统一。四、最小可运行复现下面缩略逻辑复现形状不一致def emit_error(generations): # 下游统一按 空 - [] 处理 if generations: return generations[0][0].text # 正常取 return no generations # 成功/普通错误路径 emit_error([]) # 空正确 # 流式错误路径bug emit_error([[]]) # 非空但 generations[0][0] - IndexError emit_error([[chunk], []]) # 形状怪异修复错误路径统一把 generations 规整成[]无生成或[[...]]。def normalize_generations(raw): # 无有效生成 - [] if not raw or raw [[]]: return [] return raw def on_llm_error(error, generations): gens normalize_generations(generations) # 统一形状 if gens: log(gens[0][0].text) else: log(no generations)五、解决方案第一层最小直接修复最小修法在触发on_llm_error前把generations归一化——无有效生成时一律[]有生成时保持[[...]]剔除半成品 chunk 与多余嵌套。def normalize_generations(raw): if raw is None: return [] # 去掉空内层、去掉半成品 cleaned [] for batch in raw: if not isinstance(batch, list): continue valid [g for g in batch if getattr(g, text, None) not in (None, )] if valid: cleaned.append(valid) return cleaned # 空 - [], 有 - [[...]] def fire_on_llm_error(error, raw_generations): gens normalize_generations(raw_generations) callback.on_llm_error(error, generationsgens) # 形状统一这一层让所有错误路径的generations形状一致。六、解决方案第二层结构化改进把generations 形状契约固化成策略对象作为单一事实来源明确归一化规则。from dataclasses import dataclass from typing import List, Any dataclass(frozenTrue) class LangChainGenerationsShapePolicy: on_llm_error generations 形状策略的单一事实来源。 empty_shape: tuple () # 无生成约定为 [] strip_empty_inner: bool True def normalize(self, raw: Any) - List[List[Any]]: if not raw: return [] out [] for batch in raw: if not isinstance(batch, list): continue if self.strip_empty_inner and not batch: continue out.append(batch) return out def validate(self) - None: # 归一化后只能是 [] 或 [[...]]不允许 [[]] pass回调触发前用policy.normalize形状契约集中、可测。七、解决方案第三层断言 / CI 守护用 pytest 锁死形状import pytest from policy import LangChainGenerationsShapePolicy as P def test_empty_becomes_list(): p P() assert p.normalize(None) [] assert p.normalize([]) [] def test_strips_empty_inner(): p P() assert p.normalize([[], [chunk_a]]) [[chunk_a]] def test_keeps_valid(): p P() assert p.normalize([[chunk_a, chunk_b]]) [[chunk_a, chunk_b]] def test_no_double_nesting(): p P() out p.normalize([[chunk_a]]) assert isinstance(out[0], list) and out[0][0] is chunk_aCI 加一条on_llm_error单测注入[[]]/[[chunk],[]]/[]三种断言回调收到的generations形状统一空为[]、有则为[[...]]。八、排查清单错误回调里generations[0][0]崩 IndexError→ 流式错误传了[[]]形状。各错误路径 generations 形状一致吗→ 必须统一无生成即[]。半成品 chunk 是否被塞进 generations→ 错误路径应剔除。是否有多余嵌套→[[]]是错的归一化掉。下游按空[]判断吗→ 形状不统一会误判。是否有形状一致测试→ 必须有。九、小结chat model 流式on_llm_error回调的generations形状在错误分支间不一致有时[[]]、有时[[chunk],[]]而多数路径是[]导致下游消费方崩或误判。根因是错误路径构造了错误嵌套且缺形状归一化。第一层在触发回调前归一化 generations空即[]、有即[[...]]第二层用LangChainGenerationsShapePolicy把形状契约固化成单一事实来源第三层用 pytest 守护统一形状。回调参数的通用原则传给回调的数据结构形状必须是稳定契约所有路径归一化到同一形状绝不因分支漂移。
返回列表