[如何处理长文本进行信息提取:实用策略与代码示例]

134 阅读3分钟

引言

在处理像PDF文件这样的长文本时,你可能会遇到超过语言模型上下文窗口长度的文本。为了有效处理这些文本,可以考虑以下几种策略:

  1. 更换LLM:选择支持更大上下文窗口的不同LLM。
  2. 暴力拆分:将文档分块,从每个块中提取内容。
  3. 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可能生成虚假数据:如果使用暴力方法获取单一事实,可能会得到更多虚假数据。

总结和进一步学习资源

本文演示了处理长文本进行信息提取的不同策略,展示了详细的代码示例和常见问题解决方法。进一步学习资源如下:

参考资料

  1. LangChain Documentation
  2. Pydantic Documentation
  3. Wikipedia "Car" Article

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

---END---