前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【LLM】基于LLama2构建智能助理帮你阅读PDF文件

【LLM】基于LLama2构建智能助理帮你阅读PDF文件

原创
作者头像
Freedom123
发布2024-04-27 19:47:21
1650
发布2024-04-27 19:47:21
举报
文章被收录于专栏:AIGCAIGC

toc


前言

本文将演示如何利用 LLM 从 PDF 发票中提取数据。我将构建一个 FastAPI 服务器,该服务器将接受 PDF 文件并以 JSON 格式返回提取的数据。

我们将涵盖:

  • LangChan 用于构建 API
  • Paka,用于将 API 部署到 AWS 并水平扩展它

Paka 使用单命令方法简化了大型语言模型 (LLM) 应用程序的部署和管理。

以前,将自由格式文本转换为结构化格式通常需要我编写自定义脚本。这涉及使用 Python 或 NodeJS 等编程语言来解析文本并提取相关信息。这种方法的一个大问题是我需要为不同类型的文档编写不同的脚本。

LLM 的出现使得使用单个模型从不同的文档中提取信息成为可能。在本文中,我将向您展示如何使用 LLM 从 PDF 发票中提取信息。

我对这个项目的一些目标是:

  • 使用 HuggingFace 的开源模型 (llama2-7B),避免使用 OpenAI API 或任何其他云 AI API。
  • 构建生产就绪型 API。这意味着 API 应该能够同时处理多个请求,并且应该能够水平扩展。

一、PDF样例

我们将以 Linode 发票为例。下面是发票示例:

我们将从此发票中提取以下信息:

  • Invoice Number/ID
  • Invoice Date
  • Company Name
  • Company Address
  • Company Tax ID
  • Customer Name
  • Customer Address
  • Invoice Amount

二、构建API服务

1.PDF预处理

由于 LLM 需要文本输入,因此 PDF 文件最初必须转换为文本。对于这个任务,我们可以使用 pypdf 库或 LangChain 的 pypdf 包装器 - PyPDFLoader

代码语言:python
复制
from langchain_community.document_loaders import PyPDFLoader

pdf_loader = PyPDFLoader(pdf_path)
pages = pdf_loader.load_and_split()
page_content = pages[0].page_content

print(page_content)

以下是转换结果的示例:

代码语言:shell
复制
Page 1 of 1
Invoice Date: 2024-01-01T08:29:56
Remit to:
Akamai Technologies, Inc.
249 Arch St.
Philadelphia, PA 19106
USA
Tax ID(s):
United States EIN: 04-3432319Invoice To:
John Doe
1 Hacker Way
Menlo Park, CA
94025
Invoice: #25470322
Description From To Quantity Region Unit
PriceAmount TaxTotal
Nanode 1GB
debian-us-west
(51912110)2023-11-30
21:002023-12-31
20:59Fremont, CA
(us-west)0.0075 $5.00 $0.00$5.00
145 Broadway, Cambridge, MA 02142
USA
P:855-4-LINODE (855-454-6633) F:609-380-7200 W:https://www.linode.com
Subtotal (USD) $5.00
Tax Subtotal (USD) $0.00
Total (USD) $5.00
This invoice may include Linode Compute Instances that have been powered off as the data is maintained and
resources are still reserved. If you no longer need powered-down Linodes, you can remove the service
(https://www.linode.com/docs/products/platform/billing/guides/stop-billing/) from your account.
145 Broadway, Cambridge, MA 02142
USA
P:855-4-LINODE (855-454-6633) F:609-380-7200 W:https://www.linode.com

同意,该文本对人类阅读不友好。但它非常适合 LLM。

2.提取内容

我们不是使用 Python、NodeJs 或其他编程语言中的自定义脚本进行数据提取,而是通过精心制作的提示对 LLM 进行编程。一个好的提示是让 LLM 产生所需输出的关键。

对于我们的用例,我们可以编写这样的提示:

Extract all the following values: invoice number, invoice date, remit to company, remit to address, tax ID, invoice to customer, invoice to address, total amount from this invoice: <THE_INVOICE_TEXT>

根据型号的不同,此类提示可能有效,也可能无效。为了获得一个小型的、预先训练的、通用的模型,例如 llama2-7B,以产生一致的结果,我们最好使用 Few-Shot 提示技术。这是一种奇特的说法,我们应该提供我们想要的模型输出的示例。现在我们这样写模型提示:

代码语言:shell
复制
Extract all the following values: invoice number, invoice date, remit to company, remit to address, tax ID, invoice to customer, invoice to address, total amount from this invoice: <THE_INVOICE_TEXT>

An example output:
{
  "invoice_number": "25470322",
  "invoice_date": "2024-01-01",
  "remit_to_company": "Akamai Technologies, Inc.",
  "remit_to_address": "249 Arch St. Philadelphia, PA 19106 USA",
  "tax_id": "United States EIN: 04-3432319",
  "invoice_to_customer": "John Doe",
  "invoice_to_address": "1 Hacker Way Menlo Park, CA 94025",
  "total_amount": "$5.00"
}

大多数 LLM 会欣赏这些示例并产生更准确和一致的结果。

但是,我们将使用 LangChain 方法处理此问题,而不是使用上述提示。虽然可以在没有LangChain的情况下完成这些任务,但它大大简化了LLM应用程序的开发。

使用 LangChain,我们用代码(Pydantic 模型)定义输出模式。

代码语言:shell
复制
from langchain.output_parsers import PydanticOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field


class Invoice(BaseModel):
    number: str = Field(description="invoice number, e.g. #25470322")
    date: str = Field(description="invoice date, e.g. 2024-01-01T08:29:56")
    company: str = Field(description="remit to company, e.g. Akamai Technologies, Inc.")
    company_address: str = Field(
        description="remit to address, e.g. 249 Arch St. Philadelphia, PA 19106 USA"
    )
    tax_id: str = Field(description="tax ID/EIN number, e.g. 04-3432319")
    customer: str = Field(description="invoice to customer, e.g. John Doe")
    customer_address: str = Field(
        description="invoice to address, e.g. 123 Main St. Springfield, IL 62701 USA"
    )
    amount: str = Field(description="total amount from this invoice, e.g. $5.00")


invoice_parser = PydanticOutputParser(pydantic_object=Invoice)

写下带有详细信息的字段描述。稍后,描述将用于生成提示。

然后我们需要定义提示模板,稍后将提供给 LLM。

代码语言:shell
复制
from langchain_core.prompts import PromptTemplate

template = """
Extract all the following values : invoice number, invoice date, remit to company, remit to address,
tax ID, invoice to customer, invoice to address, total amount from this invoice: {invoice_text}

{format_instructions}

Only returns the extracted JSON object, don't say anything else.
"""

prompt = PromptTemplate(
    template=template,
    input_variables=["invoice_text"],
    partial_variables={
        "format_instructions": invoice_parser.get_format_instructions()
    },
)

呵呵,这不像 Few-Shot 提示那么直观。但是 invoice_parser.get_format_instructions() 将生成一个更详细的示例供 LLM 使用。

使用 LangChain 构建的已完成提示如下所示:

代码语言:shell
复制
Extract all the following values : 
...
...
...
The output should be formatted as a JSON instance that conforms to the JSON schema below.

As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}
the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema. The object {"properties": {"foo": ["bar", "baz"]}} is not well-formatted.

Here is the output schema:

{"properties": {"number": {"title": "Number", "description": "invoice number, e.g. #25470322", "type": "string"}, "date": {"title": "Date", "description": "invoice date, e.g. 2024-01-01T08:29:56", "type": "string"}, "company": {"title": "Company
", "description": "remit to company, e.g. Akamai Technologies, Inc.", "type": "string"}, "company_address": {"title": "Company Address", "description": "remit to address, e.g. 249 Arch St. Philadelphia, PA 19106 USA", "type": "string"}, "tax_id"
: {"title": "Tax Id", "description": "tax ID/EIN number, e.g. 04-3432319", "type": "string"}, "customer": {"title": "Customer", "description": "invoice to customer, e.g. John Doe", "type": "string"}, "customer_address": {"title": "Customer Addre
ss", "description": "invoice to address, e.g. 123 Main St. Springfield, IL 62701 USA", "type": "string"}, "amount": {"title": "Amount", "description": "total amount from this invoice, e.g. $5.00", "type": "string"}}, "required": ["number", "date
", "company", "company_address", "tax_id", "customer", "customer_address", "amount"]}


Only returns the extracted JSON object, don't say anything else.

您可以看到提示更加详细和信息丰富。“Only returned the extracted JSON object, don't say anything else.” 是我添加的,以确保 LLM 不会输出任何其他内容。

现在,我们准备使用 LLM 进行信息提取。

代码语言:shell
复制
llm = LlamaCpp(
    model_url=LLM_URL,
    temperature=0,
    streaming=False,
)

chain = prompt | llm | invoice_parser

result = chain.invoke({"invoice_text": page_content})

LlamaCpp 是 Llama2-7B 模型的客户端代理,该模型将由 Paka 托管在 AWS 中。LlamaCpp 在这里定义。当 Paka 部署 Llama2-7B 模型时,它使用很棒的 llama.cpp 项目和 llama-cpp-python 作为模型运行时。

该链是一个管道,包含提示符、LLM 和输出解析器。在此管道中,提示符被馈送到 LLM 中,输出分析器分析输出。除了在提示符中创建一次性示例外,invoice_parser还可以验证输出并返回 Pydantic 对象。

3.构建API服务

有了核心逻辑,我们的下一步是构建一个 API 端点,该端点接收 PDF 文件并以 JSON 格式提供结果。我们将使用 FastAPI 来完成此任务。

代码语言:shell
复制
from fastapi import FastAPI, File, UploadFile
from uuid import uuid4

@app.post("/extract_invoice")
async def upload_file(file: UploadFile = File(...)) -> Any:
    unique_filename = str(uuid4())
    tmp_file_path = f"/tmp/{unique_filename}"

    try:
        with open(tmp_file_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)

        return extract(tmp_file_path) # extract is the function that contains the LLM logic
    finally:
        if os.path.exists(tmp_file_path):
            os.remove(tmp_file_path)

代码非常简单。它接受一个文件,将其保存到临时位置,然后调用提取函数来提取发票数据。

4.部署API服务

我们只走了一半。正如所承诺的那样,我们的目标是开发一个生产就绪的 API,而不仅仅是在我的本地机器上运行的原型。这涉及将 API 和模型部署到云中,并确保它们可以水平扩展。此外,我们需要收集日志和指标以进行监控和分析。这是一项艰巨的工作,而且不如构建核心逻辑有趣。幸运的是,我们有 Paka 帮助我们完成这项任务。

但在深入研究部署之前,让我们试着回答这个问题:“为什么我们需要部署模型,而不仅仅是使用 OpenAI 或 Google 的 API?要部署模型的主要原因:

  • Cost: 使用 OpenAI API 可能会因为大量数据而变得昂贵。
  • Vendor lock-in: 您可能希望避免被束缚在特定的提供商身上。
  • Flexibility: 您可能更愿意根据自己的需求定制模型,或者从 HuggingFace 中心选择开源选项。
  • Control: 您可以完全控制系统的稳定性和可扩展性。
  • Privacy: 您可能不希望将敏感数据暴露给外部各方。

现在,让我们使用 Paka 将 API 部署到 AWS:

1)基础环境
代码语言:shell
复制
pip install paka

# Ensure AWS credentials and CLI are set up. 
aws configure

# Install pack CLI and verify it is working (https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/)
pack --version

# Install pulumi CLI and verify it is working (https://www.pulumi.com/docs/install/)
pulumi version

# Ensure the Docker daemon is running
docker info
2)创建配置文件

使用 CPU 实例运行模型。我们可以创建一个包含以下内容的 cluster.yaml 文件:

代码语言:shell
复制
aws:
  cluster:
    name: invoice-extraction
    region: us-west-2
    namespace: default
    nodeType: t2.medium
    minNodes: 2
    maxNodes: 4
  prometheus:
    enabled: false
  tracing:
    enabled: false
  modelGroups:
    - nodeType: c7a.xlarge
      minInstances: 1
      maxInstances: 3
      name: llama2-7b
      resourceRequest:
        cpu: 3600m
        memory: 6Gi
      autoScaleTriggers:
        - type: cpu
          metadata:
            type: Utilization
            value: "50"

大多数字段都是不言自明的。modelGroups 字段是我们定义模型组的地方。在本例中,我们定义了一个名为 llama2-7b 的模型组,其实例类型为 c7a.xlarge。autoScaleTriggers 字段是我们定义自动缩放触发器的位置。我们正在定义一个 CPU 触发器,该触发器将根据 CPU 利用率扩展实例。请注意,Paka 不支持将模型组扩展到零实例,因为冷启动时间太长。我们需要保持至少一个实例处于运行状态。

要使用 GPU 实例运行模型,下面是一个集群配置示例。

3)创建集群

现在,您可以使用以下命令预配集群:

代码语言:shell
复制
# Provision the cluster and update ~/.kube/config
paka cluster up -f cluster.yaml -u

上述命令将创建具有指定配置的新 EKS 集群。它还将使用新的集群信息更新 ~/.kube/config 文件。Paka 从 HuggingFace 中心下载 llama2-7b 模型并将其部署到集群。

4)部署服务

现在,我们想将 FastAPI 应用部署到集群。我们可以通过运行以下命令来执行此操作:

代码语言:shell
复制
# Change the directory to the source code directory
paka function deploy --name invoice-extraction --source . --entrypoint serve

FastAPI 应用部署为函数。这意味着它是无服务器的。只有当有请求时,才会调用该函数。

在后台,该命令将使用构建包构建 Docker 映像,然后将其推送到 Elastic Container Registry。然后,映像将作为函数部署到集群中。

5)测试API

首先,我们需要获取 FastAPI 应用的 URL。我们可以通过运行以下命令来执行此操作:

代码语言:shell
复制
paka function list

如果所有步骤都成功,则该函数应显示在标记为“READY”的列表中。默认情况下,可通过公共 REST API 终结点访问该函数,其格式通常类似于 http://invoice-extraction.default.50.112.90.64.sslip.io

您可以通过使用 curl 或其他 HTTP 客户端向端点发送 POST 请求来测试 API。下面是一个使用 curl 的示例:

代码语言:shell
复制
curl -X POST -H "Content-Type: multipart/form-data" -F "file=@/path/to/invoices/invoice-2024-02-29.pdf" http://invoice-extraction.default.xxxx.sslip.io/extract_invoice

如果发票提取成功,响应将显示结构化数据,如下所示:

代码语言:shell
复制
{"number":"#25927345","date":"2024-01-31T05:07:53","company":"Akamai Technologies, Inc.","company_address":"249 Arch St. Philadelphia, PA 19106 USA","tax_id":"United States EIN: 04-3432319","customer":"John Doe","customer_address":"1 Hacker Way Menlo Park, CA  94025","amount":"$5.00"}
6)监控

出于监控目的,Paka 会自动将所有日志发送到 CloudWatch,以便直接在 CloudWatch 控制台中查看这些日志。此外,您可以在 cluster.yaml 中启用 Prometheus 来收集预定义的指标。

小节

本文演示了如何使用 LLM 从 PDF 发票中提取数据。我们构建了一个FastAPI服务器,能够接收PDF文件并以JSON格式返回信息。随后,我们使用 Paka 在 AWS 上部署了 API,并启用了水平扩展。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、PDF样例
  • 二、构建API服务
    • 1.PDF预处理
      • 2.提取内容
        • 3.构建API服务
          • 4.部署API服务
            • 1)基础环境
            • 2)创建配置文件
            • 3)创建集群
            • 4)部署服务
            • 5)测试API
            • 6)监控
        • 小节
        相关产品与服务
        API 网关
        腾讯云 API 网关(API Gateway)是腾讯云推出的一种 API 托管服务,能提供 API 的完整生命周期管理,包括创建、维护、发布、运行、下线等。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档