콘텐츠로 이동

궤적 평가 vs 최종 답변 평가

에이전트가 문제를 풀었다. 최종 답변은 정확하다. 그런데 루프를 들여다보니, 잘못된 파일을 세 번 열었고 불필요한 웹 검색을 다섯 번 수행했으며 중간에 환각(hallucination) 내용을 도구 파라미터로 넣었다가 에러를 받고서야 수정했다. 결과는 옳지만 경로는 나쁘다. 최종 답변 평가(final answer evaluation)는 이 모든 낭비를 감지하지 못한다.

반대 경우도 있다. 에이전트가 최종 답변에서 실패했지만, 어떤 정보를 검색하고 어떻게 추론을 수정해 나갔는지의 궤적은 상당히 합리적이었다. 이런 경우 “이 에이전트는 완전히 틀렸다”고 단정짓는 것은 부당하다. 어디서 무너졌는지 알아야 개선할 수 있다.

이것이 **궤적 평가(trajectory evaluation)**가 필요한 이유다.

┌──────────────────────────────────────────────────────────┐
│ 최종 답변 평가의 세 가지 맹점 │
├──────────────────────────────────────────────────────────┤
│ ① 경로 불투명 맞는 답이라도 비효율·환각·우연의 산물일 수 있음 │
│ ② 개선 불가 어느 스텝이 문제였는지 알 수 없어 수정 대상 불명 │
│ ③ 편향된 신호 이진 pass/fail이 에이전트 역량을 과소/과대평가 │
└──────────────────────────────────────────────────────────┘

경로 불투명성이 핵심 문제다. 에이전트가 10번의 불필요한 도구 호출을 한 뒤 우연히 맞는 답에 도달했다면, “성공”으로 기록된다. 하지만 토큰 비용은 이미 소비됐고, 이런 비효율적인 경로가 프로덕션에서 반복된다면 운영 비용이 기하급수적으로 늘어난다.

스텝별 피드백 부재는 에이전트 개선을 어렵게 만든다. “실패했다”는 결과만 알면 어디서부터 고쳐야 할지 알 수 없다. 루프 세 번째 이터레이션에서 잘못된 파일명을 환각했고, 그것이 이후 모든 스텝을 오염시켰다는 것을 알아야 개선이 가능하다.

궤적 평가는 에이전트의 행동 시퀀스 전체를 검토한다. 측정 지표는 세 범주로 나뉜다.

범주 지표 측정 방법
효율성 총 이터레이션 수, 불필요 도구 호출 수, 총 토큰 스팬 데이터에서 집계
정확성 환각 도구 파라미터 비율, 스키마 오류 횟수 execute_tool 스팬 검사
적응성 첫 에러 후 몇 이터레이션 만에 회복했는가 에러 스팬 → 재시도 스팬 거리

효율성 지표는 “이 에이전트가 작업을 얼마나 낭비 없이 수행했는가”를 측정한다. 같은 작업을 8번 이터레이션 만에 푸는 에이전트와 3번 만에 푸는 에이전트는 최종 답변이 동일해도 후자가 더 낫다.

정확성 지표는 중간 스텝의 환각을 잡는다. 도구 호출 파라미터에 존재하지 않는 파일명이 들어갔다면, 최종 답변이 맞더라도 그 경로는 취약하다. 같은 입력에서 다른 실행이 다른 환각을 만날 수 있기 때문이다.

적응성 지표는 에이전트의 회복력(resilience)을 측정한다. 도구 호출이 에러를 반환했을 때 에이전트가 즉시 다른 접근법으로 전환했는가, 아니면 같은 실패를 반복하다가 결국 포기했는가?

LLM-as-Judge로 전체 궤적 평가하기

섹션 제목: “LLM-as-Judge로 전체 궤적 평가하기”

스텝별 수치 지표만으로는 부족한 경우가 있다. 추론의 질적 일관성, 불필요한 우회의 미묘한 패턴은 규칙 기반으로 잡기 어렵다. 이럴 때 LLM-as-judge 패턴을 궤적 전체에 적용한다.

LLM-as-judge를 궤적 평가에 쓸 때는 전체 트레이스를 텍스트 표현으로 변환해 심판 모델에게 제공한다. 연구에 따르면 GPT-4 수준의 모델이 심판으로 동작할 때 인간 평가자와의 선호 일치율이 80% 이상에 달해 인간 간 일치율과 비슷한 수준을 보인다.

개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.

def trajectory_to_text(spans: list[Span]) -> str:
"""OTel 스팬 목록을 LLM이 읽을 수 있는 텍스트로 변환."""
lines = []
for span in spans:
op = span.attributes.get("gen_ai.operation.name")
if op == "chat":
lines.append(
f"[STEP {span.step}] 모델 호출: "
f"입력 {span.attributes['gen_ai.usage.input_tokens']}토큰"
)
elif op == "execute_tool":
lines.append(
f"[STEP {span.step}] 도구 실행: "
f"{span.attributes['gen_ai.tool.name']} "
f"→ {'에러' if span.error else '성공'}"
)
return "\n".join(lines)
def judge_trajectory(trajectory_text: str, task: str) -> dict:
prompt = f"""아래는 에이전트가 작업을 수행한 전체 궤적입니다.
작업: {task}
궤적:
{trajectory_text}
다음 기준으로 1-5점 평가하세요:
- 효율성: 불필요한 단계 없이 직선적으로 진행했는가?
- 정확성: 각 단계의 추론이 사실에 기반했는가?
- 적응성: 실패에서 빠르게 회복했는가?
JSON으로 응답: {{"efficiency": N, "accuracy": N, "adaptability": N, "rationale": "..."}}"""
return call_judge_model(prompt)

스텝 레벨 vs 궤적 레벨 평가의 결합

섹션 제목: “스텝 레벨 vs 궤적 레벨 평가의 결합”

실용적인 평가 시스템은 두 레벨을 결합한다. 스텝 레벨에서는 도구 파라미터 스키마 검증, 에러 발생 여부 등 결정론적(deterministic) 체크를 자동화한다. 궤적 레벨에서는 LLM-as-judge로 전체 흐름의 품질을 판단한다. 이 조합이 “에이전트가 지금 얼마나 잘 작동하는가”를 가장 완전하게 측정하는 방법이다.

다음 챕터에서는 이런 평가 아이디어를 실제 에이전트 벤치마크, 특히 SWE-bench에 적용하는 방법을 살펴본다.

참고 자료