본문으로 바로가기
리옵트 핸드북
리옵트 핸드북
AI 에이전트 오케스트레이션 패턴

개념·설계

오케스트레이션 개념과 지형도핵심 아키텍처 패턴에이전트 설계 원칙

도구·프로토콜

도구(Tool) 설계 패턴MCP와 에이전트 간 프로토콜

조율·상태

통신과 핸드오프상태 관리와 메모리 시스템라우팅과 디스패치

안정성·운영

에러 처리와 복구 전략평가와 테스트프로덕션 운영 패턴

실전

실전 아키텍처 사례프레임워크 비교와 선택

부록

검증 리포트업데이트 내역
핸드북›AI 에이전트 오케스트레이션 패턴›평가와 테스트

평가와 테스트

단위/통합/E2E 에이전트 테스트, trajectory 평가, 비결정성 대응

핵심 요약

  • 평가의 목적은 모델 우열을 가리는 게 아니라 오케스트레이션 변경이 행동 계약을 깨지 않았는지 확인하는 것입니다.
  • 평가는 Unit·Contract·Trajectory·Scenario·Online 5계층으로 나누고, Unit/Contract는 매 배포, Trajectory/Scenario는 주기적, Online은 상시 모니터링합니다.
  • 최종 답변만 보면 우연히 맞았는지 알 수 없습니다. 라우팅·tool 사용·근거 포함·recovery를 trajectory 단위로 평가하고 TraceCollector로 자동 수집합니다.
  • LLM-as-Judge는 정확성·도구 사용 적절성·효율성을 1~5점 rubric으로 채점하되 verbosity bias와 self-bias가 있어 human spot check를 병행합니다.
  • 비결정성에는 다회 실행 합의, temperature=0+seed 고정(디버깅용), assertion 기반 property 검증으로 대응하고 exact match보다 계약 준수와 금지 행위 부재를 지표로 씁니다.

에이전트 시스템은 "한 번 잘 동작했다"로 검증되지 않습니다. 평가의 목적은 모델 우열을 가리는 것이 아니라, 오케스트레이션 변경이 행동 계약을 깨지 않았는지 확인하는 것입니다.

평가 계층

계층무엇을 검증하는가예시
Unit프롬프트, tool schema, reducerschema 출력 형식, 규칙 함수
Contractagent 입력/출력 계약JSON schema, required field
Trajectorystep sequence와 handoff 품질어떤 tool을 어떤 순서로 썼는지
Scenario실제 업무 시나리오환불 triage, 리서치 보고서 생성
Online실제 운영 지표rejection rate, escalation rate

평가 피라미드

아래에서 위로 올라갈수록 검증 범위는 넓어지고 비용도 커집니다. Unit과 Contract는 매 배포마다, Trajectory와 Scenario는 주기적으로, Online은 상시 모니터링합니다.

무엇을 측정해야 하는가

지표설명
Task success rate목표 달성 여부
Contract compliance출력 스키마 준수율
Tool accuracy올바른 tool 선택과 인자 정확도
Handoff quality다음 단계가 충분한 정보를 받았는지
Human override rate자동화 신뢰 수준
Cost / latency운영 가능성 판단

trajectory 평가가 중요한 이유

최종 답변만 보면 시스템이 우연히 맞았는지, 제대로 된 경로로 맞았는지 알 수 없습니다. 멀티에이전트 시스템에서는 어떤 step를 거쳤는지가 품질과 비용을 함께 좌우합니다.

trajectory 평가 예시

항목질문
Routing올바른 agent가 선택되었는가
Tool use불필요한 tool 호출이 있었는가
Evidence근거가 handoff에 포함되었는가
Recovery실패 후 적절한 fallback이 있었는가
Final action승인 또는 중단 조건을 지켰는가

trajectory 비교: 좋은 경로 vs 나쁜 경로

같은 최종 결과라도 trajectory가 다르면 시스템 품질이 달라집니다.

위 예시의 Bad trajectory에서는 라우팅 오류, tool 중복 호출, 근거 누락이 한꺼번에 일어났습니다. 최종 답변이 비슷해 보여도 trajectory 평가에서 이 차이를 잡아야 합니다.

테스트 세트 구성 원칙

1. happy path만 넣지 않는다

다음과 같은 케이스가 반드시 포함되어야 합니다.

  • 애매한 입력
  • policy conflict
  • tool timeout
  • low confidence 분류
  • human approval 필요 케이스

2. negative example을 많이 수집한다

라우팅과 평가 루프는 애매한 입력에서 가장 잘 망가집니다. 그래서 오답 사례, 고객 불만 사례, 운영 사고 사례를 테스트 세트에 넣어 두는 편이 좋습니다.

3. 정답 하나보다 허용 범위를 정의한다

에이전트 출력은 비결정적일 수 있습니다. 완전 일치보다 "필수 필드 존재", "금지 행위 없음", "근거 포함" 같은 기준이 더 현실적입니다.

비결정성 대응

문제대응
문장 표현이 매번 다름semantic/assertion 기반 검사
tool 선택이 흔들림trajectory 평가와 deterministic pre-filter
모델 변경 후 점수 변동frozen eval set 유지
judge 편향rubric 공개, human spot check 병행

Trajectory 자동 수집 파이프라인

trajectory 평가를 자동화하려면 먼저 에이전트 실행 trace를 구조화된 trajectory로 수집해야 합니다. 수동 로깅은 누락과 형식 불일치가 잦습니다. 그래서 실행 환경에 계측(instrumentation)을 내장하는 것이 핵심입니다.

수집 아키텍처

TrajectoryStep 타입 정의

/** 에이전트 실행의 단일 단계를 표현합니다. */
type TrajectoryStep = {
  /** 이 단계를 실행한 에이전트 이름 */
  agent: string
  /** 수행한 행위 — tool 호출, handoff, 응답 등 */
  action: string
  /** 에이전트에 주어진 입력 (프롬프트, 이전 결과 등) */
  input: unknown
  /** 에이전트가 반환한 출력 */
  output: unknown
  /** 실행 소요 시간 (ms) */
  duration: number
  /** 소비된 토큰 수 (prompt + completion) */
  tokens: { prompt: number; completion: number }
}

type Trajectory = {
  traceId: string
  startedAt: string          // ISO 8601
  steps: TrajectoryStep[]
  totalDuration: number      // ms
  totalTokens: number
  success: boolean
}

수집기 구현

import { randomUUID } from 'node:crypto'

class TraceCollector {
  private steps: TrajectoryStep[] = []
  private startTime = Date.now()
  readonly traceId = randomUUID()

  /** 각 에이전트 호출을 감싸는 wrapper */
  async wrap<T>(
    agent: string,
    action: string,
    input: unknown,
    fn: () => Promise<T>,
  ): Promise<T> {
    const stepStart = Date.now()
    const tokensBefore = this.currentTokenUsage()

    const result = await fn()

    const tokensAfter = this.currentTokenUsage()
    this.steps.push({
      agent,
      action,
      input,
      output: result,
      duration: Date.now() - stepStart,
      tokens: {
        prompt: tokensAfter.prompt - tokensBefore.prompt,
        completion: tokensAfter.completion - tokensBefore.completion,
      },
    })
    return result
  }

  /** 전체 trajectory를 직렬화합니다. */
  finalize(success: boolean): Trajectory {
    const totalTokens = this.steps.reduce(
      (sum, s) => sum + s.tokens.prompt + s.tokens.completion,
      0,
    )
    return {
      traceId: this.traceId,
      startedAt: new Date(this.startTime).toISOString(),
      steps: this.steps,
      totalDuration: Date.now() - this.startTime,
      totalTokens,
      success,
    }
  }

  private currentTokenUsage() {
    // 실제 구현에서는 LLM 클라이언트의 usage 카운터를 읽습니다
    return { prompt: 0, completion: 0 }
  }
}

사용 예시

const collector = new TraceCollector()

// 라우팅 단계 수집
const routeResult = await collector.wrap(
  'router', 'classify_intent', userMessage,
  () => routerAgent.run(userMessage),
)

// 실행 단계 수집
const execResult = await collector.wrap(
  routeResult.targetAgent, 'execute', routeResult,
  () => executeAgent.run(routeResult),
)

// 저장
const trajectory = collector.finalize(execResult.success)
await trajectoryStore.save(trajectory)

분석 쿼리 패턴

수집된 trajectory에서 반복적으로 확인해야 하는 항목들입니다.

쿼리목적예시 필터
평균 step 수불필요한 단계 탐지steps.length > threshold
tool 중복 호출률비효율 패턴 발견연속 동일 action 카운트
handoff 누락률context 전달 품질output에 필수 필드 부재
에이전트별 토큰 분포비용 최적화 포인트tokens.completion 상위 10%
실패 직전 step 패턴공통 실패 원인success === false인 trajectory의 마지막 2 step

LLM-as-Judge 평가 패턴

사람이 모든 trajectory를 직접 평가하기란 현실적이지 않습니다. LLM을 judge로 쓰면 대규모 평가를 자동화하면서도 rubric과 spot check로 편향을 잡을 수 있습니다.

평가 구조

패턴 1: 정확성 평가 (Factual Correctness)

에이전트의 최종 응답이 사실에 부합하는지 평가합니다.

당신은 에이전트 응답의 사실 정확성을 평가하는 전문 평가자입니다.

## 입력
- 사용자 질문: {question}
- 에이전트 응답: {agent_response}
- 참조 정답 (있는 경우): {reference_answer}
- 에이전트가 사용한 도구 결과: {tool_outputs}

## 평가 기준
1. 응답에 포함된 사실이 도구 결과와 일치하는가
2. 도구 결과에 없는 내용을 지어내지 않았는가 (hallucination)
3. 핵심 정보가 누락되지 않았는가
4. 수치, 날짜, 고유명사가 정확한가

## 출력 형식 (JSON)
{
  "score": 1-5,
  "hallucination_found": boolean,
  "missing_facts": ["누락된 사실 목록"],
  "reasoning": "채점 근거를 2-3문장으로 설명"
}

패턴 2: 도구 사용 적절성 (Tool Selection Relevance)

에이전트가 올바른 도구를 올바른 순서로 사용했는지 평가합니다.

당신은 에이전트의 도구 사용 품질을 평가하는 전문 평가자입니다.

## 입력
- 사용자 질문: {question}
- 사용 가능한 도구 목록: {available_tools}
- 에이전트의 도구 호출 기록:
  {tool_call_sequence}

## 평가 기준
1. 질문 해결에 필요한 도구를 모두 사용했는가
2. 불필요한 도구를 호출하지 않았는가
3. 도구 호출 순서가 논리적인가 (의존 관계 준수)
4. 도구에 전달한 인자가 적절한가
5. 같은 도구를 반복 호출한 경우 그 이유가 합리적인가

## 출력 형식 (JSON)
{
  "score": 1-5,
  "unnecessary_calls": ["불필요한 호출 목록"],
  "missing_calls": ["누락된 호출 목록"],
  "order_issues": ["순서 문제 설명"],
  "reasoning": "채점 근거를 2-3문장으로 설명"
}

패턴 3: Trajectory 효율성 (Efficiency & Cost)

전체 trajectory가 비용 대비 효율적이었는지 평가합니다.

당신은 에이전트 trajectory의 효율성을 평가하는 전문 평가자입니다.

## 입력
- 사용자 질문: {question}
- 전체 trajectory:
  {trajectory_steps}
- 총 소요 시간: {total_duration}ms
- 총 토큰 사용량: {total_tokens}

## 평가 기준
1. 동일한 결과를 더 적은 단계로 달성할 수 있었는가
2. 병렬 실행 가능한 단계를 순차 실행하지 않았는가
3. 불필요한 재시도(retry)가 있었는가
4. 토큰 사용량이 작업 복잡도에 비해 과도하지 않은가
5. 전체 소요 시간이 SLA 기준을 충족하는가

## 출력 형식 (JSON)
{
  "score": 1-5,
  "redundant_steps": ["제거 가능한 step 목록"],
  "parallelizable_steps": ["병렬화 가능한 step 쌍"],
  "token_efficiency": "적정 | 과다 | 매우 과다",
  "reasoning": "채점 근거를 2-3문장으로 설명"
}

채점 Rubric (1-5점 척도)

세 가지 평가 패턴 모두에 공통으로 적용할 수 있는 일반 rubric입니다.

점수기준설명
5Excellent개선할 점이 없음. 모든 기준을 완벽히 충족
4Good사소한 개선점이 있으나 결과에 영향 없음
3Acceptable결과는 맞으나 과정에 명확한 비효율 또는 누락 존재
2Poor핵심 기준 1개 이상 미달. 결과 신뢰도에 영향
1Fail핵심 기준 다수 미달. 재실행 또는 사람 개입 필요

Judge 결과 활용 전략

Judge 편향 주의

LLM judge는 장문 응답에 높은 점수를 주는 경향(verbosity bias)과 자기 모델 출력에 관대한 경향(self-bias)이 있습니다. 반드시 human spot check를 병행하고, 점수 분포가 한쪽으로 치우치면 rubric을 재조정하세요.

  • 3점 이하 trajectory: 자동 플래그 → 사람 리뷰 큐로 전송
  • 패턴별 교차 검증: 정확성 5점이지만 효율성 2점인 경우 → 비용 최적화 대상
  • 시계열 추적: 동일 시나리오의 judge 점수 추이를 모니터링하여 회귀 감지

비결정성 대응 프레임워크

에이전트는 같은 입력을 줘도 매번 다른 결과를 낼 수 있습니다. 이건 버그가 아니라 LLM의 특성입니다. 그러니 "정확한 답"을 기대하는 테스트가 아니라 "속성(property)"을 검증하는 테스트로 전환해야 합니다.

비결정성의 원천

전략 1: 다회 실행 합의 (Multi-Run Consensus)

같은 입력을 N회 실행하여 결과 분포를 확인합니다.

async function multiRunConsensus<T>(
  fn: () => Promise<T>,
  runs: number,
  extractKey: (result: T) => string,
): Promise<{ majority: string; agreement: number; results: T[] }> {
  const results = await Promise.all(
    Array.from({ length: runs }, () => fn()),
  )

  const counts = new Map<string, number>()
  for (const r of results) {
    const key = extractKey(r)
    counts.set(key, (counts.get(key) ?? 0) + 1)
  }

  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1])
  const [majority, majorityCount] = sorted[0]

  return {
    majority,
    agreement: majorityCount / runs,  // 0.0 ~ 1.0
    results,
  }
}

// 사용 예: 라우팅 결과가 5회 중 4회 이상 일치하는지 확인
const consensus = await multiRunConsensus(
  () => routerAgent.classify(userMessage),
  5,
  (r) => r.targetAgent,
)
assert(consensus.agreement >= 0.8, '라우팅 합의율 80% 미달')

전략 2: Temperature · Seed 고정

재현 가능한 테스트를 위해 모델 파라미터를 고정합니다.

파라미터설정효과
temperature0가장 확률 높은 토큰만 선택 (greedy)
seed고정값 (예: 42)동일 seed에서 동일 출력 (provider 지원 시)
top_p1temperature 0과 함께 사용하면 결정적 출력

한계

temperature=0 + seed 고정은 단일 provider의 단일 모델 버전에서만 재현 가능합니다. 모델 업데이트, 인프라 변경, 캐시 상태에 따라 결과가 달라질 수 있으므로 이 전략은 개발 중 디버깅용으로만 사용하세요. 프로덕션 평가에서는 assertion-based testing이 더 안정적입니다.

전략 3: Assertion-Based Testing (Property 검증)

"정확한 답"이 아닌 "반드시 만족해야 하는 속성" 을 검증합니다. 비결정적 출력에서도 이 방식은 안정적으로 동작합니다.

Property 유형

유형설명예시
Structural출력 구조가 스키마를 준수하는가필수 필드 존재, 타입 일치
Behavioral금지된 행위를 하지 않았는가PII 노출 없음, 승인 없이 결제 불가
Semantic의미적으로 올바른가응답이 질문 주제와 관련 있음
Boundary제약 조건을 지켰는가토큰 한도, 시간 제한, 재시도 횟수

구현 예시

import { describe, it, expect } from 'vitest'

/** 에이전트 출력의 속성을 검증하는 assertion 모음 */
const agentAssertions = {
  /** 구조적 속성: 필수 필드가 존재하는가 */
  hasRequiredFields(output: Record<string, unknown>, fields: string[]) {
    for (const field of fields) {
      expect(output).toHaveProperty(field)
    }
  },

  /** 행위 속성: 금지 패턴이 출력에 없는가 */
  noProhibitedPatterns(text: string, patterns: RegExp[]) {
    for (const pattern of patterns) {
      expect(text).not.toMatch(pattern)
    }
  },

  /** 의미 속성: 응답이 주어진 주제와 관련 있는가 */
  async isRelevantTo(response: string, topic: string, threshold = 0.7) {
    const score = await semanticSimilarity(response, topic)
    expect(score).toBeGreaterThanOrEqual(threshold)
  },

  /** 경계 속성: 수치가 허용 범위 안인가 */
  withinBounds(value: number, min: number, max: number) {
    expect(value).toBeGreaterThanOrEqual(min)
    expect(value).toBeLessThanOrEqual(max)
  },
}

describe('환불 처리 에이전트', () => {
  it('응답에 필수 필드가 포함된다', async () => {
    const result = await refundAgent.run(sampleRequest)
    agentAssertions.hasRequiredFields(result, [
      'decision',
      'reasoning',
      'amount',
    ])
  })

  it('PII를 노출하지 않는다', async () => {
    const result = await refundAgent.run(sampleRequest)
    agentAssertions.noProhibitedPatterns(result.reasoning, [
      /\d{3}-\d{2}-\d{4}/,     // SSN
      /\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}/, // 카드번호
      /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+/,        // 이메일
    ])
  })

  it('환불 금액이 원래 주문 금액을 초과하지 않는다', async () => {
    const result = await refundAgent.run(sampleRequest)
    agentAssertions.withinBounds(
      result.amount,
      0,
      sampleRequest.orderTotal,
    )
  })

  it('5회 실행 중 결정이 80% 이상 일치한다', async () => {
    const consensus = await multiRunConsensus(
      () => refundAgent.run(sampleRequest),
      5,
      (r) => r.decision,
    )
    expect(consensus.agreement).toBeGreaterThanOrEqual(0.8)
  })
})

전략 조합 가이드

상황추천 전략이유
개발 중 디버깅temperature=0 + seed 고정재현 가능한 환경에서 빠르게 반복
CI 파이프라인assertion-based비결정성에 강건하고 자동화 가능
라우팅 안정성 검증다회 실행 합의분류 결과의 일관성 수치화
프로덕션 모니터링assertion + LLM judge규모 확장 가능하면서 의미 평가 포함
모델 교체 검증전체 조합frozen eval set에 모든 전략 적용

운영 전/후 평가 루프

단계해야 할 일
개발 중unit/contract 테스트와 small eval set
배포 전scenario eval, shadow run, canary
배포 후rejection rate, incident review, drift detection

예시: 평가 레코드

type EvalRecord = {
  caseId: string
  expectedAction: 'respond' | 'escalate' | 'abort'
  contractPassed: boolean
  toolErrors: number
  humanOverride: boolean
  score: number
}

중요한 건 완벽한 점수가 아니라 회귀를 감지할 수 있는 일관된 기록 체계입니다.

안티패턴

안티패턴문제개선
최종 응답만 평가과정 품질을 놓침trajectory 평가 추가
데모용 예제만 테스트실제 운영 문제를 못 잡음negative/ambiguous case 포함
한 번 만든 eval set을 방치drift 감지 실패분기별 갱신
judge model 결과를 절대시편향과 오판 위험rule + human sampling 병행

ADR 스타일 결론

Decision

평가는 최종 텍스트 품질만이 아니라, 라우팅, tool 사용, handoff, recovery를 포함한 trajectory 단위로 설계합니다. 비결정성을 감안해 exact match보다 계약 준수와 금지 행위 부재를 중심 지표로 사용하고, 운영 지표와 연결된 online eval을 함께 둡니다.

실무 체크리스트

  • unit, contract, scenario eval이 분리되어 있는가
  • negative example과 ambiguous case가 충분한가
  • final output뿐 아니라 trajectory도 기록하는가
  • human override rate를 보고 있는가
  • 배포 전 canary 또는 shadow run이 있는가

다음에 읽을 장

  • 에러 처리와 복구 전략
  • 프로덕션 운영 패턴
  • 실전 아키텍처 사례

관련 문서

Ch3. 평가 프레임워크

LLMOps·AgentOps 프로덕션 · 품질·안전·효율·안정성 4축과 Composite Score, trace-first 평가 루프, hard gate로 LLM 릴리즈 합격 여부를 판정하는 평가 체계

Ch12. Evals와 품질 게이트

엔터프라이즈 Eve 에이전트 개발 · Eve eval runner와 assertion surface를 활용해 에이전트 회귀를 막는 품질 게이트를 설계한다.

관측성·평가

Vercel 엔터프라이즈 AI 플랫폼 · AI Gateway, Workflow, Vercel Observability, AI SDK telemetry를 연결해 품질과 운영 신호를 하나의 루프로 관리하는 방법을 정리합니다.

에러 처리와 복구 전략

재시도/폴백/서킷브레이커, 에이전트 실패 격리, Human-in-the-Loop, 부분 실패 복구

프로덕션 운영 패턴

모델 라우팅, 캐싱, 관측성(트레이스/스팬), 비용 추적, SLO 정의, 보안/거버넌스, 가드레일, 스케일링

On this page

평가 계층평가 피라미드무엇을 측정해야 하는가trajectory 평가가 중요한 이유trajectory 평가 예시trajectory 비교: 좋은 경로 vs 나쁜 경로테스트 세트 구성 원칙1. happy path만 넣지 않는다2. negative example을 많이 수집한다3. 정답 하나보다 허용 범위를 정의한다비결정성 대응Trajectory 자동 수집 파이프라인수집 아키텍처TrajectoryStep 타입 정의수집기 구현사용 예시분석 쿼리 패턴LLM-as-Judge 평가 패턴평가 구조패턴 1: 정확성 평가 (Factual Correctness)패턴 2: 도구 사용 적절성 (Tool Selection Relevance)패턴 3: Trajectory 효율성 (Efficiency & Cost)채점 Rubric (1-5점 척도)Judge 결과 활용 전략비결정성 대응 프레임워크비결정성의 원천전략 1: 다회 실행 합의 (Multi-Run Consensus)전략 2: Temperature · Seed 고정전략 3: Assertion-Based Testing (Property 검증)Property 유형구현 예시전략 조합 가이드운영 전/후 평가 루프예시: 평가 레코드안티패턴ADR 스타일 결론실무 체크리스트다음에 읽을 장