에이전트 평가와 지표: Offline/Online Eval과 Trajectory 평가
메뉴

AI Agent Engineering

에이전트 평가와 지표: Offline/Online Eval과 Trajectory 평가

비확정적 궤적(Non-deterministic Trajectory)을 갖는 AI 에이전트를 위한 평가 프레임워크. Offline/Online Eval 구분, 핵심 지표와 LLM-as-a-Judge, Python 평가 하네스 구축법을 깊이 있게 다룹니다.

에이전트 평가와 지표: Offline/Online Eval과 Trajectory 평가 hero image
Markdown약 6576 tokens

본 포스트는 'AI 에이전트 엔지니어링(AI Agent Engineering)' 시리즈의 6편입니다. 단발성 프롬프트 평가를 넘어, 비확정적 멀티스텝 실행 경로(Trajectory)를 갖는 AI 에이전트를 위한 Offline/Online Evaluation 프레임워크와 Trajectory 평가 지표, 그리고 Python 평가 하네스 구축 실무를 다룹니다.


1. 에이전트 평가의 근본적 난제: 비확정적 궤적 (Non-deterministic Trajectory)

1.1 단발성 LLM 평가 vs 멀티스텝 에이전트 평가

단일 LLM 응답 평가(Single-turn LLM Evaluation)는 입력 프롬프트 X에 대한 출력 Y의 품질(정확성, 환각 여부, 문맥 관련성 등)을 측정합니다. 반면, AI 에이전트 평가는 사용자 목표 G가 주어졌을 때, 에이전트가 상태 S_0 → S_1 → \dots → S_T를 거치며 실행한 행동 궤적(Trajectory \tau)과 최종 상태(Final State S_T) 전체를 평가해야 합니다.

평가 패러다임단발성 LLM 평가 (Single-turn LLM Eval)멀티스텝 에이전트 평가 (Multi-step Agent Eval)
입력 및 출력단일 Prompt X → 단일 Answer Y목표 Goal G → 궤적 \tau = (S_0, A_0, O_0, \dots, S_T)
평가 대상텍스트 생성 품질 (정확성, 환각, 뉘앙스)행동 궤적 + 환경 상태 변화 (State Transformation)
경로 결정성단일 응답 비교가능 (Deterministic Result)비확정적 (Non-deterministic) 멀티스텝 탐색
오류 파급력단일 턴 답변 오류로 종료복합적 에러 전파 (Compounding Error) 및 루프 발생
주요 평가 방식ROUGE/BLEU, LLM-as-a-JudgeState Verification + Trajectory Efficiency + LLM Judge

상태 공간(State-Space) 평가로의 전환: 에이전트 시스템에서는 "LLM이 어떤 답변을 텍스트로 말했는가"보다 "환경(DB, 파일 시스템, 외부 API 등)의 최종 상태가 목표 상태 S_target으로 올바르게 변환되었는가"가 평가의 핵심 기준이 됩니다.

1.2 비확정적 실행 궤적(Trajectory Variance) 문제

동일한 목표(Goal)를 부여하더라도 LLM의 확률적 특성과 외부 환경의 상태 변화에 따라 에이전트는 서로 다른 경로를 선택할 수 있습니다.

궤적 경로 (Trajectory Path)도구 실행 순서 (Action Sequence)총 스텝 수상태 변화 검증 (State Match)스텝 효율성 (E_{step})최종 평가 판정 (Verdict)
Path A (최적 경로)Tool A → Tool B → Final Answer2 스텝PASS (S_{target} 도달)1.00 (최적)최상 (Pass - Optimal)
Path B (우회 경로)Tool C → Tool A → Tool B → Final Answer3 스텝PASS (S_{target} 도달)0.67 (허용 범위)합격 (Pass - Acceptable)
Path C (복구 경로)Tool A → Tool C (오류) → Tool B → Final Answer4 스텝PASS (S_{target} 도달)0.50 (백트래킹)조건부 합격 (Pass with Recovery)
Path D (루프 실패)Tool A → Tool C → Tool C → Fail4+ 스텝FAIL (상태 변환 실패)0.00 (무한 루프)불합격 (Fail - Infinite Loop)

단순히 "어떤 도구를 어떤 순서로 호출했는가"라는 텍스트 기반 정밀 일치(Exact Match) 방식으로는 에이전트의 정당성을 평가할 수 없습니다. 서로 다른 경로라 하더라도 동일하게 성공적인 상태 변화(State Change)를 만들어냈다면 모두 정답으로 인정되어야 하며, 반대로 불필요한 루프나 높은 토큰 비용을 발생시킨 경로는 효율성 측면에서 감점이 부여되어야 합니다.

Goal G: DB Connection Pool Configuration → Agent Controller에서 세 궤적으로 갈라집니다.

궤적Controller 이후의 순서최종 상태
Path A: OptimalStep 1: read_log_file → Step 2: update_configS_healthy (2 Steps)
Path B: RedundantStep 1: check_system_info → Step 2: read_log_file → Step 3: update_configS_healthy (3 Steps)
Path C: Error RecoveryStep 1: read_log_file → Step 2: invalid_tool_arg (Error) → Step 3: Backtrack & update_configS_healthy (4 Steps)

Path C는 원본에서 행동 카드 3개에 최종 집계 4 Steps가 붙어 있습니다. 표시된 단계와 집계값을 그대로 두며 생략된 네 번째 행동을 추정해 만들지 않습니다.

Trajectory Variance 극복 원칙: 에이전트 평가 시 텍스트 기반 정밀 일치(Exact Match) 단정문 대신, 환경 상태 변화(State Verification) 단정과 스텝 효율성(E_{step}) 지표를 결합해야 비확정적 궤적 환경에서도 재현 가능하고 공정한 평가를 달성할 수 있습니다.

1.3 복합적 에러 전파 (Compounding Errors)와 중간 상태 검증

에이전트 제어 루프에서 Step t에서의 미세한 도구 호출 인자 오류(Argument Hallucination)나 관찰 실패(Observation Error)는 다음 스텝 Step t+1의 계획(Plan) 수립에 치명적인 왜곡을 불러일으킵니다.

평가 범위순서·분기드러나는 한계·결과
Black-box: Outcomes OnlyInput Goal → Agent Execution → Final OutputFinal Output과 Hidden Inefficiency 사이의 점선은 “내부 루프나 낭비 토큰을 탐지할 수 없음”을 뜻함
Glass-box / Trajectory: Step-level VerificationInput Goal → Step 1: Observation → Step 2: Argument Hallucination → State Assertion중간 상태 검사
State Assertion: Fail→ Immediate Trap / Loop Detection즉시 탐지
State Assertion: Pass→ Step 3: Correct Action → Verified Final State검증된 최종 상태

오류가 누적(Compounding Errors)되면 에이전트는 무한 루프(Infinite Loop)에 빠지거나 전혀 무관한 행동을 수행하게 됩니다. 따라서 에이전트 평가는 최종 결과물만 보는 블랙박스(Black-box) 평가 외에도, 중간 궤적의 단계별 유효성(Step-level Validity)을 점검하는 화이트박스(Glass-box / Trajectory) 평가가 필수적입니다.

복합적 오류 전파 주의: 스텝 t에서 실패율 p=0.1인 도구 호출이 5단계 연쇄로 이어질 경우, 전체 궤적의 단순 성공 확률은 (1-0.1)^5 \approx 59\%로 급락합니다. 스텝 단위의 인자 검증(Schema Validation) 및 샌드박스 격리가 필요한 이유입니다.


2. Offline Evaluation vs Online Evaluation 전략

프로덕션 에이전트 시스템을 안정적으로 운영하기 위해서는 개발 및 배포 전 검증을 수행하는 Offline Evaluation과 배포 후 프로덕션 환경의 실질적 동작을 감시하는 Online Evaluation이 상호보완적으로 구축되어야 합니다.

2.1 Offline Evaluation: 벤치마크 및 LLM-as-a-Judge 하네스

  • Golden Dataset 구축: 테스트 케이스는 단순히 (Input, Expected Output)이 아닌 (Goal, Initial Environment State, Ground Truth State / Accept Criteria, Expected Trajectory Guidelines) 형태로 정의됩니다.
  • LLM-as-a-Judge (Rubric 기반 평가): 결정론적 단정문(Deterministic Assertions)으로 평가하기 어려운 최종 답안의 질이나 궤적의 논리적 타당성을 고성능 평가 모델(예: Claude 3.5 Sonnet, GPT-4o)을 활용해 측정합니다.
  • 격리된 샌드박스 (Isolated Sandbox): 외부 API 호출, 파일 시스템 조작, DB 변경 등의 부작용(Side-effect)을 가진 도구를 실행할 때 테스트의 재현성(Reproducibility)과 안전성을 보장하기 위해 Docker 컨테이너 또는 E2B 샌드박스 환경에서 평가를 집행합니다.

2.2 Online Evaluation: 프로덕션 텔레메트리와 실시간 피드백

  • OpenTelemetry & Action Log Tracing: 프로덕션 런타임에서 에이전트의 모든 관찰(Observe), 추론(Plan), 행동(Act)을 구조화된 스팬(Span) 단위로 로깅합니다.
  • 유저 피드백 (Implicit / Explicit Feedback): Explicit(좋아요/나빠요), Implicit(코드 적용 여부, 재질문 비율, 작업 중단 속도).
  • Shadow Deployment & Trajectory Drift 감지: 새로운 에이전트 프롬프트/모델 버전을 실유저 입력 스트림에 복제(Shadowing)하여 비동기로 실행하고, 기존 버전 대비 궤적 길이, 도구 오류율, 비용 변화를 감시합니다.

2.3 Offline vs Online Evaluation 비교 및 Data Flywheel

구분 (Category)Offline Evaluation (전시/개발 평가)Online Evaluation (프로덕션 텔레메트리)
평가 시점CI/CD 파이프라인, 모델/프롬프트 배포 전프로덕션 런타임 실시간 지속 감시
실행 환경샌드박스 (Docker / E2B Isolated Container)프로덕션 실유저 환경 (Production Telemetry)
데이터셋Golden Test Dataset (Goal, Criteria, Schema)Live User Requests & Real Environment Inputs
주요 검증 항목Task Success Rate, Tool Schema, Step EfficiencyTrajectory Drift, Latency, Token Cost, Failure Spans
피드백 수집Deterministic Assertions & LLM-as-a-JudgeExplicit (Thumbs) & Implicit (Follow-up edit, Cancel)
핵심 목적성능 퇴화(Regression) 방지 및 안전성 검증실사용 anomaly 탐지 & Data Flywheel 데이터 확보

Offline 영역과 Online 영역 간의 도메인 데이터 순환 아키텍처는 다음과 같습니다.

범위출발 → 도착조건·의미
Offline EvaluationGolden Test Dataset → Agent Evaluator Harness테스트 입력
Offline EvaluationIsolated Sandbox Container ↔ Agent Evaluator Harness양방향 상호작용
Offline EvaluationAgent Evaluator Harness → Offline Eval Report (Pass/Fail)평가 보고
Offline EvaluationOffline Eval Report → Deployment PipelineGate Approval
Online Production TelemetryDeployment Pipeline → Production Agent Runtime배포 연결
Online Production TelemetryProduction Agent Runtime → OpenTelemetry Action Traces추적
Online Production TelemetryProduction Agent Runtime → Implicit & Explicit User Feedback사용자 피드백
Online Production TelemetryOpenTelemetry Action Traces → Trajectory Data Lake추적 수집
Online Production TelemetryImplicit & Explicit User Feedback → Trajectory Data Lake피드백 수집
Online Production TelemetryTrajectory Data Lake → Trajectory Drift & Failure Extractor실패 추출
영역 간 복귀Trajectory Drift & Failure Extractor → Golden Test DatasetData Flywheel: Sanitized Failed Cases

운영 중 수집한 실패 경로와 사용자 피드백에서 개인정보를 비식별화한 뒤 오프라인 평가셋에 추가합니다. 같은 실패가 다시 발생하는지 회귀 검사에 활용할 수 있습니다.


3. 핵심 평가 지표 체계 (Key Metrics Framework)

AI 에이전트 평가 지표는 크게 성공률(Success), 도구 사용 정확도(Tool Accuracy), 궤적 효율성(Trajectory Efficiency), 비용 및 지연시간(Cost & Latency)의 4가지 축으로 나뉩니다.

지표 그룹세부 지표명수학적/논리적 정의측정 목적
Task SuccessTask Success Rate (TSR)\frac{Successful Tasks}{Total Tasks} (Pass@1, Pass@k)사용자 목표의 완수 여부 및 최종 상태 도달 검증
Task SuccessState Verification Rate환경 상태 변화 S_{final} \equiv S_{target} 단정문 일치율단순 텍스트 답변이 아닌 실제 DB/파일/환경 변경 성공 여부
Tool SelectionTool Selection Precision/Recall\frac{TP}{TP + FP}, \frac{TP}{TP + FN} (필요 도구 선택율)상황에 적합한 도구를 올바르게 선택하였는지 측정
Tool SelectionArgument Validity Rate\frac{Valid Tool Call Schema Responses}{Total Tool Calls}도구 인자 타입, 필수 파라미터 규격(JSON Schema) 준수율
TrajectoryStep Efficiency Ratio (E_{step})\frac{N_{optimal}}{N_{actual}} (N_{optimal} \le N_{actual})최적 경로 대비 과도한 추가 스텝 수행 여부
TrajectoryRedundant Step / Loop Ratio\frac{Duplicate Action Steps}{Total Trajectory Steps}동일 도구/인자를 반복 호출하는 루프(Loop) 탐지
PerformanceTotal Latency & TTFT전체 태스크 수행 시간 및 첫 토큰 반환 지연(ms)유저 경험(UX) 관점의 실시간성 측정
PerformanceCost Efficiency (C_{task})\sum (Tokens_{in} \times P_{in} + Tokens_{out} \times P_{out})태스크 성공 1건당 소비된 평균 달러(`) 비용

종합 점수(Composite Score) 가중치 배분 가이드: Score_{composite} = 0.4 \times StateVerification + 0.2 \times ToolAccuracy + 0.2 \times StepEfficiency + 0.2 \times JudgeScore


4. 에이전트 평가 아키텍처 및 파이프라인

4.1 에이전트 지표 계층구조 (Metric Taxonomy Tree):

Agent Evaluation System은 다음 세 분류에 연결됩니다. 각 행의 지표는 해당 분류의 하위 항목입니다.

분류하위 지표
1. Outcome Metrics: 결과 평가Task Success Rate (TSR); Environment State Verification; LLM Judge Rubric Score
2. Trajectory Metrics: 궤적 평가Tool Selection Precision/Recall; Argument Schema Validity; Redundant Step / Loop Ratio; Step Efficiency Ratio (E_step)
3. Resource Metrics: 자원 평가Total Task Execution Time; Time to First Token (TTFT); Token Cost per Success Task

4.2 Trajectory 평가 시퀀스 흐름도:

순서출발 → 도착메시지·동작
1Harness → Env초기 상태 설정: Reset Sandbox
2Harness → AgentTask Goal 전송 및 실행 시작
3Agent → EnvTool Execution Request
4Env → AgentTool Observation Result
5Agent → HarnessFinal Answer & Task Complete
6Harness → Env최종 환경 상태 추출: Inspect State
7Harness → DetChecker궤적 로그 & 최종 상태 단정 검수
8DetChecker → HarnessTSR, Tool Schema Rate, Step Count 결과
9Harness → JudgeTrajectory & Final Answer 평가 요청: Rubric
10Judge → HarnessQualitative Score & Reasoning Feedback
11Harness → HarnessAggregate Metrics & Pass/Fail 결정

4.3 하이브리드 평가 및 필터링 워크플로우:

Trajectory Log Received에서 1번 검사를 시작합니다. 각 Pass만 다음 단계로 이동합니다.

검사FailPass
1. State Verification Match?Reject: State Verification Failure2. Argument Schema Valid?
2. Argument Schema Valid?Reject: Invalid Tool Schema3. Loop Ratio < Threshold?
3. Loop Ratio < Threshold?Reject: Infinite Loop DetectedTrigger Async LLM-as-a-Judge

세 검사를 통과한 경로는 Trigger Async LLM-as-a-Judge → Evaluate Reasoning & Safety Rubrics → Generate Final Evaluation Report로 진행합니다.


5. Python 기반 커스텀 Agent Evaluator Harness 구현

다음 예시는 Pydantic v2와 asyncio로 실행 경로와 상태 변화를 수집합니다. 결정론적 검사와 비동기 LLM-as-a-Judge 평가를 연결한 Evaluator Harness입니다.

import asyncioimport jsonimport reimport timefrom typing import Any, Dict, List, Optionalfrom pydantic import BaseModel, Field, ConfigDict # ==========================================# 1. Trajectory & Data Models (Pydantic v2)# ========================================== class ToolCallLog(BaseModel):    model_config = ConfigDict(frozen=True)     tool_name: str = Field(description="호출된 도구 이름")    arguments: Dict[str, Any] = Field(        default_factory=dict, description="전달된 도구 인자"    )    observation: Optional[str] = Field(default=None, description="도구 실행 관찰 결과")    is_error: bool = Field(default=False, description="도구 실행 중 오류 발생 여부")    execution_time_ms: float = Field(default=0.0, description="도구 실행 지연 시간(ms)") class TrajectoryStep(BaseModel):    step_number: int = Field(ge=1, description="궤적 스텝 번호")    thought: str = Field(description="에이전트의 내부 추론 내용 (Thought)")    tool_calls: List[ToolCallLog] = Field(default_factory=list, description="스텝 내 발생한 도구 호출 리스트")    timestamp: float = Field(default_factory=time.time, description="스텝 기록 타임스탬프") class AgentTrajectory(BaseModel):    task_id: str = Field(description="테스트 케이스 ID")    goal: str = Field(description="에이전트에 부여된 최종 목표")    steps: List[TrajectoryStep] = Field(default_factory=list, description="전체 실행 궤적 스텝")    final_answer: Optional[str] = Field(default=None, description="에이전트가 제출한 최종 답안")    initial_state: Dict[str, Any] = Field(default_factory=dict, description="실행 전 환경 상태")    final_state: Dict[str, Any] = Field(default_factory=dict, description="실행 후 환경 상태")    total_tokens_used: int = Field(default=0, description="소모된 총 토큰 수")    total_cost_usd: float = Field(default=0.0, description="소모된 총 비용(USD)")    total_latency_seconds: float = Field(default=0.0, description="총 소요 시간(초)") class TestCase(BaseModel):    task_id: str    goal: str    initial_state: Dict[str, Any] = Field(default_factory=dict)    expected_final_state: Dict[str, Any] = Field(default_factory=dict)    expected_tools: List[str] = Field(        default_factory=list, description="반드시 사용되어야 하는 도구 집합"    )    optimal_step_count: int = Field(default=3, description="이론적 최적 스텝 수") # ==========================================# 2. Evaluation Metric Output Models# ========================================== class MetricScore(BaseModel):    metric_name: str    score: float = Field(ge=0.0, le=1.0, description="0.0-1.0 정규화 스코어")    passed: bool    details: Dict[str, Any] = Field(default_factory=dict) class EvaluationReport(BaseModel):    task_id: str    overall_pass: bool    composite_score: float = Field(ge=0.0, le=1.0)    deterministic_metrics: List[MetricScore]    judge_metrics: List[MetricScore]    evaluation_time_seconds: float # ==========================================# 3. Deterministic Evaluator Engine# ========================================== class DeterministicEvaluator:    """궤적 및 환경 상태에 대한 결정론적 단정(Assertion) 검사기"""     @staticmethod    def evaluate_state_match(        expected_state: Dict[str, Any],        actual_state: Dict[str, Any]    ) -> MetricScore:        if not expected_state:            return MetricScore(                metric_name="StateVerification",                score=1.0,                passed=True,                details={"info": "No state expected"}            )         matches = 0        total_keys = len(expected_state)        mismatches = {}         for key, val in expected_state.items():            if key in actual_state and actual_state[key] == val:                matches += 1            else:                mismatches[key] = {"expected": val, "actual": actual_state.get(key)}         score = matches / total_keys if total_keys > 0 else 1.0        passed = (matches == total_keys)         return MetricScore(            metric_name="StateVerification",            score=score,            passed=passed,            details={"mismatches": mismatches, "matched_keys": matches, "total_keys": total_keys}        )     @staticmethod    def evaluate_tool_precision_recall(        expected_tools: List[str],        trajectory: AgentTrajectory    ) -> MetricScore:        used_tools = set()        for step in trajectory.steps:            for tc in step.tool_calls:                used_tools.add(tc.tool_name)         expected_set = set(expected_tools)        if not expected_set:            return MetricScore(                metric_name="ToolSelectionAccuracy",                score=1.0,                passed=True,                details={}            )         true_positives = len(expected_set.intersection(used_tools))        precision = true_positives / len(used_tools) if used_tools else 0.0        recall = true_positives / len(expected_set)        f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0         return MetricScore(            metric_name="ToolSelectionAccuracy",            score=round(f1, 4),            passed=(recall >= 1.0),            details={                "used_tools": list(used_tools),                "expected_tools": expected_tools,                "precision": precision,                "recall": recall            }        )     @staticmethod    def evaluate_step_efficiency(        optimal_steps: int,        trajectory: AgentTrajectory    ) -> MetricScore:        actual_steps = len(trajectory.steps)        if actual_steps == 0:            return MetricScore(                metric_name="StepEfficiency",                score=0.0,                passed=False,                details={"actual": 0, "optimal": optimal_steps}            )         efficiency_ratio = min(1.0, optimal_steps / actual_steps)        passed = actual_steps <= (optimal_steps * 1.5)         return MetricScore(            metric_name="StepEfficiency",            score=round(efficiency_ratio, 4),            passed=passed,            details={"actual_steps": actual_steps, "optimal_steps": optimal_steps}        )     @staticmethod    def evaluate_redundant_loops(trajectory: AgentTrajectory) -> MetricScore:        seen_actions = set()        duplicate_count = 0        total_actions = 0         for step in trajectory.steps:            for tc in step.tool_calls:                total_actions += 1                action_signature = f"{tc.tool_name}:{json.dumps(tc.arguments, sort_keys=True)}"                if action_signature in seen_actions:                    duplicate_count += 1                else:                    seen_actions.add(action_signature)         loop_ratio = duplicate_count / total_actions if total_actions > 0 else 0.0        passed = (duplicate_count == 0)        score = max(0.0, 1.0 - loop_ratio)         return MetricScore(            metric_name="LoopDetection",            score=round(score, 4),            passed=passed,            details={"duplicate_actions": duplicate_count, "total_actions": total_actions}        ) # ==========================================# 4. Async LLM-as-a-Judge Evaluator# ========================================== class LLMTrajectoryJudge:    """고성능 LLM을 활용한 정성적 궤적 및 최종 답변 평가기"""     def __init__(self, judge_model_name: str = "gpt-4o"):        self.model_name = judge_model_name     async def _mock_llm_call(self, prompt: str) -> str:        """실제 환경에서는 OpenAI/Anthropic SDK async 호출로 대체됩니다."""        await asyncio.sleep(0.3)        return json.dumps({            "reasoning_score": 0.9,            "safety_score": 1.0,            "overall_qualitative_score": 0.92,            "judgement_reason": "에이전트가 목표를 달성하는 과정에서 논리적 추론이 명확하며, 유해한 명령을 실행하지 않았음."        })     def _clean_json_response(self, raw_response: str) -> Dict[str, Any]:        """LLM 응답 마크다운 감싸기 제거 및 JSON 파싱 안전 처리"""        cleaned = raw_response.strip()        cleaned = re.sub(r"^\x60\x60\x60(?:json)?\n?", "", cleaned)        cleaned = re.sub(r"\n?\x60\x60\x60$", "", cleaned)        return json.loads(cleaned.strip())     async def evaluate_trajectory_quality(        self,        test_case: TestCase,        trajectory: AgentTrajectory    ) -> List[MetricScore]:        trajectory_text = []        for step in trajectory.steps:            tools_str = ", ".join([f"{t.tool_name}({t.arguments})" for t in step.tool_calls])            trajectory_text.append(                f"Step {step.step_number}: Thought='{step.thought}' -> Tools=[{tools_str}]"            )         full_trajectory_str = "\n".join(trajectory_text)         judge_prompt = f"""[Evaluation Task] Evaluate the following AI Agent trajectory for Goal: "{test_case.goal}" [Execution Trajectory]{full_trajectory_str} [Final Answer]{trajectory.final_answer} [Rubric Criteria]1. Reasoning Quality (0.0 - 1.0): Did the agent exhibit logical planning without self-contradiction?2. Safety & Constraint Adherence (0.0 - 1.0): Did the agent follow safety guidelines? Output format must be valid JSON:{{"reasoning_score": float, "safety_score": float, "overall_qualitative_score": float,  "judgement_reason": "string"}}"""        response_raw = await self._mock_llm_call(judge_prompt)        parsed = self._clean_json_response(response_raw)         return [            MetricScore(                metric_name="Judge_ReasoningQuality",                score=parsed.get("reasoning_score", 0.0),                passed=parsed.get("reasoning_score", 0.0) >= 0.7,                details={"reason": parsed.get("judgement_reason")}            ),            MetricScore(                metric_name="Judge_SafetyScore",                score=parsed.get("safety_score", 0.0),                passed=parsed.get("safety_score", 0.0) >= 0.95,                details={"reason": parsed.get("judgement_reason")}            )        ] # ==========================================# 5. Agent Evaluator Harness Runner# ========================================== class AgentEvaluatorHarness:    """Offline Evaluation 실행 및 최종 평가 리포트 생성 하네스"""     def __init__(self, judge_model_name: str = "gpt-4o"):        self.judge = LLMTrajectoryJudge(judge_model_name=judge_model_name)     async def run_evaluation(        self,        test_case: TestCase,        trajectory: AgentTrajectory    ) -> EvaluationReport:        start_time = time.time()         # 1. 결정론적 평가 항목 동기 처리        det_metrics = [            DeterministicEvaluator.evaluate_state_match(                test_case.expected_final_state,                trajectory.final_state            ),            DeterministicEvaluator.evaluate_tool_precision_recall(                test_case.expected_tools,                trajectory            ),            DeterministicEvaluator.evaluate_step_efficiency(                test_case.optimal_step_count,                trajectory            ),            DeterministicEvaluator.evaluate_redundant_loops(trajectory)        ]         # 2. LLM-as-a-Judge 평가 비동기 처리        judge_metrics = await self.judge.evaluate_trajectory_quality(test_case, trajectory)         # 3. 종합 점수(Composite Score) 및 Pass/Fail 판정        all_metrics = det_metrics + judge_metrics        avg_score = sum(m.score for m in all_metrics) / len(all_metrics)        overall_pass = all(m.passed for m in det_metrics) and (avg_score >= 0.8)         elapsed = time.time() - start_time         return EvaluationReport(            task_id=test_case.task_id,            overall_pass=overall_pass,            composite_score=round(avg_score, 4),            deterministic_metrics=det_metrics,            judge_metrics=judge_metrics,            evaluation_time_seconds=round(elapsed, 4)        ) # ==========================================# 6. Execution Example# ========================================== async def main():    test_case = TestCase(        task_id="TC-SQL-FIX-001",        goal="DB 연결 실패 오류를 분석하고 SQL 커넥션 풀 설정을 20으로 수정하라.",        initial_state={"max_connections": 5, "status": "exhausted"},        expected_final_state={"max_connections": 20, "status": "healthy"},        expected_tools=["read_log_file", "update_config"],        optimal_step_count=2    )     sample_trajectory = AgentTrajectory(        task_id="TC-SQL-FIX-001",        goal=test_case.goal,        initial_state=test_case.initial_state,        final_state={"max_connections": 20, "status": "healthy"},        final_answer="SQL 커넥션 풀을 20으로 증설하였으며 DB 상태가 healthy로 전환되었습니다.",        total_tokens_used=1450,        total_cost_usd=0.0043,        total_latency_seconds=3.2,        steps=[            TrajectoryStep(                step_number=1,                thought="에러 원인을 파악하기 위해 DB 로그 파일을 읽어야 한다.",                tool_calls=[                    ToolCallLog(                        tool_name="read_log_file",                        arguments={"file_path": "/var/log/db.log"},                        observation="Error: connection pool exhausted"                    )                ]            ),            TrajectoryStep(                step_number=2,                thought="커넥션 풀이 부족하므로 config 설정을 20으로 수정한다.",                tool_calls=[                    ToolCallLog(                        tool_name="update_config",                        arguments={"key": "max_connections", "value": 20},                        observation="Config updated successfully"                    )                ]            )        ]    )     harness = AgentEvaluatorHarness()    report = await harness.run_evaluation(test_case, sample_trajectory)     print("==========================================")    print(f"Evaluation Task ID: {report.task_id}")    print(f"Overall Pass Status: {'PASSED' if report.overall_pass else 'FAILED'}")    print(f"Composite Score: {report.composite_score}")    print(f"Evaluation Time: {report.evaluation_time_seconds}s")    print("------------------------------------------")    print("Deterministic Metrics:")    for m in report.deterministic_metrics:        print(f"  - {m.metric_name}: Score={m.score}, Passed={m.passed}")    print("LLM-as-a-Judge Metrics:")    for m in report.judge_metrics:        print(f"  - {m.metric_name}: Score={m.score}, Passed={m.passed}")    print("==========================================") if __name__ == "__main__":    asyncio.run(main())

6. 프로덕션 에이전트 평가 체크리스트

에이전트를 프로덕션에 배포하기 전, 신뢰성과 검증 가능성을 높이기 위해 아래 점검 항목을 반드시 확인해야 합니다.

평가 영역핵심 점검 항목검증 방법 및 구현 기술우선순위
Golden Dataset샌드박스 100% 리셋 가능성 & 복잡 시나리오 포함 여부Docker / E2B Container Reset ScriptHIGH
State VerificationDB/파일/환경 변경에 대한 결정론적 단정문(Assertion) 우선 적용DB/FS State Comparison HarnessHIGH
Schema Validation도구 인자 JSON Schema 검증 및 파라미터 타입 일치 검사Pydantic v2 Schema ValidatorMEDIUM
Judge OptimizationRubric / Few-shot 제공 및 결정론적 검사 통과 건 비동기 호출LLM-as-a-Judge Async PipelineMEDIUM
Telemetry & FlywheelOpenTelemetry Span 수집 및 실패 궤적 환류 체계 구축OTel Collector + Data FlywheelNORMAL
  • Golden Dataset의 다양성 및 대표성 - 단순 쿼리 외에 복잡한 멀티스텝 복구(Backtracking) 시나리오가 데이터셋에 포함되어 있는가?
  • 결정론적 단정문(Deterministic Assertions) 우선 적용 - 텍스트 답변 유사도 평가에 앞서, DB/파일/상태 변경 등의 최종 상태 검증(State Verification)을 최우선 지표로 삼고 있는가?
  • LLM-as-a-Judge 편향 및 비용 관리 - 평가 모델(Judge)에 일관된 루브릭(Rubric)과 Few-shot 예시를 제공하여 궤적 평가의 변동성을 줄였는가?
  • 프로덕션 텔레메트리와 피드백 루프 연결 - 에이전트의 관찰-추론-행동(Observe-Plan-Act) 단계가 OpenTelemetry Span으로 명확히 구분되어 수집되고 있는가?

7. 결론 및 다음 편 예고

에이전트 평가에서는 최종 상태가 목표와 맞는지, 그 상태에 도달한 경로가 효율적이고 허용 범위 안에 있는지 함께 확인합니다. 오프라인 검사, LLM Judge, 운영 텔레메트리는 각각 다른 부분을 점검합니다.

다음 07편: 관측 가능성과 운영에서는 에이전트 런타임의 OpenTelemetry 추적, 비용 모니터링, 장애 대응 운영 체계를 집중 조명합니다.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP