13.4.1 산술 검증(Arithmetic Verification): 단가 × 수량 = 합계 검증 로직 구현
2단계 로직/의미론적 오라클(Semantic Consistency Oracle)의 가장 첫 번째 관문이자, 비정형 재무 문서 처리 파이프라인에서 LLM이 가장 비일비재하게 발생시키는 환각을 잡아내는 절대적이고 물리적인 그물망은 바로 **산술 검증(Arithmetic Verification)**이다.
대규모 언어 모델(LLM)은 근본적으로 ’현재 문맥 다음에 올 단어(Token)를 통계적 확률에 의해 추론’하는 고도화된 언어 엔진(Language Engine)일 뿐이지, 연산 회로(ALU, Arithmetic Logic Unit)를 내부에 탑재한 논리적 계산기가 아니다. (물론 최신 모델들은 도구 사용 기능으로 계산기를 호출할 수 있으나 본질은 같다.)
만약 입력된 영수증 이미지에 수량 3, 단가 1,500원이 적혀 있고 우측의 합계 부분이 도장이나 구겨짐으로 인해 번져서 잘 보이지 않는다면 어떻게 될까? 인간 회계사는 암산을 통해 4,500원이라는 정답을 백지에 적어내지만, LLM 파서는 주변 문맥의 통계적 노이즈에 의해 눈덩이처럼 5000이나 3000, 혹은 엉뚱한 15000이라는 환각(Hallucination) 숫자를 창조해 낼 가능성이 언제나 숨 막히도록 상존한다.
따라서 2단계 오라클 백엔드는 1단계를 거친 JSON 추출 배열 속의 개별 세부 품목(Line Item) 단위로 깊숙이 내려가, UnitPrice_n \times Quantity_n = LineTotal_n 이라는 가장 기초적인 대수학 공식이 수학적으로 완벽하게 성립하는지 반드시 교차 피로 검증(Cross-validation)을 아키텍처 레벨에 강제시켜야만 한다.
1. IEEE 부동소수점 오차(Floating-point Error) 방어를 위한 Decimal 파싱 원칙
이 산술 검증 코드를 런타임에 작성할 때, 수많은 주니어 데이터 엔지니어들이 파이썬의 내장 float 타입을 그대로 사용하여 곱셈 연산을 수행하는 치명적 안일함의 실수를 저지른다.
컴퓨터 공학의 기초에 등장하는 0.1 + 0.2 != 0.3 이슈처럼, IEEE 754 부동소수점 시스템의 이진법 변환 태생적 한계로 인해, 15.55 * 3을 파이썬에서 연산하면 시스템 메모리는 46.64999999999999... 라는 결과를 반환하게 된다. 만약 이를 원본 문서에 또렷이 기재된 LLM의 완벽한 추출 값인 46.65 와 비교 연산자(==)로 대조해 버리면, 완벽한 정상 데이터임에도 불구하고 이 미세한 소수점 편차 때문에 오라클이 환각으로 오인하여 예외를 격발 시키는 치명적인 ‘False Positive(위양성)’ 오류가 발생하게 된다.
따라서 2단계 산술 오라클 파이프라인은 Float를 배척하고, 반드시 파이썬의 네이티브 decimal.Decimal 메모리 타입으로 값을 인스턴스화하여 단 1비트의 오차도 허용하지 않는 엄격한 십진법 연산을 수행해야 한다.
from decimal import Decimal, ROUND_HALF_UP
def verify_line_item_arithmetic(unit_price: float, quantity: int, line_total: float) -> bool:
""" 단가와 수량을 곱한 값이 LLM이 추출한 품목 합계와 일치하는지 Decimal로 무성 검증한다. """
# 1. IEEE 부동소수점 오차의 침투를 완벽히 막기 위한 10진수(Decimal) 강제 캐스팅
# (주의: float를 그대로 Decimal에 넣지 말고, 반드시 str로 한 번 묶어 캐스팅해야 안전하다)
d_price = Decimal(str(unit_price))
d_qty = Decimal(str(quantity))
d_target_total = Decimal(str(line_total))
# 2. 오라클의 직접 산술 연산 수행 (단가 * 수량)
calculated_total = d_price * d_qty
# 3. 소수점 이하 통화(예: 달러/센트 체계)의 물리적 반올림 처리 (소수점 2째 자리 유지)
# 벤더사의 1센트 단위 미세 버림/올림 차이로 인한 False Positive를 방어하는 아키텍트의 배려
rounded_calculated = calculated_total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
rounded_target = d_target_total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
# 4. 오라클의 최종 무결성 비교 심판
if rounded_calculated != rounded_target:
# 단돈 1원의 차이라도 발생하면 트랜잭션의 멱살을 잡고 즉각 익셉션을 공중으로 투척한다.
raise ValueError(
f"[산술 교차검증 실패] 단가({d_price}) * 수량({d_qty}) 오라클 연산 결과({rounded_calculated})가 "
f"LLM이 환각으로 추출해 낸 합계 금액({rounded_target})과 논리적으로 일치하지 않습니다!"
)
return True
2. 오라클의 기계적 관용: 오차 허용 한계(Tolerance) 설정 전략
하지만 기업 생태계의 모든 문서가 교과서처럼 수학적으로 완벽하게 떨어지지는 않는다.
어떤 글로벌 벤더사의 레거시 ERP 시스템은, 세금이 이미 포함된 전체 금액 합계에서 역순으로 나누기 연산을 수행하여 수량(Quantity)이나 단가를 역산하는 기괴한 방식으로 영수증을 찍어내기도 한다. 이 경우 수식을 곱해보면 필연적으로 0.01 달러 혹은 1원 이하의 불가항력적인 미세한 단수 차이가 물리적으로 문서에 인쇄되어 발생하게 된다.
이때 시스템 오라클 설계자는 비즈니스의 도메인 성격에 따라, 완전 무결하고 융통성 없는 절대 동치(==) 비교 메커니즘을 쓸 것인지, 아니면 아주 미세한 여백 공간인 ‘오차 허용 한계(Tolerance, \epsilon 마진)’ 범위를 적용하여 시스템이 부드럽게 묵인하고 통과(Bypass) 시킬 것인지에 대한 무거운 아키텍처 결정을 직접 내려야만 한다.
# 절대적 Tolerance(엡실론)를 적용한 유연한 2단계 오라클 대수학 검증망 구현 로직
absolute_difference = abs(rounded_calculated - rounded_target)
# 통상적인 결제 시스템에서 5센트 이하의 반올림 단수 차이는 무해한 것으로 간주하여 묵인 허용함
TOLERANCE = Decimal('0.05')
if absolute_difference > TOLERANCE:
raise ValueError(
f"[치명적 산술 모순] 벤더사의 시스템 허용 오차({TOLERANCE}) 범위마저 초과했습니다. "
f"발생한 실제 오차: {absolute_difference}. LLM의 명백한 환각이므로 차단합니다."
)
어떤 복잡한 문서라도 근본적으로 살펴보면, 이처럼 **단가(Unit Price), 수량(Quantity), 합계(Total)**라는 세 개의 꼭짓점으로 이루어진 가장 작은 단위의 ’산술 삼각형’이 끝없이 모자이크처럼 결합되어 있는 구조물이다.
이 쪼갤 수 없는 가장 미세한 산술 삼각구조의 논리적 무결성을 철옹성처럼 지켜내는 것. 그것이 바로 매일 수십억 원의 트랜잭션을 홀로 외롭게 지탱하는 엔터프라이즈 2단계 오라클 시스템의 가장 위대하고도 무거운 첫걸음이다.