多源检索结果合并

难点 : 并行分支的状态合并与顺序不确定

两个并行节点同时写同一个列表字段,结果顺序时而是 A→B,时而是 B→A。

为什么难?同一 super-step 内的并行节点看不到彼此的写入(都拿到该步开始时的快照), 合并发生在 super-step 结束时,且顺序不保证。很多人误以为并行节点可以像串行一样接力。

攻克方法:

① 接受顺序不确定,不写依赖顺序的逻辑

② 需要稳定顺序就给元素加序号字段,最后排序;

③ 需要"各自写各自的"就改用 dict + 自定义深合并 reducer;

④ 需要接力就必须串行(用边连接,别用 fan-out)。

   假设我们有三个并行检索节点(搜索网页、查询数据库、查找PDF),它们各自返回一个结果,然后合并到一个列表中。如果不加序号,合并后的顺序可能每次不同。

不加序号:顺序不确定

import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    query: str
    results: Annotated[list, operator.add]  # 累积,但顺序不确定

def search_web(state: State):
    return {"results": ["网页结果:LangGraph教程"]}

def search_db(state: State):
    return {"results": ["数据库结果:LangGraph教程"]}

def search_pdf(state: State):
    return {"results": ["PDF结果:LangGraph教程"]}

def merge(state: State):
    print(f"合并后的顺序:{state['results']}")
    # 可能输出:['网页结果', '数据库结果', 'PDF结果']
    # 也可能:   ['PDF结果', '网页结果', '数据库结果']
    # 或任意排列
    return state

# 建图:三个并行分支
builder = StateGraph(State)
builder.add_node("web", search_web)
builder.add_node("db", search_db)
builder.add_node("pdf", search_pdf)
builder.add_node("merge", merge)

builder.add_edge(START, "web")
builder.add_edge(START, "db")
builder.add_edge(START, "pdf")
builder.add_edge("web", "merge")
builder.add_edge("db", "merge")
builder.add_edge("pdf", "merge")
builder.add_edge("merge", END)

graph = builder.compile()
graph.invoke({"query": "LangGraph", "results": []})
# 每次运行,results 的顺序可能不同!

✅ 加序号字段:顺序稳定

核心思路:每个节点返回的结果带上序号,最后在合并节点中按序号排序。

import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    query: str
    # 不再直接用 list[str],而是用 list[dict],每个 dict 包含序号和数据
    results: Annotated[list, operator.add]

def search_web(state: State):
    # 返回带序号的数据
    return {"results": [{"seq": 1, "data": "网页结果:LangGraph教程"}]}

def search_db(state: State):
    return {"results": [{"seq": 2, "data": "数据库结果:LangGraph教程"}]}

def search_pdf(state: State):
    return {"results": [{"seq": 3, "data": "PDF结果:LangGraph教程"}]}

def merge(state: State):
    # 按 seq 排序,确保顺序稳定
    sorted_results = sorted(state["results"], key=lambda x: x["seq"])
    print(f"排序前的顺序:{state['results']}")
    print(f"排序后的顺序:{sorted_results}")
    # 提取数据
    final_data = [item["data"] for item in sorted_results]
    print(f"最终结果:{final_data}")
    return {"results": []}  # 清空,避免重复累积

# 建图相同
builder = StateGraph(State)
builder.add_node("web", search_web)
builder.add_node("db", search_db)
builder.add_node("pdf", search_pdf)
builder.add_node("merge", merge)

builder.add_edge(START, "web")
builder.add_edge(START, "db")
builder.add_edge(START, "pdf")
builder.add_edge("web", "merge")
builder.add_edge("db", "merge")
builder.add_edge("pdf", "merge")
builder.add_edge("merge", END)

graph = builder.compile()
graph.invoke({"query": "LangGraph", "results": []})

# 输出示例(每次运行都一致):
# 排序前的顺序:[{'seq': 1, 'data': '网页结果'}, {'seq': 3, 'data': 'PDF结果'}, {'seq': 2, 'data': '数据库结果'}]
# 排序后的顺序:[{'seq': 1, 'data': '网页结果'}, {'seq': 2, 'data': '数据库结果'}, {'seq': 3, 'data': 'PDF结果'}]
# 最终结果:['网页结果:LangGraph教程', '数据库结果:LangGraph教程', 'PDF结果:LangGraph教程']

更通用的模式:封装成 reducer

如果你经常需要“带序号的列表合并”,可以封装成一个自定义 reducer:

from typing import Any

def ordered_append(left: list[dict], right: list[dict]) -> list[dict]:
    """带序号的追加,最后可通过排序稳定顺序"""
    return left + right

def sort_by_seq(items: list[dict], key: str = "seq") -> list[dict]:
    """工具函数:按序号排序"""
    return sorted(items, key=lambda x: x[key])

# 在 State 中使用
class State(TypedDict):
    results: Annotated[list[dict], ordered_append]  # 稳定的合并

# 在合并节点中统一排序
def merge(state: State):
    ordered = sort_by_seq(state["results"])
    # 提取数据
    data = [item["data"] for item in ordered]
    return {"final_output": data}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值