DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

StateGraph 节点返回列表时报 Expected dict,先查漏改的 return

StateGraph 使用字典状态时,普通节点应返回状态更新字典。检查正常、空结果及异常分支的 return,把检索列表放进正确字段,再离线核对最终状态。

StateGraph 节点返回列表时报 Expected dict,先查漏改的 return

图可以正常编译,一跑到检索节点却报:

InvalidUpdateError: Expected dict, got ['whoops']

如果节点使用 TypedDict 状态,先看报错节点实际返回了什么。检索结果可以是列表,但普通节点交给图的返回值应是状态更新字典。例如状态字段叫 documents,应返回 {"documents": documents},不能直接把 documents 交出去。

这个区别容易在空结果分支被漏掉:正常检索返回字典,没搜到时顺手写了 return []。于是同一张图只对部分问题失败,看起来像模型输出不稳定。

把模型拿掉,先跑这张小图

下面没有模型请求,也不需要 API key。需要环境已安装 langgraph;版本诊断可以运行 python -m pip show langgraph langchain-core。下方示例适用于 StateGraph 字典状态接口,离线验收版本为 langgraph 1.2.14。

from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    query: str
    documents: list[str]
    status: str

def retrieve(state: State) -> dict:
    if not state["query"].strip():
        return {"documents": [], "status": "empty_query"}
    # 固定数据仅用于验证图的状态更新,不代表真实检索结果。
    docs = ["订单保留 30 天"] if "订单" in state["query"] else []
    return {"documents": docs, "status": "ok" if docs else "no_match"}

builder = StateGraph(State)
builder.add_node("retrieve", retrieve)
builder.add_edge(START, "retrieve")
builder.add_edge("retrieve", END)
graph = builder.compile()

for query, expected in [("订单", "ok"), ("天气", "no_match"), (" ", "empty_query")]:
    result = graph.invoke({"query": query, "documents": [], "status": "new"})
    assert result["status"] == expected
    assert isinstance(result["documents"], list)
print("three branches passed")

这段测试故意覆盖了三条路。只测能命中资料的正常问题,抓不到空列表直接返回的故障。

回到原来的节点检查什么

从节点入口走一遍每个 return,尤其看提前退出、except、缓存命中三个地方。缓存里存的是结果列表,取出后仍需要包装成状态更新;except 分支如果允许继续执行,也要返回明确定义的错误状态。不要为了消除类型错误吞掉异常,否则后续节点可能把失败当成“没有相关资料”。

还要检查包装的键名是否属于状态定义。把 return docs 改成 return {"result": docs},但图里实际字段是 documents,修复依然没有对上接口。验收时直接断言最终 documents 的值,不要只看进程是否退出。

如果原节点已经返回字典,堆栈却显示收到了列表,继续查注册到图里的包装函数。有时修的是业务函数,add_node 实际注册的是另一个适配器。

这里讨论普通状态更新节点。使用 Command 等专门控制对象时,需要按对应 API 判断,不能用“所有节点只能返回 dict”去否定这些合法入口。

官方依据:INVALID_GRAPH_NODE_RETURN_VALUE。