구조화 데이터 추출 입문

구조화 데이터 추출 입문

LLM은 데이터 이해에 아주 능숙해서, 가장 중요한 유스 케이스 중 하나를 만들어내요. 바로 사람이 쓰는 평범한 언어(우리는 이것을 비정형 데이터라고 불러요)를 컴퓨터 프로그램이 쓰는 구체적이고 규칙적인 형태로 바꾸는 거예요. 이 변환 과정의 결과물을 구조화 데이터라고 하죠. 변환 과정에서 군더더기 데이터가 많이 무시되기 때문에, 우리는 이 과정을 추출(extraction) 이라고 불러요.

LlamaIndex에서 구조화 데이터 추출이 동작하는 핵심은 Pydantic 클래스예요. Pydantic으로 데이터 구조를 정의하면, LlamaIndex가 Pydantic과 협력해 LLM의 출력을 그 구조로 맞추어 주죠.

출처: 공식문서

Pydantic이 뭘까요

Pydantic은 널리 쓰이는 데이터 검증·변환 라이브러리예요. Python 타입 선언에 크게 의존하죠. Pydantic 프로젝트 문서에 아주 방대한 가이드가 있지만, 여기서는 아주 기초만 다뤄볼게요.

Pydantic 클래스를 만들려면 Pydantic의 BaseModel 클래스를 상속하면 돼요.

from pydantic import BaseModel


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

이 예제에서는 idname 두 필드를 가진 User 클래스를 만들었어요. 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]

이제 Spamfoobars를 가지게 됐어요. Foocount와 선택적인 size를, bars는 각각 applebanana 속성을 가진 객체의 리스트죠.

Pydantic 객체를 JSON 스키마로 변환하기

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 형식의 스키마가 LLM에 전달되면 LLM이 데이터를 어떻게 돌려줄지에 대한 지침으로 쓰이기 때문이에요.

어노테이션 사용하기

앞서 말했듯 LLM은 Pydantic의 JSON 스키마를 데이터 반환 지침으로 사용해요. LLM을 돕고 반환 데이터의 정확도를 높이려면, 객체·필드가 뭘 하는지에 대한 자연어 설명을 포함해 주는 게 도움이 돼요. Pydantic은 이를 docstringFields로 지원해요.

앞으로의 모든 예제에서는 다음 Pydantic 클래스를 사용할게요.

from datetime import datetime


class LineItem(BaseModel):
    """A line item in an invoice."""

    item_name: str = Field(description="The name of this item")
    price: float = Field(description="The price of this item")


class Invoice(BaseModel):
    """A representation of information from an invoice."""

    invoice_id: str = Field(
        description="A unique identifier for this invoice, often a number"
    )
    date: datetime = Field(description="The date this invoice was created")
    line_items: list[LineItem] = Field(
        description="A list of all the items in this invoice"
    )

이건 훨씬 더 복잡한 JSON 스키마로 확장돼요.

{
  "$defs": {
    "LineItem": {
      "description": "A line item in an invoice.",
      "properties": {
        "item_name": {
          "description": "The name of this item",
          "title": "Item Name",
          "type": "string"
        },
        "price": {
          "description": "The price of this item",
          "title": "Price",
          "type": "number"
        }
      },
      "required": ["item_name", "price"],
      "title": "LineItem",
      "type": "object"
    }
  },
  "description": "A representation of information from an invoice.",
  "properties": {
    "invoice_id": {
      "description": "A unique identifier for this invoice, often a number",
      "title": "Invoice Id",
      "type": "string"
    },
    "date": {
      "description": "The date this invoice was created",
      "format": "date-time",
      "title": "Date",
      "type": "string"
    },
    "line_items": {
      "description": "A list of all the items in this invoice",
      "items": {
        "$ref": "#/$defs/LineItem"
      },
      "title": "Line Items",
      "type": "array"
    }
  },
  "required": ["invoice_id", "date", "line_items"],
  "title": "Invoice",
  "type": "object"
}

이제 Pydantic과 그것이 만들어내는 스키마에 대한 기본기를 익혔어요. 다음으로 LlamaIndex에서 Pydantic 클래스를 구조화 데이터 추출에 쓰는 방법, Structured LLMs부터 시작해 볼게요.

더 알아보기

실전에서 추출을 바로 써보고 싶다면 use_cases의 '구조화 데이터 추출' 페이지에서 추출 결과를 데이터베이스로 보내거나 워크플로를 자동화하는 흐름을 확인할 수 있어요. Structured Outputs 모듈 가이드에는 더 낮은 수준의 모듈까지 담겨 있어요.