은행 명세서를 확인하는 것은 누구에게도 즐거운 일이 아닙니다. 명세서는 스캔된 PDF, CSV 내보내기, 또는 OFX와 같은 난해한 약어로 꾸며진 XML 파일 형태로 전달됩니다. 회계사, 장부 기입자, 핀테크 개발자들에게 이러한 문서를 깨끗하고 구조화된 데이터로 변환하는 일은 끊임없는 골칫거리입니다. 대규모 언어 모델(LLM)이 등장했을 때, 그것은 마치 탈출구처럼 보였습니다. 그저 기계에 PDF를 입력하고 JSON으로 달라고 요청하면 되는 것이었죠. 무엇이 잘못될 수 있을까요?

저는 StatementDecoder를 만들면서 무엇이 잘못될 수 있는지 정확히 배웠습니다. StatementDecoder는 은행 명세서를 사용 가능한 데이터로 변환하도록 설계된 도구입니다. 많은 개발자와 마찬가지로, 저 역시 다양한 문서 레이아웃을 읽도록 시스템을 가르치는 것이 어려운 부분일 것이라고 가정했습니다. 하지만 제 생각이 틀렸습니다. 문서를 읽는 것은 거의 사소한 일이었습니다. 진짜 악몽은 기계가 조용히 숫자를 지어내거나 거래 금액의 두 자릿수를 서로 바꿔버렸을 때 이를 알아차리는 것이었습니다.

너무 잘 작동해버린 데모

저의 첫 번째 시도는 유혹적일 만큼 간단했습니다. 은행 명세서를 LLM에 직접 입력하고 그 결과로 구조화된 JSON을 요청했습니다. 결과는 마치 마법 같았습니다. 모델은 다양한 레이아웃을 쉽게 처리했습니다. 표준 파서들이 처리하지 못하는 스캔된 PDF도 읽어냈습니다. 명시적인 지침 없이도 표, 헤더, 그리고 여러 페이지에 걸친 명세서를 이해하는 것처럼 보였습니다. 몇 시간 동안의 영광스러운 시간 동안, 저는 문제가 해결되었다고 생각했습니다.

하지만 실제 고객 데이터를 대상으로 테스트하자 마법은 사라졌습니다. 영국 은행들은 저마다의 명세서 디자인을 사용하며, 그 차이는 단순히 외관상의 문제만이 아닙니다. Wise의 명세서는 그들만의 독특한 서식 특징을 가지고 있습니다. Revolut의 CSV 내보내기는 다통화 거래와 메타데이터 필드를 처리하는 방식을 확인하기 전까지는 단순해 보입니다. 1990년대 유물처럼 보이는 오래된 OFX 파일은 현대적인 마크업을 기대하는 파서에 고대의 태그 구조와 인코딩 문제를 던져줍니다.

모델은 여전히 기존의 템플릿 시스템보다 훨씬 더 잘 데이터를 추출했습니다. 하지만 돈이 걸린 문제에서 '훨씬 더 잘하는 것'만으로는 충분하지 않습니다.

99%의 정확도가 실패인 이유

금융 데이터 추출에 AI를 사용할 때 발생하는 근본적인 문제는 바로 이것입니다. 만약 모델이 200개의 거래 행을 처리하여 199개를 정확히 맞췄다면, 결과물은 완벽해 보입니다. JSON 형식도 잘 갖춰져 있고, 키와 값도 일치합니다. 대충 훑어본다면 아무런 의심스러운 점도 발견하지 못할 것입니다. 하지만 그 단 하나의 오류가 금액의 두 자릿수를 뒤바꾸거나, 입금을 출금으로 바꾸거나, 소수점 위치를 옮겨버린다면 여러분의 장부는 오염됩니다. 구조화된 데이터 더미를 눈으로 훑어보는 것만으로는 이를 잡아낼 수 없습니다.

가공되지 않은 JSON을 검토하는 사람이 거래 금액의 바뀐 숫자를 찾아내는 일은 매우 드뭅니다. 형식이 완벽하기 때문에 역설적으로 그 실수는 더 위험해집니다. 대부분의 경우에만 맞춘 금융 도구를 출시할 수는 없습니다. 반드시 정확하거나, 아니면 불확실하다는 사실을 강력하게 알려야 합니다.

저의 초기 반응은 예상 가능했습니다. 더 나은 프롬프트를 설계했습니다. 더 유능한 모델로 업그레이드했습니다. 모델이 작업 과정을 보여줄 수 있도록 생각의 사슬(chain-of-thought) 추론을 실험했습니다. 하지만 그 어떤 것도 핵심 문제를 해결하지 못했습니다. 저는 동일한 확률론적 시스템에 답을 생성하라고 요청한 뒤, 다시 그 동일한 시스템에 그 답이 맞는지 확인해달라고 요청하고 있었던 것입니다. 그것은 검증이 아닙니다. 그것은 자기 일관성을 보여주기 위한 연극일 뿐입니다.

수학이 결정하게 하라

은행 명세서에는 대부분의 문서에는 없는 특징이 하나 있습니다. 바로 내장된 산술적 제약 조건입니다. 기초 잔액에 모든 거래 금액의 합계를 더하면 반드시 기말 잔액과 일치해야 합니다. 잔액이 행마다 표시되는 경우, 각 행의 잔액도 일치해야 합니다. 이것은 스타일의 선호 문제가 아니라 엄격한 규칙입니다.

저는 이 통찰을 바탕으로 아키텍처를 재구축했습니다. 이제 모든 추출 데이터는 출처에 관계없이 사용자가 보기 전에 검증 레이어를 거칩니다. 데이터가 모호한 PDF를 해석하는 LLM에서 왔든, 스캔된 페이지를 읽는 OCR 엔진에서 왔든, 직접적인 CSV 파싱을 통해 왔든 상관없습니다. 검증기는 모든 소스를 똑같이 의심스러운 것으로 취급합니다.

검증 방식은 잔인할 정도로 단순합니다. 모든 거래 금액을 기초 잔액에 더합니다. 그 결과와 명시된 기말 잔액을 비교합니다. 숫자가 일치하지 않으면 무언가 잘못된 것입니다. 해당 명세서를 검토 대상으로 표시합니다. 추출을 거부합니다. 사용자가 이를 보지 못하게 합니다.

이 단 한 번의 변화가 제품의 성격을 완전히 바꾸어 놓았습니다. 언어 모델은 더 이상 완벽할 필요가 없었습니다. 수학적 시험을 통과할 수 있을 만큼만 충분히 잘하면 되었습니다. 압박감의 대상은 제약 없는 영역에서 불가능한 정확도를 달성하는 것에서, 생성과 검증 사이의 긴밀한 피드백 루프를 구축하는 것으로 바뀌었습니다.

The validator also exposed patterns in the errors. Certain document types consistently failed the math check, which told me exactly where to invest effort. Instead of blindly improving prompt engineering across the board, I could see that specific bank layouts caused systematic mistakes.

Code Where Code Belongs, AI Where It Shines

Perhaps the most humbling lesson was realizing how much of the pipeline did not need AI at all. When I encountered messy Australian OFX files, my instinct was to throw tokens at the problem. I briefly considered feeding the broken XML to the model and asking it to repair the structure before parsing. Instead, I wrote twenty lines of deterministic code. It fixed the encoding quirks and malformed tags instantly, with zero cost per file and perfect reproducibility.

That experience crystallized how extraction pipelines should be organized. There are three distinct jobs, and they should not be mixed together.

  • The model understands messy documents. Scanned PDFs with warped tables, mixed fonts, and handwritten