agentic-harness

방법론 · 03

Eval-Driven Development

Hamel Husain 등이 강조하는 “LLM 시스템에서 평가가 곧 단위 테스트다” 라는 방법론을 현실 사례와 함께 정리했습니다.

한 번 솔직히 생각해 봅시다 — 평소 LLM 으로 무언가를 만들 때, 어떻게 “이게 잘 되고 있는지” 를 판단하시나요? 직접 몇 번 눈으로 확인해 보고 “음, 잘 되네” 하고 넘어가신 적이 분명히 있을 겁니다. Hamel Husain 같은 분들이 강조하는 Eval-Driven Development(EDD) 는 그 “눈으로 확인” 단계를 자동화 가능한 평가(eval) 로 바꿉니다. 곧, “LLM 에 대한 단위 테스트” 입니다.

왜 평가가 핵심인가

실패하는 LLM 제품은 거의 모두 같은 한 가지 뿌리를 공유합니다 — 견고한 평가 시스템이 없습니다. Hamel 의 표현을 빌리자면, 잘하는 팀은 도구 이야기를 거의 하지 않고 측정과 반복(measurement and iteration) 만 이야기합니다. 평가는 다음 셋을 모두 가능하게 합니다.

  • Unit tests for LLMs — 작고, 단정 기반이고, 자주 돌릴 수 있는 테스트
  • 인간 + 모델 평가 — LLM-as-judge 로 출력 품질에 대한 비판을 자동화
  • A/B 테스트 — 충분히 성숙한 기능을 실제 사용자 트래픽에서 검증
Most successful approaches to building AI products use evals as a forcing function for everything else. Evals are the most important thing to get right.

Eval 루프는 이렇게 돕니다

  1. 평가를 먼저 적습니다 — 입력, 기대 동작, 채점 방식
  2. 에이전트를 평가 데이터셋에 대해 실행하고 trace 를 수집합니다
  3. 출력을 채점합니다 (LLM-as-judge, 사람, 도메인 메트릭)
  4. 실패 케이스의 패턴을 분석합니다
  5. 프롬프트, 도구, 컨텍스트, 모델을 개선합니다
  6. 다시 평가를 돌려 개선이 실제로 효과가 있었는지 확인합니다

이건 TDD 의 LLM 버전입니다. 다른 점은 기대값이 정확한 문자열이 아니라 “이 출력이 충분히 좋은가” 라는 판단이라는 것뿐입니다. 그 판단을 LLM-as-judge 또는 사람 또는 도메인 메트릭으로 자동화하면, “테스트 없이는 코드 없음” 원칙을 LLM 에도 그대로 적용할 수 있습니다.

실제 코드 — Python eval 스위트

evals/agentic_harness_evals.pypython
# 에이전틱 하네스의 평가 스위트
import json
from datetime import datetime
from typing import Callable

class HarnessEval:
    """AIOps 에이전트 하네스 평가 스위트."""

    def __init__(self):
        self.results = {"passed": 0, "failed": 0, "traces": []}

    def eval_tool_parsing(self, agent_output: str) -> bool:
        """단위 평가: 에이전트가 도구 스펙을 정확히 파싱하는가?"""
        try:
            parsed = json.loads(agent_output)
            required = ["name", "description", "parameters"]
            return all(f in parsed for f in required)
        except Exception:
            return False

    def eval_agent_concurrency(self, agent_traces: list) -> dict:
        """모델 평가: 에이전트가 병렬 서브에이전트를 올바르게 사용했는가?"""
        prompt = f'''
        다음 에이전트 세션 trace 를 검토해 주세요. 에이전트가:
        1. read-only 리뷰 서브에이전트를 병렬로 띄웠습니까?
        2. 에이전트들 사이에 write 충돌을 피했습니까?
        3. eval deadline 안에 완료했습니까?

        Trace: {json.dumps(agent_traces, indent=2)}
        '''
        return call_llm_judge(prompt)

    def run_suite(self, agent_callable: Callable):
        for test in test_cases:
            output = agent_callable(test["input"])
            passed = self.eval_tool_parsing(output)
            self.results["traces"].append({
                "test": test, "output": output, "passed": passed,
                "timestamp": datetime.now().isoformat(),
            })
            self.results["passed" if passed else "failed"] += 1
        return self.results

# CI/CD 게이트
if __name__ == "__main__":
    evals = HarnessEval()
    results = evals.run_suite(agent_main)
    pass_rate = results["passed"] / (results["passed"] + results["failed"])
    assert pass_rate >= 0.95, f"Evals failed: {pass_rate*100:.1f}% pass rate"
    print(json.dumps(results, indent=2))

다른 방법론 글도 있습니다 → Karpathy 방법론 · Ralph Loop · Context Engineering · Agent Teams · Self-Improving Systems · AutoResearch 심층 분석