快速结论:该问题通常出现在 LangChain Core 流式输出(streaming)场景,当使用 `AIMessageChunk.__add__` 聚合 `additional_kwargs` 或 `response_metadata` 时,如果同一 key 在不同 chunk 中携带不同的布尔值(如 `True` 和 `False`),`merge_dicts` 会静默将类型从 `bool` 转换为 `int`(结果为 `1`)。优先排查你是否在流式聚合时遇到了布尔类型被“求和”的情况,并更新到包含修复的版本。
适用环境:根据 Issue 确认:langchain-core 1.4.6,Python 3.12.13,Linux 操作系统,pydantic 2.13.4。
最快修复方案:暂无确认的独立修复方案;Issue 中已提出补丁(PR #38071/#38081 待合并),核心修复是在 `merge_dicts` 的 `int` 分支之前添加明确的 `bool` 检查,并采用“后写覆盖(last-wins)”语义。可优先尝试升级到包含该修复的最新版 langchain-core。
注意事项:该修复采用“后写覆盖”语义(与 `index`/`created`/`timestamp` 整数字段处理一致),而非直接抛出 `TypeError`;原因是 `merge_dicts` 处于流式聚合热路径,抛错会中断整个流。修复尚未合并到主分支时,生产环境请谨慎使用。
问题场景
在 LangChain 中使用 `AIMessageChunk` 进行流式输出聚合时,`additional_kwargs` 或 `response_metadata` 中同一 key 在不同 chunk 间携带不同的布尔值。例如,某模型供应商流式返回 `refusal` 标志(可能为 `True` 或 `False`),多个 chunk 中该值不一致时,`merge_dicts`(通过 `AIMessageChunk.__add__` 调用)会把布尔值当作整数相加:`True + False = 1`,导致类型从 `bool` 静默变为 `int`。
报错原文
bug(core): `merge_dicts` silently coerces differing `bool` values to `int` during streaming chunk aggregation
# Direct call
merge_dicts({"a": True}, {"a": False}) # -> {'a': 1} (expected: a bool)
# Via AIMessageChunk.__add__
a = AIMessageChunk(content="", additional_kwargs={"refusal": True})
b = AIMessageChunk(content="", additional_kwargs={"refusal": False})
(a + b).additional_kwargs # -> {'refusal': 1} (type changed bool -> int)
原因分析
可能原因:Python 中 `bool` 是 `int` 的子类,因此 `isinstance(True, int)` 返回 `True`。在 `merge_dicts` 的整数合并分支中:
elif isinstance(merged[right_k], int):
if right_k in {"index", "created", "timestamp"}:
merged[right_k] = right_v
else:
merged[right_k] += right_v # True + False -> 1
当两个 chunk 的同一 key 携带不同布尔值时,会进入整数分支执行 `+=`,将 `True` 和 `False` 相加得到整数 `1`。这个过程中没有抛出异常,但值的类型被静默改变。相等的布尔值则不会触发此问题(会被前面的相等判断短路跳过)。该问题的核心矛盾在于:其他不支持的冲突类型(如 `float`、`tuple`)都会“大声”抛出 `TypeError`,唯独 `bool` 既不安全合并也不报错,导致数据静默损坏。
环境排查
- 确认 langchain-core 版本是否为 1.4.6 或更早(可通过 `python -m langchain_core.sys_info` 查看)。
- 确认 Python 版本为 3.12.13 或更早是否存在相同行为。
- 检查流式输出场景是否涉及 `AIMessageChunk` 的 `additional_kwargs` 或 `response_metadata` 聚合。
- 检查模型供应商是否返回了布尔类型的元数据字段(如 `refusal`、`finished`、`is_valid` 等)。
解决步骤
- 确认问题来源:优先在本地直接调用 `merge_dicts({“a”: True}, {“a”: False})`,验证是否输出 `{‘a’: 1}`(此时为 bug 触发)。
- 检查 langchain-core 版本:升级到包含修复的最新版本(合并 PR #38071/#38081 后的发布版);若暂未发布,可考虑从源码分支安装。
- 若无法升级版本,可考虑绕过方案:在流式聚合前手动清洗 `additional_kwargs` 或 `response_metadata`,确保同一 key 的布尔值一致,或使用 `reset_metadata`/`override` 等方式覆盖而非合并。
- 如果自行修改源码,修复方式是在 `merge_dicts` 的 `int` 分支前增加 `bool` 检查,并采用“后写覆盖”语义:
elif isinstance(merged[right_k], bool):
merged[right_k] = right_v # last-wins
elif isinstance(merged[right_k], int):
...
验证方法
运行下面的回归测试确认问题已解决:
# 1. 直接调用应输出 bool 类型,而非 int
print(merge_dicts({"a": True}, {"a": False})) # 修复后应输出 {'a': False},类型为 bool
# 2. 等值布尔仍走短路逻辑
print(merge_dicts({"a": True}, {"a": True})) # 应输出 {'a': True}
# 3. 通过 AIMessageChunk.__add__ 验证类型保持
a = AIMessageChunk(content="", additional_kwargs={"refusal": True})
b = AIMessageChunk(content="", additional_kwargs={"refusal": False})
result = (a + b).additional_kwargs
assert isinstance(result["refusal"], bool), "布尔类型应被保留,而不是被转换为 int"
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


