用例(User Case)
抽取(Extraction)
操作指南(HowTo)
处理长文本(HandleLongText)

处理长文本

当处理文件(如 PDF)时,您可能会遇到超出语言模型上下文窗口的文本。为了处理这些文本,考虑以下策略:

  1. 更改 LLM 选择支持更大上下文窗口的不同 LLM。
  2. 蛮力 将文档分块,并从每个块中提取内容。
  3. RAG 将文档分块,对块进行索引,并仅从看起来“相关”的块中提取内容。

请记住,这些策略有不同的权衡,最佳策略可能取决于您正在设计的应用程序!

设置

我们需要一些示例数据!让我们下载一篇关于 汽车的维基百科文章 (opens in a new tab),并将其作为 LangChain Document 加载。

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))
78967

定义模式

在这里,我们将定义模式来提取文本中的关键发展信息。

from typing import List, Optional
 
from langchain.chains import create_structured_output_runnable
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
 
 
class KeyDevelopment(BaseModel):
    """关于汽车历史中发展的信息。"""
 
    # ^ 人物实体的文档字符串。
    # 此文档字符串将作为描述模式 Person 的 LLM 发送到,它可以帮助改进提取结果。
    # 请注意,所有字段都是必需的,而不是可选的!
    year: int = Field(
        ..., description="重要历史发展发生的年份。"
    )
    description: str = Field(
        ..., description="这一年发生了什么?有什么发展?"
    )
    evidence: str = Field(
        ...,
        description="重复从中提取年份和描述信息的句子的原文",
    )
 
 
class ExtractionData(BaseModel):
    """关于汽车历史中关键发展的提取信息。"""
 
    key_developments: List[KeyDevelopment]
 
 
# 定义自定义提示以提供说明和任何额外的上下文。
# 1)您可以将示例添加到提示模板中以改善提取质量
# 2)引入其他参数以考虑上下文(例如,包括从中提取文本的文档的元数据)。
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "您是一个在文本中识别关键历史发展的专家。"
            "仅提取重要的历史发展信息。如果在文本中找不到重要信息,请不要提取任何内容。",
        ),
        # MessagesPlaceholder('examples'), # 继续阅读此用例以了解如何使用示例来提高性能
        ("human", "{text}"),
    ]
)
 
 
# 我们将使用工具调用模式,这需要一个支持工具调用的模型。
llm = ChatOpenAI(
    # 考虑使用一个好的模型进行基准测试,以了解可能的最佳质量。
    model="gpt-4-0125-preview",
    # 记得将温度设为 0 来进行提取!
    temperature=0,
)
 
extractor = prompt | llm.with_structured_output(
    schema=ExtractionData,
    method="function_calling",
    include_raw=False,
)
/home/eugene/.pyenv/versions/3.11.2/envs/langchain_3_11/lib/python3.11/site-packages/langchain_core/_api/beta_decorator.py: 86: LangChainBetaWarning: The function `with_structured_output` is in beta. It is actively being worked on, so the API may change.
  warn_beta(

蛮力方法

将文档分块,使每个块适合 LLM 的上下文窗口。

from langchain_text_splitters import TokenTextSplitter
 
text_splitter = TokenTextSplitter(
    # 控制每个块的大小
    chunk_size=2000,
    # 控制块之间的重叠
    chunk_overlap=20,
)
 
texts = text_splitter.split_text(document.page_content)

使用 .batch 功能在每个块上以 并行 方式运行提取!

::: ⚠⚠⚠

通常可以使用 .batch() 来并行化提取! batch 在幕后使用线程池来帮助并行化工作负载。

如果您的模型通过 API 公开,这可能会加速提取流程!

:::

# 仅限前 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)
 
key_developments[:20]
[KeyDevelopment(year = 1966, description = "丰田卡罗拉开始生产,并被认为是世界上销量最好的汽车。", evidence = "丰田卡罗拉自 1966 年开始生产,被认为是世界上销量最好的汽车。"),
 KeyDevelopment(year = 1769, description ='Nicolas-Joseph Cugnot 制造了第一辆蒸汽动力公路车。', evidence ='法国发明家 Nicolas-Joseph Cugnot 于 1769 年制造了世界上第一辆蒸汽动力公路车。'),
 KeyDevelopment(year = 1808, description ='François Isaac de Rivaz 设计并制造了第一辆内燃机汽车。', evidence ='法籍瑞士发明家 François Isaac de Rivaz 于 1808 年设计并制造了世界上第一辆内燃机汽车。'),
 KeyDevelopment(year = 1886, description ='Carl Benz 获得了他的 Benz Patent-Motorwagen 专利,发明了现代汽车。', evidence ='现代汽车——作为实用的、可销售的日常使用汽车——在 1886 年被发明出来,当时德国发明家 Carl Benz 获得了他的 Benz Patent-Motorwagen 专利。'),
 KeyDevelopment(year = 1908, description ='福特汽车公司制造了大规模生产的经济车型 1908 款 T 型车,用于大众市场。', evidence ='1908 款 T 型车是一辆大众市场可负担的汽车,由福特汽车公司制造。'),
 KeyDevelopment(year = 1881, description ='Gustave Trouvé 展示了一辆由电力驱动的三轮车。', evidence ='1881 年 11 月,法国发明家 Gustave Trouvé 在电气展览会上展示了一辆由电力驱动的三轮车。'),
 KeyDevelopment(year = 1888, description = "Bertha Benz 乘汽车进行了第一次公路旅行,以证明丈夫发明的汽车的适应公路能力。", evidence = "1888 年 8 月,卡尔·本茨的妻子 Bertha Benz 乘汽车进行了第一次公路旅行,以证明丈夫发明的汽车的适应公路能力。"),
 KeyDevelopment(year = 1896, description ='本茨设计并获得了第一个内燃平面发动机的专利,称为 boxermotor。', evidence ='1896 年,本茨设计并获得了第一个内燃平面发动机的专利,称为 boxermotor。'),
 KeyDevelopment(year = 1897, description ='Nesselsdorfer Wagenbau 生产了 Präsident automobil,世界上第一批量生产的汽车之一。', evidence ='第一辆内燃机汽车在中欧由捷克公司 Nesselsdorfer Wagenbau(后更名为 Tatra)于 1897 年生产,名为 Präsident automobil。'),
 KeyDevelopment(year = 1890, description ='Daimler Motoren Gesellschaft(DMG)由 Daimler 和 Maybach 在 Cannstatt 成立。', evidence ='Daimler 和 Maybach 于 1890 年在 Cannstatt 成立了 Daimler Motoren Gesellschaft(DMG)。'),
 KeyDevelopment(year = 1902, description ='新型的 DMG 车型投产,并以 Maybach 引擎命名为 Mercedes。', evidence ='两年后,即 1902 年,生产了一款新型的 DMG 汽车,并以 Maybach 引擎的名字命名该型号,该引擎产生 35 马力。'),
 KeyDevelopment(year = 1891, description ='Auguste Doriot 和 Louis Rigoulot 乘坐由 Daimler 动力的 Peugeot Type 3 车完成了最长的汽油驱动车辆旅行。', evidence ='1891 年,Auguste Doriot 和他的 Peugeot 同事 Louis Rigoulot 乘坐他们自己设计和制造的以 Daimler 动力为驱动的 Peugeot Type 3 汽车完成了 2100 公里(1300 英里)的旅行,从 Valentigney 到巴黎和布雷斯特,然后返回。'),
 KeyDevelopment(year = 1895, description ='George Selden 获得了一项有关二冲程汽车发动机的美国专利。', evidence ='经过 16 年的延迟和一系列附件的附加,Selden 于 1895 年 11 月 5 日获得了一项关于二冲程汽车发动机的美国专利(美国专利号 549,160)。'),
 KeyDevelopment(year = 1893, description ='Duryea 兄弟制造了第一辆运行良好、以汽油驱动的美国汽车,并对其进行了路试。', evidence ='1893 年,斯普林菲尔德(马萨诸塞州)的 Duryea 兄弟制造了第一辆运行良好、以汽油驱动的美国汽车,并对其进行了路试。'),
 KeyDevelopment(year = 1897, description ='鲁道夫·迪泽尔制造了第一台柴油发动机。', evidence ='1897 年,他制造了第一台柴油发动机。'),
 KeyDevelopment(year = 1901, description ='Ransom Olds 在他的 Oldsmobile 工厂开始大规模生产经济汽车的流水线制造。', evidence ='Ransom Olds 于 1901 年在他的 Lansing(密歇根州)的 Oldsmobile 工厂开始大规模生产经济汽车的流水线制造。'),
 KeyDevelopment(year = 1913, description ='Henry Ford 在 Highland Park Ford 工厂开始世界上第一个车辆流水线组装线。', evidence ='这个概念由 Henry Ford 在 1913 年开始大幅发展,他在 Highland Park Ford 工厂开设了世界上第一个车辆流水线组装线。'),
 KeyDevelopment(year = 1914, description ='变速器车间工人工资可以购买一辆 Model T 汽车。', evidence ='1914 年,一个变速器车间工人的工资足以购买一辆 Model T 汽车。'),
 KeyDevelopment(year = 1926, description ='开发了干燥快速的 Duco 涂料,可提供各种汽车颜色。', evidence ='只有日本黑漆可以干得足够快,迫使该公司在 1913 年之前放弃了多种可用颜色的选择,直到 1926 年开发出了干燥快速的 Duco 涂料。')]## 常见问题

不同的方法在成本、速度和准确性方面都有各自的优缺点。

请注意以下问题:

  • 对内容进行分块处理意味着如果信息分散在多个块上,LLM 可能无法提取到信息。
  • 大块的重叠可能导致相同的信息被提取两次,因此需要做好去重的准备!
  • LLM 可能会编造数据。如果在大文本中寻找一个单一事实并采用蛮力方法,可能会得到更多编造的数据。