직접 해 본 결과만 적습니다 · 확인하지 못한 것은 그렇게 표시합니다
써 AI 마케팅 적용기 시중에 나온 AI 방법을 실무에 넣어 보고, 쓸만한지 남깁니다
콘텐츠 만들기

AI검수를 통과했다고 정상인 건 아닙니다 — 형식은 맞는데 값이 틀린 파일

계속 쓴다2026-08-10·읽기 6분·글 써봄

AI검수와 형식 검사를 붙여 두고 문서자동화를 돌리고 있었습니다. 만들어진 파일이 검사를 전부 통과했습니다.

그런데 실제로 열어 보니 이상했습니다. 크기 값 하나가 음수였습니다.

1. 형식 검사가 무엇까지 보는지 확인합니다 2. 값의 타당성은 따로 검사합니다 3. 정상이던 옛 판과 구조를 대조합니다 4. 찾은 유형을 검사 항목에 추가합니다

AI검수 증상 — 검사는 전부 통과합니다

증상 — 만들어진 파일이 모든 검사를 통과합니다. 그런데 결과물이 이상합니다.

구조·압축·참조 검사는 통과하는데 크기 값이 음수인 상태를 정리한 터미널 화면

검사 항목 결과
파일 구조 통과
압축 무결성 통과
참조 관계 통과
크기 값 음수인데 아무도 안 봄

원인 — 형식 검사는 "형식이 맞나" 만 봅니다. 값이 말이 되는지는 안 봅니다.

숫자가 들어갈 자리에 숫자가 들어 있으면 통과입니다. 그 숫자가 -128 이어도 형식상으로는 정상입니다. 크기가 음수일 수 없다는 건 사람이 아는 상식이지 형식 규칙이 아닙니다.

저는 이걸 몰라서 "검사를 통과했으니 괜찮겠지" 하고 넘어갔습니다. 검사기가 여러 개 붙어 있으니 더 안심했습니다. 전부 같은 종류의 검사였다는 걸 나중에야 알았습니다.

원인 — 계산이 음수가 될 수 있는 자리

원인 — 값을 계산하는 코드에서 빼기를 하는데, 그 결과가 음수가 될 수 있었습니다.

size = total - used        # used 가 total 보다 크면 음수
write_field("size", size)  # 그대로 기록된다

그리고 파일 형식은 그 값을 그냥 받습니다. 저장할 때 아무도 안 막습니다.

저는 이 코드를 쓸 때 used 가 total 보다 클 수 있다는 생각을 안 했습니다. 논리적으로 그럴 리가 없다고 봤습니다. 실제로는 다른 경로에서 used 가 먼저 갱신되면서 순간적으로 역전되는 경우가 있었습니다.

"그럴 리 없다" 고 생각한 자리가 대개 이렇게 터집니다. 그럴 리 없으니 검사를 안 붙이고, 검사가 없으니 터져도 모릅니다.

이런 자리는 생각보다 많았습니다.

자리 음수가 되는 경우
남은 용량 사용량이 전체보다 클 때
경과 시간 시계가 뒤로 갔을 때
개수 차이 순서를 바꿔 뺐을 때
좌표·여백 계산 순서가 틀렸을 때

문서자동화에서 이걸 어떻게 찾았나

해결 — 검사기로는 못 찾아서 정상이던 옛 판과 대조했습니다.

옛 판과 새 판의 구조를 나란히 놓고 대조하는 방법을 정리한 터미널 화면

old = parse(old_file)
new = parse(new_file)

for key in set(old) | set(new):
    a, b = old.get(key), new.get(key)
    if type(a) is not type(b) or (isinstance(a, (int, float)) and (a >= 0) != (b >= 0)):
        print(f"{key}: {a} -> {b}   부호나 형식이 달라졌다")

검사기가 아니라 대조가 찾아냈습니다. 옛 판에는 전부 양수였던 자리에 음수가 하나 있었습니다.

저는 그전까지 이걸 눈으로 찾으려 했습니다. 파일을 열어 값을 훑어봤는데, 항목이 많아서 어느 게 이상한지 판단이 안 됐습니다. -128 도 그냥 숫자로 보였습니다. 무엇과 비교해야 할지를 모르니 이상함을 못 알아본 겁니다.

정상이던 판을 하나 남겨 두는 게 이래서 필요합니다. 비교할 기준이 없으면 "원래 이런가" 를 판단할 수 없습니다. 저는 이 일 뒤로 배포할 때마다 직전 정상 판을 따로 보관합니다.

cp out/result.bin "backup/result-$(date +%Y%m%d-%H%M%S).bin"

용량이 걱정되면 최근 몇 개만 남기면 됩니다. 하나도 없는 것과 하나 있는 것의 차이가 큽니다.

검사 항목에 추가한 것

해결 — 값의 타당성을 보는 항목을 따로 넣었습니다.

크기·범위·개수 검사를 항목에 추가한 목록을 정리한 터미널 화면

def sanity_check(fields: dict) -> list[str]:
    """형식이 아니라 '값이 말이 되는가' 를 본다."""
    bad = []
    for k in SIZE_FIELDS:
        if fields.get(k, 0) <= 0:
            bad.append(f"{k} 가 0 이하: {fields[k]}")
    for k, (lo, hi) in RANGES.items():
        v = fields.get(k)
        if v is not None and not (lo <= v <= hi):
            bad.append(f"{k} 가 범위 밖: {v} (기대 {lo}~{hi})")
    if abs(len(fields) - EXPECTED_COUNT) > TOLERANCE:
        bad.append(f"항목 수가 크게 다름: {len(fields)}")
    return bad
추가한 검사 무엇을 잡나
크기가 0 이하인가 음수·빈 값
값이 예상 범위 안인가 터무니없이 크거나 작은 값
항목 수가 옛 판과 비슷한가 통째로 빠진 부분
뺄셈 결과가 들어가는 자리인가 음수가 될 수 있는 지점

마지막 줄은 코드를 보고 표시해 둔 것입니다. 뺄셈이 들어가는 자리는 전부 후보로 봅니다.

검사기를 여러 개 붙여도 안 되는 이유

주의 — 검사기 수를 늘리는 것으로는 해결이 안 됐습니다.

세 개를 붙여 뒀는데 전부 형식 검사였습니다. 같은 종류를 여러 번 하는 셈이라, 형식은 세 번 확인되고 값은 여전히 아무도 안 봅니다.

검사 종류 무엇을 보나
형식 검사 구조가 규격에 맞나
타당성 검사 값이 말이 되나
대조 검사 이전 판과 크게 다른가
실행 검사 실제로 열어서 써지나

성격이 다른 검사를 섞어야 합니다. 같은 종류를 늘리면 개수만 늘고 잡히는 건 그대로입니다.

저는 검사기가 세 개나 되니 꽤 촘촘하다고 생각하고 있었습니다. 실제로는 같은 자리를 세 번 확인하고 나머지는 아무도 안 보는 상태였습니다. 개수가 아니라 종류를 세야 했습니다.

지금은 검사를 붙일 때마다 위 표의 어느 칸인지 적어 둡니다. 빈 칸이 보이면 그쪽부터 채웁니다.

확인 못 한 것

타당성 검사 항목을 자동으로 만들 수 있는지는 확인하지 못했습니다. 지금은 문제가 생길 때마다 하나씩 추가하는 식입니다.

정상 판을 여러 개 모아서 각 값의 범위를 학습시키면 될 것 같기도 한데, 정상 판이 몇 개나 있어야 믿을 만한 범위가 나오는지 모르겠습니다. 그리고 그 범위 자체가 틀리면 오탐이 늘 것 같습니다.

판정 — 계속 쓴다

타당성 검사를 따로 두는 것은 값어치가 큽니다. 형식 검사와 성격이 달라서 잡는 것이 겹치지 않습니다.

정상 판을 남겨 두는 것도 그렇습니다. 비교할 기준이 있어야 "원래 이런가" 를 판단할 수 있습니다.

"검사를 통과했다" 를 "정상이다" 로 읽지 않는 습관이 이 글에서 제일 중요합니다. 검사는 검사하는 것만 봅니다.

직접 하실 분께

하나. 붙여 둔 검사기가 각각 무엇을 보는지 적어 보세요. 전부 같은 종류일 수 있습니다.

둘. 값이 음수가 될 수 있는 자리를 코드에서 찾아 표시합니다. 뺄셈이 들어가는 곳이 후보입니다.

셋. 정상이던 판을 하나 남겨 둡니다. 대조할 기준이 없으면 이상한 걸 못 알아봅니다.

넷. 결과물을 실제로 한 번 열어 봅니다. 검사 통과와 쓸 수 있는 것은 다릅니다.

다음에는 정상 판 여러 개에서 각 값의 범위를 뽑아 타당성 검사를 자동으로 만들 수 있는지 해보려고 합니다. 되면 이어서 적겠습니다.