빠른 에이전트 품질 게이트: LLM Judge 대신 결정론적 규칙 사용하기
요약
LLM Judge의 모호함을 극복하기 위해 결정론적 규칙을 활용한 에이전트 품질 게이트 구축 방법을 제안합니다. 재사용 가능한 규칙을 통해 에이전트의 실행 안정성, 비용, 보안 등을 정밀하게 검증하는 엔지니어링 접근법을 다룹니다.
핵심 포인트
- LLM Judge 대신 결정론적 규칙을 사용하여 명확한 엔지니어링 계약 검증
- 좋은 품질 게이트의 5가지 조건: 결정론적, 좁은 범위, 실행 가능성, 버전 관리, 이식성
- 정규화된 메타데이터와 구조화된 증거를 통한 안정적인 트레이스 평가
- 필수 단계 및 인과적 순서 검증을 통한 에이전트 회귀 방지
결정론적(Deterministic) 에이전트 테스트는 테스트 파일 곳곳에 흩어져 있는 일회성 단언(assertion)이 아니라, 재사용 가능한 품질 게이트(quality gates)로 표현될 때 훨씬 더 가치 있어집니다.
게이트는 좁은 범위의 엔지니어링 질문에 답합니다: 데이터를 쓰기 전에 실행이 검증되었는가? 재시도(retries)가 정책 범위 내에 있었는가? 토큰 사용량이 기록되었고 예산 내에 있는가? 시작된 모든 스팬(span)이 완료되었는가? 결과는 안정적이고 빨라야 하며, 개발자가 무엇을 수정해야 할지 알 수 있을 정도로 충분히 구체적이어야 합니다.
LLM judge는 여전히 의미론적 평가(semantic evaluation)에서 역할을 수행합니다. 하지만 구조적인 에이전트 회귀(regression)와 프로덕션 사이를 가로막는 유일한 수단이 되어서는 안 됩니다.
좋은 게이트의 조건은 무엇인가?
실용적인 품질 게이트는 다섯 가지 속성을 가집니다:
- 결정론적 (Deterministic): 동일한 정규화된 트레이스(trace)는 동일한 결과를 생성합니다.
- 좁은 범위 (Narrow): 하나의 명시적인 계약(contract)을 평가합니다.
- 실행 가능성 (Actionable): 실패 시 규칙, 증거 및 기대 상태를 식별합니다.
- 버전 관리 (Versioned): 규칙 및 트레이스 스키마(trace-schema) 변경 사항을 검토할 수 있습니다.
- 이식성 (Portable): 로컬, CI, 그리고 재생된 픽스처(fixtures)를 대상으로 실행할 수 있습니다.
"답변 품질이 6/10입니다"는 신호(signal)이긴 하지만, 좁은 범위의 엔지니어링 계약은 아닙니다. 반면 "쓰기 도구(write tool)가 권한 부여가 완료되기 전에 실행되었습니다"는 계약입니다.
안정적인 트레이스 계약부터 시작하기
게이트 엔진은 프레임워크 특정 콜백 객체(callback objects)보다는 정규화된 메타데이터를 소비해야 합니다.
type StepKind = 'run' | 'model' | 'tool' | 'retrieval' | 'policy' | 'fallback';
type TraceStep = {
...
평가하기 전에 변동성이 큰 필드들을 정규화하십시오. 무작위 ID는 부모-자식 체크를 위해 남겨둘 수 있지만, 타임스탬프(timestamps), 임시 경로(temporary paths), 요청 ID(request IDs), 그리고 원시 페이로드(raw payloads)는 결정론적 결과에 영향을 주어서는 안 됩니다.
규칙 인터페이스 정의하기
규칙은 단언 에러(assertion errors)를 직접 던지는 대신 구조화된 증거(structured evidence)를 반환해야 합니다.
type Severity = 'error' | 'warning';
type RuleResult = {
...
규칙에 버전을 매기면 베이스라인(baseline) 변경 사항이 명시적으로 드러납니다. 만약 max_model_calls의 의미가 변경된다면, 검토자는 에이전트가 퇴보했다고 가정하는 대신 정책이 변경되었음을 확인할 수 있습니다.
Gate 1: 필수 단계 (Required Steps)
function requireSteps(required: string[]): TraceRule {
return {
id: 'required_steps',
...
필수 단계 (Required-step) 규칙은 검증 (validation), 검색 (retrieval), 정책 확인 (policy checks), 그리고 필수 정리 작업 (mandatory cleanup)에 효과적입니다. 모든 구현 세부 사항을 요구하지 마십시오. 게이트 (gate)는 사용자, 비용, 안전성 또는 정확성에 중요한 동작을 보호해야 합니다.
Gate 2: 인과적 순서 (Causal Order)
순차적 의존성 (sequential dependencies)의 경우, 기록기 (recorder)의 단조 시퀀스 (monotonic sequence)를 비교하십시오. 병렬 작업 (concurrent work)의 경우, 완료 순서 대신 부모 관계 (parentage)를 확인하십시오.
function requireOrder(before: string, after: string): TraceRule {
return {
id: `order:${before}:${after}`,
...
'쓰기 전 권한 부여 (Authorization-before-write)' 및 '생성 전 검색 (retrieval-before-generation)'은 좋은 인과적 게이트 (causal gates)입니다. 모든 트레이스 단계 (trace step)에 순서를 매기는 것은 취약한 테스트를 만들고 무해한 병렬화 (parallelization)를 차단합니다.
Gate 3: 블록 이후의 금지된 작업 (Forbidden Work After a Block)
const noExternalWorkAfterBlock: TraceRule = {
id: 'no_external_work_after_block',
version: 1,
...
이는 최종 상태 (final status)만 확인하는 것보다 더 강력합니다. 오케스트레이션 (orchestration)이 잘못된 방식으로 계속된다면, 실행 (run)이 blocked를 보고하더라도 모델 호출이나 도구 호출 (tool call)이 여전히 유출될 수 있습니다.
Gate 4: 시도 및 루프 (Attempts and Loops)
재시도 (Retries)에는 명시적인 attempt 필드가 포함되어야 합니다. 병렬 이벤트 (parallel events)가 서로 섞일 수 있으므로, 연속된 이름을 찾는 대신 작업 (operation) 및 부모 스팬 (parent span)별로 시도 횟수를 계산하십시오.
function maxAttempts(stepName: string, limit: number): TraceRule {
return {
id: `max_attempts:${stepName}`,
...
더 넓은 범위의 루프 탐지 (loop detection)를 위해서는 planner_state + selected_tool + outcome과 같은 안정적인 상태 시그니처 (stable state signature)를 정의하십시오. 동일한 시그니처가 정책을 초과하여 반복될 때 실패 처리합니다. 단계 이름 패턴 매칭 (Step-name pattern matching)만으로는 정당한 반복 워크플로 (iterative workflows)에서 오탐 (false positives)이 발생하는 경우가 많습니다.
Gate 5: 토큰 예산 (Token Budgets)
누락된 사용량 데이터가 조용히 0이 되어서는 안 됩니다. 측정값이 없는 비용 게이트 (cost gate)는 통과할 수 없습니다.
function maxTotalTokens(limit: number): TraceRule {
return {
id: 'max_total_tokens',
...
제공업체에 따라 캐시된 입력(cached input)은 자체적인 필드와 비용 정책이 필요할 수 있습니다. 게이트(gate)가 손실이 발생하는 totalTokens 값에 의존하지 않도록, 정규화된 트레이스(normalized trace)에 원시 사용량 차원(raw usage dimensions)을 유지하십시오.
게이트 6: 트레이스 무결성 (Trace Integrity)
에이전트의 동작을 평가하기 전에, 텔레메트리(telemetry) 자체가 신뢰할 수 있는지 확인하십시오:
- 단계 ID(Step IDs)가 고유해야 합니다.
- 루트가 아닌 모든 부모(non-root parent)가 동일한 트레이스 내에 존재해야 합니다.
- 시퀀스 번호(Sequence numbers)가 고유하고 단조 증가(monotonic)해야 합니다.
- 정확히 하나의 루트 단계(root step)가 존재해야 합니다.
- 종료 상태(Terminal status)가 존재해야 합니다.
- 수치형 메트릭(Numeric metrics)은 유한하고 음수가 아니어야 합니다.
- 금지된 페이로드 키(forbidden payload keys)가 나타나지 않아야 합니다.
잘못된 형식의 트레이스는 오해의 소지가 있는 정책 결과를 생성하는 대신, 계측(instrumentation) 오류로 실패 처리되어야 합니다.
실행 규칙 및 단일 보고서 생성
type GateReport = {
fixture: string;
passed: boolean;
...
자동화를 위해 보고서를 JSON 형식으로 작성하고, 풀 리퀘스트(pull-request) 로그 또는 CI 어노테이션(annotations)을 위해 짧은 마크다운(Markdown) 요약본을 작성하십시오. 규칙 버전, 증거, 피스처(fixture) 이름, 그리고 정규화된 트레이스 아티팩트(normalized trace artifact)로의 링크 또는 경로를 포함하십시오.
스냅샷 취약성 없이 베이스라인 비교하기
정확한 트레이스 스냅샷(trace snapshots)은 유지 관리하기 어렵습니다. 지속 가능한 메트릭(durable metrics)의 요약을 선호하십시오:
type TraceBaseline = {
fixture: string;
requiredTools: string[];
...
절대적 한도와 상대적 한도를 모두 사용하십시오. 100에서 150으로 토큰이 50% 증가하는 것은 무해할 수 있지만, 20,000에서 30,000으로 50% 증가하는 것은 비용이 많이 들 수 있습니다. 반대로, 500 토큰의 절대적 증가는 작은 워크플로(workflow)에서는 유의미하지만, 매우 큰 워크플로에서는 노이즈에 불과할 수 있습니다.
동작 변경과 동일한 풀 리퀘스트 내에서 의도적인 베이스라인 업데이트를 요구하십시오. 리뷰에서는 왜 새로운 예산(budget)이나 도구 경로(tool path)가 허용 가능한지를 설명해야 합니다.
합성(Synthetic) 및 라이브(Live) 임계값 분리
스크립트로 작성된 오케스트레이션(orchestration) 테스트는 가상 시계(fake clocks)와 정확한 예산을 사용해야 합니다. 라이브 모델(Live-model) 및 네트워크 테스트는 자연적인 변동성(variance)이 있으므로 더 넓은 통계적 임계값(statistical thresholds)이 필요합니다.
두 가지 모두에 동일한 임계값(threshold)을 사용하지 마십시오. 갑자기 10초가 소요되는 로컬 픽스처(local fixture)는 버그를 나타낼 가능성이 높습니다. 반면, 좁은 지연 시간(latency) 임계값을 한 번 초과하는 실제 프로바이더(provider) 호출은 일시적인 인프라 상태를 반영하는 것일 수 있습니다.
라이브 실행(live runs)의 경우, 충분한 샘플에 대해 중앙값(median) 및 꼬리 지연 시간(tail latency)과 같은 이동 분포(rolling distributions)를 비교하십시오. 프로젝트가 이를 안정적으로 운영할 역량이 없다면, 이러한 추세 확인(trend checks)은 가장 빠른 풀 리퀘스트(pull-request) 게이트 외부에서 유지하십시오.
CI 계약 (A CI Contract)
프로바이더 중립적인 CI 작업은 다음과 같은 순서를 따를 수 있습니다:
1. 스크립트된 에이전트 픽스처(agent fixtures) 실행
2. 모든 정규화된 트레이스(normalized trace) 검증
3. 구성된 규칙 세트(rule set) 평가
...
설정 세부 정보를 문서나 게이트 엔진에 하드코딩(hard-coding)하는 대신, 프로젝트의 기존 런타임 버전 파일과 패키지 매니저 락파일(lockfile)을 사용하십시오. 실제 모델 자격 증명(credentials)은 결정론적(deterministic) 작업에서 접근할 수 없어야 합니다.
의미론적 평가(semantic evaluations)는 명시적인 권한 부여, 비용 제어 및 더 느린 주기를 가진 별도의 작업에서 실행하십시오.
게이트 인플레이션 방지 (Avoid Gate Inflation)
너무 많은 취약한 게이트(brittle gates)는 개발자들이 시스템을 무시하게 만듭니다. 의미 있는 계약을 보호하고 담당자가 있는 경우에만 규칙을 추가하십시오.
정확성, 안전성 또는 엄격한 예산 위반에는 error를 사용하십시오. 검토가 필요하지만 즉시 차단해서는 안 되는 추세에는 warning을 사용하십시오. 경고의 기간(warning age)을 추적하십시오. 실행 가능한 상태로 전환되지 않는 경고는 제거하거나 실제 정책으로 전환해야 합니다.
수용된 동작에 대해 게이트가 반복적으로 실패하는 경우, 규칙이나 기준선(baseline)을 수정하십시오. 영구적으로 빨간색(실패) 상태인 CI를 정상적인 것으로 간주(normalize)하지 마십시오.
마지막 생각 (Final Thought)
에이전트 품질 게이트는 실행을 엔지니어링 산출물(engineering artifact)로 취급할 때 가장 효과적입니다. 정규화된 트레이스, 버전 관리된 규칙 세트, 그리고 증거가 풍부한 보고서는 누락된 검증, 승인되지 않은 작업, 재시도 폭풍(retry storms), 비용 회귀(cost regressions) 및 손상된 계측(instrumentation)을 몇 초 만에 잡아낼 수 있습니다.
트레이스(trace)가 증명할 수 있는 계약(contracts)에는 결정론적 게이트(deterministic gates)를 사용하세요. 언어 및 추론 품질과 같이 확률적 평가(probabilistic evaluation)가 실제로 필요한 경우에는 의미론적 판사(semantic judges)를 유지하십시오. 이러한 분리는 CI(지속적 통합)를 더 빠르게 만들고, 모든 실패 사례를 더 신뢰하기 쉽게 만듭니다.
다음 기사에서는 규칙에서 통합(integrations)으로 넘어갑니다. 어댑터(adapters)가 핵심(core)을 특정 SDK에 결합하지 않고 어떻게 서로 다른 TypeScript 에이전트 프레임워크를 하나의 트레이스 모델로 변환하는지에 대해 다룹니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기