导读:本期聚焦于小伙伴创作的《如何在 LangChain 中实现带记忆与检索的 Map-Reduce 链式问答?》,敬请观看详情。面对成百上千页的文档,单次大模型调用根本装不下全部上下文。Map-Reduce 链式把文档切分后并行摘要再汇总,可突破长度限制。但原生实现没有会话记忆,用户追问时模型会丢失前文。本文说明如何给 Map-Reduce 链接入向量检索与记忆缓冲区,让问答既能查资料也能记住聊过什么。我们会拆解检索器、映射提示、合并提示与记忆变量的挂接位置,并给出可运行代码示例,帮你避开上下文错乱与重复检索的坑。

在构建企业知识库问答系统时,开发者常常遇到一个矛盾:文档总量远超大模型上下文窗口,而用户又希望连续多轮对话。LangChain 提供的 Map-Reduce 链能将长文本分块映射再归约,但它默认无状态。要让它同时具备外部检索能力和会话记忆,需要把检索器、记忆模块与链内部提示词显式编织在一起。下面从工程结构、检索整合与记忆挂接三个层面详细说明。

如何在 LangChain 中实现带记忆与检索的 Map-Reduce 链式问答?

Map-Reduce 链的基础结构与工作原理

Map-Reduce 链的核心思想是分而治之。假设我们有一份三百页的产品手册,直接丢给模型会触发截断。LangChain 的 MapReduceDocumentsChain 会先将文档按 chunk_size 切分为若干片段,然后对每个片段调用 map 提示生成局部摘要,最后把所有摘要送入 combine 提示产生全局答案。这种结构天然适合批处理,但要注意 map 阶段彼此独立,模型看不到其他块的内容。

在代码层面,我们需要准备两个提示模板:一个用于映射,一个用于合并。映射提示通常要求“根据以下文段回答问题,若无关则回不知道”;合并提示则要求“基于若干摘要回答同一问题”。由于 map 是并行或串行执行的,如果问题本身依赖跨段落推理,就必须在 combine 阶段补上必要的前后文。这也是为什么单纯 Map-Reduce 仍需要检索环节先做筛选,而不是对所有文档无差别摘要。

下面给出一个最小可运行的骨架,展示如何定义链并传入文档列表。注意这里暂未接入记忆,仅体现结构。

from langchain.chains import MapReduceDocumentsChain, ReduceDocumentsChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)

map_prompt = PromptTemplate.from_template(
    "文段:{context}n问题:{question}n请仅根据该文段作答:"
)
map_chain = LLMChain(llm=llm, prompt=map_prompt)

combine_prompt = PromptTemplate.from_template(
    "多个摘要:{context}n问题:{question}n综合回答:"
)
reduce_chain = ReduceDocumentsChain(
    combine_documents_chain=LLMChain(llm=llm, prompt=combine_prompt),
    collapse_documents_chain=LLMChain(llm=llm, prompt=combine_prompt),
    token_max=3000,
)

chain = MapReduceDocumentsChain(
    llm_chain=map_chain,
    reduce_documents_chain=reduce_chain,
    document_variable_name="context",
    return_intermediate_steps=False,
)

将向量检索嵌入 Map-Reduce 前置流程

带检索的问答并不是把检索器塞进 Map-Reduce 内部,而是在链外先完成召回。具体做法是使用 VectorStoreRetriever 根据用户问题取出 top-k 相关片段,再把这些片段交给 Map-Reduce 链处理。这样做能把三百页文档缩减到五段最相关文本,既省 token 又提升准确率。检索器返回的 Document 对象带有 page_contentmetadata,可直接复用。

一个常见误区是每次用户提问都重新嵌入全部文档。正确做法是启动时建一次向量库,之后只做相似度搜索。若文档频繁变动,可用增量写入。下面示例展示如何结合 Chroma 与检索结果驱动 Map-Reduce。注意检索语句中的 question 变量来自前端输入,要防注入可加白名单校验。

检索与 Map-Reduce 的组合让系统从“读全部”变成“读相关”。但在多轮对话里,用户第二问可能省略主语,例如首轮问“退款政策”,次轮问“那跨境订单呢”。若只拿次轮短句去检索,向量库会丢失“退款”语境。这就引出了下一节记忆模块的必要性。

from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
vectordb = Chroma(collection_name="manual", embedding_function=embeddings)
retriever = vectordb.as_retriever(search_kwargs={"k": 4})

def retrieve_then_mapreduce(question):
    docs = retriever.get_relevant_documents(question)
    return chain.run(documents=docs, question=question)

answer = retrieve_then_mapreduce("如何申请退款")
print(answer)

为 Map-Reduce 链挂载多轮对话记忆

LangChain 的 ConversationBufferMemory 可以保存历史消息。但 Map-Reduce 链本身不消费记忆变量,我们需要手动把历史拼进问题或提示。推荐方案是维护一个记忆缓冲区,在每次调用前用模板将历史问答与当前问题融合成“增强问题”,再送去检索与映射。例如把历史最后三轮转成文本前缀,让检索器理解指代。

另一种更严谨的做法是定义带 chat_history 变量的 map 提示,在每段映射时都注入精简历史。不过这样会放大 token 消耗,仅建议在跨块推理强的场景使用。通常把历史放在检索查询增强与 combine 提示就够了。下面代码演示记忆类与链调用的衔接,其中 memory.load_memory_variables({}) 取出过往对话。

需要强调的是,记忆内容不能代替检索。记忆负责语境连贯,检索负责事实来源。二者通过“增强查询”桥梁连接:先用记忆补全当前问题,再用补全句检索,最后 Map-Reduce 汇总。如此用户问“它支持微信支付吗”时,系统已知“它”指前文的某产品,从而召回准确片段并给出答案。

from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

def chat(question):
    history = memory.load_memory_variables({}).get("chat_history", [])
    enhanced_q = question
    if history:
        enhanced_q = history[-1].content + " 之后问:" + question
    docs = retriever.get_relevant_documents(enhanced_q)
    result = chain.run(documents=docs, question=enhanced_q)
    memory.save_context({"input": question}, {"output": result})
    return result

print(chat("退款要几天"))
print(chat("跨境订单也适用吗"))

工程落地中的参数与避坑要点

在真实部署时,token_max 参数决定合并阶段单次传入的摘要长度。若设得过大,combine 提示会超出模型窗口;过小则触发多次 collapse,延迟升高。一般取模型上下文的六成。另外检索的 k 值不宜超过八,否则 map 阶段并行摘要成本陡增,且噪声片段降低信噪比。

另一个坑是记忆无限增长。ConversationBufferMemory 不截断,长会话会撑爆上下文。可改用 ConversationSummaryMemory 自动压缩,或限定只保留最近五轮。同时,Map-Reduce 的 map 提示要避免输出长篇,应加一句“用两句话以内回答”,防止中间摘要膨胀。最后,所有用户输入进提示前需做转义,避免恶意构造的文段闭合模板标签。

整体看,带记忆与检索的 Map-Reduce 链是一条“检索召回相关块、记忆补全指代、分块摘要再归约”的流水线。它平衡了长度限制、事实性与对话连贯,是中等规模知识库问答的务实选择。当文档跨过十万页,再考虑引入重排序与层级 Reduce 来替代扁平合并。

LangChainMap-Reduceretrieval_chain修改时间:2026-08-14 03:45:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。