
core: RunnablePick may not return a dict if keys is a string
快速结论:该问题描述的是 LangChain 中 RunnablePick 在 keys 参数为字符串(而非列表)时,其返回类型与类型标注/文档不符——它返回的是单个键对应的值,而不是一个 dict。优先排查 keys 参数的类型是否为字符串,并关注核心库对返回类型的处理。
问题场景
用户在使用 LangChain 的 RunnablePick 工具时触发。该工具用于从 dict 类型的输入中筛选指定 key 的数据。当 keys 参数传入一个字符串(如 "foo")时,返回结果是该 key 对应的值("bar"),而不是一个 dict {"foo": "bar"}。此行为与类签名中标注的返回类型不符。
报错原文
# 按类和链式调用:
# 预期返回类型为 dict[str, Any],实际返回单个值(如字符串 "bar")
# 因此可能在使用链式调用的后续步骤中触发类型错误,例如:
# TypeError: string indices must be integers
# 或
# AttributeError: 'str' object has no attribute 'get'
# 本质问题类型签名与实际行为不一致:
class RunnablePick(RunnableSerializable[dict[str, Any], dict[str, Any]]):
# 如果 keys 是字符串,实际返回非 dict 类型(如 Any)
# 链式调用中下游可能隐含对 dict 类型的依赖
原因分析
这是 LangChain 核心库中 RunnablePick 的一个设计/实现缺陷。在 keys 参数为字符串时,其行为 直接返回该 key 对应的值(类型为 Any),而不是始终返回一个 dict。这与类签名中声明的 dict[str, Any] 返回类型不匹配。可能原因如下:
- 实现时为了兼容字符串参数,直接返回了键值(类似于
dict[key]的便捷行为)。 - 类型系统无法表达“当 keys 是字符串时返回 Any,是列表时返回 dict”的这一条件类型(社区讨论确实提到类型依赖难以实现)。
- 这是一个历史遗留行为,在类型标注强化后暴露出不一致性。
环境排查
- Python 版本:3.13.2(Issue 中报告的环境)
- langchain-core 版本:0.3.47(Issue 中报告的版本)
- langchain 版本:0.3.21
- langchain-community 版本:0.3.20
- 操作系统:macOS Darwin(Issue 中报告)
- 其他相关包:langsmith、aiohttp、numpy 等
解决步骤
- 确认 keys 参数类型:检查代码中
RunnablePick的keys参数是字符串还是字符串列表。如果是字符串,你正在使用一种触发此不一致行为的方式。 - 方案A(推荐,非破坏性):将
keys参数改为字符串列表,即使只选取一个 key:RunnablePick(["foo"])。这样返回始终是 dict。 - 方案B(等待官方修复,可能涉及 breaking change):关注 Issue 中提到的两个修复路线:
- PR #31321:将签名改为
RunnablePick(RunnableSerializable[dict[str, Any], Any]),承认返回 Any,不破坏现有行为。此方案已被合并。 - PR #32389:改变行为使其始终返回 dict(破坏性变更)。社区讨论可能延迟到 v1.0 版本。
- PR #31321:将签名改为
- 方案C(代码级规避):在调用
RunnablePick之前,预先处理字符串为列表,或在使用返回值时手动判断类型。
验证方法
编写并运行回归测试,确认 keys 参数为字符串时的返回类型与预期一致:
from langchain_core.runnables import RunnablePick
chain = RunnablePick(["foo"]) # 使用列表
result = chain.invoke({"foo": "bar", "baz": 42})
# 预期:{"foo": "bar"}
assert isinstance(result, dict)
assert result == {"foo": "bar"}
# 如果仍使用字符串,确认你的下游代码能处理返回的任意类型
chain2 = RunnablePick("foo")
result2 = chain2.invoke({"foo": "bar"})
# 当前行为返回 "bar"(字符串),不是 dict
# 确认这不会导致后续步骤报错
此外,运行完整的单元测试套件(pytest tests/unit_tests/runnables/test_pick.py)确认没有回归。



