【AI大模型应用开发】【LangChain系列】实战案例6:利用大模型进行文本总结的方法探索,文本Token超限怎么办?

慈云数据 7个月前 (04-27) 技术支持 45 0
  • 大家好,我是同学小张,日常分享AI知识和实战案例
  • 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
  • +v: jasper_8017 一起交流💬,一起进步💪。
  • 微信公众号也可搜【同学小张】 🙏

    本站文章一览:

    在这里插入图片描述


    假设有一组文档(PDF、Notion页面、客户问题等),你想要总结内容。可以利用大模型来帮你。今天来系统看下利用大模型来对文本进行总结的方法,以LangChain的使用为例。

    参考:https://python.langchain.com/docs/use_cases/summarization

    文章目录

    • 0. 方法概述
    • 1. 实操练习
      • 1.1 快速开始
        • 1.1.1 代码示例
        • 1.1.2 运行一下
        • 1.2 Stuff方法
          • 1.2.1 StuffDocumentsChain
          • 1.3 Map-Reduce方法
            • 1.3.1 代码示例
              • 1.3.1.1 文本分块
              • 1.3.1.2 对分块文本的总结Chain
              • 1.3.1.3 ReduceDocumentsChain
              • 1.3.1.4 Map-Reduce组合Chain: MapReduceDocumentsChain
              • 1.3.2 运行及结果
              • 2. 部分源码
              • 3. 总结

                0. 方法概述

                在利用大模型总结文本的过程中,最主要的工作是如何将文档内容传递给大模型。目前有两种常见的方法:

                1. Stuff方法:简单地将所有文档“填充”到单个提示中。这种方法的主要优点是简单,但缺点是当文档很长或数量很多时,可能会超出模型的上下文窗口限制,导致信息丢失或模型性能下降。

                2. Map-reduce方法:这种方法分为两步。首先,在“map”步骤中,单独对每个文档进行摘要。然后,在“reduce”步骤中,将这些摘要合并成一个最终摘要。这种方法的主要优点是它可以处理大量或长文档,因为它在合并之前先对它们进行了压缩。但是,这种方法可能需要额外的逻辑来确保在“reduce”步骤中生成的最终摘要是有意义和连贯的。

                在这里插入图片描述

                1. 实操练习

                1.1 快速开始

                1.1.1 代码示例

                from langchain.chains.summarize import load_summarize_chain
                from langchain_community.document_loaders import WebBaseLoader
                from langchain_openai import ChatOpenAI
                loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
                docs = loader.load()
                llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
                chain = load_summarize_chain(llm, chain_type="stuff")
                result = chain.run(docs)
                print(result)
                

                代码示例中,使用了 LangChain 的 load_summarize_chain 来总结文本,chain_type="stuff" 表明采用 Stuff 方式。后面会给大家展示load_summarize_chain的部分实现。

                1.1.2 运行一下

                运行结果如下:

                在这里插入图片描述

                1.2 Stuff方法

                这种方法就是直接将全部文本塞给大模型,让大模型直接总结。

                1.2.1 StuffDocumentsChain

                在上面的示例代码中,我们使用 load_summarize_chain 时,传入的 chain_type="stuff" ,其实底层用的是 LangChain 中的 StuffDocumentsChain。

                看下直接 StuffDocumentsChain 的使用示例:

                from langchain.chains.combine_documents.stuff import StuffDocumentsChain
                from langchain.chains.llm import LLMChain
                from langchain.prompts import PromptTemplate
                # Define prompt
                prompt_template = """Write a concise summary of the following:
                "{text}"
                CONCISE SUMMARY:"""
                prompt = PromptTemplate.from_template(prompt_template)
                # Define LLM chain
                llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-16k")
                llm_chain = LLMChain(llm=llm, prompt=prompt)
                # Define StuffDocumentsChain
                stuff_chain = StuffDocumentsChain(llm_chain=llm_chain, document_variable_name="text")
                docs = loader.load()
                print(stuff_chain.run(docs))
                

                Prompt很简单,一眼就能看出其工作原理,它就是将docs全部扔给了大模型,让大模型给出一个简要的总结:

                prompt_template = """Write a concise summary of the following:
                "{text}"
                CONCISE SUMMARY:"""
                

                1.3 Map-Reduce方法

                首先使用LLMChain将每个文档映射到一个单独的摘要。然后,使用ReduceDocumentsChain将这些摘要合并为一个全局摘要。

                1.3.1 代码示例

                1.3.1.1 文本分块
                text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
                    chunk_size=1000, chunk_overlap=0
                )
                split_docs = text_splitter.split_documents(docs)
                
                1.3.1.2 对分块文本的总结Chain
                # Map
                map_template = """The following is a set of documents
                {docs}
                Based on this list of docs, please identify the main themes 
                Helpful Answer:"""
                map_prompt = PromptTemplate.from_template(map_template)
                map_chain = LLMChain(llm=llm, prompt=map_prompt)
                

                重点看Prompt,给定一个文档列表,根据这个文档列表,识别出主题思想。

                来看下其执行的结果(输入分割后的一段文本,输出这段文本的主题思想。每段文本都调用一次大模型,执行一次该操作,所以,注意你的API KEY的次数消耗…):

                在这里插入图片描述

                1.3.1.3 ReduceDocumentsChain

                有了上面分块的总结,下面的步骤就是根据分块总结合并成一条完整的总结。在LangChain中可以使用 ReduceDocumentsChain 类来实现此步骤。

                # Reduce
                reduce_template = """The following is set of summaries:
                {docs}
                Take these and distill it into a final, consolidated summary of the main themes. 
                Helpful Answer:"""
                reduce_prompt = PromptTemplate.from_template(reduce_template)
                # Run chain
                reduce_chain = LLMChain(llm=llm, prompt=reduce_prompt, verbose=True)
                # Takes a list of documents, combines them into a single string, and passes this to an LLMChain
                combine_documents_chain = StuffDocumentsChain(
                    llm_chain=reduce_chain, document_variable_name="docs", verbose=True
                )
                # Combines and iteratively reduces the mapped documents
                reduce_documents_chain = ReduceDocumentsChain(
                    # This is final chain that is called.
                    combine_documents_chain=combine_documents_chain,
                    # If documents exceed context for `StuffDocumentsChain`
                    collapse_documents_chain=combine_documents_chain,
                    # The maximum number of tokens to group documents into.
                    token_max=4000,
                    verbose=True
                )
                

                从代码中可以看到,ReduceDocumentsChain 设置了4个参数,我们分别来解释下。

                • combine_documents_chain:这是最终执行总结的Chain。它的值为 combine_documents_chain。而 combine_documents_chain 定义为一个 StuffDocumentsChain 类型的Chain,也就是简单地将前面分块总结的内容塞给大模型,让它根据分块总结再汇总总结一次。

                • collapse_documents_chain:这个Chain的作用,是来处理塞给大模型的Token超限的情况。如果文本特别多,分块特别多,那分块总结出来的东西也会非常多。单纯的将分块总结内容合并在一起,还是很有可能超过大模型上下文窗口限制。这个Chain,会按设置的最大Token数将内容再次拆分,然后再利用 StuffDocumentsChain 进行分块总结,直到最终各分块总结合并起来能一次塞给大模型才停止。

                  这是个递归分割总结的过程,注意Token或者调用次数的消耗,都是钱啊…

                  • token_max:最大Token数,超过这个Token数执行上面的collapse_documents_chain

                  • verbose:开详细日志

                    来直观感受下它的运行(合并分块总结内容作为输入,输出最终总结结果):

                    在这里插入图片描述

                    本例中分块总结文本合并后没有超限,所以没用到 collapse_documents_chain。

                    1.3.1.4 Map-Reduce组合Chain: MapReduceDocumentsChain
                    # Combining documents by mapping a chain over them, then combining results
                    map_reduce_chain = MapReduceDocumentsChain(
                        # Map chain
                        llm_chain=map_chain,
                        # Reduce chain
                        reduce_documents_chain=reduce_documents_chain,
                        # The variable name in the llm_chain to put the documents in
                        document_variable_name="docs",
                        # Return the results of the map steps in the output
                        return_intermediate_steps=False,
                        verbose=True
                    )
                    

                    1.3.2 运行及结果

                    print(map_reduce_chain.run(split_docs))
                    

                    在这里插入图片描述

                    2. 部分源码

                    (1)ReduceDocumentsChain 中,如果Token超限的处理:collapse_documents_chain,直接一个 while 循环压缩Token数。

                    在这里插入图片描述

                    (2)load_summarize_chain 的封装,在1.1中我们使用了 chain_type = "stuff",它其实也可以使用 “map_reduce” 或 “refine”。

                    在这里插入图片描述

                    如果chain_type设置为map_reduce,看它的源码,跟我们1.3节中的代码几乎一样。load_summarize_chain 就是对这几种方法的高层封装!

                    def _load_map_reduce_chain(
                        llm: BaseLanguageModel,
                        map_prompt: BasePromptTemplate = map_reduce_prompt.PROMPT,
                        combine_prompt: BasePromptTemplate = map_reduce_prompt.PROMPT,
                        combine_document_variable_name: str = "text",
                        map_reduce_document_variable_name: str = "text",
                        collapse_prompt: Optional[BasePromptTemplate] = None,
                        reduce_llm: Optional[BaseLanguageModel] = None,
                        collapse_llm: Optional[BaseLanguageModel] = None,
                        verbose: Optional[bool] = None,
                        token_max: int = 3000,
                        callbacks: Callbacks = None,
                        *,
                        collapse_max_retries: Optional[int] = None,
                        **kwargs: Any,
                    ) -> MapReduceDocumentsChain:
                        map_chain = LLMChain(
                            llm=llm, prompt=map_prompt, verbose=verbose, callbacks=callbacks
                        )
                        _reduce_llm = reduce_llm or llm
                        reduce_chain = LLMChain(
                            llm=_reduce_llm, prompt=combine_prompt, verbose=verbose, callbacks=callbacks
                        )
                        # TODO: document prompt
                        combine_documents_chain = StuffDocumentsChain(
                            llm_chain=reduce_chain,
                            document_variable_name=combine_document_variable_name,
                            verbose=verbose,
                            callbacks=callbacks,
                        )
                        if collapse_prompt is None:
                            collapse_chain = None
                            if collapse_llm is not None:
                                raise ValueError(
                                    "collapse_llm provided, but collapse_prompt was not: please "
                                    "provide one or stop providing collapse_llm."
                                )
                        else:
                            _collapse_llm = collapse_llm or llm
                            collapse_chain = StuffDocumentsChain(
                                llm_chain=LLMChain(
                                    llm=_collapse_llm,
                                    prompt=collapse_prompt,
                                    verbose=verbose,
                                    callbacks=callbacks,
                                ),
                                document_variable_name=combine_document_variable_name,
                            )
                        reduce_documents_chain = ReduceDocumentsChain(
                            combine_documents_chain=combine_documents_chain,
                            collapse_documents_chain=collapse_chain,
                            token_max=token_max,
                            verbose=verbose,
                            callbacks=callbacks,
                            collapse_max_retries=collapse_max_retries,
                        )
                        return MapReduceDocumentsChain(
                            llm_chain=map_chain,
                            reduce_documents_chain=reduce_documents_chain,
                            document_variable_name=map_reduce_document_variable_name,
                            verbose=verbose,
                            callbacks=callbacks,
                            **kwargs,
                        )
                    

                    3. 总结

                    本文我们学习和实践了利用 LangChain 进行文本总结的两种方法,知道了其实现原理,所以,我们应该不用 LangChain的这些封装也可以自己实现一套文档总结流程。

                    其实,LangChain 还有其它的文档总结的Chain,例如 RefineDocumentsChain 和 AnalyzeDocumentsChain,大体原理与本文介绍的两种方式都差不多,主要是封装的差异,感兴趣的也可以去试试。

                    如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~


                    • 大家好,我是 同学小张,日常分享AI知识和实战案例
                    • 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
                    • +v: jasper_8017 一起交流💬,一起进步💪。
                    • 微信公众号也可搜【同学小张】 🙏

                      本站文章一览:

                      在这里插入图片描述

微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon