13.11.1 텍스트 추출을 넘어선 공간적/레이아웃(Spatial Layout) 보존 여부 검사 로직

13.11.1 텍스트 추출을 넘어선 공간적/레이아웃(Spatial Layout) 보존 여부 검사 로직

단순히 2차원 평면 이미지 픽셀에서 검은색 글자를 텍스트 문자열(String)로 뜯어내는 것(단순 OCR)을 넘어서, 그 글자들이 원래 종이 문서 위에서 정확히 어떤 **‘기하학적 위상과 공간적 레이아웃(Spatial Topology & Layout)’**을 점유하고 있었는지를 그대로 보존하여 추출하는 것은, 차세대 멀티모달(Multimodal) 데이터 추출 파이프라인이 해결해야 할 가장 높은 난이도의 기술적 성배(Holy Grail)다.

엔터프라이즈의 복잡한 재무 계산서나 법률 문서에서 단락의 들여 쓰기(Indentation), 텍스트 간의 위아래 상하 포개짐 관계, 그리고 블록들을 감싸고 있는 여백(Whitespace)의 형태는 결코 예쁘게 보이기 위한 단순한 디자인 장식이 아니다. 이 공간적 배치 그 자체가 계약의 논리적 종속(Dependency)과 산술적 포함 관계를 규정하는 가장 강력하고 암묵적인 핵심 의미(Semantic) 데이터 그 자체이기 때문이다.

1. 공간 왜곡 평탄화(Spatial Flattening)의 무서운 재앙

만약 B2B 의료 보험 청구서 문서의 중앙 픽셀 영역에 다음과 같은 레이아웃의 텍스트가 인쇄되어 있다고 가정해 보자.

[요양 급여 치료비 총액]: $10,000
    - 면책 조항 적용 금액 (Customer Liability): -$2,000
    - 추가 약제비 (Additional Pharmacy): $500

이 물리적인 시각적 레이아웃에서 $2,000$500이라는 수치는 명백히 최상위 노드인 치료비 총액에 속하여 계산을 보정하는 들여 쓰기 된 자식(Child) 노드의 지위를 가진다.
하지만 VLM(Vision LLM)이 이러한 시각적 여백과 들여 쓰기가 의미하는 암묵적인 수학적 종속성을 완전히 무시하거나 망각한 채, 단순히 이미지를 위에서 아래로 스와이프하며 선형적인 1차원 텍스트 배열(Flattened Array)로만 데이터를 평탄화하여 뜯어내면 참사가 일어난다. 시스템 뒷단의 분개장 봇은 $10,000$2,000, $500을 모두 독립적이고 동등한 계층의 청구 비용으로 멍청하게 덧셈하여, 환자에게 $12,500을 부당 청구하는 치명적인 산술 환각과 법적 소송의 빌미를 일으키게 된다.

2. 트리(Tree) 구조를 강제하는 재귀적 Pydantic 스키마 설계

이러한 공간의 무참한 평탄화(Flattening) 현상을 오라클 레벨에서 근원적으로 통제하고 방어하기 위해서는, 프롬프트 엔지니어링으로 기계에게 “제발 들여 쓰기를 눈여겨봐 달라고” 애원하며 구걸하는 짓을 멈춰야 한다.
대신 우리가 쥐어주는 Pydantic 구조화 스키마(거푸집) 자체가 애초에 완벽한 트리의 계층성을 내포한 재귀적(Recursive) 구조로 뼈대가 설계되어 있어야 한다.

from pydantic import BaseModel, Field, model_validator
from typing import List, Optional

class LineItemNode(BaseModel):
    description: str
    amount: float
    
    # 공간적 깊이 레벨을 물리적 숫자로 명시적 박제
    indentation_level: int = Field(
        description="[시각적 레이아웃 계층] 문서 좌측 마진(Margin)으로부터 떨어진 들여 쓰기 깊이 레벨. (0: 최상위 부모 라인, 1: 한 번 들여 쓰기 된 단락 자식, 2: 두 번 들여 쓰기 됨)"
    )
    
    # 부모-자식 레이아웃 포함 관계를 강제 저장하기 위한 자기 참조(Self-referencing) 자료구조
    sub_items: Optional[List['LineItemNode']] = Field(
        default=None, 
        description="원본 이미지 상에서 해당 항목 바로 밑으로 여백을 통해 들여 쓰기(Indent)되어 시각적으로 완전히 종속되어 있는 모든 하위 자식 노드 항목들"
    )

# Pydantic V2 재귀 구조 트리거
LineItemNode.model_rebuild()

3. 공간 정합성(Spatial Consistency) 오라클 로직 탑재

LLM이 위와 같이 LineItemNode의 트리 형태로 JSON을 엮어서 토해냈을 때, 우리의 Pydantic 2단계 오라클은 이 데이터가 시각적 진리를 왜곡하지 않았는지 다음과 같은 가혹한 유효성 검증을 런타임에 즉시 수행해야 한다.

  1. [위상 기하학적 역전 금지 룰]:
    자식 노드(Child)로 분류된 텐서 객체의 indentation_level 정수 값은 반드시 자신을 소유한 부모 노드(Parent)의 값보다 수학적으로 무조건 커야 한다. (예: 부모 노드가 Level 0 이면, 그 자식 노드 리스트 안에 들어있는 아이템들은 반드시 Level 1 이상이어야 함). 만약 VLM이 환각에 빠져 Level 0짜리 메인 타이틀을 Level 1 자식 항목 목록에 쑤셔 넣었다면, 오라클은 ValidationError: Spatial Topology Inversion을 뿜어내며 전체 출력을 파괴한다.
  2. [부모-자식 간의 산술적 지배력 (Hierarchical Math Constraint)]:
    “모든 Level\_1 자식 노드 sub_items 금액(amount) 들의 절댓값 총합은, 물리적으로 그들을 위에서 덮고 있는 Level\_0 메인 부모 노드의 총액 산술 파이를 결코 논리적으로 초과(Overflow) 할 수 없다“라는 절대적 상한선(Ceiling) 룰을 Pydantic @model_validator 함수 안에 주입한다. 레이아웃의 종속성은 곧 숫자의 종속성이기 때문이다.

이러한 시각-레이아웃 보존 오라클의 가시밭길을 무사히 뚫고 나온 재귀적 JSON 데이터 텐서만이, **“단순한 텍스트 환각을 넘어 픽셀 위에서 벌어지는 기하학적 인과관계 조작의 시험까지 완벽히 통과한 진실된 입체적 의미망(Semantic Web)”**이라고 비로소 엔터프라이즈 마스터 DB에 당당히 규정될 수 있다.