
Claude Code 분류기를 운영하며 정밀도를 높이는 방법 — 오분류 로그를 다음 프롬프트에 환원하는 설계
요약
Claude Code를 활용한 Terraform plan 분류기의 정밀도를 높이기 위해 오분류 로그를 피드백 루프로 환원하는 설계 방식을 제안합니다. 완벽한 프롬프트 대신 인간의 정정 데이터를 구조화된 로그로 축적하여 지속적으로 개선하는 메커니즘에 집중합니다.
핵심 포인트
- 완벽한 프롬프트 대신 틀렸을 때 바로잡히는 메커니즘 구축
- PR 리뷰 과정에서 발생하는 인간의 정정을 정답 데이터로 활용
- 오분류 사례를 JSONL 형식으로 구조화하여 축적
- 조직 고유의 문맥과 리소스별 경계 모호성 해결
초기 프롬프트의 분류기는 반드시 어딘가에서 틀린다
terraform plan의 ~ (in-place 업데이트)를 Claude가 분류하게 만드는 메커니즘은, CI에 적용한 첫날부터 완벽하게 작동하지 않는다. 조직마다 다른 리소스 명명 규칙, IAM 구성, 태그 운영 방식 등을 사전에 알 수 없기 때문이다. 초기 프롬프트의 분류는 운영 과정 중 반드시 틀리게 되어 있다.
정밀도를 높이는 열쇠는 프롬프트를 감으로 계속 다시 쓰는 것이 아니다. 운영 중에 발생한 오분류를 기록하고, 이를 다음 프롬프트에 환원하는 피드백 루프(Feedback Loop)를 만드는 것이다.
이 기사는 Terraform plan의 ~ 위험도를 Claude Code가 분류하게 하는 운영 편에 해당한다. 분류기를 '만드는' 이야기 다음에, '운영하면서 정밀도를 높이는' 이야기를 쓴다.
왜 초기 프롬프트에서는 정밀도가 나오지 않는가
이유는 세 가지가 있다. 세 가지 모두 "프롬프트를 정성스럽게 쓰면 방지할 수 있는" 종류의 문제가 아니다.
조직 고유의 문맥이 프롬프트에 포함되어 있지 않음: aws_iam_role_policy의 Resource를 *로 확장하는 변경이 위험한지 여부는 해당 조직의 권한 설계에 의존한다. 범용 프롬프트에는 "당신의 회사에서는 Resource 와일드카드를 금지하고 있다"라는 전제가 들어있지 않다.
카테고리 경계는 리소스마다 흔들림: cidr_blocks에 0.0.0.0/0을 추가하는 것은 security(보안)이지만, 내부 VPC 한정의 CIDR 추가는 operational(운영)에 가깝다. 같은 속성이라도 값에 따라 판정이 달라진다. 경계는 사례를 보고 나서야 비로소 언어화할 수 있다.
LLM의 판정 흔들림은 남음: 동일한 입력이라도 Haiku는 때때로 흔들린다. 프롬프트를 수정하더라도 판정의 분산(Variance)을 제로로 만들 수는 없다.
따라서 "완벽한 프롬프트를 쓰는 것"을 목표로 하지 않는다. "틀렸을 때 바로잡히는 메커니즘"을 목표로 한다.
정답 데이터는 "인간의 정정"에서 얻는다
정밀도를 측정하려면 정답(Ground Truth, 정답 데이터)이 필요하다. 분류기의 정답은 PR 리뷰에서 인간이 카테고리를 정정한 사실로부터 얻는다.
Claude가 operational이라고 판정한 ~ 행을 리뷰어가 "이것은 security다"라고 고친다. 이 정정이야말로 정답 데이터다. 별도의 전용 라벨링(Labeling) 작업을 할 필요는 없다. 리뷰의 부산물로서 정답이 쌓인다.
단, 한 가지 주의할 점이 있다. 인간이 정정하지 않았다고 해서 반드시 정답인 것은 아니다. 리뷰어도 놓칠 수 있다. 침묵을 정답으로 간주하면, 놓친 부분이 측정에서 누락된다. 따라서 "정정된 사실"은 강한 신호(Signal)로 취급하고, "정정되지 않은 사실"은 약한 신호로 취급한다.
오분류 로그를 구조화하여 축적한다
정정 내용을 1건당 1행의 JSONL로 남긴다. 나중에 집계와 프롬프트 환원 모두에 사용하기 위해 구조화해 둔다.
{"pr": 482, "address": "aws_iam_role_policy.app", "ai_category": "operational", "human_category": "security", "attr": "policy", "reason": "Resource 를 arn:aws:s3:::app-bucket/* 에서 * 로 확대", "date": "2026-05-30"}
{"pr": 491, "address": "aws_security_group_rule.api", "ai_category": "safe", "human_category": "security", "attr": "cidr_blocks", "reason": "0.0.0.0/0 를 추가", "date": "2026-06-01"}
기록하는 항목은 최소한으로 압축한다. ai_category(AI의 판정)와 human_category(인간의 정정)의 차이가 오분류의 본체다. attr과 reason은 나중에 프롬프트에 환원할 때의 소재가 된다.
정정 기록은 1행 추가하는 것으로 끝낼 수 있어야 한다. 리뷰어가 부담을 느끼면 쌓이지 않기 때문이다. PR에 코멘트를 쓰는 운영 방식이라면, 코멘트 끝의 정형화된 문구를 CI에서 낚아채 append(추가)하는 형태로 만들면 리뷰어의 수고는 1행으로 끝난다.
이 "실패를 구조화하여 자산으로 만드는" 발상은 known-failures.md를 키워나가는 운영과 같다 (Claude Code의 known-failures.md를 키우는 것). 오분류 로그는 분류기에게 있어 known-failures에 해당한다.
로그를 다음 프롬프트에 환원한다
쌓아둔 로그는 2단계로 프롬프트에 되돌린다.
1. 직전의 정정을 few-shot (몇 가지 예시 제시)으로 삽입한다
먼저, 직전의 정정 사례를 몇 건 프롬프트에 예시로 삽입한다. "과거에 이렇게 틀렸다"를 보여주면, 동일한 종류의 차이(diff)에서 같은 실수를 저지르기 어려워진다.
# 직전 8건의 정정을 few-shot 예시로 정형화한다
EXAMPLES=$(jq -rs '
sort_by(.date) | reverse | .[0:8]
...
분류 프롬프트에 삽입구를 하나 추가한다.
## 지금까지 인간이 정정한 예시 (유사한 차이는 동일하게 판정할 것)
{{learned_examples}}
CI 측에서 {{resource_json}}과 함께 치환한다.
prompt=$(jq -n --arg j "$row" --arg ex "$EXAMPLES" --rawfile tmpl .github/prompts/classify-tilde.md \
'$tmpl | gsub("{{resource_json}}"; $j) | gsub("{{learned_examples}}"; $ex)')
2. 반복되는 오분류는 명시적 규칙으로 승격시킨다
few-shot은 간편하지만, 예시가 흘러가면 잊어버리게 된다. 동일한 오분류가 계속해서 발생한다면, 그것은 "예시"가 아니라 "규칙"이다. 프롬프트에 영구적인 규칙으로 작성한다.
반복을 탐지하려면, 정정 사항을 attr과 정정 대상 카테고리로 묶어서 카운트한다.
jq -s '
group_by(.attr + ">" + .human_category)
| map({pattern: (.[0].attr + " → " + .[0].human_category), count: length})
...
3회 이상 반복된 정정은 프롬프트에 명시적 규칙으로 고정한다.
## 고정 규칙 (오분류가 반복되었기에 명문화함)
- `aws_iam_role_policy`의 Resource가 `*`로 확장되는 변경은 반드시 security
(이유: 과거에 operational로 오판하여 권한 확대를 놓칠 뻔함. 2026-05-30 이후 3건)
...
규칙에는 이유와 날짜를 덧붙인다. 나중에 "이 규칙이 아직 필요한가"를 판단할 수 있도록 하기 위해서다. 이유가 없는 규칙은 아무도 제거할 수 없게 된다.
few-shot과 규칙의 구분은 단순하다. 흔들리는 예시는 few-shot, 굳어진 판정은 규칙이다. few-shot은 시행착오의 장이고, 규칙은 확정된 지식의 저장소라고 생각하면 된다.
위험 카테고리의 간과를 최우선 KPI로 삼는다
정확도를 하나의 숫자로만 보면 판단을 그르칠 수 있다. 분류기의 실수에는 비용의 비대칭성이 존재하기 때문이다.
safe를 security로 오해하는 경우(위양성, false positive)는 리뷰가 한 번 늘어나는 것으로 끝난다. 반면, security를 safe로 오해하는 경우(위음성, false negative)는 위험한 변경이 그대로 apply 된다. 후자는 사고로 이어진다.
따라서 전체 정답률이 아니라, 위험 카테고리(security / hidden-destruction)의 간과(miss) 건수를 최우선 KPI로 삼는다.
# 간과: 인간이 security / hidden-destruction로 정정했음에도 AI는 안전 측이었던 건수
MISS=$(jq -s '[.[]
| select((.human_category == "security" or .human_category == "hidden-destruction")
...
MISS가 증가하면, 해당 정정 사항을 최우선적으로 고정 규칙으로 승격시킨다. OVER는 리뷰 비용이 늘어날 뿐이므로 어느 정도는 허용한다. 분류기는 "안전한 쪽으로 치우치도록" 설계해 둔다.
참고로, 이 KPI는 정정된 사실만을 모수로 한다. 앞서 언급했듯이 "인간도 놓친 간과"는 포착할 수 없다. 완전한 재현율 (recall, 재현율)의 대용품이라는 점은 감안하여 사용한다.
어디까지 프롬프트로 버티고, 언제 모델을 바꿀 것인가
오분류는 우선 프롬프트 환원을 통해 해결한다. 그럼에도 한계에 부딪혔을 때를 대비해 단계별 선택지를 가지고 있어야 한다.
- 프롬프트 환원: few-shot → 고정 규칙. 대부분의 오분류는 여기서 수렴한다. 비용이 가장 낮다.
- 모델 승격: IAM 정책의 jsonencode 내부나
lifecycle.ignore_changes
영향 등 의미 해석이 필요한 차이로 인해 Haiku가 놓친다면, 해당 유형만 Sonnet으로 넘긴다. 모든 건을 Sonnet으로 처리하지 않고, 위험 카테고리 후보만 승격시키면 비용을 억제할 수 있다.
- 파인튜닝 (Fine-tuning): 정답 데이터가 수백 건 쌓이고, 프롬프트 개선 및 모델 승격만으로는 한계에 도달했을 때 검토한다. 단, 운영 비용과 데이터 정비 부담이 한 단계 높아진다. 최후의 수단으로 생각해야 한다.
순서가 중요하다. 갑자기 파인튜닝으로 넘어가면, 데이터와 노하우가 쌓이지 않은 상태에서 운영 비용만 떠안게 된다. 프롬프트 환원을 통해 어떤 카테고리가 취약한지 파악한 뒤에 다음 단계로 올라간다.
인간이 공급하는 것은 「정정」이라는 정답 데이터
분류기의 정밀도는 프롬프트를 작성한 순간의 완성도로 결정되지 않는다. 운영 중에 인간이 내놓는 정정을 얼마나 자산화할 수 있느냐에 따라 결정된다.
인간이 하는 일은 「카테고리를 바로잡는 것」뿐이다. 그 정정이 JSONL에 쌓여 퓨샷 (few-shot)이 되고, 고정 규칙이 되며, 다음 판정의 수준을 끌어올린다. 리뷰의 부산물이 그대로 분류기의 학습 데이터 (training data)가 된다.
Claude에게 맡기는 것은 「~ 안의 내용을 매번 분류하는 작업」이며, 인간이 담당하는 것은 「틀렸을 때 정답을 주는 판단」이다. 어떤 변경 사항을 적용 (apply)해도 좋을지에 대한 최종 결정은 PR의 의도와 비즈니스 영향을 읽을 수 있는 인간이 내린다. 이 경계는 운영 편에서도 변하지 않는다.
분류기는 만들어서 끝나는 것이 아니라, 정정을 통해 키워나가는 것이다. 오분류 로그를 다음 프롬프트에 환원하는 메커니즘을 처음부터 구축해 두면, 첫날에 실수가 있더라도 운영을 통해 따라잡을 수 있다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기