한줄 요약
AI에게 세법을 그냥 묻지 않으려고 공식 법령 원문을 직접 옮겨 위키로 만들었습니다. 옮긴 결과가 원문과 같은지는 기계가 검사하도록 했고, 검사를 통과한 것만 정본으로 올렸습니다.
그런데 그 “통과”가 네 번 배신했습니다. 조문 개수도 파일 지문도 맞는데 문장이 통째로 빠져 있었고, 그걸 잡으라고 만든 검사기가 같은 실수를 같이 하고 있었고, 근거로 딸려 나온 계산식은 원문과 뜻이 달랐고, AI 코드 리뷰를 여덟 번 통과한 코드가 다른 법 62건에서 6건을 틀렸습니다.
공통점이 하나 있었습니다. 네 번 다 검사기가 아니라 검사기 바깥에서 걸렸습니다.
이런 분께
AI에게 규정·약관·법령처럼 “틀리면 곤란한” 자료를 물어보고 싶은데, 답을 어디까지 믿어야 할지 모르겠는 분
자료를 옮기거나 정리하는 작업을 AI에게 맡겨봤고, “검사 통과” 메시지를 받아본 적 있는 분
테스트가 다 통과했는데 왜 여전히 불안한지 설명이 안 되던 분
반대로, 이미 검증 설계를 오래 해오신 분께는 새로운 내용이 없을 수 있습니다
왜 원문을 직접 옮기기로 했나
세법은 일상적인 질문에서 시작됩니다. 부모님께 얼마까지 증여받으면 세금이 없는지, 집을 팔면 뭘 내야 하는지 같은 것들입니다. 검색보다 AI에게 묻는 쪽이 빠르니 저도 그렇게 했습니다.
문제는 답이 그럴듯하다는 것이었습니다. 틀렸을 때도 그럴듯했습니다. 특히 최근에 바뀐 조항일수록 그랬습니다. AI는 “모르겠다”고 물러나는 대신, 예전에 학습한 내용과 그럴듯한 근거를 섞어서 빈칸을 채웠습니다. 더 최신 모델로 바꿔도 그때만 해결될 뿐, 다음 개정이 오면 같은 일이 반복될 구조였습니다.
그래서 방향을 바꿨습니다. AI에게 세법을 묻지 않고, AI가 답할 때 되돌아볼 수 있는 공식 원문을 먼저 만들자. 법제처가 운영하는 국가법령정보 시스템에는 법령 원문을 기계가 받아갈 수 있는 공식 통로(API, 외부 서비스에서 데이터를 자동으로 받아오는 창구)가 열려 있었습니다. 사람이 웹페이지에서 복사·붙여넣기 하는 대신 그 통로로 받기로 했습니다.
여기까지는 흔한 판단입니다. 진짜 이야기는 그다음부터입니다.
“통과한 것만 올린다”는 규칙
받아온 원문을 그대로 쌓으면 위키가 아니라 덩어리입니다. 조문 하나하나를 따로 찾을 수 있어야 하니 쪼개야 했고, 쪼개는 과정에서 내용이 상하면 원문 위키라는 말이 무색해집니다.
그래서 흐름을 이렇게 잡았습니다.
받아오기 → 조문 단위로 쪼개기 → 검사 → 통과한 것만 정본 폴더로이걸 말로만 정해두면 지키다 말게 되므로, 폴더를 아예 셋으로 나눴습니다.
inbox/ # 공식 통로로 막 받아온 원문. 아직 아무것도 검사하지 않은 상태
staging/ # 조문 단위로 쪼갠 결과. 검사 대기 중이고, 여기서 떨어지면 버려짐
source/ # 검사를 통과한 것만. AI가 답할 때 근거로 삼는 유일한 폴더핵심은 마지막 폴더입니다. 검사를 통과하지 못한 결과물은 source에 못 들어옵니다. 거꾸로 말하면, 거기 들어 있다는 사실 자체가 “검사를 통과했다”는 뜻입니다.
이 구조가 마음에 들었습니다. 규칙을 지키려고 노력하는 게 아니라, 안 지키면 파일이 물리적으로 다음 칸에 못 가니까요.
검사는 세 종류로 나눴습니다. 하나에 다 맡기지 않은 이유는, 검사마다 잘 잡는 문제가 다르기 때문입니다.
검사
쉽게 말하면
맡은 일
1단
서류 형식 확인
필요한 항목이 다 있고 형식이 맞는지
2단
파일 지문 확인
내용이 중간에 바뀌거나 깨지지 않았는지
3단
원문 대조
원본에 있던 내용이 옮기는 과정에서 빠지지 않았는지
2단의 “파일 지문”은 해시(파일 내용을 요약한 짧은 문자열, 한 글자만 바뀌어도 완전히 달라짐)를 말합니다. 정본에 올릴 때 지문을 같이 기록해두면, 나중에 누가 6억원을 60억원으로 바꿔놓아도 지문이 안 맞아 걸립니다. 다만 이건 올린 이후의 변조를 잡는 장치입니다. 애초에 잘못 옮긴 상태로 지문을 뜨면 그 지문은 잘못된 내용과 사이좋게 일치합니다. 이 구멍이 바로 다음 이야기입니다.
첫 결과는 좋았습니다. 상속세 및 증여세법 111개 조문, 소득세법 326개 조문을 옮겼고, 반드시 맞아야 한다고 미리 정해둔 대표 테스트 11개도 전부 통과했습니다. 화면에는 초록불이 떴습니다.
이 글은 그 초록불이 네 번 틀렸던 기록입니다.
첫 번째 — 개수도 지문도 맞는데, 문장이 없었습니다
작업 중에 습관처럼 하는 확인이 있습니다. 결과물이 정말 실제 조문처럼 생겼는지 대표 조문 md 파일 몇 개를 그냥 열어보는 것입니다. 검사가 다 통과했으니 형식적인 확인이라고 생각했습니다.
그런데 상속세 및 증여세법 제53조에서 이상한 게 보였습니다. 증여재산 공제, 그러니까 흔히 말하는 “배우자한테는 6억까지, 부모한테는 5천까지”가 들어 있는 조문입니다.
파일에는 이런 것들이 남아 있었습니다.
1. 배우자로부터 증여를 받은 경우: 6억원
2. 직계존속으로부터 증여를 받은 경우: 5천만원
3. 직계비속으로부터 증여를 받은 경우: 5천만원
4. 그 밖의 친족으로부터 증여를 받은 경우: 1천만원숫자는 다 맞습니다. 그런데 원문에서 이 목록 바로 앞에 있어야 할 문장이 통째로 사라져 있었습니다.
거주자가 다음 각 호의 어느 하나에 해당하는 사람으로부터
증여를 받은 경우에는 다음 각 호의 구분에 따른 금액을
증여세 과세가액에서 공제한다.이 문장이 없으면 아래 숫자들이 독립적으로 무엇을 의미하는 것인지 알 수 없습니다. 6억원이 세금인지 공제액인지, 누구에게 적용되는지, 무엇에서 빼는 금액인지가 전부 앞문장에 들어 있었습니다.
왜 빠졌냐면, 받아온 원본 데이터에서 이 둘이 서로 다른 칸에 담겨 있었기 때문입니다.
원본에서
담긴 내용
쪼갠 결과에
조문 내용 칸
“거주자가 다음 각 호의 어느 하나에…공제한다” (앞문장)
안 실림
호 목록 칸
1. 배 우자 6억원 / 2. 직계존속 5천만원 / …
실림
쪼개는 코드가 목록 칸만 보고 있었던 것입니다. 조문 하나가 통째로 빠졌으면 개수가 안 맞아서 바로 걸렸을 텐데, 조문은 있고 그 안의 한 칸만 비어서 아무 검사에도 안 걸렸습니다.
여기서 중요한 건 검사가 이걸 못 잡았다는 사실입니다. 조문 111개가 들어와야 하는데 111개가 들어왔으니 개수 검사는 통과합니다. 들어온 내용은 손상되지 않았으니 지문 검사도 통과합니다. “각 조문 안에서 뭐가 빠졌는지”는 아무도 안 보고 있었습니다.
그래서 3단 검사, 그러니까 원본과 대조해서 빠진 내용을 잡는 검사를 새로 만들었습니다. 이걸로 닫혔다고 생각했습니다.
두 번째 — 그 검사기가 같은 실수를 같이 하고 있었습니다
구현을 한번 한 뒤, 코드를 다른 AI에게 넘겨 독립적으로 검토받았습니다. 제가 만든 흐름 안에서는 안 보이는 걸 보라는 취지였습니다.
두 리뷰어가 같은 문제를 1순위로 지적했습니다. 새로 만든 3단 검사가 자기 자신을 확인하는 꼴에 가깝다는 것이었습니다.
이유를 듣고 나니 명백했습니다. 조문을 쪼개는 코드와 그 결과를 검사하는 코드가 원본을 읽는 부품을 공유하고 있었습니다. 그러면 쪼개는 코드가 어떤 칸을 안 보고 지나쳤을 때, 검사하는 코드도 똑같이 그 칸을 안 보고 지나갑니다. 시험 문제를 낸 사람이 자기 답안지로 채점하는 상황이었습니다. 겉으로는 대조를 하는데, 같은 전제를 공유하니 같이 속습니다.
제53조 사고를 잡으라고 만든 검 사기가, 제53조 사고와 정확히 같은 이유로 눈이 멀어 있었던 셈입니다.
고치는 방향은 단순했습니다. 검사 코드가 쪼개는 코드의 방식에 기대지 않고, 원본 데이터에서 필요한 항목을 따로 모아 비교하도록 다시 짰습니다. 검사는 같은 코드를 한 번 더 돌리는 일이 아니라 다른 길로 확인하는 일이라는 걸 이때 배웠습니다.
여기까지 오면 검사에 대한 감각이 조금 생겼다고 느끼게 됩니다. 다음 사고는 그 감각이 닿지 않는 곳에서 나왔습니다.
세 번째 — 근거로 딸려 나온 계산식이 원문과 달랐습니다
위키가 어느 정도 쌓인 뒤로는, 질문을 던지면 관련 조문을 찾아 근거와 함께 답하는 구조를 만들었습니다. 답이 맞았는지 사람이 확인하는 화면도 따로 뒀습니다. 저는 그걸 검수대라고 부릅니다. 질문, AI의 답, 그리고 그 답이 근거로 들고 온 조문 발췌가 나란히 뜹니다.
어느 날 검수대에서 답을 넘겨보다가, 근거로 딸려온 계산식이 눈에 걸렸습니다. 실제 법조문의 계산식과 모양이 달랐습니다.
“종합소득세를 기한 내에 신고 안 하면 가산세가 얼마나 붙나요?”라는 질문에 달려온 근거가 일곱 개인데, 일곱 개 전부에 초록색으로 “원문 일치”가 찍혀 있습니다. 아래에서 여섯 번째를 보시면 됩니다.
원문은 이렇게 생겼습니다. 법령 사이트가 계산식을 그림처럼 문자로 그려 넣기 때문입니다.
┌────────────────────────────┐
│ 가산세 = A × B × 100분의 5 │
│ ─── │
│ C │
│ A: 종합소득산출세액 │
│ B: 사업소득금액 │
│ C: 종합소득금액 │
└────────────────────────────┘가운데 짧은 가로줄이 분수선입니다. B가 분자, C가 분모입니다. 그런데 근거로 실려 온 발췌는 가산세 = A × B × 100분의 5 이 한 줄뿐이었습니다.
이 한 줄은 원문에 글자 그대로 존재합니다. 그래서 “발췌가 원문에 있는가”를 확인하는 검사는 통과합니다. 통과하는데, 분모 C가 사라졌습니다.
읽히는 식
원문의 뜻
A × (B ÷ C) × 5%
잘린 발췌의 뜻
A × B × 5%
세금 계산식에서 분모가 증발한 것입니다. 화면에서 그 발췌의 “원문 펼치기”를 눌러보면 이렇게 보입니다.
한국사이트 스크린샷
노란색으로 표시된 부분이 근거로 실려 온 한 줄이고, 그 바로 아래에 ───와 C가 있습니다. 화면 위쪽은 여전히 “원문 일치”라고 말하고 있습니다.
검사는 글자를 봤고, 사람은 뜻을 봤습니다. 그 사이가 벌어질 수 있다는 걸 그때 알았습니다.
이 경우는 원문을 다시 받아와도 해결되지 않습니다. 원문이 원래 그렇게 조판되어 있기 때문입니다. 그래서 발췌를 쓰는 시점에 막기로 했습니다. 조문에서 박스 문자로 둘러싸인 구간을 찾고, 발췌가 그 구간을 일부만 잘라 왔는지 보고, 세 갈래로 판정합니다.
분수식을 일부만 잘라 온 경우 → 차단. 이후 작업이 진행되지 않습니다
표 형태거나 판단이 애매한 경우 → 사람 검토 대기로 표시
박스 전체를 인용했거나 애초에 박스와 무관한 경우 → 통과
이 가드를 만들면서 숫자가 셋 나왔는데, 서로 다른 것을 세고 있어서 나눠 적습니다.
숫자
무엇을 센 것
결과
8개
가드와 함께 새로 만든 시험 케이스
분수식을 일부만 자른 가짜 발췌를 일부러 넣어, 실제로 차단되는지 확인 (전체 테스트 254 → 262)
97건
기존에 쓰이던 근거 발췌 전수
차단 대상 0건
9건
정답지와 후보 발췌를 따로 전수 스캔
표 형태라 판단이 애매해 사 람 검토 대기로 넘김 (정답지 6 + 후보 3)
가운데 줄의 0건은 자랑이 아닙니다. 문제가 없어서 0이 아니라, 이미 사람이 두 건을 찾아내 고쳐놓은 뒤라서 0이었습니다. 가드는 지난 사고를 잡은 게 아니라 다음 사고를 막으려고 만든 것이고, 그게 실제로 막히는지는 위의 8개 시험 케이스가 확인했습니다. 검사를 새로 만들고 그 검사로 0건을 얻는 건 아무것도 증명하지 않습니다. 막혀야 할 것을 일부러 넣어봐야 압니다.
그런데 이때까지의 사고 세 건은 전부 제 위키 안에서 일어난 일이었습니다. 마지막 배신은 위키 밖에서 왔습니다.
네 번째 — 여덟 번 통과했는데, 여덟 번 다 같은 방 안이었습니다
앞의 세 번은 기계가 짠 검사가 통과시킨 경우였습니다. 네 번째는 조금 다릅니다. 이번에 통과 도장을 찍은 건 사람이 만든 검사가 아니라 AI 리뷰어였습니다.
문제가 된 건 인용 표기를 정리하는 코드였습니다. 위키의 답변은 본문에 조문을 인용합니다. “상속세 및 증여세법 제19조에 따라”처럼요. 그런데 표기가 지저분했습니다. 정식 명칭과 줄임말이 섞이고, “시행령 제31조”처럼 어느 법의 시행령인지 빠진 것도 있었습니다. 이걸 기계가 알아듣게 정리해서, 각 인용이 어느 법 몇 조를 가리키는지 확정하는 코드였습니다.
구현은 금방 끝났는데 검토가 길었습니다. 고친 코드를 외부 AI 리뷰어에게 넘기면 “이런 입력이면 엉뚱한 법에 붙는다”는 반례가 나왔습니다. 고치면 그 수리가 만든 새 반례가 나왔고, 또 고치면 또 나왔습니다.
세 번 연속 같은 자리에서 터졌는데, 매번 반대 방향이었습니다. 처음엔 “영리