LlamaIndex 第七篇 结构化数据提取

大型语言模型(LLMs)在数据理解方面表现出色,这也促成了它们最重要的应用场景之一:能够将常规的人类语言(我们称之为非结构化数据)转化为特定的、规范的、可被计算机程序处理的格式。我们将这一过程的输出称为结构化数据。由于在转换过程中通常会忽略大量冗余数据,因此我们称其为提取(extraction)。

LlamaIndex 中结构化数据提取的核心是 Pydantic 类:你可以在 Pydantic 中定义一个数据结构,然后 LlamaIndex 会与 Pydantic 协作,将 LLM 的输出强制转换为该结构。

Pydantic 介绍

Pydantic 是一个广泛使用的数据验证和转换库。它 heavily 依赖于 Python 的类型声明。该项目的官方文档中有一份关于 Pydantic 的详尽指南,但在这里我们将仅介绍其基本内容。

要创建一个 Pydantic 类,需要继承自 Pydantic 的 BaseModel 类:

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name: str = "Jane Doe"

在这个示例中,你创建了一个 User 类,包含两个字段:idname。你将 id 定义为整数,name 定义为字符串,默认值为 Jane Doe

你可以通过嵌套这些模型来创建更复杂的结构:

from typing import List, Optional
from pydantic import BaseModel


class Foo(BaseModel):
    count: int
    size: Optional[float] = None


class Bar(BaseModel):
    apple: str = "x"
    banana: str = "y"


class Spam(BaseModel):
    foo: Foo
    bars: List[Bar]

现在,Spam 拥有一个 foo 和一个 barsFoo 包含一个 count 和一个可选的 size,而 bars 是一个对象列表,每个对象都有一个 applebanana 属性。

将 Pydantic 对象转换为 JSON Schema

Pydantic 支持将 Pydantic 类转换为符合流行标准的 JSON 序列化模式对象。例如,上面的 User 类将被序列化为以下形式:

{
  "properties": {
    "id": {
      "title": "Id",
      "type": "integer"
    },
    "name": {
      "default": "Jane Doe",
      "title": "Name",
      "type": "string"
    }
  },
  "required": [
    "id"
  ],
  "title": "User",
  "type": "object"
}

这一特性至关重要:这些 JSON 格式的模式经常被传递给大型语言模型(LLMs),而 LLMs 会将其用作如何返回数据的指令。

使用注解

如前所述,大型语言模型(LLMs)会使用来自 Pydantic 的 JSON 模式作为返回数据的指令。为了帮助 LLM 更好地理解并提高返回数据的准确性,建议在模式中包含对对象和字段的自然语言描述,说明它们的用途。Pydantic 通过文档字符串(docstrings)和 Field 支持这一功能。

使用结构化 LLM

在 LlamaIndex 中提取结构化数据的最高级方式是实例化一个结构化 LLM(Structured LLM)。首先,让我们像之前一样实例化我们的 Pydantic 类:

from datetime import datetime
from typing import List

from pydantic import BaseModel, Field


class LineItem(BaseModel):
    """  发票中的一项明细。 """
    item_name: str = Field(description="产品名称")
    price: float = Field(description="产品价格")


class Invoice(BaseModel):
    """ 发票信息 """
    invoice_id: str = Field(description="该发票的唯一标识符,通常是一个数字")
    date: datetime = Field(description="该发票的创建日期")
    line_items: List[LineItem] = Field(description="该发票中所有项目的列表")

接着安装用于读取 PDF 文件的模块:

pip install llama-index-readers-file -i https://mirrors.aliyun.com/pypi/simple/

现在,我们来加载一份实际发票的文本内容:

from llama_index.readers.file import PDFReader
from pathlib import Path

pdf_reader = PDFReader()
documents = pdf_reader.load_data(file=Path("./uber_receipt.pdf"))
text = documents[0].text

接下来,我们实例化一个 LLM,传入我们的 Pydantic 类,并让它使用发票的纯文本内容进行补全处理:

from llama_index.llms.openai import OpenAI

llm = OpenAI(model="gpt-4o")
sllm = llm.as_structured_llm(Invoice)

response = sllm.complete(text)

response 是一个 LlamaIndex 的 CompletionResponse 对象,它包含两个属性:textraw。其中,text 包含经过 Pydantic 处理后的响应的 JSON 序列化形式:

json_response = json.loads(response.text)
print(json.dumps(json_response, indent=2))
{
    "invoice_id": "Visa \u2022\u2022\u2022\u20224469",
    "date": "2024-10-10T19:49:00",
    "line_items": [
        {"item_name": "Trip fare", "price": 12.18},
        {"item_name": "Access for All Fee", "price": 0.1},
        {"item_name": "CA Driver Benefits", "price": 0.32},
        {"item_name": "Booking Fee", "price": 2.0},
        {"item_name": "San Francisco City Tax", "price": 0.21},
    ],
}

请注意,这张发票没有编号,因此 LLM 尽力而为,使用了信用卡号作为替代。需要强调的是,Pydantic 的验证并不能保证数据一定准确!

responseraw 属性(名称上有些令人困惑)包含了 Pydantic 对象本身:

from pprint import pprint

pprint(response.raw)
Invoice(
    invoice_id="Visa ••••4469",
    date=datetime.datetime(2024, 10, 10, 19, 49),
    line_items=[
        LineItem(item_name="Trip fare", price=12.18),
        LineItem(item_name="Access for All Fee", price=0.1),
        LineItem(item_name="CA Driver Benefits", price=0.32),
        LineItem(item_name="Booking Fee", price=2.0),
        LineItem(item_name="San Francisco City Tax", price=0.21),
    ],
)

请注意,Pydantic 创建的是一个完整的 datetime 对象,而不仅仅是将日期表示为字符串。

结构化 LLM 的行为与常规的 LLM 类完全相同:你可以调用 chatstreamachatastream 等方法,它在所有情况下都会返回 Pydantic 对象。你还可以将结构化 LLM 作为参数传递给 VectorStoreIndex.as_query_engine(llm=sllm),它将自动以结构化对象的形式响应你的 RAG 查询。

结构化 LLM 会自动为你处理所有的提示(prompting)工作。如果你想对提示内容拥有更多控制权,请继续学习《结构化预测(Structured Prediction)》相关内容。

结构化预测

结构化预测(Structured Prediction)让您对自己的应用程序如何调用大语言模型(LLM)具有更细致的控制,并且可以使用 Pydantic。我们将沿用之前示例中的同一个 Invoice 类,以相同的方式加载 PDF 文件,并继续使用 OpenAI。不同之处在于,我们不再创建一个结构化的 LLM,而是直接在 LLM 本身上调用 structured_predict 方法;这是每个 LLM 类都提供的一个方法。

结构化预测(Structured predict)接受一个 Pydantic 类和一个提示模板(Prompt Template)作为参数,同时还接受提示模板中所需变量的关键词参数。

from my_llms.MyLLMsClients import MyLLMsClients
from llama_index.core.prompts import PromptTemplate
from models.MyModels import Invoice

from llama_index.readers.file import PDFReader
from pathlib import Path

llm = MyLLMsClients.deepseek_client()

pdf_reader = PDFReader()
documents = pdf_reader.load_data(file=Path("../data/发票/发票_20250510202033.pdf"))
text = documents[0].text
#print(text)

prompt = PromptTemplate(
    "从以下文本中提取发票信息。如果找不到发票编号(invoice ID),则使用公司名称“{company_name}”和日期作为发票编号:{text}"
)

response = llm.structured_predict(
    Invoice, prompt, text=text, company_name="Uber"
)

print(response)
invoice_id='10000000' date=datetime.datetime(2025, 5, 10, 0, 0) line_items=[LineItem(item_name='长杆日式良品沐浴刷后背搓泥澡巾成人软毛搓背不求人洗澡 搓背神器', price=6.98), LineItem(item_name='kato 的新版散粉定妆粉持久不脱妆旗舰店 koto 送粉扑', price=24.8), LineItem(item_name='奥莱店专柜清仓捡漏金好事花生仿和田玉手链女手串', price=58.0)]

如你所见,这使我们能够在 Pydantic 无法完全正确解析数据时,为 LLM 提供额外的提示指令。在这种情况下,返回的对象本身就是 Pydantic 对象。如果需要,我们还可以将其输出为 JSON 格式:

json_output = response.model_dump_json()
print(json.dumps(json.loads(json_output), indent=2, ensure_ascii=False))
{
  "invoice_id": "10000000",
  "date": "2025-05-10T00:00:00",
  "line_items": [
    {
      "item_name": "长杆日式良品沐浴刷后背搓泥澡巾成人软毛搓背不求人洗澡搓背神器",
      "price": 6.98
    },
    {
      "item_name": "kato 的新版散粉定妆粉持久不脱妆旗舰店 koto 送粉扑",
      "price": 24.8
    },
    {
      "item_name": "奥莱店专柜清仓捡漏金好事花生仿和田玉手链女手串",
      "price": 58.0
    }
  ]
}

structured_predict 拥有多种变体,适用于不同的使用场景,包括异步调用(astructured_predict)和流式传输(stream_structured_predict、astream_structured_predict)。

根据你使用的 LLM 不同,structured_predict 会使用两种不同的类中的一种来处理调用 LLM 和解析输出。

FunctionCallingProgram

如果你使用的 LLM 拥有函数调用(function calling)API,FunctionCallingProgram 将会:

  1. 将 Pydantic 对象转换为一个工具(tool)

  2. 在提示(prompt)LLM 的同时强制其使用该工具

  3. 返回生成的 Pydantic 对象

这通常是一种更可靠的方法,如果可用,将优先使用该方法。然而,某些 LLM 仅支持文本输出,这类模型将会使用另一种方法。

LLMTextCompletionProgram

如果 LLM 仅支持文本输出,LLMTextCompletionProgram 将会:

  1. 将 Pydantic 对象的结构以 JSON 格式输出

  2. 将该结构和数据一同发送给 LLM,并通过提示指令要求其以符合该结构的形式进行响应

  3. 在 Pydantic 对象上调用 model_validate_json() 方法,并传入 LLM 返回的原始文本

这种方法的可靠性明显较低,但所有基于文本的 LLM 都支持这种方式。

直接调用预测类

在实际使用中,structured_predict 应该可以很好地兼容任何 LLM,但如果你需要更底层的控制,也可以直接调用 FunctionCallingProgram 和 LLMTextCompletionProgram,并进一步自定义其行为:

textCompletion = LLMTextCompletionProgram.from_defaults(
    output_cls=Invoice,
    llm=llm,
    prompt=PromptTemplate(
        "从以下文本中提取发票信息。如果找不到发票编号(invoice ID),则使用公司名称“{company_name}”和日期作为发票编号:{text}"
    ),
)

output = textCompletion(company_name="Uber", text=text)

以上操作等同于在没有函数调用 API 的 LLM 上调用 structured_predict,它会像 structured_predict 一样返回一个 Pydantic 对象。不过,你可以通过继承 PydanticOutputParser 来自定义输出的解析方式:

from llama_index.core.output_parsers import PydanticOutputParser

class MyOutputParser(PydanticOutputParser):
    def get_pydantic_object(self, text: str):
        # do something more clever than this
        return self.output_parser.model_validate_json(text)

textCompletion = LLMTextCompletionProgram.from_defaults(
    output_cls=Invoice,
    llm=llm,
    prompt=PromptTemplate(
        "从以下文本中提取发票信息。如果找不到发票编号(invoice ID),则使用公司名称“{company_name}”和日期作为发票编号:{text}"
    ),
    output_parser=MyOutputParser(output_cls=Invoice),
)

output = textCompletion(company_name="Uber", text=text)

print(output)

如果你使用的是性能较弱的 LLM,需要在解析过程中获得额外帮助,这种方法将非常有用。

<think>好的,用户想了解半结构化数据的概念和处理方法,特别是具体的示例。首先,我需要回顾一下半结构化数据的定义。根据引用[1],半结构化数据像是结构化和非结构化之间的桥梁,没有严格的数据模型,但比非结构化数据更易于存储,比如JSON、CSV、XML这些格式。接下来,处理方法的示例需要结合用户提供的引用内容。 引用[2]提到LlamaIndex利用大模型(LLM)将非结构化数据转化为结构化数据,并增强文本到SQL的功能。这可能涉及到如何用工具处理半结构化数据。引用[3]讨论了大模型输出结构化数据的方法,这里可能涉及解析和转换技术。引用[4]则介绍了NLP服务中的统计技术,如聚类、分类等,这些可能用于处理半结构化数据中的文本内容。 用户需要具体的处理方法示例,所以应该分点说明,比如数据解析、转换、查询优化、NLP结合等。例如,处理JSON可能用Python的json库,XML用ElementTree,CSV用pandas。转换部分可以提到将JSON转为关系型数据库的表,或者用大模型提取信息。查询优化方面,可以举XPath或SQL查询的例子。结合NLP的话,可能用文本分析技术处理半结构中的非结构化部分。 还要注意用户可能的应用场景,比如数据集成或分析,因此需要强调半结构化数据的灵活性和处理后的用途。确保每个示例都有引用支持,比如LlamaIndex的应用对应引用[2],NLP技术对应引用[4]。最后检查是否符合格式要求,比如使用中文,数学公式用$...$,独立公式用$$...$$,但问题中没有涉及数学表达式,所以可能不需要。生成的相关问题要围绕半结构化数据的其他方面,如与结构化的对比、工具推荐、NLP结合案例,以及挑战等。</think>### 半结构化数据的处理方法与示例 半结构化数据(如 JSON、XML、CSV)介于结构化和非结构化数据之间,其特点是没有严格的数据模式,但包含一定的标签或标记来组织信息[^1]。以下是其核心处理方法及示例: --- #### 1. **数据解析与标准化** - **方法**:通过语法解析器提取结构化数据中的字段和层级关系,并转换为统一格式(如关系型数据库表)。 - **示例**: - **JSON 解析**:使用 Python 的 `json` 库解析嵌套数据: ```python import json data = '{"user": {"name": "Alice", "age": 30}}' parsed = json.loads(data) print(parsed["user"]["name"]) # 输出:Alice ``` - **XML 解析**:通过 `ElementTree` 提取标签内容: ```python import xml.etree.ElementTree as ET xml_data = "<person><name>Bob</name></person>" root = ET.fromstring(xml_data) print(root.find("name").text) # 输出:Bob ``` --- #### 2. **模式推断与动态转换** - **方法**:利用工具(如 Apache Spark、Pandas)自动推断数据结构,或通过大模型(LLM)生成结构化模式[^3]。 - **示例**: - **CSV 到 DataFrame**:使用 Pandas 读取 CSV 并推断数据类型: ```python import pandas as pd df = pd.read_csv("data.csv") print(df.dtypes) # 输出各列数据类型 ``` - **LLM 辅助提取**:通过提示词让大模型从文本生成结构化 JSON: ``` 输入:会议时间为2023-10-05,地点北京,参与人:张三、李四。 输出:{"date": "2023-10-05", "location": "北京", "participants": ["张三", "李四"]} ``` --- #### 3. **查询与索引优化** - **方法**:使用半结构化数据专用查询语言(如 XPath、JSONPath)或增强 SQL 扩展(如 PostgreSQL 的 `JSONB` 类型)[^2]。 - **示例**: - **JSONB 查询**:在 PostgreSQL 中查询嵌套字段: ```sql SELECT data->'user'->>'name' FROM table WHERE data->'age' > 25; ``` - **XPath 过滤**:从 XML 中提取特定节点: ```xpath //book[price < 30]/title ``` --- #### 4. **与 NLP 结合的信息提取** - **方法**:对半结构化数据中的文本字段(如日志、评论)应用 NLP 技术(如实体识别、关键词提取)[^4]。 - **示例**: - **评论情感分析**:从 JSON 格式的评论中提取情感倾向: ```python {"review": "产品很好,但物流太慢", "rating": 4} # 输出:{"sentiment": "正面", "complaint": "物流"} ``` --- #### 5. **数据集成与融合** - **方法**:将不同来源的半结构化数据(如 API 返回的 JSON、日志文件)整合到统一存储(如数据仓库)。 - **示例**:使用 Snowflake 直接加载 JSON 数据并关联结构化表: ```sql CREATE TABLE user_data AS SELECT parse_json(raw_data):user.id AS id, parse_json(raw_data):user.name AS name FROM staging_table; ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值