LLM이 만든 출력의 100%가 문법적으로 완벽한 JSON이었습니다. 그런데 정작 필요한 데이터 규격을 만족한 건 0%였습니다. 형식은 흠잡을 데 없는데, 안에 들어 있어야 할 내용이 통째로 비어 있었던 거죠.

마이크로소프트 ISE(Industrial Solutions Engineering) 팀이 산업 현장의 교대 근무 로그를 구조화된 요약으로 변환하는 LLM 시스템을 만들면서 겪은 실패와 해법을 공개했습니다. 이 요약은 다음 교대조, 규정 준수 대시보드, 감사 기록이 그대로 가져다 쓰는 데이터라, 정해진 항목이 빠짐없이 채워져야 했습니다. 핵심은 문제의 원인이 프롬프트가 아니라 ‘LLM에게 맡길 일과 맡기지 말아야 할 일을 구분하지 않은 것’에 있었다는 진단입니다.
출처: Separating Deterministic Extraction from AI Inference in Industrial Summarization – Microsoft ISE Developer Blog
형식은 100점, 내용은 0점
프로토타입은 빠르게 나왔습니다. 며칠 만에 작동하는 데모가 만들어졌고, JSON은 매번 정상적으로 파싱됐습니다. 담당자가 요약문을 읽으며 고개를 끄덕일 정도였죠. 그런데 평가 파이프라인을 돌리자 결과가 갈렸습니다. 형식 유효성은 100%, 스키마 준수율은 0%였습니다.
무슨 뜻일까요. 출력은 모두 올바른 JSON 형태였지만, 필수 항목이 통째로 빠져 있거나 빈 배열로 채워져 있었습니다. 다음 교대조 작업자가 이 요약을 열면 발생한 사건도, 처리할 작업도, 예상되는 상황도 찾을 수 없었습니다. 실제로 아무 일이 없어서가 아니라, 모델이 조용히 그 내용을 빠뜨렸기 때문입니다.
이게 왜 무서운 문제일까요. 형식이 깨졌다면 곧바로 오류로 잡혔을 겁니다. 하지만 JSON은 멀쩡했기 때문에 시스템은 아무 문제가 없다고 판단했습니다. 팀은 이 현상을 LLM이 손실 직렬화기(lossy serializer)처럼 작동한다고 표현합니다. 입력을 읽고, 무엇을 담을지 스스로 정하고, 전부 생성하는 과정에서 필요한 내용을 소리 없이 흘려버린다는 뜻입니다.
“이 필드는 AI가 만들 일인가, 그냥 복사할 일인가”
첫 접근은 가장 자연스러운 방식이었습니다. 교대 로그 전체와 스키마를 하나의 프롬프트에 넣고, 완성된 JSON 문서를 통째로 만들어내게 하는 것이었죠. 팀이 내린 결론은 분명했습니다. 프롬프트 자체는 문제가 없었고, 진짜 문제는 소프트웨어가 책임져야 할 일을 LLM에게 떠넘긴 데 있었습니다.
그래서 팀은 출력 스키마의 모든 필드를 놓고 단순한 질문 하나를 던졌습니다.
이 필드는 입력 데이터에 이미 존재하는 값과 1:1로 대응하는가?
작업 자체는 의외로 저기술(low-tech)이었습니다. 스프레드시트 한 칸에는 출력 필드를, 옆 칸에는 그 입력 출처를 적고, 한 줄씩 원본 데이터로 거슬러 올라가며 예/아니오를 판정했습니다. 며칠이 아니라 몇 시간 만에 끝났고, 전체 필드의 절반 이상이 ‘복사’ 쪽으로 분류됐습니다.
분류 결과는 깔끔하게 둘로 나뉘었습니다.
- 결정론적 필드: 입력에 정답이 이미 존재하는 값. 타임스탬프, 로그 항목 ID, 작업자가 매긴 심각도, 현장에서 직접 적은 설명 같은 것들입니다. 정답이 하나뿐이고 그 값이 입력에 그대로 있습니다. 이걸 LLM에게 ‘생성’하라고 시키는 건, 가끔 실수하는 복사기에게 베껴 쓰라고 맡기는 셈입니다.
- AI 판단 필드: 해석이 필요한 값. 어떤 사건이 어느 유형에 속하는지 분류하거나, 타임라인을 따져 이슈가 아직 진행 중인지 추론하거나, 자유 서술된 인계 노트에서 후속 작업을 뽑아내는 일입니다. 입력에 단일한 정답이 없어, 모델이 읽고 추론하고 결정해야 합니다.
팀은 이 구분을 YAML 형식의 ‘필드 분류 레지스트리’로 코드화했습니다. 각 필드를 deterministic(원본에서 복사, LLM 개입 금지)과 llm_required(AI 해석 필요)로 못 박은 이 표가 시스템 전체의 설계 계약이 됐습니다.
사실은 코드가 보장하고, AI는 좁은 판단만
이 분류를 바탕으로 시스템은 4단계 파이프라인으로 재설계됐습니다. 작동 흐름은 다음과 같습니다.
- 골격 만들기(코드): 소프트웨어가 결정론적 필드를 입력에서 그대로 복사하고, 여러 타임라인을 시간순으로 병합합니다. 원본과 동일하므로 정답이 보장됩니다. “출력의 심각도 == 입력의 심각도”를 단위 테스트로 검증할 수 있는 수준입니다.
- 제한된 모델 판단(AI): 모델에게는 레지스트리가
llm_required로 표시한 결정만 묻습니다. 각 기록이 어느 섹션에 들어갈지(라우팅), 어떤 값으로 분류될지(분류)입니다. 모든 답은 원본 로그의 고유 ID에 묶여, AI의 결정 하나하나가 특정 입력 기록까지 추적됩니다. - 방어적 병합(코드): 가장 중요한 단계입니다. AI의 출력을 골격에 합칠 때, 시스템은 항상 원본에서 기록을 다시 만든 뒤 모델이 맡은 필드만 덧붙입니다. 만약 LLM이 결정론적 필드에 대해 원본과 다른 값을 내놨다면, 시스템은 원본 값을 쓰고 LLM의 시도를 위반 기록으로 남깁니다. 결정론적 필드는 설계상 무조건 원본이 이깁니다.
- 근거 매핑(AI): 요약이 완성된 뒤에야 모델이 각 결론을 어떤 원본 기록에서 끌어왔는지 연결합니다.
핵심은 발상의 전환입니다. “필수 항목이 들어 있는지 검사”하는 게 아니라, “필수 항목이 설계상 빠질 수 없게” 만든 것이죠. 결정론적 사실은 소프트웨어가 보장하고, 확률적 판단만 모델에게 좁고 명시적으로 위임합니다.
0%에서 100%로, 그리고 남는 질문
모듈형 파이프라인은 스키마 준수율을 0%에서 47%로 끌어올렸습니다. 남은 실패는 평가 방식이 해결했습니다. 팀은 개별 출력을 들여다보는 대신, 각 스키마 누락을 입력의 특성과 대조했습니다. 그러자 실패한 모든 사례에서 공통점이 드러났습니다. 작업자가 시스템 상태를 하나도 기록하지 않은 교대였던 겁니다.
여기서 나온 원칙이 인상적입니다. 내용을 지어내지 말고, 부재를 기록하라. 기록할 조건이 없으면 “무엇을 검토했고 무엇이 발견되지 않았는지”를 실제 메타데이터로 남깁니다. 덕분에 다운스트림 독자는 “아무 일도 없었음”과 “버그로 섹션이 비었음”을 항상 구분할 수 있습니다. 이 보완으로 준수율은 47%에서 73%, 다시 100%에 도달했습니다. 기반 모델을 다른 것으로 바꿔도 100%가 유지돼, 결정론과 AI를 가르는 이 설계가 특정 모델에 의존하지 않는다는 점도 확인됐습니다.
이 사례가 주는 메시지는 “AI를 써야 하는가”가 아니라 “AI를 어디에 둘 것인가”입니다. 라우팅, 분류, 근거 매핑, 모호함 속 추론에는 LLM을 적극적으로 썼습니다. 다만 정답이 이미 입력에 있는 일까지 모델에게 맡기지 않았을 뿐입니다. LLM을 실무에 통합해 본 사람이라면 “형식은 맞는데 내용이 어긋난다”는 경험이 낯설지 않을 텐데, 그 해법이 더 정교한 프롬프트가 아니라 책임의 경계를 다시 긋는 일이었다는 점이 이 글의 가장 큰 통찰입니다.
다만 팀도 한계를 분명히 합니다. 모든 평가는 실제 운영 데이터가 아니라 구조적 다양성을 위해 만든 합성 로그로 진행됐고, 마지막 근거 매핑 단계는 아직 가장 검증이 덜 된 부분이라고 밝힙니다.

답글 남기기