운영자 신뢰를 무너뜨리는 가장 큰 원인(Killer)을 포착하는 15줄의 테스트
요약
본 글은 AI 에이전트의 이상 징후 탐지 시스템(anomaly detection system)을 실제 운영 환경에서 테스트하는 중요성을 강조합니다. 개별 탐지기 테스트만으로는 놓치기 쉬운, 여러 탐지기가 동시에 작동할 때 발생하는 오탐지(false positives) 문제를 지적하며, 이를 포착하는 15줄의 핵심 테스트 코드를 제시합니다.
핵심 포인트
- 개별 테스트로는 실제 환경의 복합적인 상호작용을 파악하기 어렵다.
- 운영자 신뢰를 잃게 만드는 가장 큰 원인은 오탐지(false positives)이다.
- 모든 탐지기가 정상으로 보이는 '깨끗한 트레이스'에서 문제가 발생할 수 있다.
- 제시된 테스트는 AI 에이전트의 배포 전 실패 모드를 포착하는 데 유용하다.
이전 프로젝트에서는 10만 개의 트레이스에서 56,869개의 이상 징후(anomalies)가 발견되었습니다. 노이즈를 제거한 후에도 11,294개가 남아 있었습니다. 이는 80% 감소를 의미하며, 원래의 이상 징후 중 45,575개는 오탐지(false positives)였습니다.
운영자는 56,869개의 경고(alerts)를 보았습니다. 이를 조사한 결과, 다섯 개 중 네 개가 쓸모없는 정보(junk)라는 것을 알게 되었습니다. 그리고 시스템에 대한 신뢰를 잃었습니다. 해결책은 더 나은 탐지기(detectors)를 만드는 것이 아니었습니다. 너무 쉽게 작동하는 탐지기를 제거하고 엄격하게 조정하는 것이었습니다. 아무도 믿지 않는 탐지기는 아예 없는 것보다 못한 법입니다.
이것이 누군가 이상 징후 감지 시스템을 실제 운영 환경에서 사용할지 말지를 결정하는 실패 모드(failure mode)입니다. 그리고 이것을 배포 전에 포착할 수 있는 테스트는 거의 아무도 작성하지 않는 15줄의 코드입니다.
왜 개별 탐지기 테스트로는 이를 놓치는가
개별 탐지기 테스트는 각 탐지기를 독립적으로 확인합니다. 하지만 실제 운영 환경에서는 모든 43개의 탐지기가 동일한 트레이스에서 동시에 실행됩니다. 깨끗한(clean) 트레이스는 격리 테스트(isolation testing)가 절대 밝혀낼 수 없는 방식으로 우연히 탐지기의 트리거 조건(trigger conditions)을 만족시킬 수 있습니다.
실제 사례 몇 가지를 보여드리겠습니다:
- 세 번의 호출 중 한 번이 잘못된 경우 오류율은 33%로, 30% 임계치를 초과합니다. 이 오류는 일시적인 문제(transient blip)였습니다. 트레이스는 깨끗합니다. 탐지기가 작동합니다.
- 깨끗한 트레이스에 있는 1초 간격은 30초 비활동 규칙보다 훨씬 짧습니다. 하지만 느린 API 호출이 35초가 걸렸습니다. 트레이스는 깨끗하며, 에이전트가 정당하게 기다리고 있었던 것입니다. 탐지기가 작동합니다.
- 세 가지 다른 도구는 모두 괜찮아 보입니다.
search_kb호출 두 번을 연속으로 한 경우, 임계치가 2인 설정은 평범한 검색 워크플로우에서도 작동합니다.
각 탐지기는 독립적으로는 정확합니다. 하지만 이 모든 것은 오탐지(false positive)입니다. 그리고 개별 탐지기 테스트로는 이를 볼 수 없습니다. 왜냐하면 그들은 결코 탐지기들을 함께 실행하지 않기 때문입니다.
이 내용은 agentwatch에서 가져온 것으로, agentsec-ecosystem의 일부입니다. 이는 AI 에이전트를 위한 오픈 소스이며, 특정 하네스에 구애받지 않는(harness-agnostic) 보안 도구 모음입니다. 43개의 탐지기, 226가지 시나리오 모두 녹색(green)을 표시합니다. 제가 하나만 남길 수 있다면, 거의 작성되지 않았던 그 테스트를 남기고 싶습니다.
트레이스
수정은 의도적으로 지루한 트레이스에서 시작됩니다. 모든 값이 모든 임계값과 멀리 떨어져 있어, 무언가 발화(fires)하더라도 그것이 발화했어야 하는지에 대한 논쟁의 여지가 없습니다:
def _clean_trace():
return (
RunSummary(
...
파일 쓰기 도구도 없고, 네트워크 도구도 없고, 거부되거나 오류가 난 상태도 없는, 편안하게 긴 출력이 있는 독립적인 도구들. 여기에는 어떤 것도 규칙의 트리거에 가깝지 않습니다. 만약 무언가가 발화한다면, 그것은 잘못된 것이고 논쟁할 여지가 없습니다.
검사(The check)
async def run_clean():
summary, spans = _clean_trace()
fired = []
...
이것은 모든 43개 감지기에서 빈 fired 리스트를 반환합니다. 그리고 그렇지 않을 때, 이 리스트는 어떤 감지기가 어느 심각도로 어떤 설명과 함께 발화했는지 알려주어 추가 조사 없이 진단하고 수정할 수 있게 합니다.
팁: 모든 감지기를 아는 정상 입력(known-normal input)에 대해 함께 실행하여 0개의 발화를 주장하세요. 이것이 존재하는 가장 높은 레버리지의 감지기 테스트이며, 보통 빠져있는 바로 그것입니다. 15줄, 하나의 트레이스, 모든 감지기, 예상되는 발화 0개.
포착하는 것들
개별 감지기 테스트가 놓치는 다섯 가지 버그 클래스:
-
임계값(Thresholds)이 너무 낮게 설정됨. 임계값이 20이어야 할 상황에서 도구 호출(tool calls) 3회만으로 작동하는 감지기(detector)가 있습니다. 개별 감지기 테스트는 임계값을 독립적으로 확인하지만, 클린 트레이스(clean trace)는 정상 데이터에서도 오작동하는 임계값을 포착합니다.
-
오류 로직(Buggy logic).
status == "success"일 때 작동하는 감지기가 있습니다. 이는 논리 반전(if status != "error"대신if status == "error"여야 함) 때문에 발생했습니다. 개별 감지기 테스트는 해당 감지기의 "성공(success)" 사례를 테스트하지 못할 수 있습니다. -
잘못 파싱된 속성(Misparse attributes).
gen_ai.tool.name을 읽어와서, "resolve"라는 값이 제거 목록 패턴(denylist pattern)과 일치하기 때문에 작동하는 감지기가 있습니다. 개별 감지기 테스트는 이 제거 목록을 트리거하지 않을 수 있는 특정 도구 이름만 사용합니다. -
교차 감지기 연쇄 반응(Cross-detector cascades). 감지기 X의 출력이 감지기 Y를 작동시킵니다.
anomaly_cluster는 트레이스에서 몇 개의 고유한 이상 징후 유형이 발생했는지 확인합니다. 만약 3개의 감지기가 거짓으로 작동하면,anomaly_cluster도 과도하게 작동하여 잘못된 양성(false positive) 연쇄 반응을 일으킵니다. 개별 감지기 테스트는 한 번에 하나의 감지기만 실행하기 때문에 이를 포착할 수 없습니다. -
상태 누수(State leakage). 이전 시나리오의 상태가 클린 트레이스를 오염시킵니다.
EmbeddingDriftDetector의 기준 텍스트(baseline texts)는 이전에 설정된 시나리오에 의해 결정되어, 이를 기반으로 클린 트레이스와 비교하여 작동하게 됩니다. 개별 감지기 테스트는 모든 시나리오가 끝난 후 모든 감지기를 함께 실행하지 않기 때문에 이를 포착할 수 없습니다.
마지막 두 가지(4번과 5번)는 모든 감지기가 함께 실행될 때만 존재하며, 이는 프로덕션 환경에서 실제로 발생하는 상황이자 개별 감지기 테스트가 다루지 못하는 영역입니다.
선행 데이터 (The predecessor data)
agent-exec-trace v2 보고서는 이 테스트를 필수적인 것으로 만든 사례 연구였습니다:
| Detector | Before fixes | After fixes | Reduction |
|---|---|---|---|
premature_completion | 35,930 | 0 | 제거됨 (모든 오류 상태에서 작동) |
| ... |
45,575개의 잘못된 양성(false positives). 모든 이상 징후의 80%가 노이즈였습니다. 운영자는 56,869개의 알림을 보고 시스템에 대한 신뢰를 잃었습니다. 이 수정 사항들은 새로운 감지기를 추가한 것이 아니라, 너무 쉽게 작동하던 기존 감지기들을 제거하거나 강화하는 것이었습니다.
클린 트레이스 테스트(clean-trace test)를 사용했다면 premature_completion(어떤 오류 상태에서도 발생)과 argument_loop(인수가 누락되었을 때 발생하며, 이는 일부 도구 유형에서는 정상임)이 프로덕션에 도달하기 전에 포착했을 것입니다. 15줄의 코드가 45,575개의 오탐(false positives)보다 낫습니다.
premature_completion이 가장 명확한 예시입니다. 이 오류는 오류 상태를 가진 모든 트레이스에서 발생했습니다. 하지만 에러는 에이전트 실행 과정에서 정상적으로 발생하는 일입니다. 도구 호출에 실패하고, 에이전트가 재시도하며, 결국 실행은 성공합니다. 탐지기는 '오류 상태'를 보고 작동하여 10만 개의 트레이스 중 35,930개의 이상치를 생성했는데, 이들은 모두 오탐이었습니다. 해결책은 해당 탐지기를 완전히 제거하는 것이었습니다. 단 한 번의 일시적인 오류가 있는 클린 트레이스로 테스트했다면 이를 포착했을 것입니다: 트레이스는 정상인데, 탐지기가 작동하여 테스트가 실패합니다.
argument_loop도 비슷합니다. 이 오류는 도구 인수가 누락되었을 때 발생했는데, 인수 누락은 인수를 사용하지 않는 일부 도구 유형에서는 정상입니다. 5,768개의 오탐이 있었습니다. 해결책은 제거였습니다. 인수가 없는 도구를 호출하는 클린 트레이스로 테스트했다면 이를 포착했을 것입니다.
제가 얻은 교훈
오탐 노이즈(False-positive noise)가 운영자 신뢰를 무너뜨리는 1순위 원인입니다. 놓친 탐지보다 더 위험합니다. 사람들이 무시하는 경고 시스템은 아예 없는 것보다 나쁩니다.
개별 탐지기 테스트는 로직을 확인하고; 클린 트레이스 테스트는 통합(integration)을 확인합니다. 둘 다 필요하며, 어느 하나만으로는 충분하지 않습니다.
클린 트레이스는 모호함 없이 정상이어야 합니다. 엣지 케이스도, 경계선 값도 없어야 합니다. 모든 값은 모든 임계값에서 멀리 떨어져 있어야 합니다. 만약 탐지기가 작동한다면, 그곳에는 모호성이 없습니다. 그것은 오탐입니다.
테스트하는 탐지기 하나만 실행하지 말고, 모든 탐지기를 실행하세요. 프로덕션에서는 모두 함께 실행됩니다. 크로스-탐지기 상호작용(Cross-detector interactions)과 상태 누수(state leakage)는 모든 탐지기가 동일한 트레이스로 실행될 때만 나타납니다.
fired 목록은 무엇을 고쳐야 하는지 정확히 알려줍니다. 탐지기 이름, 심각도, 설명 등 추가 조사 없이 오탐을 진단하고 수정하기에 충분합니다.
이것은 agentwatch v0.1.0의 226가지 시나리오 하네스(harness)의 일부이며, 모든 43개 탐지기에서 작동했을 때 0개의 발생으로 통과합니다.
모든 이상 징후 시스템(anomaly system)에 던져야 할 질문은 이것입니다. 깨끗한 입력 1,000개에 대해 몇 개의 오탐지(false positives)를 발생시키나요? 만약 그 답을 알 수 없다면, 클린 트레이스 테스트(clean-trace test)는 15분 거리에 있습니다. 그리고 이 테스트는 시스템이 신뢰를 얻는지 아니면 잃게 하는지를 알아내는 가장 저렴한 방법입니다.
선행 모델의 80% 노이즈율은 염두에 두어야 할 수치입니다. 경고(alert) 5개 중 4개가 쓸모없다면, 운영자는 더 이상 확인하지 않습니다. 그리고 아무도 살펴보지 않는 탐지기는 존재하지 않는 것과 같습니다.
참고 자료 (References)
- 릴리스: agentwatch v0.1.0 on GitHub · on PyPI
- 스위트: agentsec-ecosystem — 전체 에이전트 보안 도구 세트
- 현장 테스트 보고서 (Field test report) (클린 실행 확인, FT-15)
- 하네스:
scenario_validation.py - 선행 모델 노이즈 데이터:
agent-exec-trace현장 테스트 보고서 v2
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기