本月早些时候,我们 宣布 了最新的 OSS 用例加速器:一项从非结构化来源(如文本和 PDF 文档)中提取结构化数据的服务。今天,我们推出了该服务的托管版本,并带有一个简单的前端。该应用程序可以免费使用,但不适用于生产工作负载或敏感数据。其目的是展示 2024 年在此类别中可能实现的功能,并帮助开发人员快速启动他们自己的应用程序。
关键链接
- YouTube 演示: https://youtu.be/-FMUt3OARy0
- 托管提取服务: https://extract.langchain.com/
- GitHub 仓库: https://github.com/langchain-ai/langchain-extract
为什么现在?
结构化数据提取已成为大型语言模型的一个重要用例,大型语言模型可以推理非结构化文本的歧义性,并将信息强制转换为所需的模式。模型提供商越来越多地支持长上下文窗口和函数调用功能,这两者都是数据提取的关键特性。并且我们最近改进了 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 的提取功能提供反馈和贡献!