LLM이 작동함을 증명하기 위해 평가 하네스(Eval Harness)를 구축했지만, 결과는 정반대였습니다.
요약
LLM을 활용한 통합 실패 분류 시도 결과, 결정론적 규칙(Rules)이 LLM보다 더 효율적이고 정확함을 확인했습니다. LLM은 분류보다는 장애 요약과 같은 생성 작업에 집중하는 것이 비용과 성능 면에서 유리합니다.
핵심 포인트
- 분류 작업에서 규칙 기반 방식이 LLM보다 높은 정확도(89.7%)를 기록함
- LLM은 구조화된 데이터 추출보다 텍스트 요약 작업에 더 적합함
- 비용 효율적인 아키텍처를 위해 분류는 규칙, 설명은 AI를 사용하는 하이브리드 방식 제안
- 대용량 로그 처리를 위한 스트리밍 파싱 및 결정론적 데이터 보강의 중요성
저는 언어 모델(Language Model)이 통합 실패(Integration failures)를 분류하도록 만들고자 했습니다. 그것이 제대로 작동함을 증명하기 위해 평가 하네스(Evaluation harness)를 구축했습니다. 하지만 그 하네스는 그것이 사용할 가치가 없음을 증명했습니다.
최종 아키텍처: 결정론적 규칙(Deterministic rules)이 분류와 구조화된 추출(Structured extraction)을 수행합니다. LLM은 평이한 영어로 된 장애 요약(Incident summaries)을 작성하는 단 한 가지 일만 수행합니다. 분류를 위한 저렴한 규칙, 설명을 위한 AI를 사용하는 방식입니다.
하네스 위치: https://github.com/fissible/llm-triage-eval
수치 데이터와 그 도출 과정을 소개합니다.
핵심 요약
- 분류 (Classification): 58개의 수동 검토 사례를 대상으로 규칙(Rules) 89.7%, LLM 87.9%를 기록했습니다.
- 프롬프트 엔지니어링 (Prompt engineering): v1 = v2 = v3 = 89.7%. 세 번의 프롬프트 수정에도 변화가 없었습니다.
- 추출 (Extraction): 공정한 기준선(Baseline)과 비교했을 때, LLM과 정규 표현식(Regex)은 구조화된 필드에 대해 90~97% 일치했습니다. 추출이 LLM의 영역이라는 저의 이전 주장은 틀렸습니다.
- 요약 (Summaries): 충실도(Faithfulness)는 검증된 결과 4.62/5였습니다. 완전성(Completeness)은 4.26/5로, 철회되었습니다 (이에 대한 자세한 내용은 아래에서 설명합니다).
- 비용 (Cost): $0. Ollama를 통한 로컬 모델을 사용했으며, 사례당 약 8~10초가 소요되었습니다.
문제 상황
레거시 통합 계층(Legacy integration layer)이 폐기되고 재구축되고 있습니다. 해당 계층의 실패 사례는 양이 방대하고, 반복적이며, 추론하기 어렵습니다. 즉, 동일한 몇 가지 근본 원인이 수천 번 반복되는 스택 트레이스(Stack traces)의 벽과 같습니다.
저는 실패 분류 체계(Failure taxonomy)와 분류(Triage) 도구를 원했습니다. 내보낸 로그를 입력하면, 구분되지 않은 더미가 아니라 분류된 별개의 문제 세트를 결과로 얻는 방식입니다.
중요하게 작용한 부차적인 이점: 라벨링된 실패 세트는 마이그레이션 수락 테스트(Migration-acceptance suite)의 역할도 겸하게 되었습니다. 만약 교체된 시스템이 58개의 별개 실패를 모두 올바르게 처리한다면, 이는 기존 시스템이 실제로 실패했던 부분들을 커버한다는 의미가 됩니다.
데이터 파이프라인
약 4,000개의 파싱된 실패 사례는 오류 유형, 정규화된 메시지, 발생 요소를 기준으로 중복을 제거하여 58개의 별개 시그니처(Signatures)로 압축되었습니다. 한 사례의 경우, 675개의 표현식 오류(Expression errors)가 거의 하나의 버그와 맞먹었습니다.
모델과 관련된 것보다 훨씬 더 중요한 두 가지 사항이 있었습니다.
스트리밍 파싱 (Streaming parse). 제너레이터 (Generator)를 통한 라인별 처리로, 100MB 이상의 파일에서도 일정한 메모리 사용량을 유지합니다. 헤더 정규 표현식 (Header regex) 및 블록 필드 정규 표현식 (Block-field regex)을 사용합니다.
상관관계 상세 정보 보강 (Correlated-detail enrichment). 이것이 핵심적인 움직임이며, 완전히 결정론적 (Deterministic)입니다. 실패한 트랜잭션은 ERROR 레벨에 일반적인 오류를 기록하고, 이와 별도로 WARN 레벨에 상관관계 ID (Correlation ID)를 키로 하여 실제 근본 원인을 담은 JSON 블롭 (JSON blob)을 기록합니다. 2패스 스캔 (Two-pass scan)을 통해 상관관계 ID별로 상세 블록을 수집하고 이를 오류 케이스에 부착합니다.
그 영향은 매우 컸습니다. 하나의 일반적인 라우팅 카테고리가 287개 케이스에서 85개로 식별이 명확해졌습니다(de-aliased). 데이터베이스 타임아웃 (Database timeout) 카테고리는 0개에서 60개로 늘어났는데, 이는 실제 원인이 래퍼 (Wrapper) 뒤에 숨겨져 보이지 않았기 때문입니다.
이 점은 깊이 생각해 볼 가치가 있습니다. 분류 품질의 가장 큰 개선은 모델이 무엇인가를 보기 전에, 정규 표현식 (Regex)을 사용하여 상관관계가 있는 로그 라인들을 하나로 엮어낸 것에서 나왔습니다.
분류 체계 (Taxonomy)와 원칙 (Doctrine)
실제 로그의 빈도수에서 도출된 13개의 카테고리는 **에러 코드 (Error code)가 아닌 근본 원인 (Root cause)**에 의해 정의됩니다.
제가 '원칙 (Doctrine)'이라 부를 레이블링 표준은 다음과 같습니다: 일반적인 래퍼 (Wrapper)가 아니라, 상관관계 상세 정보에서 보이는 가장 깊은 근본 원인에 따라 분류하십시오. 근본적인 상세 내용이 중복 키 위반 (Duplicate key violation)인 라우팅 에러는 라우팅 에러가 아니라 제약 조건 위반 (Constraint violation)입니다. 한 가지 미묘한 차이가 있다면: HTTP를 통해 다운스트림 (Downstream) 서비스를 호출하는 타임아웃은 다운스트림 타임아웃이며, 진정한 데이터베이스 타임아웃은 실패하는 컴포넌트 내부에 JDBC 호출이 포함되어 있어야 합니다.
규칙 기반 분류기 (Rule-based classifier)가 모든 케이스에 사전 태깅을 수행했기에, 골든 세트 (Golden set)를 구축하는 작업은 무작위 레이블링이 아닌 검토 작업이 되었습니다. 이 약한 레이블러 (Weak labeler)는 LLM이 극복해야 할 결정론적 베이스라인 (Deterministic baseline)이 되었으며, 이는 프로젝트에서 가장 중대한 설계 결정이었음이 밝혀졌습니다.
58개의 케이스는 정제되었고, 환경 태그가 지정되었으며, 사람이 검토했습니다. 원래의 에러 유형은 모델의 입력에서 제외되었는데, 이를 포함하면 작업이 너무 쉬워지기 때문입니다.
하네스 (Harness)
세 가지 평가 (Eval) 유형:
- 분류 (Classification). 정확도 (Accuracy), 카테고리별 정밀도 (Precision) 및 재현율 (Recall), 혼동 행렬 (Confusion matrix)을 산출하며, 규칙 기반 베이스라인 (Rule baseline)과 비교합니다.
- 추출 (Extraction). HTTP 메서드 (Method), 상태 (Status), 대상 엔티티 (Target entity), 오류 유형 (Error type), 작업 (Operation), 제약 조건 (Constraint)에 대한 필드별 정확도를 산출하며, 정규 표현식 파서 (Regex parser)와 비교합니다.
- 요약 (Summary). 모델이 한 문장의 사고 요약 (Incident summary)을 작성하면, LLM-as-judge가 충실도 (Faithfulness)와 완전성 (Completeness)을 1점에서 5점 사이로 점수를 매깁니다.
전 과정에 걸친 규율: 온도 (Temperature) 0, 스키마 (Schemas)를 통한 구조화된 출력 (Structured output), 매 실행 시 타임스탬프가 찍힌 JSON 보고서 작성, 회귀 (Regression) 발생 시 차이 분석 (Diffable)이 가능하도록 프롬프트 버전 관리, 그리고 골든 세트 (Golden set)를 회귀 테스트 대상으로 설정했습니다.
php artisan triage:eval --source=db --only-reviewed --prompt=v3
php artisan triage:eval-extract
php artisan triage:eval-summary
결과 (Findings)
분류 작업에서는 규칙이 모델을 이겼습니다. 89.7% 대 87.9%의 결과가 나왔습니다. 압도적인 차이는 아니지만, 모델이 지연 시간 (Latency), 비용 (Cost), 비결정론 (Nondeterminism)을 정당화하려면 반드시 더 뛰어나야 하는데, 전혀 그렇지 않았습니다.
프롬프트 엔지니어링 (Prompt engineering)은 아무런 효과가 없었습니다. 세 가지 프롬프트 버전을 테스트했으나 정확도는 동일했습니다. 이 프로젝트에서 얻은 전체 개선 사항, 즉 81%에서 89.7%로의 8.7포인트 상승은 라벨링 교리 (Labeling doctrine)를 일관되게 만든 데서 왔습니다. 프롬프트 덕분이 아닙니다. 무엇이 올바른 라벨인지 명확하게 정의할 수 있게 되자 규칙과 모델 모두 개선되었으며, 규칙이 더 많이 개선되었습니다.
하네스가 스스로의 버그를 잡아냈습니다. v2 프롬프트 플래그를 사용하여 실행하면 v2라고 라벨링된 보고서가 생성되었지만, 러너 (Runner)가 분류기 (Classifier)로 버전을 전달하지 않아 조용히 v1을 다시 실행했습니다. 이를 알 수 있었던 단서는 서로 다른 실행임에도 불구하고 토큰 수 (Token counts)가 동일했다는 점입니다. 하네스가 실행당 토큰 수를 기록하지 않았다면, 세 가지 프롬프트 버전은 가짜 결과를 만들어내는 배선 버그 (Wiring bug)가 아니라 실제 부정적인 결과처럼 보였을 것입니다.
공정한 베이스라인 (Baseline)은 추출 (Extraction) 작업에서 모델이 가졌던 겉보기 우위를 지워버렸습니다. 단순한 정규 표현식 (Regex)을 사용했을 때 모델은 정규 표현식이 무시한 상관관계가 있는 세부 사항들을 파고들었기 때문에 훨씬 더 뛰어난 것처럼 보였습니다. 하지만 정규 표현식이 세부 사항을 인식하도록 만들자 그 격차는 좁혀졌습니다: HTTP 메서드(Method) 96.5%, 상태(Status) 94.8%, 제약 조건(Constraint) 93.1%, 작업(Operation) 89.7%의 일치율을 보였습니다. 여전히 차이가 발생하는 두 필드인 에러 유형 (Error type) 62%와 대상 엔티티 (Target entity) 83%는 정확도(Accuracy)의 문제라기보다 정의상의 이유로 다릅니다. 즉, 실패 체인 (Failure chain)이 여러 에러 코드를 포함하고 있을 때, 어떤 것이 "그" 유형인지 결정하는 것은 추출기 (Extractor)의 문제가 아니라 분류 체계 (Taxonomy)에 관한 문제입니다.
저는 이전에 추출 작업이야말로 LLM이 제 역할을 다하는 영역이라고 주장했습니다. 그 주장은 약한 베이스라인 (Weak baseline)이 만들어낸 결과물이었습니다.
모델이 승리하는 지점은 요약 (Summaries)입니다. 상관관계가 있는 세부 사항이 포함된 실패 사례가 주어지면, 모델은 정규 표현식으로는 절대 작성할 수 없는, 읽기 쉬운 근본 원인 및 연쇄 반응 (Root-cause-and-cascade) 서사를 생성합니다.
판사 (Judge) 검증하기
요약 모델과 동일한 모델을 사용하는 LLM 판사 (LLM judge)로 요약을 점수 매기는 것은 자기 평가 (Self-evaluation)이며, 저는 이를 확인하지 않고 수치를 보고하고 싶지 않았습니다. 그래서 15개의 요약을 수동으로 점수 매기고 비교했습니다.
| 축 (Axis) | 일치 (Exact) | 1점 차 이내 (Within-1) | 평균 편향 (Mean bias) | 이차 가중 카파 (Quadratic-weighted κ) | 판결 (Verdict) |
|---|---|---|---|---|---|
| 충실도 (Faithfulness) | 80% | 100% | -0.07 | 0.57 | 보통 (Moderate) |
| 완전성 (Completeness) | 40% | 80% | +0.13 | 0.10 | 신뢰 불가 (Do not trust) |
충실도는 유지됩니다. 1점 이상 차이 난 적이 없으며, 편향이 거의 0에 가깝고, κ 값이 0.57입니다. 4.62라는 점수는 보통 수준의 신뢰도로 유지됩니다. 이는 모델이 사실을 지어냈는지 여부를 묻는 것이기에 가장 중요한 축입니다.
완전성은 그렇지 않습니다. κ 값이 0.10이며, 정확한 일치율은 40%입니다. 4.26이라는 수치는 인용하지 마십시오. 두 가지 요인이 얽혀 있을 가능성이 큽니다: 완전성은 실제로 더 주관적이며, 15개의 항목만 점수를 매겼고 대부분 4점 또는 5점으로 기록되었기 때문에, 1점 차 이내 일치율이 80%인 경우에도 κ 값이 불안정해져 낮게 나타납니다. 어느 쪽이든 정직한 결론은 불확실하다는 것입니다.
여기서 제가 배운 가장 유용한 점은 통계적인 것이 아니었습니다. 완성도(completeness)를 수동으로 점수 매기는 것은 매우 어려웠으며, 저조차도 이전에 내렸던 제 판단에 동의하지 못했습니다. 이것은 판사(judge)의 문제가 아니라 루브릭(rubric, 평가 기준)의 문제입니다. 저는 분류 레이블(classification labels)에 대해서는 일관된 원칙을 작성했지만, 요약에서 무엇이 "완성된" 상태인지를 정의하는 원칙은 작성하지 않았습니다. 판사가 제 의견에 동의하지 않았던 이유는 저 스스로가 제 의견에 동의하지 않았기 때문입니다.
LLM-as-judge는 객관적인 축(objective axis)에서는 작동했지만 주관적인 축(subjective axis)에서는 실패했습니다. 이는 대략 여러분이 기대할 만한 결과이며, 당연하게 가정하기보다는 알아둘 가치가 있는 사실입니다.
결과적인 아키텍처 (The resulting architecture)
결정론적 규칙(Deterministic rules)과 파서(parsers)가 분류(classification)와 구조화된 추출(structured extraction)을 수행합니다. LLM은 요청 시 평이한 영어(plain-English)로 된 요약을 작성합니다.
LLM 분류기(classifier)와 추출기(extractor)는 여전히 존재하지만, 오직 평가 하네스(eval harness) 내부에서 측정 대상(contender)으로서만 존재합니다. 더 강력한 모델로 교체하는 것은 두 가지 값의 설정(config) 변경일 뿐이므로, 프런티어 모델(frontier model)이 답변을 바꾸는지 알고 싶을 때 벤치마크는 이미 준비되어 있습니다. 단지 실제 서비스 경로(live path)에 포함되어 있지 않을 뿐입니다.
제가 여러분에게 전하고 싶은 점
- 평가 하네스(Eval harness)를 먼저 구축하세요. 평가 하네스의 역할은 모델이 작동함을 증명하는 것이 아닙니다. 모델이 어디에 적합한지를 찾아내는 것이며, 그 답은 "거의 어디에도 해당하지 않음"일 수도 있습니다.
- 공정한 베이스라인(Baseline)이 전부입니다. 약한 정규 표현식(Regex)은 모델을 훌륭해 보이게 만들었습니다. 강력한 정규 표현식은 그 우위를 지워버렸습니다. 모델이 승리했다고 결론 내리기 전에 베이스라인을 강화하세요.
- 일관된 레이블링 원칙(Coherent labeling doctrine)이 프롬프트 엔지니어링(Prompt engineering)보다 강력합니다. 이 프로젝트에서 얻은 모든 이득은 정답이 무엇인지 아는 것에서 나왔습니다. 프롬프트 버전들은 노이즈에 불과했습니다.
- 하네스는 구축자의 실수를 잡아냅니다. 하네스는 저의 조용한 프롬프트 배선(prompt-wiring) 버그를 잡아냈고, 추출(extraction)에 대한 저의 성급한 주장을 잡아냈습니다. 둘 다 저의 실수였으며, 측정 없이는 둘 다 보이지 않았을 것입니다.
- 근본 원인은 일반적인 래퍼(Wrapper) 뒤에 숨어 있습니다. 상관관계가 있는 컨텍스트(Context)를 하나로 엮는 것이 프로젝트에서 가장 레버리지가 높은 작업이었으며, 그것은 바로 정규 표현식(Regex)이었습니다.
- 판단자(Judge)를 검증하세요. 그리고 판단자가 당신의 의견과 다를 때, 당신의 루브릭(Rubric)이 문제일 수 있음을 고려하십시오.
- 로컬 우선(Local-first) 방식은 비용에 대해 정직합니다. 무료 반복(Iteration), 데이터가 기기를 떠나지 않음, 그리고 호스팅된 모델을 가정하는 대신 나중에 측정할 수 있는 벤치마크를 제공합니다.
이 모든 것의 불편한 진실은 이렇습니다: 저는 프로젝트 기간 대부분을 "그것을 만들지 마라"고 말해주는 도구를 만드는 데 보냈습니다. 그것이 바로 프로젝트가 올바르게 작동하고 있었다는 증거였습니다.
하네스는 GitHub에 있습니다: https://github.com/fissible/llm-triage-eval
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기