
프로덕션용 LLM Judge 구축하기: 엔터프라이즈 감사 엔진(Enterprise Audit Engine)에서의 교훈
요약
엔터프라이즈 감사 엔진 구축 과정에서 에이전트의 오류를 방지하기 위한 LLM Judge 도입 사례를 다룹니다. 메인 에이전트와 독립적인 컨텍스트를 가진 별도의 LLM 호출을 통해 추론의 정확성을 검증하는 설계 원칙을 설명합니다.
핵심 포인트
- LLM Judge는 에이전트의 응답이 사용자에게 전달되기 전 독립적으로 검증함
- 에이전트와 동일한 컨텍스트를 공유하지 않는 것이 독립적 검증의 핵심
- 정책 파일의 독립적 재독해를 통해 에이전트의 편향과 오류를 차단
- 불일치 발생 시 출력을 차단하고 로그를 남겨 시스템 신뢰성 확보
엔터프라이즈 감사 엔진(Enterprise Audit Engine)을 구축할 때, LLM Judge는 제가 가장 마지막에 추가하려고 계획했던 것이었습니다. 그것은 과잉 엔지니어링(Over-engineering)처럼 느껴졌습니다. 메인 에이전트(Agent)는 이미 실시간 장치 상태에 접근할 수 있는 MCP 도구 액세스 권한과, 추론의 근거가 될 정책 파일, 그리고 경로를 이탈하지 않게 유지해 주는 LangGraph 상태 머신(State Machine)을 갖추고 있었습니다. 그것만으로도 충분하다고 느꼈습니다.
하지만 테스트 도중, 에이전트가 규정에 맞지 않는 펌웨어 버전을 정확히 찾아냈음에도 불구하고 — 완전히 다른 장치 카테고리에 속하는 조치 사항을 권장하는 일이 발생했습니다. 추론 과정 자체는 내부적으로 일관성이 있었습니다. 단지 잘못된 규칙을 사용했을 뿐이었습니다. 파이프라인의 그 어떤 것도 이를 잡아내지 못했습니다. 출력값은 깔끔해 보였습니다. 두 번째 확인 절차가 없었다면, 그 결과는 그대로 사용자에게 전달되었을 것입니다.
그때 저는 Judge를 추가했습니다. 이것이 어떻게 작동하는지, 그리고 이를 구축하며 무엇을 배웠는지 소개합니다.
Judge가 실제로 하는 일
Judge는 에이전트의 응답이 사용자에게 도달하기 전, 모든 응답 직후에 실행되는 별도의 LLM 호출(Call)입니다. Judge는 두 가지 입력을 받습니다:
• 에이전트의 출력 — 제안된 준수 여부 판결 및 제안된 조치 사항
• 정책 파일의 새로운 읽기 — 에이전트의 컨텍스트(Context)에서 가져오는 것이 아니라, 독립적으로 읽어 들임
Judge의 역할은 이 두 가지를 비교하여 에이전트의 추론이 실제 정책에 부합하는지 결정하는 것입니다. 감사를 다시 실행하는 것이 아닙니다. 에이전트가 생성한 답변이 에이전트가 적용했어야 하는 규칙과 일치하는지를 확인하는 것입니다.
추론이 확인되면 출력은 다음 단계로 넘어갑니다. 만약 일치하지 않는 부분이 있다면, Judge는 이를 차단하고, 불일치 사항을 로그로 남기며, 판결 대신 에러를 반환합니다.

지능보다 독립성이 중요한 이유
Judge를 실제로 유용하게 만드는 설계 결정은 간단합니다. 바로 메인 에이전트 (Main Agent)와 컨텍스트 (Context)를 공유하지 않는 것입니다.
대부분의 다단계 에이전트 파이프라인 (Multi-step agent pipelines)은 실행 과정에서 검색된 청크 (Retrieved chunks), 중간 추론 (Intermediate reasoning), 도구 출력 (Tool outputs) 등의 컨텍스트를 축적합니다. 에이전트가 최종 답변을 생성할 때쯤이면, 이미 여러 단계에 걸쳐 특정 컨텍스트 윈도우 (Context window) 안에서 추론을 진행한 상태가 됩니다. 이러한 컨텍스트는 에이전트가 무엇을 타당하다고 판단할지를 결정짓습니다.
만약 Judge가 동일한 컨텍스트 윈도우를 읽는다면, 이는 진정으로 독립적인 검증을 수행하는 것이 아닙니다. 그저 약간 다른 프롬프트 (Prompt)를 통해 동일한 정보를 다시 읽는 것에 불과합니다. 에이전트가 축적한 편향 (Bias)이나 오류가 있다면, Judge 또한 이를 그대로 물려받게 됩니다. 그것은 검증이 아니라 단순한 승인 (Rubber stamp)일 뿐입니다.
해결책은 명확합니다. Judge는 정책 파일 (Policy file)을 직접 읽습니다. 캐시 (Cache)에서 읽는 것도 아니고, 에이전트가 검색한 결과에서 읽는 것도 아닌, 파일 자체에서 직접 읽습니다. 매번 말이죠. 이것이 바로 메인 에이전트가 놓친 카테고리 불일치 (Category mismatch)를 잡아낼 수 있는 능력의 원천입니다. 에이전트는 추론 루프 (Reasoning loop) 동안 충분한 컨텍스트를 축적했기 때문에 잘못된 규칙이 타당해 보였던 것이지만, 정책을 새로 읽은 Judge는 즉시 그 불일치를 발견할 수 있었습니다.
LangGraph 상태 머신(state machine) 내에서의 위치
Judge는 그래프를 감싸는 래퍼(wrapper)가 아니라, LangGraph 그래프 내의 하나의 노드(node)입니다. 이 차이는 시스템을 통해 상태(state)가 흐르는 방식에 있어 매우 중요합니다.
check_compliance가 실행되어 판결(verdict)을 생성한 후, 그래프는 다른 어떤 작업을 수행하기 전에 llm_judge로 경로를 지정합니다. Judge 노드는 정책 파일(policy file)을 읽고, 이를 제안된 판결과 비교한 뒤, 그래프 상태(graph state)에 통과/실패(pass/fail) 플래그를 설정합니다.
그 지점부터 그래프는 다음과 같이 분기됩니다:
• FAIL — 출력이 차단되고, 오류는 AgentOps에 기록되며, 사용자에게 반환되는 응답에는 판결을 검증할 수 없음을 설명합니다.
• PASS — 그래프가 suggest_remediation 단계로 이동하며, 여기서 에이전트(agent)는 시정 조치(corrective action)를 제안합니다.
suggest_remediation 이후에는 엄격한 게이트(hard gate)가 존재합니다. 그래프는 execute_remediation으로 자동으로 진행되지 않습니다. 유일한 진행 경로는 사용자가 Streamlit UI에서 'Approve(승인)'를 클릭하는 것입니다. 해당 승인 이벤트가 최종 노드를 트리거하며, 이 노드는 전체 그래프에서 데이터베이스에 쓰기 작업(write operation)이 발생하는 유일한 지점입니다.
Judge와 HITL(Human-in-the-loop) 게이트는 서로 별개의 관심사입니다. Judge는 정확성(correctness)에 관한 것입니다. 즉, 에이전트가 올바른 규칙을 적용했는가를 다룹니다. 게이트는 권한 부여(authorization)에 관한 것입니다. 즉, 사람이 해당 작업에 승인했는가를 다룹니다. 두 가지 모두 필수적이며, 어느 하나가 다른 하나를 대체할 수 없습니다.

기록되는 내용 (What it logs)
모든 Judge(판사)의 결정은 AgentOps에 구조화된 이벤트 (structured event)로 기록됩니다: 에이전트(agent)가 제안한 판결, Judge가 대조하여 확인한 정책 섹션, 최종 통과/실패 여부, 그리고 실패했을 경우 발견된 구체적인 불일치 사항이 포함됩니다.
이러한 로깅 (logging)은 제가 예상했던 것보다 훨씬 더 유용했습니다. 개발 단계에서는 디버깅 (debugging) 속도를 훨씬 빠르게 만들어 주었습니다. Judge가 정확히 무엇을 확인하고 있었는지, 그리고 왜 그런 결정을 내렸는지 정확히 볼 수 있었기 때문입니다. 프로덕션용 엔터프라이즈 시스템의 경우, 이는 시스템이 다루는 모든 컴플라이언스 (compliance) 결정에 대해 완전한 감사 추적 (audit trail)을 생성합니다.
나중에 누군가가 왜 특정 장치가 플래그(flagged) 처리되었는지 또는 승인(cleared)되었는지 묻는다면, 완전한 기록이 남아 있습니다: 에이전트가 무엇을 발견했는지, Judge가 무엇을 검증했는지, 그리고 누가 조치 (remediation)를 승인했는지에 대한 기록입니다. 이러한 종류의 추적성 (traceability)은 시스템이 프로덕션에 배포된 이후에는 사후에 적용하기가 매우 어렵습니다.
내가 다르게 했을 점
가장 크게 바꾸고 싶은 점은 Judge (판사)의 출력값에 이진적인 Pass/Fail (합격/불합격) 대신 신뢰도 임계값 (confidence threshold)을 추가하는 것입니다.
현재 Judge는 통과시키거나 차단하는 방식입니다. 이는 명확한 사례에는 효과적이지만, 중간 범주—추론은 대부분 맞지만 세부 사항 하나가 불확실한 출력—가 존재하며, 이런 경우에는 무조건적인 차단이 적절한 대응이 아닐 수 있습니다. 신뢰도 점수 (confidence score)를 도입하면 시스템이 이러한 사례를 단순히 조용히 차단하는 대신, 플래그 (flag)를 달아 인간 검토자에게 노출할 수 있습니다.
또 다른 점은 지연 시간 (latency)입니다. 모든 응답에 두 번째 LLM 호출을 추가하는 것은 시간을 소모하며, 전체 감사 (audit)가 필요하지 않은 단순한 쿼리의 경우 이러한 오버헤드 (overhead)는 그만한 가치가 없습니다. FastAPI 게이트웨이의 의도 분류기 (intent classifier)가 이미 단순 쿼리를 NIM 워커 (worker)로 라우팅하고 전체 에이전트 (agent)를 건너뛰고 있습니다. 이 로직을 확장하여 위험도가 낮은 쿼리에 대해서는 Judge도 건너뛰도록 만든다면 도움이 될 것입니다.
요약
Judge가 유용한 이유는 메인 에이전트보다 똑똑해서가 아니라, 에이전트로부터 독립적이기 때문입니다. 에이전트가 추론 루프 (reasoning loop) 동안 축적한 컨텍스트 (context)를 상속받지 않고 정책을 새롭게 읽어들이는 것이, 에이전트가 자신의 컨텍스트 윈도우 (context window) 내부에서는 볼 수 없는 오류를 잡아낼 수 있는 능력을 부여합니다.
이는 지연 시간과 비용을 증가시킵니다. 하지만 프로덕션 인프라에서 컴플라이언스 (compliance, 준수) 결정을 내리는 시스템을 구축한다면, 이는 합리적인 트레이드오프 (trade-off)입니다.
코드는 GitHub에 있습니다. Judge 구현체는 거버넌스 레이어 (governance layer)에 위치합니다. 댓글을 통해 프롬프트 디자인 (prompt design)이나 AgentOps 통합에 대해 논의하는 것을 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기