Open Source Extraction Service

开源提取服务

7 分钟阅读

本月早些时候,我们 宣布 了最新的 OSS 用例加速器:一项从非结构化来源(如文本和 PDF 文档)中提取结构化数据的服务。今天,我们推出了该服务的托管版本,并带有一个简单的前端。该应用程序可以免费使用,但不适用于生产工作负载或敏感数据。其目的是展示 2024 年在此类别中可能实现的功能,并帮助开发人员快速启动他们自己的应用程序。

关键链接

为什么现在?

结构化数据提取已成为大型语言模型的一个重要用例,大型语言模型可以推理非结构化文本的歧义性,并将信息强制转换为所需的模式。模型提供商越来越多地支持长上下文窗口和函数调用功能,这两者都是数据提取的关键特性。并且我们最近改进了 LangChain 对 数据提取的支持,使开发人员可以轻松地处理各种文件类型、模式格式、模型、少量示例和提取方法(例如,工具调用、JSON 模式或解析)。托管参考应用程序允许用户使用最新的工具来试验自己的用例,并将他们看到的内容与底层的 OSS 实现联系起来。

功能

  • 支持 PDF、HTML 和文本;
  • 定义和持久化具有自身模式和自定义指令的提取器;
  • 为上下文学习添加少量示例;
  • 在用户之间共享提取器;
  • 切换 LLM 模型;
  • 用于核心提取逻辑的 LangServe 端点,允许将其插入您自己的 Langchain 工作流程中;
  • 一个前端,允许您用自然语言定义提取模式,与其他用户共享,并在文本或文件上测试它们(尚不支持少量示例)。

演示

让我们通过一个示例,从一家上市公司的财报电话会议中提取财务数据。这里我们使用 Uber 投资者关系部门在线提供的 Uber 2023 年第四季度财报电话会议的准备好的发言稿

大多数上市公司都会举行财报电话会议,为其管理层提供讨论过去财务业绩和未来计划的机会。这些电话会议的自然语言记录可能包含有用的信息,但通常必须首先从文档中提取这些信息,并将其整理成结构化形式,以便可以跨时间和与其他公司进行分析或比较。

让我们首先获取 PDF

import requests

pdf_url = "https://s23.q4cdn.com/407969754/files/doc_earnings/2023/q4/transcript/Uber-Q4-23-Prepared-Remarks.pdf"

# Get PDF bytes
pdf_response = requests.get(pdf_url)
assert(pdf_response.status_code == 200)
pdf_bytes = pdf_response.content

接下来,我们将为自己生成一个唯一的标识符。我们的应用程序不管理用户或包含合法的身份验证。对提取器、少量示例和其他工件的访问通过此 ID 控制。将其视为秘密,不要丢失它!

from uuid import uuid4

user_id = str(uuid4())
headers = {"x-key": user_id}

接下来,我们指定我们打算提取的内容的模式。这里我们指定财务数据的记录。我们允许 LLM 推断各种属性,例如记录的时间段。这里我们使用 Pydantic 以提高可读性,但最终服务依赖于 JSON 模式。

from pydantic import BaseModel, Field

class FinancialData(BaseModel):
    name: str = Field(..., description="Name of the financial figure, such as revenue.")
    value: float = Field(..., description="Nominal earnings in local currency.")
    scale: str = Field(..., description="Scale of figure, such as MM, B, or percent.")
    period_start: str = Field(..., description="The start of the time period in ISO format.")
    period_duration: int = Field(..., description="Duration of period, in months")
    evidence: str = Field(..., description="Verbatim sentence of text where figure was found.")

请注意,我们包含了一个证据属性,该属性为预测提供上下文并支持结果的下游验证。

一旦我们定义了模式,我们就可以通过将其发布到应用程序来创建一个提取器

url = "https://extract-server-f34kggfazq-uc.a.run.app"

data = {
    "user_id": user_id,
    "description": "Financial revenues and other figures.",
    "schema": FinancialData.schema(),
    "instruction": (
        "Extract standard financial figures, specifically earnings and "
        "revenue figures. Only extract historical facts, not estimates or guidance."
    )
}

response = requests.post(f"{url}/extractors", json=data, headers=headers)
extractor = response.json()

我们已经发布了提取器,现在我们可以使用其唯一 ID 访问它。现在我们可以在我们的 PDF 上尝试提取器

result = requests.post(
    f"{url}/extract",
    data={"extractor_id": extractor["uuid"], "model_name": "gpt-3.5-turbo"},
    files={"file": pdf_bytes},
    headers=headers,
)

result.json()

我们得到返回

{'data': [{'name': 'Adjusted EBITDA',
   'scale': 'million',
   'value': 1300,
   'evidence': 'These strong top-line trends, combined with continued rigor on costs, translated to $1.3 billion in Adjusted EBITDA and $652 million in GAAP operating income.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'GAAP operating income',
   'scale': 'million',
   'value': 652,
   'evidence': 'These strong top-line trends, combined with continued rigor on costs, translated to $1.3 billion in Adjusted EBITDA and $652 million in GAAP operating income.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'Revenue',
   'scale': 'billion',
   'value': 9.9,
   'evidence': 'We grew our revenue by 13% YoY on a constant-currency basis to $9.9 billion.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'Adjusted EBITDA',
   'scale': '$',
   'value': 1.26,
   'evidence': 'We expect Adjusted EBITDA of $1.26 billion to $1.34 billion.',
   'period_start': '2023-01-01',
   'period_duration': 12},
  {'name': 'Adjusted EBITDA',
   'scale': '$',
   'value': 1.34,
   'evidence': 'We expect Adjusted EBITDA of $1.26 billion to $1.34 billion.',
   'period_start': '2023-01-01',
   'period_duration': 12}]}

请注意,结果的格式在某些方面偏离了模式中的描述——例如,我们输出“million”和“billion”,而不是按指示输出“MM”或“B”。不同的模型在此方面会有不同程度的困难。在寻求更大的模型之前,明智地选择少量示例通常是澄清我们意图的有效方法。让我们向我们的提取器添加一个示例

examples = [
    {
        "text": "In 2022, Revenue was $1 million and EBIT was $2M.",
        "output": [
            FinancialData(
                name="revenue",
                value=1,
                scale="MM",
                period_start="2022-01-01",
                period_duration=12,
                evidence="In 2022, Revenue was $1 million and EBIT was $2M.",
            ).dict(),
            FinancialData(
                name="ebit",
                value=2,
                scale="MM",
                period_start="2022-01-01",
                period_duration=12,
                evidence="In 2022, Revenue was $1 million and EBIT was $2M.",
            ).dict()
        ],
    },
]

responses = []
for example in examples:
    create_request = {
        "extractor_id": extractor["uuid"],
        "content": example["text"],
        "output": example['output'],
    }
    response = requests.post(f"{url}/examples", json=create_request, headers=headers)
    responses.append(response)

这里我们添加一个包含两条记录的单个示例,其中更新了“name”字段的大小写和“scale”字段的格式。在文档上重新运行提取,我们恢复了预期的格式

result = requests.post(
    f"{url}/extract",
    data={"extractor_id": extractor["uuid"], "model_name": "gpt-3.5-turbo"},
    files={"file": pdf_bytes},
    headers=headers,
)

result.json()

我们得到

{'data': [{'name': 'adjusted ebitda',
   'scale': 'MM',
   'value': 1300.0,
   'evidence': 'These strong top-line trends, combined with continued rigor on costs, translated to $1.3 billion in Adjusted EBITDA and $652 million in GAAP operating income.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'gaap operating income',
   'scale': 'MM',
   'value': 652.0,
   'evidence': 'These strong top-line trends, combined with continued rigor on costs, translated to $1.3 billion in Adjusted EBITDA and $652 million in GAAP operating income.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'gross bookings',
   'scale': 'B',
   'value': 37.6,
   'evidence': 'Gross Bookings growth accelerated to 21% YoY on a constant-currency basis (23% excluding Freight), as we generated Gross Bookings of $37.6 billion.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'revenue',
   'scale': 'B',
   'value': 9.9,
   'evidence': 'We grew our revenue by 13% YoY on a constant-currency basis to $9.9 billion.',
   'period_start': '2023-10-01',
   'period_duration': 3},
  {'name': 'adjusted ebitda',
   'scale': 'B',
   'value': 1.3,
   'evidence': 'We expect Adjusted EBITDA of $1.26 billion to $1.34 billion.',
   'period_start': '2023-01-01',
   'period_duration': 12}],
 'content_too_long': False}

LangServe 客户端

最后一点说明:由于我们使用 LangServe 托管了核心提取逻辑,我们可以通过 RemoteRunnable 接口访问它,并将其插入到更大的链和代理工作流程中。

可运行对象可以按通常的方式调用

from langserve import RemoteRunnable

runnable = RemoteRunnable(f"{url}/extract_text/")
response = runnable.invoke(
    {
        "text": "Our 2023 revenue was $100.",
        "schema": FinancialData.schema(),
    }
)
print(response)

我们得到

{'data': [{'name': 'revenue',
   'value': 100,
   'scale': '$',
   'period_start': '2023-01-01',
   'period_duration': 12,
   'evidence': 'Our 2023 revenue was $100.'}]}

或者在下面,我们将其合并到检索场景中。在这里,我们不是直接在输入上提取,而是索引一些文档并将输入视为搜索查询。在下面搜索“rev”,将检索包含收入的文档并将提取限制在其中

from operator import itemgetter

from langchain_community.vectorstores import FAISS
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings


doc_contents = ["Our 2023 revenue was $100", "Our Q1 profit was $10 in 2023."]
vectorstore = FAISS.from_texts(doc_contents, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()

larger_runnable = (
    {
        "text": itemgetter("text") | retriever | (lambda docs: docs[0].page_content),  # fetch content of top doc,
        "schema": itemgetter("schema"),
    }
    | runnable
)
larger_runnable.invoke({"text": "rev", "schema": FinancialData.schema()})

产生结果

{'data': [{'name': 'revenue',
   'value': 100,
   'scale': '$',
   'period_start': '2023-01-01',
   'period_duration': 12,
   'evidence': 'Our 2023 revenue was $100'}]}

我们很高兴看到您构建的提取工作流程,并欢迎您对 LangChain 的提取功能提供反馈和贡献!