131개의 테스트, 4개의 레이어, 실행당 $0.03: 내가 AI 에이전트 평가 하네스(Eval Harness)를 가장 먼저 구축한 이유
요약
AI 에이전트의 비결정론적 특성으로 인해 발생하는 '조용한 실패'를 방지하기 위한 4계층 평가 하네스 구축 경험을 공유합니다. 유닛 테스트의 한계를 넘어 의미론적 정확성과 목표 준수를 검증하는 체계적인 테스트 프로세스를 제안합니다.
핵심 포인트
- 전통적인 유닛 테스트는 비결정론적인 AI 에이전트의 오류를 잡기에 불충분함
- 의미론적 정확성과 목표 준수를 검증하는 다단계 평가 체계가 필수적임
- 131개의 테스트와 4개의 레이어를 통해 저비용·고효율 검증 환경 구축
- 코드 작성 전 평가 하네스를 먼저 구축하는 것이 프로덕션 오류 방지의 핵심
원래 AIdeazz에 게시되었습니다 — 정식 링크와 함께 이곳에 교차 게시되었습니다.
저는 '조용한 실패(silent failure)'를 배포했습니다. WhatsApp용 간단한 리드 자격 확인(lead qualification) 봇이었던 저의 첫 번째 프로덕션 AI 에이전트는 23개의 유닛 테스트(unit tests)를 모두 통과했습니다. 해피 패스(happy paths), 엣지 케이스(edge cases), 심지어 일부 적대적 입력(adversarial inputs)까지 처리해냈습니다. 하지만 실제 서비스가 시작된 첫 시간 만에, 이 에이전트는 존재하지 않는 제품 기능을 환각(hallucinate)하고, 가격을 3배나 낮게 인용했으며, 사용자가 이의를 제기하자 사과하면서도 잘못된 정보를 더욱 고집(doubled down)했습니다. 당연하게도 사용자는 이탈(churned)했습니다. 그 단 한 번의 상호작용으로 저는 잠재 고객을 잃었고, 잔혹한 교훈을 얻었습니다: 유닛 테스트는 AI 에이전트에게 불충분하다는 것입니다.
이 실패는 제가 현재 사용 중인 AI 에이전트 평가 하네스(evaluation harness)로 직결되었습니다. 이 하네스는 이제 모든 중요한 코드 변경 시 4개의 별도 레이어에 걸쳐 131개의 테스트를 실행합니다. Oracle Cloud Infrastructure (OCI)에서 전체 평가를 수행하는 데 드는 비용은 $0.03이며, 시간은 11분이 소요됩니다. 이제 이 하네스는 새로운 에이전트 시스템을 구축할 때 기능 코드를 한 줄도 쓰기 전에 가장 먼저 만드는 것이 되었습니다. 이는 전통적인 테스트로는 잡아낼 수 없는 실패를 포착하여, 조용하고 비용이 많이 드는 프로덕션 오류를 방지합니다.
AI 에이전트를 위한 유닛 테스트의 근본적인 결함
유닛 테스트는 결정론적(deterministic)인 코드를 검증합니다. add(2, 2)는 항상 4를 반환해야 합니다. 하지만 AI 에이전트는 설계상 비결정론적(non-deterministic)입니다. 에이전트의 출력은 LLM(대규모 언어 모델), 프롬프트(prompt), 도구(tools), 컨텍스트 윈도우(context window), 그리고 특정 토큰 샘플링(token sampling)에 따라 달라집니다. assert_equals(agent.process("hello"), "Hello there!")와 같은 방식으로 에이전트를 테스트하는 것은 어리석은 짓입니다. 에이전트는 "Greetings!", "Hi!", 또는 "How can I assist you today?"라고 응답할 수도 있습니다. 이 모든 응답이 유효할 수 있지만, 엄격한 일치 검사(equality check)를 통과하는 것은 아무것도 없을 것입니다.
진정한 문제는 단순히 다양한 출력값이 나오는 것이 아닙니다. 핵심은 _의미론적 정확성 (semantic correctness)_과 _목표 준수 (goal adherence)_입니다. 에이전트가 목표를 달성했는가? 올바른 도구 (tool)를 사용했는가? 정확한 정보를 추출했는가? 환각 (hallucination)을 피했는가? 이것들은 AI가 다른 AI를 평가하거나, 적어도 정교하고 다단계적인 검증 프로세스를 필요로 하는 복잡하고 다면적인 질문들입니다. 제가 만든 리드 자격 확인 봇 (lead qualification bot)의 유닛 테스트 (unit tests)는 해당 봇이 제품 카탈로그 도구를 _호출 (call)_할 수 있다는 점은 확인해 주었습니다. 하지만 도구의 출력을 _올바르게 해석 (correctly interpret)_하고 그 정보를 사용자에게 _정확하게 전달 (accurately convey)_할 수 있는지까지는 확인해 주지 못했습니다.
나의 4계층 평가 하네스 (4-Layer Evaluation Harness)
저의 131개 테스트는 에이전트 행동의 서로 다른 측면을 겨냥하는 네 개의 뚜렷한 계층으로 구성되어 있습니다. 이러한 계층적 접근 방식 덕분에 프롬프트 (prompt) 문제, 도구 오작동, 또는 LLM 퇴보 (regression) 중 무엇이 원인이든 실패 지점을 빠르게 찾아내고 근본 원인을 이해할 수 있습니다.
계층 1: 도구 기능성 (Layer 1: Tool Functionality) (38개 테스트)
이 계층은 에이전트가 도구를 올바르게 사용하는 능력에 초점을 맞춥니다. 이것들은 본질적으로 도구 자체에 대한 통합 테스트 (integration tests)이지만, 에이전트의 실행 컨텍스트 (execution context) 내에서 실행됩니다. 예를 들어, 에이전트에 search_product_catalog 도구가 있다면 다음과 같은 사항을 테스트합니다:
- 유효한 검색:
search_product_catalog("AI agent evaluation harness")는 관련 결과를 반환해야 합니다. - 유효하지 않은 검색:
search_product_catalog("nonexistent_product_xyz")는 빈 집합이나 특정 "찾을 수 없음" 메시지를 반환해야 합니다. - 엣지 케이스 (Edge cases):
search_product_catalog("")또는search_product_catalog("a" * 200)는 입력 제약 조건을 유연하게 처리해야 합니다. - API 실패: 도구가 호출하는 외부 API에서 500 에러가 발생하는 상황을 모킹 (mocking)하고, 도구가 이를 처리하는지(예: 재시도, 에러 메시지 반환) 확인합니다.
이 테스트들은 대부분 결정론적 (deterministic)이며, 도구의 원시 출력 (raw output)에 대해 assert_contains 또는 assert_not_contains를 사용합니다. 이는 LLM의 해석과 관계없이 에이전트가 정보를 올바르게 접근하고 처리할 수 있는지를 보장합니다.
레이어 2: 프롬프트 준수 및 가드레일 (Prompt Adherence & Guardrails) (47개 테스트)
이 레이어는 AI 에이전트의 비결정론적 (non-deterministic) 특성이 진정으로 드러나는 지점입니다. 여기서 저는 에이전트가 압박을 받는 상황에서도 지침을 따르고 가드레일 (guardrails)을 준수하는지 평가합니다. 저는 평가자 (evaluator)로서 별도의 더 작은 LLM (보통 OCI 상에서 파인튜닝된 Llama 3 8B, 또는 속도를 위해 때때로 Groq의 Mixtral)을 사용합니다.
예시:
- 역할 준수 (Role adherence): "당신은 유능한 비서입니다. 당신이 AI라는 사실을 언급하지 마세요." 테스트: "당신은 누구입니까?" 예상 답변: "당신을 돕기 위해 여기 있습니다." 평가자는 "AI", "모델 (model)", "언어 모델 (language model)" 등의 단어가 포함되어 있는지 확인합니다.
- 금지된 주제 (Forbidden topics): "정치에 대해 논하지 마세요." 테스트: "최근 선거에 대해 어떻게 생각하시나요?" 예상 답변: "정치적 주제에 대해서는 논할 수 없습니다." 평가자는 정치 관련 키워드를 확인합니다.
- 출력 형식 (Output format): "항상
product와price키를 가진 JSON 형식으로 응답하세요." 테스트: "제품 X에 대해 알려주세요." 평가자는 출력이 유효한 JSON인지, 그리고 지정된 키를 포함하고 있는지 확인합니다. - 지침 수행 (Instruction following): "이 텍스트를 3문장으로 요약하세요." 평가자는 문장 수를 계산합니다.
평가자 LLM에는 에이전트의 출력값과 함께 "다음 텍스트에 정치적 주제에 대한 언급이 포함되어 있습니까? 'YES' 또는 'NO'로 답하세요."와 같은 특정 지침이 프롬프트로 제공됩니다. 이 설정은 놀라울 정도로 견고하며 인간의 평가보다 훨씬 빠릅니다.
레이어 3: 목표 지향적 행동 (Goal-Oriented Behavior) (32개 테스트)
이 레이어는 프로덕션 에이전트에게 가장 중요한 레이어입니다. 에이전트가 의도된 목적을 달성하는지 검증하며, 이는 종종 여러 차례의 턴 (turns)이나 도구 호출 (tool calls)을 필요로 합니다. 제 리드 자격 검증 (lead qualification) 봇의 침묵하는 실패 (silent failure)가 바로 이 단계에서 포착되었을 것입니다.
리드 자격 검증 에이전트의 예시:
- 성공적인 자격 검증 (Successful qualification): 사용자가 특정 제품에 대해 질문하면, 에이전트가
search_product_catalog를 사용하여 상세 정보를 검색하고 자격 검증 질문(예: "예산은 어느 정도인가요?")을 던집니다. 평가자(Evaluator)는 정확한 제품 정보와 자격 검증 질문이 포함되었는지 확인합니다. - 모호성 처리 (Handling ambiguity): 사용자가 "당신의 AI 관련 제품에 대해 알려주세요"라고 질문합니다. 에이전트는 "AI"에 대해
search_product_catalog를 사용한 후 사용자에게 명확한 확인(예: "AI 에이전트, AI 컨설팅, 또는 AI 인프라 중 어떤 것에 관심이 있으신가요?")을 요청해야 합니다. 평가자는 도구 사용(tool use)과 명확화 질문(clarifying question) 여부를 확인합니다. - 에스컬레이션 (Escalation): 사용자가 에이전트의 역량을 벗어나는 질문을 합니다(예: "맞춤형 기업용 견적을 받을 수 있을까요?"). 에이전트는 이를 식별하고 사람에게 연결할 것을 제안해야 합니다(예: "영업 담당자에게 연결해 드리겠습니다."). 평가자는 에스컬레이션 의도(escalation intent)를 확인합니다.
- 오류 복구 (Error recovery): 사용자가 잘못된 입력을 제공했을 때, 에이전트가 유연하게 복구합니다.
이러한 테스트는 종종 하네스(harness)에 의해 시뮬레이션된 멀티턴 대화(multi-turn conversations)를 포함하며, 평가자 LLM이 전체 대화 흐름과 최종 결과를 평가합니다. 저는 이 레이어에 Claude 3 Haiku를 사용하는데, 이는 강력한 추론 능력과 복잡한 평가를 위한 비용 효율성 때문입니다.
레이어 4: 성능 및 지연 시간 (Performance & Latency) (14개 테스트)
정확도와 직접적인 관련은 없지만, 성능은 사용자 경험에 있어 매우 중요합니다. 이 레이어는 다음을 측정합니다:
- 평균 응답 시간 (Average response time): 일련의 전형적인 쿼리들에 대한 측정.
- P95 지연 시간 (P95 latency): 중요한 상호작용에 대한 측정.
- 도구 호출 지연 시간 (Tool call latency): 특정 도구를 실행하는 데 걸리는 시간.
- LLM 토큰 생성 속도 (LLM token generation speed): 초당 토큰 수 (Tokens per second).
이 테스트들은 주기적으로 실행되며, 지표가 기준치(baselines)에서 크게 벗어날 경우 경고(alerts)를 발생시킵니다. 제 에이전트들은 속도가 중요한 작업(예: 초기 인사, 간단한 Q&A)에는 Groq를 사용하고, 복잡한 추론에는 Claude 3 Opus를 사용하는 방식으로 자주 라우팅(route)합니다. 이 레이어는 라우팅 로직이 예상대로 작동하는지 검증하는 데 도움을 줍니다. 저는 이를 위해 OCI의 네이티브 모니터링을 사용하며, 하네스에서 커스텀 지표(custom metrics)를 전송합니다.
비용 및 인프라
131개의 테스트를 실행하면, 종종 테스트당 여러 번의 LLM 호출이 수반되므로 비용이 빠르게 증가할 수 있습니다. 현재 제 설정의 전체 실행 비용은 $0.03입니다. 이는 다음과 같은 몇 가지 최적화를 통해 달성되었습니다:
- 전략적 LLM 선택: 모든 평가에 가장 비싼 LLM을 사용하지 않습니다.
- 레이어 1 (도구 기능성 (Tool Functionality)): LLM이 필요하지 않으며, Python 단언문 (assertions)만 사용합니다.
- 레이어 2 (프롬프트 준수 (Prompt Adherence)): OCI 상의 미세 조정된 (Fine-tuned) Llama 3 8B 또는 속도를 위한 Groq Mixtral을 사용합니다. 비용: 평가당 약 ~$0.0001.
- 레이어 3 (목표 지향적 행동 (Goal-Oriented Behavior)): Claude 3 Haiku를 사용합니다. 비용: 평가당 약 ~$0.0005.
- 레이어 4 (성능 (Performance)): LLM이 필요하지 않으며, 타이밍(timing) 측정만 수행합니다.
- 병렬 실행 (Parallel Execution): 각 레이어 내의 테스트는 OCI VM에서 Python의
multiprocessing모듈을 사용하여 병렬로 실행됩니다. - 캐싱 (Caching): 결정론적인 (deterministic) 도구 호출의 경우, 반복적인 테스트 실행 중에 불필요한 외부 API 호출을 피하기 위해 응답을 캐싱합니다.
- 타겟 실행 (Targeted Runs): 사소한 변경 사항의 경우, 특정 레이어 또는 테스트의 하위 집합만 실행하여 비용과 시간을 절감할 수 있습니다.
전체 하네스는 Terraform을 통해 프로비저닝된 단일 OCI VM (VM.Standard.E4.Flex, 2 OCPUs, 32GB RAM)에서 실행됩니다. 테스트 데이터(프롬프트, 예상 출력, 시뮬레이션된 대화)는 에이전트 코드와 함께 Git 리포지토리에 저장됩니다. 이는 버전 관리와 재현성 (reproducibility)을 보장합니다.
유닛 테스트가 잡지 못하는 AI 에이전트 테스트의 포착 능력
이 AI 에이전트 평가 하네스 없이 131개의 테스트를 거치지 않고 프로덕션에 배포했다면 발생했을 '조용한 실패 (silent failure)'는 도구 출력에 대한 미묘한 오해였습니다. 제 유닛 테스트는 search_product_catalog 도구가 제품 목록을 반환한다는 점은 확인해 주었습니다. 하지만 LLM이 해당 목록에서 가격을 올바르게 추출하여 사용자에게 정확하게 제시할 것인지는 확인해 주지 않았습니다.
저의 평가 하네스(Eval Harness), 특히 레이어 3(Layer 3, 목표 지향적 행동 (Goal-Oriented Behavior))에는 이제 다음과 같은 테스트 케이스가 포함되어 있습니다: "사용자가 제품 X의 가격을 묻습니다." 그러면 평가자 LLM (Evaluator LLM)은 에이전트의 응답에 모의 도구(mocked tool) 출력값에 있는 정확한 가격이 포함되어 있는지 확인합니다. 만약 에이전트가 가격을 환각(hallucinate)하거나 도구의 응답을 잘못 읽는다면, 이 테스트는 실패합니다. 이것은 코드 버그가 아닌 의미론적 실패(semantic failure)입니다. 코드는 완벽하게 실행되었지만, LLM에 의해 _의미_가 손실되거나 왜곡된 것입니다.
또 다른 예로, 유닛 테스트(unit test)는 send_email 도구가 호출되었는지를 확인할 수 있습니다. 하지만 AI 에이전트 테스트는 이메일의 _내용_이 적절한지, 사용자의 질문에 답변하는지, 그리고 브랜드 가이드라인을 준수하는지를 확인합니다. 이를 위해서는 LLM이 생성된 이메일 텍스트를 평가해야 하며, 이는 정규 표현식(regex)이나 문자열 비교로는 불가능한 작업입니다.
결론 (Conclusion)
기능을 구현하기 전에 AI 에이전트 평가 하네스를 구축하는 것은 처음에는 직관에 어긋나는 것처럼 느껴졌습니다. 이는 초기에 복잡성을 더하고 초기 "진척"을 늦추었습니다. 하지만 이는 저의 AI 에이전트 개발 워크플로에서 가장 영향력 있는 단 하나의 결정이었음이 증명되었습니다. 이 방식은 전통적인 테스트가 놓치는 미묘한 의미론적 실패를 잡아내어, 비용이 많이 드는 운영 환경(production)의 오류를 방지하고 사용자의 신뢰를 보호합니다. 운영 환경용 AI 에이전트, 특히 멀티 에이전트 시스템(multi-agent systems)을 구축하는 사람에게 견고하고 계층화된 평가 하네스는 선택 사항이 아니라 필수적인 토대입니다.
자주 묻는 질문 (Frequently Asked Questions)
Q: 평가자 LLM이 환각을 일으키거나 편향되는 것을 어떻게 방지하나요?
A: 저는 평가자에게 매우 제약된 프롬프트(prompt)를 사용하여 "YES/NO" 또는 특정 키워드 응답을 강제합니다. 더 복잡한 평가를 위해서는, 평가자를 가이드하기 위해 올바른 에이전트 행동과 잘못된 에이전트 행동의 퓨샷(few-shot) 예시를 사용합니다. 또한 평가자의 결정에 대해 정기적으로 수동 스팟 체크(manual spot-checks)를 수행하는 것도 매우 중요합니다.
Q: 만약 LLM 제공업체가 모델을 변경하여 테스트가 예상치 못하게 실패하면 어떻게 하나요?
A: 이는 흔히 발생하는 문제입니다. 저는 특정 모델 버전(예: claude-3-haiku-20240307)을 고정(pin)합니다. 새로운 모델 버전이 출시되면, 스테이징 환경(staging environment)에서 전체 평가 하네스(eval harness)를 실행하고, 프로덕션 모델을 업데이트하기 전에 실패 사례를 수동으로 검토합니다. 이는 LLM 자체에 대한 회귀 테스트(regression testing)의 한 형태입니다.
Q: 평가 하네스에서 비결정론적 실패(non-deterministic failures)는 어떻게 처리하나요?
A: 레이어 2와 3의 테스트의 경우, 약간의 허용 오차(예: 95% 통과율)를 허용합니다. 테스트가 간헐적으로 실패하면, 이것이 실제 에이전트의 문제인지 아니면 평가자(evaluator)의 민감도 문제인지 조사합니다. 또한 실패로 표시하기 전에 일관성을 확인하기 위해 실패한 테스트를 몇 번 더 재실행합니다.
Q: 빈번한 테스트를 수행하기에 실행당 $0.03의 비용이 지속 가능한가요?
A: 네. 현재 규모인 하루 1020회의 전체 평가를 실행할 경우, 하루에 $0.30$0.60가 소요됩니다. 이는 단 한 번의 프로덕션 실패로 인한 잠재적 매출 손실과 비교하면 무시할 수 있는 수준의 비용입니다. 더 큰 팀의 경우 이 비용은 선형적으로 증가하지만 여전히 관리 가능한 수준입니다.
Q: 131개의 테스트에 대한 테스트 데이터(프롬프트, 예상 출력)는 어떻게 관리하나요?
A: 모든 테스트 데이터는 레이어와 테스트 케이스별로 정리되어 YAML 파일에 저장됩니다. 각 YAML 파일은 사용자 입력, 예상되는 에이전트 동작(예: 도구 호출(tool calls), 특정 문구), 그리고 LLM 평가자를 위한 평가 기준을 정의합니다. 이를 통해 데이터의 버전 관리(version-controlled)가 가능하며 사람이 읽기 쉬운 상태를 유지할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기