突破Token限制!LangChain三大文档处理策略实战指南

当使用大语言模型(如 GPT-3、GPT-4)时,模型的上下文窗口(Token 限制)往往成为瓶颈。LangChain 作为一个强大的语言模型编排框架,提供了三种核心策略——MapReduceRefine 和 Stuff,以解决这一难题。

本文将深入探讨这三种方法的工作原理、适用场景、技术实现以及如何根据实际需求做出最佳选择。

注:因本文会介绍三种方法的详细代码,建议收藏后观看

问题背景:为什么需要文档处理策略?

大语言模型(LLM)虽然功能强大,但其上下文窗口通常有限(例如 GPT-3 的 4096 Token)。当面对以下场景时,直接输入所有文档会导致模型无法处理或效果下降:

  1. 1. 长文本处理:如书籍、研究报告的总结生成;

  2. 2. 多文档分析:如企业知识库的跨文档问答;

  3. 3. 复杂逻辑任务:如需要多步推理的代码生成或数学解题。

LangChain 通过结构化链式处理(Chains)提供灵活的策略,将输入文档拆分、重组,最终生成符合要求的输出。下面我们将逐一解析三种主流方法。

对比分析与选型指南

维度StuffRefineMapReduce
处理逻辑

一次性填充

迭代优化

分治并行

Token 效率

低(可能溢出)

中(分段控制)

高(分布式处理)

上下文保留

完整但可能过载

部分保留(依赖顺序)

局部关联可能丢失

延迟

低(单次调用)

高(多次调用)

中(取决于并行度)

开发复杂度

简单

中等

复杂(需设计两阶段指令)

选型建议:
  1. 1. 优先选择 Stuff:当文档总长度 < 模型 Token 限制的 70% 且任务简单时;

  2. 2. 使用 Refine:处理书籍、长报告等需要逻辑连贯性的任务;

  3. 3. 启用 MapReduce:面对海量文档(如 >100 篇)或需要高吞吐的场景。

Stuff 策略:简单直接的“暴力填充”

核心原理

Stuff(直接填充) 是最基础的方法,其逻辑简单粗暴:将所有文档内容拼接成一个字符串,直接塞进模型的上下文窗口中,并附加任务指令(如“请总结以下内容”)。

技术实现示例

代码为完整代码,可直接运行

# 导入必要库
import os
from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.runnables import RunnablePassthrough
from langchain.chains.llm import LLMChain
from langchain.chains.combine_documents.stuff import StuffDocumentsChain
import logging

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
logger = logging.getLogger(__name__)

# AI服务配置
AI_SERVER = "http://192.168.1.1:11434"
AI_MODEL = "qwen2.5:7b"

# 代理设置(如需使用)
# PROXY_CONFIG = {'http': '127.0.0.1:7890', 'https': '127.0.0.1:7890'}
# os.environ.update(PROXY_CONFIG)

classDocumentSummarizer:
    """文档摘要生成器类"""
    
    def__init__(self, model_name=AI_MODEL, server_url=AI_SERVER):
        """初始化摘要生成器"""
        logger.info(f"初始化AI模型: {model_name}")
        self.llm = ChatOllama(model=model_name, base_url=server_url)
        
    defload_from_url(self, url):
        """从网址加载文档"""
        logger.info(f"正在从以下地址加载文档: {url}")
        web_loader = WebBaseLoader(url)
        return web_loader.load()
    
    defcreate_summary_chain(self):
        """创建摘要生成链"""
        # 定义摘要提示模板
        summary_template = """针对下面的内容,写一个简洁的总结摘要:
"{text}"
简洁的总结摘要:"""
        
        summary_prompt = PromptTemplate.from_template(summary_template)
        
        # 创建LLM链
        process_chain = LLMChain(llm=self.llm, prompt=summary_prompt)
        
        # 创建文档处理链
        return StuffDocumentsChain(
            llm_chain=process_chain, 
            document_variable_name='text'
        )
    
    defgenerate_summary(self, url):
        """生成文档摘要的主函数"""
        # 加载文档
        documents = self.load_from_url(url)
        
        # 创建摘要链
        summarizer = self.create_summary_chain()
        
        # 执行摘要生成
        logger.info("开始生成摘要...")
        result = summarizer.invoke(documents)
        
        return result['output_text']

# 主程序
if __name__ == "__main__":
    # 创建摘要器实例
    summarizer = DocumentSummarizer()
    
    # 目标文章URL
    target_url = 'https://datamining.blog.csdn.net/article/details/144689191'
    
    # 获取并打印摘要
    summary = summarizer.generate_summary(target_url)
    print("\n摘要结果:\n" + "="*50)
    print(summary)
    print("="*50)

优点与局限

  • • ✅ 优点:

    • • 实现简单,适合快速开发原型;

    • • 保留完整的上下文关联性(例如文档间的因果关系)。

  • • ❌ 缺点:

    • • 文档总长度超过 Token 限制时会直接报错;

  • • 模型可能因信息过载而忽略关键内容(“注意力稀释”现象)。

适用场景

  • • 单篇短文本的摘要生成(如新闻文章);

  • • 小规模文档的关键词提取或实体识别;

  • • 对上下文完整性要求高的简单任务。

Refine 策略:迭代优化的“渐进式加工”

核心原理

Refine(迭代优化) 采用递进式处理,类似于人类阅读长文本时的“逐步理解”过程。其工作流程分为以下步骤:

  1. 1. 初始化:将第一个文档输入模型,生成初始结果;

  2. 2. 迭代优化:将前一步的结果与下一个文档拼接,再次输入模型,要求“基于新内容优化答案”;

  3. 3. 循环:重复上述步骤直至处理完所有文档。

技术实现示例
"""
智能文档分析与摘要系统
基于LangChain框架和Ollama模型的中文文档自动摘要工具
"""

import os
from typing importList, Dict, Any

# 网络环境配置
os.environ['http_proxy'] = '127.0.0.1:7890'
os.environ['https_proxy'] = '127.0.0.1:7890'

# LangChain组件导入
from langchain_ollama import ChatOllama
from langchain_core.prompts import PromptTemplate
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain.chains.summarize import load_summarize_chain

classDocumentSummarizer:
    """文档摘要生成器"""
    
    def__init__(self, model_endpoint: str = "http://192.168.1.1:11434"):
        """初始化摘要生成器"""
        self.model_endpoint = model_endpoint
        self.ai_model = self._initialize_model()
        self.summarization_chain = None
    
    def_initialize_model(self) -> ChatOllama:
        """初始化语言模型"""
        return ChatOllama(
            model='qwen2.5:7b',
            base_url=self.model_endpoint
        )
    
    def_create_text_processor(self) -> RecursiveCharacterTextSplitter:
        """创建文本处理器"""
        return RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=150,
            separators=["\n\n", "\n", "。", ";", ",", "!", "?", " ", ""],
            keep_separator=True
        )
    
    def_setup_prompts(self) -> tuple:
        """设置提示模板"""
        # 初始提示模板
        initial_summary_template = """
请为以下内容创建一个精炼的摘要:

{text}

摘要:"""
        
        # 优化提示模板
        refining_template = """
我们正在创建一篇文章的综合摘要。

已有的摘要信息:
{existing_answer}

需要考虑的新内容:
{text}

请根据新内容完善已有摘要。如新内容无关紧要,请保持原摘要不变。
请用中文输出最终摘要。
"""
        
        return (
            PromptTemplate.from_template(initial_summary_template),
            PromptTemplate.from_template(refining_template)
        )
    
    def_build_chain(self):
        """构建摘要处理链"""
        initial_prompt, refining_prompt = self._setup_prompts()
        
        self.summarization_chain = load_summarize_chain(
            llm=self.ai_model,
            chain_type="refine",
            question_prompt=initial_prompt,
            refine_prompt=refining_prompt,
            return_intermediate_steps=False,
            input_key="input_documents",
            output_key="output_text"
        )
    
    defprocess_url(self, target_url: str) -> str:
        """处理指定URL的文档并生成摘要"""
        # 1. 加载网页内容
        print(f"正在获取网页内容: {target_url}")
        content_loader = WebBaseLoader(target_url)
        raw_documents = content_loader.load()
        
        # 2. 分割文档
        print("正在处理文档内容...")
        text_processor = self._create_text_processor()
        document_segments = text_processor.split_documents(raw_documents)
        print(f"文档已分割为{len(document_segments)}个片段")
        
        # 3. 初始化摘要链
        ifnotself.summarization_chain:
            self._build_chain()
        
        # 4. 生成摘要
        print("正在生成摘要...")
        result = self.summarization_chain.invoke(
            {"input_documents": document_segments}, 
            return_only_outputs=True
        )
        
        return result["output_text"]


if __name__ == "__main__":
    # 运行摘要生成器
    summarizer = DocumentSummarizer()
    article_url = "https://datamining.blog.csdn.net/article/details/144689191"
    
    summary = summarizer.process_url(article_url)
    
    print("\n" + "="*50)
    print("文章摘要")
    print("="*50)
    print(summary)
    print("="*50 + "\n")

优点与局限

  • • ✅ 优点:

    • • Token 消耗可控,适合超长文本;

    • • 保留部分历史上下文,适合需要连贯性的任务。

  • • ❌ 缺点:

    • • 文档处理顺序可能影响最终结果(例如关键信息在末尾时容易被忽略);

  • • 多次调用模型导致延迟较高。

适用场景

  • • 书籍或论文的章节式总结(如逐章分析后生成全书摘要);

  • • 多轮对话中的上下文继承(例如客服聊天机器人);

  • • 需要结合历史数据的时序性任务(如股票报告分析)。

四、MapReduce 策略:分治并行的“工业化流水线”

核心原理

MapReduce 借鉴分布式计算思想,将任务拆分为 Map(映射) 和 Reduce(归约) 两个阶段:

  1. 1. Map 阶段:将每个文档独立输入模型,生成局部结果(如单文档摘要);

  2. 2. Reduce 阶段:汇总所有局部结果,再次输入模型生成最终答案。

技术实现示例

代码为完整代码,可直接运行

"""
基于MapReduce范式的大型文档分析系统
MapReduce是一种编程模型,适用于大规模数据处理:
- Map阶段:将输入数据分解为较小的子任务并并行处理
- Reduce阶段:合并所有子任务结果到最终输出
"""

import os
import logging
import time
from typing importList, Dict, Optional, Tuple

from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from pydantic import BaseModel, Field

# 日志配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger("mapreduce_analyzer")

# 网络和模型配置
PROXY_CONFIG = {'http': '127.0.0.1:7890', 'https': '127.0.0.1:7890'}
os.environ.update(PROXY_CONFIG)

MODEL_URL = "https://192.168.1.1:11434"
MODEL_NAME = "qwen2.5:7b"

classDocumentInsight(BaseModel):
    """文档洞察数据模型"""
    key_points: List[str] = Field(description="文档段落的关键点")
    segment_summary: str = Field(description="段落摘要")
    segment_id: int = Field(description="段落ID")

classMapReduceProcessor:
    """基于MapReduce范式的文档处理系统"""
    
    def__init__(self, llm_url: str = MODEL_URL, llm_name: str = MODEL_NAME):
        """初始化MapReduce处理器"""
        logger.info(f"MapReduce处理器初始化,模型: {llm_name},端点: {llm_url}")
        self.llm = ChatOllama(model=llm_name, base_url=llm_url)
        
        # 配置文档分割器 - Map阶段的预处理步骤
        self.splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=100,
            separators=["\n\n", "\n", "。", ".", " ", ""]
        )
        
        # 初始化性能指标
        self.metrics = {"load_time": 0, "map_time": 0, "reduce_time": 0}
    
    defload_document(self, url: str) -> List[Document]:
        """加载文档 - MapReduce的输入数据准备阶段"""
        start_time = time.time()
        logger.info(f"[数据加载] 从URL获取文档: {url}")
        
        try:
            loader = WebBaseLoader(url)
            documents = loader.load()
            logger.info(f"[数据加载] 完成,文档大小: {sum(len(doc.page_content) for doc in documents)} 字符")
            
            self.metrics["load_time"] = time.time() - start_time
            return documents
        except Exception as e:
            logger.error(f"[数据加载] 失败: {e}")
            raise RuntimeError(f"文档加载失败: {str(e)}")
    
    defpartition_data(self, documents: List[Document]) -> List[Document]:
        """数据分区 - 为Map阶段准备并行处理单元"""
        logger.info("[数据分区] 将文档分割为更小的数据块")
        chunks = self.splitter.split_documents(documents)
        logger.info(f"[数据分区] 完成,共 {len(chunks)} 个数据块")
        return chunks
    
    defmap_function(self, doc_chunks: List[Document]) -> List[str]:
        """Map阶段 - 对每个文档块并行应用转换函数"""
        start_time = time.time()
        logger.info(f"[Map阶段] 开始处理 {len(doc_chunks)} 个文档块")
        
        # Map阶段提示模板
        map_prompt = """
        # 文档块分析任务
        
        分析以下文本块,提取核心信息:
        
        ---
        {chunk_content}
        ---
        
        请提供这段文本的关键信息提取:
        """
        
        map_template = ChatPromptTemplate.from_template(map_prompt)
        
        # 执行Map操作
        map_results = []
        for i, chunk inenumerate(doc_chunks):
            logger.info(f"[Map] 处理块 {i+1}/{len(doc_chunks)}")
            result = self.llm.invoke(
                map_template.format(chunk_content=chunk.page_content)
            )
            map_results.append(result.content)
        
        self.metrics["map_time"] = time.time() - start_time
        logger.info(f"[Map阶段] 完成,生成了 {len(map_results)} 个中间结果")
        return map_results
    
    defreduce_function(self, map_results: List[str]) -> str:
        """Reduce阶段 - 合并所有Map结果到最终输出"""
        start_time = time.time()
        logger.info(f"[Reduce阶段] 开始合并 {len(map_results)} 个Map结果")
        
        # Reduce阶段提示模板
        reduce_prompt = """
        # 信息整合任务
        
        以下是从文档不同部分提取的信息片段:
        
        {map_results}
        
        ## 任务
        将以上所有信息整合成一个连贯、全面的文档摘要。你的摘要应该:
        
        1. 包含所有主要信息点
        2. 消除重复和冗余
        3. 保持逻辑连贯性
        4. 按主题组织内容
        5. 保留重要细节和数据
        
        ## 综合摘要:
        """
        
        reduce_template = ChatPromptTemplate.from_template(reduce_prompt)
        
        # 格式化Map结果
        formatted_results = "\n\n".join([f"[片段 {i+1}]\n{result}"for i, result inenumerate(map_results)])
        
        # 执行Reduce操作
        result = self.llm.invoke(
            reduce_template.format(map_results=formatted_results)
        )
        
        self.metrics["reduce_time"] = time.time() - start_time
        logger.info("[Reduce阶段] 完成,生成了最终摘要")
        return result.content
    
    defexecute_mapreduce(self, url: str) -> Dict[str, any]:
        """执行完整的MapReduce流程"""
        total_start = time.time()
        logger.info(f"[MapReduce任务] 开始处理URL: {url}")
        
        # 1. 加载数据 - 预处理
        raw_documents = self.load_document(url)
        
        # 2. 分区 - 准备Map阶段
        document_chunks = self.partition_data(raw_documents)
        
        # 3. Map阶段 - 并行处理每个块
        map_results = self.map_function(document_chunks)
        
        # 4. Reduce阶段 - 合并结果
        final_result = self.reduce_function(map_results)
        
        # 5. 计算总时间并返回结果
        total_time = time.time() - total_start
        self.metrics["total_time"] = total_time
        
        logger.info(f"[MapReduce任务] 完成,总耗时: {total_time:.2f}秒")
        
        return {
            "summary": final_result,
            "metrics": self.metrics,
            "chunks_processed": len(document_chunks)
        }

if __name__ == "__main__":
    # 创建MapReduce处理器
    processor = MapReduceProcessor()
    
    # 执行MapReduce任务
    url = 'https://datamining.blog.csdn.net/article/details/144689191'
    result = processor.execute_mapreduce(url)
    
    # 输出结果和性能指标
    print("\n" + "="*50)
    print("MapReduce 文档分析结果:")
    print("="*50)
    print(result["summary"])
    print("\n" + "-"*30)
    print("性能指标:")
    print(f"- 数据加载: {result['metrics']['load_time']:.2f}秒")
    print(f"- Map阶段: {result['metrics']['map_time']:.2f}秒")
    print(f"- Reduce阶段: {result['metrics']['reduce_time']:.2f}秒")
    print(f"- 总处理时间: {result['metrics']['total_time']:.2f}秒")
    print(f"- 处理的数据块数: {result['chunks_processed']}")
    print("="*50)

优点与局限

  • • ✅ 优点:

    • • 支持并行处理,大幅提升吞吐量;

    • • 突破单文档 Token 限制,适合海量数据。

  • • ❌ 缺点:

    • • Map 阶段的独立处理可能丢失文档间关联(例如跨文档的指代关系);

  • • 需要设计合理的 Map 和 Reduce 指令以避免信息失真。

适用场景

  • • 企业知识库的批量问答(如从 1000 份产品文档中提取参数);

  • • 学术论文集的趋势分析(如从多篇论文摘要中提炼领域热点);

  • • 需要分布式计算的云原生应用。

实战技巧与进阶优化

技巧 1:混合使用策略

  • • 对于超长单文档,可先用 MapReduce 分块处理,再用 Refine 合并结果

  • • 示例:处理一篇 50 页的 PDF 研究报告时,先按章节拆分(Map),再逐章迭代优化(Refine)。

技巧 2:优化 Reduce 阶段的提示词

  • • 在 MapReduce 的 Reduce 阶段,使用结构化指令明确聚合逻辑:python复制
    reduce_prompt = """请根据以下局部摘要列表,生成一个全局总结:
    要求:
    1. 按时间顺序排列关键事件;
    2. 标注不同观点的冲突点;
    3. 字数不超过 300 字。
    
    局部摘要列表:{text}"""

技巧 3:规避 Refine 的顺序偏差

  • • 在迭代前对文档按重要性排序(例如用 TF-IDF 筛选关键段落);

  • • 在每次迭代时附加元数据(如“当前已处理 3/10 个文档”),帮助模型理解进度。

总结

LangChain 的 MapReduce、Refine 和 Stuff 策略为不同规模的文本处理任务提供了灵活解决方案。理解它们的底层逻辑和适用边界,能够帮助开发者在成本、效率和质量之间找到最佳平衡。随着 LLM 技术的演进,这些策略或许会进一步融合,但在可预见的未来,分治与迭代仍将是处理超长文本的核心方法论。

 我们该怎样系统的去转行学习大模型 ?

很多想入行大模型的人苦于现在网上的大模型老课程老教材,学也不是不学也不是,基于此,我用做产品的心态来打磨这份大模型教程,深挖痛点并持续修改了近100余次后,终于把整个AI大模型的学习门槛,降到了最低!

在这个版本当中:

第一您不需要具备任何算法和数学的基础
第二不要求准备高配置的电脑
第三不必懂Python等任何编程语言

您只需要听我讲,跟着我做即可,为了让学习的道路变得更简单,这份大模型教程已经给大家整理并打包,现在将这份 LLM大模型资料 分享出来: 😝有需要的小伙伴,可以 扫描下方二维码领取🆓↓↓↓

一、大模型经典书籍(免费分享)

AI大模型已经成为了当今科技领域的一大热点,那以下这些大模型书籍就是非常不错的学习资源。

二、640套大模型报告(免费分享)

这套包含640份报告的合集,涵盖了大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。(几乎涵盖所有行业)
在这里插入图片描述

三、大模型系列视频教程(免费分享)

在这里插入图片描述

四、2025最新大模型学习路线(免费分享)

我们把学习路线分成L1到L4四个阶段,一步步带你从入门到进阶,从理论到实战。

L1阶段:启航篇丨极速破界AI新时代

L1阶段:我们会去了解大模型的基础知识,以及大模型在各个行业的应用和分析;学习理解大模型的核心原理、关键技术以及大模型应用场景。

L2阶段:攻坚篇丨RAG开发实战工坊

L2阶段是我们的AI大模型RAG应用开发工程,我们会去学习RAG检索增强生成:包括Naive RAG、Advanced-RAG以及RAG性能评估,还有GraphRAG在内的多个RAG热门项目的分析。

L3阶段:跃迁篇丨Agent智能体架构设计

L3阶段:大模型Agent应用架构进阶实现,我们会去学习LangChain、 LIamaIndex框架,也会学习到AutoGPT、 MetaGPT等多Agent系统,打造我们自己的Agent智能体。

L4阶段:精进篇丨模型微调与私有化部署

L4阶段:大模型的微调和私有化部署,我们会更加深入的探讨Transformer架构,学习大模型的微调技术,利用DeepSpeed、Lamam Factory等工具快速进行模型微调。

L5阶段:专题集丨特训篇 【录播课】

全套的AI大模型学习资源已经整理打包,有需要的小伙伴可以微信扫描下方二维码,免费领取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值