비정형 문서 → 구조화 데이터: LLM 추출에 검증 게이트를 거는 이유
거래명세서 같은 비정형 문서를 LLM으로 구조화할 때 진짜 위험은 빈 칸 누락이 아니라 그럴듯하고 확신에 찬 오류다. 생성된 값을 그대로 믿지 않고, 문서 자체가 강제하는 산술(수량×단가=금액, 공급가액+세액=합계)과 필드별 신뢰도 임계값으로 자동 적재와 사람 검토를 가르는 결정론적 검증 게이트의 설계와 작동 예시, 그리고 게이트가 못 잡는 한계까지 정리한다.
거래명세서 한 장을 시스템에 입력 가능한 구조화 데이터로 바꾸는 작업을 생각해 보자. 사람이 보면 품목, 수량, 단가, 금액, 합계가 있다. 이것을 JSON으로 만드는 가장 단순한 접근은 LLM에게 텍스트를 주고 스키마에 맞춰 추출하게 한 뒤 그 출력을 그대로 적재하는 것이다.
이 접근의 실패 모드는 누락이 아니다. 빈 칸은 눈에 띈다. 진짜 문제는 모델이 그럴듯하고 확신에 찬 틀린 값을 내놓을 때다. 18,000원이 8,000원으로 읽히고, 그 값이 자연스러운 숫자라서 어떤 경고도 뜨지 않는다. 추출 단계만 있으면 이 오류는 그대로 데이터베이스로 들어간다.
모델은 확신을 갖고 틀린다
실제 샘플을 보자. 다음은 노이즈가 섞인 거래명세서 텍스트다.
USB 메모리 16GB 10 18,000 원 180,000 원
모니터 클리너 대형 4 35,000 원 140,000 원
공급가액: 320,000 원
모델은 첫 줄의 단가 18,000원을 8,000원으로 읽었다. 추출된 표는 이렇다.
| 품목 | 수량 | 단가(추출) | 금액(추출) | 신뢰도 |
|---|---|---|---|---|
| USB 메모리 16GB | 10 | 8,000원 | 180,000원 | 0.91 |
| 모니터 클리너 대형 | 4 | 35,000원 | 140,000원 | 0.97 |
단가 필드의 신뢰도는 0.91이다. 신뢰도만 보면 통과한다. 모델 스스로는 자기가 틀렸다는 신호를 주지 않는다. 이것이 추출만으로는 부족한 이유다.
“Beware of bugs in the above code; I have only proved it correct, not tried it.”
옳다고 증명한 것과 실제로 맞는 것은 다르다. 모델이 높은 신뢰도를 보고했다는 사실은 옳다고 주장했다는 뜻이지 실제로 맞다는 보장이 아니다. 그 간극을 메우는 것이 검증 단계다.
파싱: 추출 전에 입력을 정규화한다
추출에 들어가기 전에 입력 텍스트를 한 번 정규화한다. 실제 문서는 깨끗하지 않다. 천 단위 콤마가 들어가고(180,000), 숫자와 단위 사이에 공백이 끼고(18,000 원), 전각과 반각 문자가 섞이고(공 급 가 액), 발행일이 2024 - 04 - 02처럼 분리된다. OCR을 거친 텍스트라면 정렬이 무너지고 글자가 인접 칸으로 밀려나기도 한다.
정규화 단계는 이 잡음을 모델이 읽기 쉬운 형태로 줄인다. 숫자에서 콤마와 단위를 떼고, 반복 공백을 접고, 날짜 구분자를 통일한다. 다만 정규화도 틀릴 수 있는 작업이라 보수적으로 한다. 토큰을 합치거나 나누는 판단처럼 확신할 수 없는 변형은 하지 않고 모델과 검증 단계로 넘긴다. 정규화의 목적은 추출 정확도를 올리는 것이지, 이 단계에서 의미를 결정하는 것이 아니다.
생성이 아니라 설정을 검증한다
핵심 설계는 검증 대상을 바꾸는 것이다. 모델의 생성 과정을 검증하려 하지 않는다. 그건 또 다른 모델을 믿는 일이다. 대신 문서 자체가 강제하는, 모델과 무관한 결정론적 제약을 검증한다.
거래명세서에는 세 가지 산술 항등식이 항상 성립한다.
- 각 품목: 수량 × 단가 = 금액
- 품목 금액의 합 = 공급가액
- 공급가액 + 세액 = 합계
이 세 가지는 모델이 무엇을 추출하든 독립적으로 계산할 수 있다. 앞의 샘플에 적용하면, 10 × 8,000 = 80,000 인데 추출된 금액은 180,000이다. 불일치. 게이트가 즉시 포착한다.
산술을 수정하지 말 것. 문서에 보이는 값을 그대로 추출하고, 하위 검증 단계가 산술을 확인한다.
추출 단계에 산술을 맡기지 않는 것이 의도적이다. 모델이 단가가 8,000이면 금액은 80,000이어야 한다고 판단해 금액을 고치면, 산술은 맞아떨어지지만 데이터는 더 틀려진다. 추출은 본 대로만 옮기고, 산술 대조는 결정론적 코드가 한다. 이것이 생성 검증이 아니라 설정 검증이다.
산술로 잡히지 않는 경우를 위한 두 번째 그물이 신뢰도 임계값이다. 필드별 신뢰도가 임계값 미만이면 사람 검토로 보낸다.
산술과 타입 체크가 하나라도 실패하거나 신뢰도 임계값 미만 필드가 하나라도 있으면 needs_review로 보낸다. 모두 통과할 때만 auto_load한다.
세 갈래 라우팅
게이트는 추출 결과를 셋 중 하나로 보낸다. 합성 샘플 세 개로 각 경로를 보면 이렇다.
| 샘플 | 산술 | 최저 신뢰도 | 판정 | 포착 근거 |
|---|---|---|---|---|
| 정상 | 일치 | 0.94 | auto_load | (통과) |
| 단가 오인식 | 불일치 | 0.89 | needs_review | 산술 게이트 |
| 공급자명 번짐 | 일치 | 0.62 | needs_review | 신뢰도 임계 |
두 번째 행이 이 설계의 요점이다. 단가 오인식 샘플은 모든 필드 신뢰도가 0.89 이상이다. 신뢰도만으로는 절대 걸러지지 않는다. 그런데도 산술 게이트가 잡는다. 확신에 찬 오류를 잡는 것은 신뢰도가 아니라 결정론적 대조다.
10 × 8,000 = 80,000 이 추출 금액 180,000과 불일치. 품목 합 320,000이 모델이 보고한 공급가액 220,000과 불일치. 신뢰도 플래그는 0건, 판정은 needs_review.
세 번째 행은 반대 경우다. 공급자명이 인감 번짐으로 신뢰도 0.62로 추출됐지만 산술은 완벽하다. 산술 게이트는 통과하지만 신뢰도 임계값이 잡아 사람 검토로 보낸다. 두 그물이 서로 다른 종류의 오류를 잡는다.
신뢰도는 약한 신호다
필드별 신뢰도는 모델이 스스로 보고한 값이다. 추출 스키마에 confidence 필드를 두고 0.0에서 1.0 사이로 채우게 한다. 이 값은 유용하지만 약한 신호다. 모델이 확신을 갖고 틀리는 경우, 신뢰도도 높게 보고된다. 앞의 단가 오인식 샘플이 정확히 그 경우다. 0.91이라는 신뢰도는 틀린 값에 붙은 높은 자신감이었다.
그래서 신뢰도는 1차 방어선이 아니라 2차 그물이다. 1차는 산술처럼 모델과 무관하게 계산되는 결정론적 제약이다. 신뢰도는 산술 제약이 없는 필드(공급자명, 비고 같은 자유 텍스트)에서 사실상 유일한 신호이므로 거기서 값을 한다. 임계값을 낮추면 이 그물이 성겨지고, 높이면 촘촘해지지만 사람 검토 큐가 길어진다. 신뢰도에만 의존하는 설계가 위험한 이유는, 가장 비싼 오류인 확신에 찬 오류를 신뢰도가 통과시키기 때문이다.
오라클은 어디서 오는가
검증이 가능하려면 정답에 해당하는 기준, 즉 오라클이 필요하다. 거래명세서의 장점은 오라클이 문서 안에 공짜로 들어 있다는 것이다. 합계는 품목들의 함수이고, 그 관계는 문서가 스스로 명시한다. 별도 정답 데이터를 만들 필요 없이 문서가 자기 자신을 검증하는 셈이다. 이것이 산술 대조가 강력한 이유다.
모든 필드가 이런 내부 오라클을 갖지는 않는다. 공급자명, 주소, 품목 설명 같은 자유 텍스트는 문서 안에서 교차 검증할 대상이 없다. 이런 필드는 외부 참조가 필요하다. 사업자등록번호는 체크섬 규칙과 국세청 조회로, 거래처명은 거래처 마스터 대조로, 날짜는 허용 범위로 검증한다. 검증 규칙을 추가한다는 것은 곧 더 많은 필드에 오라클을 붙이는 일이다. 내부 오라클이 있는 필드부터 결정론적으로 잠그고, 없는 필드는 외부 참조를 붙이거나 신뢰도 그물에 맡긴다.
HITL은 줄이는 비용이지 없애는 목표가 아니다
사람 검토(HITL)는 비용이다. 모든 행을 사람이 보면 자동화의 의미가 없다. 게이트의 목적은 사람이 봐야 하는 행을 줄이는 것이다. 산술과 신뢰도를 모두 통과한 행은 사람을 거치지 않고 적재된다. 검토 큐에는 실제로 의심스러운 행만 남는다.
임계값은 이 비율을 조정하는 손잡이다. 0.85를 올리면 사람 검토 큐가 길어지는 대신 오적재가 준다. 내리면 반대다. 적정값은 오적재 한 건의 비용이 얼마인가에 달려 있다. 정산 데이터처럼 틀리면 돈이 어긋나는 경우는 임계값을 높게 잡는다.
운영 경로와 비용
추출 단계는 실제로 LLM을 호출한다. 운영 코드는 이 사이트의 기존 인테이크 도우미(spec-assist)와 같은 패턴을 따른다. API 키는 환경 변수로 주입하고, 키가 없으면 기능을 닫는다(fail-closed). 호출은 같은 스키마를 요구하는 단일 메시지이고, 응답은 JSON으로 파싱한다.
이 글의 데모 페이지는 비용 때문에 라이브 호출을 하지 않는다. 합성 샘플에 대한 추출 결과를 기록해 두고, 검증 게이트만 렌더링 시 실시간으로 돌린다. 검증은 결정론적이고 외부 호출이 없으므로 비용이 0이고 항상 같은 결과를 낸다. 공개 페이지에 열린 LLM 엔드포인트를 두면 호출 비용과 남용 위험이 생기므로, 라이브 추출이 필요한 운영 환경에서는 인테이크 도우미와 동일하게 IP 기준 레이트 리밋을 건다. 추출(비싸고 비결정론적)과 검증(싸고 결정론적)을 분리하면, 반복 가능한 부분을 무료로 시연하면서 비싼 부분은 운영에서만 켤 수 있다.
게이트가 못 잡는 것
정직하게 한계를 적는다. 산술 제약이 없는 필드의 의미 오류는 산술로 잡히지 않는다. 예를 들어 공급자명을 높은 신뢰도로 잘못 읽으면, 산술은 멀쩡하고 신뢰도도 높아서 두 그물을 다 통과한다. 이런 오류는 게이트의 사각지대다.
이 사각지대를 줄이는 방법은 제약을 늘리는 것이다. 사업자등록번호 체크섬, 날짜 범위, 거래처 마스터 대조 같은 추가 규칙을 검증 단계에 더하면 더 많은 의미 오류가 산술과 같은 방식으로 결정론적으로 걸러진다. 게이트의 강도는 검증 규칙의 개수에 직접 비례한다.
Program testing can be used to show the presence of bugs, but never to show their absence.
검증 게이트도 같은 한계 안에 있다. 게이트를 통과했다는 것은 오류가 없다는 증명이 아니라, 현재 규칙이 잡는 종류의 오류가 없다는 뜻일 뿐이다. 게이트는 오류율을 낮추지 0으로 만들지 못한다. 그래서 게이트는 사람 검토를 대체하는 장치가 아니라, 사람이 봐야 하는 양을 규칙으로 깎아 내는 장치다. 규칙을 늘릴수록 그 양은 줄지만, 마지막 한 겹은 사람이 남는다.
같은 게이트, 다른 문서
거래명세서를 예로 들었지만, 게이트의 구조는 문서 종류에 묶여 있지 않다. 견적서, 발주서, 정산서, 각종 신청서처럼 항목과 합계가 있는 문서는 모두 같은 산술 항등식과 같은 검증, HITL 라우팅 로직을 재사용한다. 문서마다 바뀌는 것은 추출 스키마와 파서 앞단, 그리고 외부 참조 규칙뿐이다. 검증과 라우팅이라는 뼈대는 그대로 둔다.
이 분리가 실용적인 이유는, 새 문서 종류를 받을 때 다시 만드는 부분이 가장 적기 때문이다. 스키마를 정의하고 파서를 갈아 끼우면, 검증의 강도와 HITL 동작은 이미 검증된 코드를 그대로 따른다. 결정론적인 부분을 한 번 제대로 만들어 두면 비결정론적인 추출이 어느 문서로 바뀌든 같은 안전망 위에서 돈다.
직접 확인
세 샘플이 게이트를 통과하는 과정을 단계별로 보려면 데모 페이지를 열면 된다. 검증 게이트는 페이지 렌더링 시 실시간으로 실행된다. 기록된 판정이 아니다.
데모: /demo/extraction
FAQ
자주 묻는 질문
데모에 보이는 추출 결과는 실제 모델이 만든 것인가요?
합성 샘플 거래명세서에 대한 기록된 모델 출력입니다. 검증 게이트(산술 대조, 신뢰도 임계값)는 데모 페이지 렌더링 시 실시간으로 실행됩니다. 실제 운영 경로는 같은 스키마를 Anthropic API로 추출하며 코드는 extract.ts에 있습니다. 클라이언트 데이터는 처리하지 않습니다.
검증 게이트가 못 잡는 오류는 무엇인가요?
산술 제약이 없는 의미 오류입니다. 예를 들어 공급자명을 높은 신뢰도로 잘못 읽으면 산술도 멀쩡하고 신뢰도도 높아 두 그물을 모두 통과합니다. 이 사각지대는 사업자등록번호 체크섬이나 거래처 마스터 대조 같은 검증 규칙을 늘려 줄입니다.
신뢰도 임계값 0.85는 어떻게 정하나요?
자동 적재 비율과 오적재 비용의 트레이드오프입니다. 임계값을 올리면 사람 검토 큐가 길어지는 대신 오적재가 줄고, 내리면 반대입니다. 정산처럼 틀리면 금액이 어긋나는 데이터는 임계값을 높게 잡습니다.
다른 종류의 문서에도 같은 게이트를 쓰나요?
검증 규칙(산술 일치, 타입, 신뢰도 임계값)은 문서 종류와 무관한 재사용 코어입니다. 문서마다 바뀌는 것은 추출 스키마와 파서 앞단뿐이고, 검증과 HITL 라우팅 로직은 그대로 재사용합니다.