引言
在处理像PDF文件这样的长文本时,你可能会遇到超过语言模型上下文窗口长度的文本。为了有效处理这些文本,可以考虑以下几种策略:
- 更换LLM:选择支持更大上下文窗口的不同LLM。
- 暴力拆分:将文档分块,从每个块中提取内容。
- Retrieval-Augmented Generation (RAG):将文档分块并索引,仅从看起来“相关”的块中提取内容。
需注意的是,这些策略各有利弊,最合适的策略依赖于你设计的应用程序。
本文将演示如何实现策略2和3。
主要内容
设置环境
首先,我们需要一些示例数据。让我们下载一篇关于汽车的维基百科文章,并将其作为LangChain文档加载。
import re
import requests
from langchain_community.document_loaders import BSHTMLLoader
# 下载内容
response = requests.get("https://en.wikipedia.org/wiki/Car")
# 将其写入文件
with open("car.html", "w", encoding="utf-8") as f:
f.write(response.text)
# 使用HTML解析器加载
loader = BSHTMLLoader("car.html")
document = loader.load()[0]
# 清理代码
# 用一个新行替换连续的新行
document.page_content = re.sub("\n\n+", "\n", document.page_content)
print(len(document.page_content)) # => 79174
定义提取模式
我们将使用Pydantic定义希望提取的信息的模式。这里,我们将提取包含年份和描述的“关键发展”列表。
from typing import List
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
class KeyDevelopment(BaseModel):
year: int = Field(..., description="发生重要历史发展的年份。")
description: str = Field(..., description="这一年的发展是什么?")
evidence: str = Field(..., description="重复提取年份和描述信息的句子。")
class ExtractionData(BaseModel):
key_developments: List[KeyDevelopment]
prompt = ChatPromptTemplate.from_messages([
("system", "你是一名擅长识别文本中关键历史发展的专家。仅提取重要的历史发展。如果文本中找不到重要信息,则不提取。"),
("human", "{text}")
])
创建提取器
选择一个支持工具调用功能的LLM,并创建提取器。
from langchain_openai import ChatOpenAI
import getpass
import os
os.environ["OPENAI_API_KEY"] = getpass.getpass()
llm = ChatOpenAI(model="gpt-4-0125-preview", temperature=0)
extractor = prompt | llm.with_structured_output(
schema=ExtractionData,
include_raw=False,
)
暴力拆分法
将文档拆分为适合LLM上下文窗口的块。
from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(
chunk_size=2000,
chunk_overlap=20,
)
texts = text_splitter.split_text(document.page_content)
并行提取每个块中的信息:
first_few = texts[:3] # 仅限制前3个块,以便快速重新运行代码
extractions = extractor.batch(
[{"text": text} for text in first_few],
{"max_concurrency": 5}, # 通过传递最大并发限制并发
)
key_developments = []
for extraction in extractions:
key_developments.extend(extraction.key_developments)
print(key_developments[:10]) # 展示前10个关键发展
基于RAG的方法
将文档拆分并索引,仅提取最相关的块。
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever(search_kwargs={"k": 1})
rag_extractor = {"text": retriever | (lambda docs: docs[0].page_content)} | extractor
results = rag_extractor.invoke("Key developments associated with cars")
for key_development in results.key_developments:
print(key_development)
常见问题和解决方案
- 拆分内容时信息丢失:信息跨多个块时,LLM可能无法提取信息。
- 大的块重叠导致重复信息:准备好去重。
- LLM可能生成虚假数据:如果使用暴力方法获取单一事实,可能会得到更多虚假数据。
总结和进一步学习资源
本文演示了处理长文本进行信息提取的不同策略,展示了详细的代码示例和常见问题解决方法。进一步学习资源如下:
- LangChain Documentation
- LLM API Reference # 使用API代理服务提高访问稳定性
参考资料
如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!
---END---