如何有效处理长文本进行信息提取
在处理文本文件(如PDF)时,你可能会遇到超出语言模型上下文窗口长度的文本。为了有效处理这些文本,可以考虑以下策略:
- 更换语言模型:选择支持较大上下文窗口的不同语言模型。
- 暴力分块处理:将文档拆分为小块,并从每个块中提取内容。
- 检索增强生成(RAG):将文档分块、索引,并仅从看似“相关”的子块中提取内容。
请注意,这些策略各有利弊,最佳策略取决于你设计的应用程序!本指南将演示如何实现策略2和3。
设置
我们需要一些示例数据!让我们下载来自Wikipedia的关于汽车的文章,并将其加载为LangChainDocument。
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, Optional
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)。你可以选择OpenAI、Google、Cohere等提供商的模型。
这里我们以OpenAI的GPT-4为例:
import os
from langchain_openai import ChatOpenAI
os.environ["OPENAI_API_KEY"] = "your-openai-api-key" # 使用API代理服务提高访问稳定性
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)
并行地运行提取:
# 仅限前3个块以便快速重新运行代码
first_few = texts[: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])
基于RAG的方法
将文本分块而不是从每个块中提取信息,只专注于最相关的块。
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
texts = text_splitter.split_text(document.page_content)
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("与汽车相关的关键发展")
for key_development in results.key_developments:
print(key_development)
常见问题与解决方案
- 信息分块:LLM可能无法提取分布在多个块中的信息。
- 重复信息:如果块间重叠较大,相同信息可能被提取两次,需要去重。
- 数据虚构:如果在大量文本中寻找单一事实,可能会得到虚构的数据。
总结与进一步学习资源
在处理长文本时,选择合适的方法至关重要。以上示例展示了如何通过暴力处理和RAG方法有效提取信息。可以参考以下资料以获取更多信息:
参考资料
- LangChain Documentation
- OpenAI API Docs
- FAISS Official Website
如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!
—END—