- 大家好,我是同学小张,日常分享AI知识和实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- 一起交流💬,一起进步💪。
- 微信公众号也可搜【同学小张】 🙏
本站文章一览:
假设有一组文档(PDF、Notion页面、客户问题等),你想要总结内容。可以利用大模型来帮你。今天来系统看下利用大模型来对文本进行总结的方法,以LangChain的使用为例。
参考:https://python.langchain.com/docs/use_cases/summarization
0. 方法概述
在利用大模型总结文本的过程中,最主要的工作是如何将文档内容传递给大模型。目前有两种常见的方法:
- Stuff方法:简单地将所有文档“填充”到单个提示中。这种方法的主要优点是简单,但缺点是当文档很长或数量很多时,可能会超出模型的上下文窗口限制,导致信息丢失或模型性能下降。
- 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知识和实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- 一起交流💬,一起进步💪。
- 微信公众号也可搜【同学小张】 🙏
本站文章一览: