AWS 기술 블로그

Amazon Bedrock 모델 promptfoo 로 성능 평가하기

들어가며

“프롬프트를 바꿨는데, 정말 더 나아진 걸까?” 생성형 AI 애플리케이션을 개발하는 엔지니어라면 누구나 한 번쯤 마주치는 질문입니다. 프롬프트 한 줄을 수정하거나 모델을 교체할 때마다 품질이 좋아졌는지 확신하기 어렵고, 여러 모델을 두고 어느 쪽이 서비스에 더 맞는지 판단하려면 같은 질문을 양쪽에 던져 결과를 일일이 눈으로 비교해야 합니다.

문제는 대규모 언어 모델(Large Language Model, 이하 LLM)이 비결정적(non-deterministic) 이라는 점입니다. 같은 입력에도 출력이 매번 달라지기 때문에, 전통적인 소프트웨어의 단위 테스트처럼 assertEquals 한 줄로 품질을 판정하기가 어렵습니다. 모델 스코어 카드만으로는 실제 응답 품질을 확신할 수 없기 때문에, 가장 확실한 방법은 골든 테스트셋으로 직접 측정해 비교하는 것입니다. 다만 무엇을 어떻게 측정할지 정하는 것도, 평가 환경을 준비하는 것도 만만치 않습니다.

본 글에서는 오픈소스 LLM 평가 프레임워크인 promptfoo를 사용해 LLM 응답 품질을 손쉽게 평가하는 방법을 다룹니다. 평가 대상으로는 Amazon Bedrock에서 제공하는 모델 3종(Anthropic Claude Sonnet 4.6, Claude 3 Haiku, Amazon Nova Pro)을 사용하며, 채점용 모델(LLM judge)까지 Amazon Bedrock으로 구성합니다.

Amazon Bedrock 은 Claude와 Nova처럼 서로 다른 제공사의 모델은 물론, 채점용 평가 모델까지 별도의 API 키 발급 없이 하나의 API와 IAM 인증만으로 호출할 수 있어 아주 편리합니다. 비교할 모델을 추가하거나 교체하고 싶을 때도 설정 파일에 모델 ID 한 줄만 더하면 되니, 새로운 모델이 나올 때마다 부담 없이 평가 대상에 올려볼 수 있습니다. 작은 설정 파일 하나로 시작하는 이 평가 과정을, 지금부터 단계별로 살펴보겠습니다.

promptfoo 란?

promptfooLLM 성능 평가를 위한 단위 테스트 프레임워크입니다. 소프트웨어 개발에서 코드를 변경할 때마다 단위 테스트를 돌려 로직에 오류가 없는지 확인하듯이, promptfoo는 프롬프트나 모델을 변경할 때마다 평가를 실행하여 응답 품질이 유지되는지 확인할 수 있게 해줍니다. 프롬프트, 비교할 모델 목록, 테스트 케이스를 하나의 YAML 파일에 선언적으로 정의하면, 여러 모델 × 여러 테스트 케이스 × 여러 평가 기준을 한 번에 실행하고 그 결과를 비교할 수 있습니다.

promptfoo 는 다음과 같은 장점을 제공합니다.

  • 로컬 우선(Local-first) 실행: 기본 실행이 로컬 환경에서 이루어지고 결과도 로컬에 저장됩니다. 테스트 데이터가 외부 SaaS로 전송되지 않아 보안·컴플라이언스 측면에서 유리합니다.
  • 80+개 이상의 프로바이더 지원: OpenAI, Anthropic, Amazon Bedrock, Google Vertex AI, Ollama는 물론 임의의 HTTP 엔드포인트까지 동일한 방식으로 평가할 수 있습니다.
  • 선언적 설정: 프롬프트, 프로바이더, 테스트 케이스를 YAML 하나에 정의하므로, 설정 자체를 코드처럼 Git으로 버전 관리할 수 있습니다.
  • CI/CD 친화적: promptfoo eval 명령은 테스트 실패 시 0이 아닌 종료 코드(exit code)를 반환합니다. 따라서 GitHub Actions 같은 파이프라인에 그대로 연결하여 품질 미달 시 빌드를 자동 중단시킬 수 있습니다.
  • 시각화 리포트: promptfoo view 명령으로 모델별 응답, 차이(diff), 점수를 브라우저에서 한눈에 확인할 수 있습니다.

promptfoo의 3대 요소

promptfoo설정 파일(promptfooconfig.yaml)을 이용하여 무엇을 묻고(prompts), 누구에게 묻고(providers), 어떻게 채점할지(tests) 라는 세 가지를 정의합니다.

  • prompts: 테스트할 프롬프트 템플릿입니다. {{변수}} 형태로 정의해 두면, tests에 정의한 입력값이 그 자리에 채워져 실행 됩니다. 프롬프트를 여러 개 등록하면 각 프롬프트가 모든 모델에 대해 따로 평가되므로, “프롬프트 A와 B 중 어느 쪽이 더 나은가”를 비교할 수 있습니다.
  • providers: 비교할 모델 목록입니다. 여기에 나열한 모델 수만큼 동일한 프롬프트가 각각 호출됩니다. 같은 모델을 temperature나 max_tokens 같은 파라미터만 바꿔 여러 번 등록하면, 파라미터가 결과에 미치는 영향도 비교할 수 있습니다.
  • tests: 각 테스트 케이스는 프롬프트에 주입할 입력값(vars)과 그 응답을 검사할 평가 기준(assert)으로 이루어집니다. 모든 케이스에 공통으로 적용할 기준은 defaultTest에 한 번만 선언하여 중복을 없앨 수 있고, 특정 케이스에만 적용되는 기준은 그 케이스의 assert에만 추가합니다.

위의 세 요소는 곱(조합)으로 동작합니다. 즉 ‘프롬프트 수 × 모델(provider) 수 × 테스트 케이스 수‘ 만큼의 실행이 발생하며, 그 각각에 대해 assert가 채점됩니다. 예를 들어 프롬프트 1개, 모델 3개, 테스트 케이스 3개라면 총 9번(1 × 3 × 3)의 응답이 생성되고 채점됩니다. 이렇게 한 번의 실행으로 여러 조합을 동시에 비교할 수 있다는 점이 promptfoo의 최대 장점 입니다.

promptfoo 평가 방식

promptfoo 가 강력한 이유는 결정적(Deterministic) 방식과 모델 기반(Model-assisted) 평가 방식을 동시에 제공하기 때문입니다.

결정적(Deterministic) 평가

결정적 평가는 코드로 합격 여부를 판정하는 방식입니다. 특정 문자열 포함 여부(contains), JSON 형식 검증(is-json), 응답 시간 제한(latency), 비용 제한(cost) 등이 이에 해당하며, 명확하고 객관적인 평가 기준이 있을 때 매우 유용합니다.

assert:
  ` `*# 출력이 유효한 JSON 형식인가*
   - type: is-json
   *# 출력에 특정 문자열이 포함되어 있는가*
   - type: contains
     value: "Asia/Seoul"
   *# 응답 시간이 5초를 넘으면 실패*
   - type: latency
     threshold: 5000

위 예시는 JSON 형식과 특정 문자열 포함 여부, 응답 시간을 검사합니다. 셋 다 판정 기준이 명확하기 때문에, 채점용 LLM 없이 코드만으로 pass/fail이 결정됩니다.

모델 기반(Model-assisted) 평가

반면 모델 기반 평가는 채점용 LLM이 다른 LLM의 출력을 채점하는 방식으로, llm-rubric이 대표적입니다. 자연어로 채점 기준을 작성하면 채점용 LLM이 이를 해석해 점수를 매기고 합격 여부를 판정합니다. 코드로 딱 떨어지지 않는 “답변이 친절한가?”, “기술적으로 정확한가?” 같은 주관적인 기준도 평가할 수 있어, 사람이 일일이 검수하는 수고를 크게 줄여줍니다. 한 가지 유의할 점은, 채점용 LLM으로는 평가 대상보다 더 뛰어난 모델을 쓰는 것이 바람직하다는 것입니다. 채점이 대상 모델의 답변을 제대로 짚어낼 만큼 충분한 판단력을 갖춰야 하기 때문입니다. 예를 들어 경량 모델인 Claude Haiku의 답변을 평가할 때는, 상위 모델인 Claude Sonnet이나 Opus를 평가 모델로 지정하는 식입니다.

아래 예시는 llm-rubric 기능을 활용한 모델 기반 평가로, “기술적으로 정확하고 초보자도 이해하기 쉬운가”라는 주관적 기준을 자연어 문장 그대로 채점 기준으로 사용하였습니다.

assert:
    *# 채점 기준을 자연어로 작성하면 평가 모델이 판정합니다*
    - type: llm-rubric
        value: "답변이 기술적으로 정확하고, 초보자도 이해하기 쉽게 설명하는가?"

이렇게 하는 경우 평가 모델은 이 기준에 따라 다음과 같이 score(0.0~1.0)와 근거(reason), 통과 여부(pass)를 반환하게 됩니다.

{
    "reason": "기술적으로 정확하며, 예시를 들어 친절하게 설명함",
    "score": 0.9,
    "pass": true
}

참고로, Rubric은 “평가 기준표”라는 뜻으로, 라틴어 rubrica(붉은 황토)에서 유래되었습니다.

합격 여부를 정하는 규칙 — threshold와 weight

앞서 본 두 평가 방식, 즉 결정적 평가와 모델 기반 평가는 각 기준마다 0.0~1.0 사이의 점수를 만들어냅니다. 결정적 평가는 통과를 1, 실패를 0으로 내고, 모델 기반 평가는 그 사이의 연속값을 냅니다. 그렇다면 한 테스트 케이스에 기준이 여러 개일 때, 케이스 전체의 합격 여부는 어떻게 정해질까요? promptfoo는 먼저 케이스 안의 모든 기준 점수를 가중 평균합니다. 각 기준에는 weight(가중치)를 지정할 수 있어서, 더 중요한 기준에 큰 값을 주면 그만큼 전체 점수에 크게 반영됩니다. weight를 명시하지 않으면 모든 기준이 동일한 가중치(기본값 1)를 가지므로, 결국 단순 평균과 같아집니다. 그다음 합격 여부는 threshold 설정에 따라 갈립니다.

  • threshold를 지정하면: 가중 평균 점수가 그 값 이상일 때 통과입니다. 개별 기준 하나가 실패하더라도, 평균이 기준선을 넘으면 케이스는 통과로 처리됩니다.
  • threshold를 지정하지 않으면: 가중 평균과 무관하게, 모든 기준이 각각 통과해야 케이스가 통과합니다. 하나라도 실패하면 케이스는 실패입니다.

즉 threshold는 평가 방식이 아니라, “여러 기준을 하나의 합격/불합격으로 어떻게 묶을지”를 정하는 규칙입니다.

예시

한 테스트 케이스에 평가 기준 두 개를 두고, 통과 기준선(threshold)을 0.7로 설정했다고 합시다. 이때 “기술적 정확성”을 “초보자 이해도”보다 두 배 중요하게 보고 싶다면, weight로 비중을 다르게 줍니다.

tests:
  - vars:
      question: "Docker 컨테이너와 가상머신의 차이는?"
    # 아래 기준들의 가중 평균이 0.7 이상이어야 이 케이스는 통과
    threshold: 0.7
    assert:
      - type: llm-rubric
        value: "기술적으로 정확한가?"
        weight: 2          # 2배 비중
      - type: llm-rubric
        value: "초보자도 이해할 수 있는가?"
        weight: 1          # 기본 비중

평가 결과가 기술 정확성 0.9, 초보자 이해도 0.5로 나왔다면, 가중 평균은 다음과 같습니다.

(0.9 × 2 + 0.5 × 1) ÷ (2 + 1) = 2.3 ÷ 3 ≈ 0.77

0.77은 기준선 0.7 이상이므로 케이스는 통과입니다. 만약 가중치를 주지 않았다면 단순 평균이 (0.9 + 0.5) ÷ 2 = 0.7로 아슬아슬하게 걸쳤겠지만, 더 중요한 기준(정확성)의 점수가 높았던 덕분에 가중 평균이 기준선 위로 올라간 것입니다.

Amazon Bedrock 사용하기

본 블로그에서는 promptfoo의 테스트 대상 모델로 Amazon Bedrock 에서 제공하는 모델을 사용합니다. Bedrock은 Claude, Nova 등 여러 회사의 모델을 하나의 API와 하나의 인증 방식(IAM)으로 호출할 수 있어, 모델 제공사마다 API 키를 따로 발급받을 필요가 없습니다. 채점을 맡는 채점용 모델까지 Bedrock으로 두면, 평가에 쓰이는 모든 모델을 한곳에서 관리할 수 있다는 장점도 있습니다.

1. AWS 인증 설정

promptfoo가 Bedrock 모델을 호출하려면 먼저 “이 사람이 AWS 자원을 쓸 권한이 있는지”를 확인하는 인증이 필요합니다. 다행히 따로 복잡한 설정을 할 필요는 없습니다. promptfoo는 AWS가 자격 증명을 찾는 표준 순서를 그대로 따르기 때문에, 평소 AWS를 쓰던 방식대로 인증만 해두면 알아서 그 정보를 가져다 씁니다. 가장 간단한 방법은 AWS CLI로 자격 증명을 한 번 등록해 두는 것입니다. 아래 명령을 실행하고 발급받은 키와 사용할 리전을 입력하면 됩니다.

% aws configure
AWS Access Key ID [None]: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
AWS Secret Access Key [None]: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
Default region name [None]: ap-northeast-2
Default output format [None]: json

키를 파일로 저장하는 대신, 환경 변수로 직접 넣어줄 수도 있습니다.

export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_DEFAULT_REGION="ap-northeast-2"

참고로 Amazon EC2나 AWS Lambda처럼 AWS 안에서 실행하는 환경이라면, 거기에 부여된 IAM 역할이 자동으로 인증을 처리해 주기 때문에 위와 같은 키 설정조차 필요하지 않습니다.

2. Bedrock 지원 모델 확인

Bedrock 의 경우 리전별로 제공되는 모델이 다르므로, 설정된 리전을 확인한 뒤 해당 리전에서 호출 가능한 모델 ID를 조회합니다.

aws configure get region
aws bedrock list-foundation-models \
  --query "modelSummaries[*].modelId" \
  --output table

--------------------------------------------------
|              ListFoundationModels              |
+------------------------------------------------+
|  anthropic.claude-opus-4-8                     |
|  amazon.nova-2-lite-v1:0                       |
|  anthropic.claude-opus-4-7                     |
|  anthropic.claude-haiku-4-5-20251001-v1:0      |
|  twelvelabs.pegasus-1-2-v1:0                   |
|  twelvelabs.marengo-embed-3-0-v1:0             |
|  cohere.embed-v4:0                             |
|  anthropic.claude-sonnet-4-5-20250929-v1:0     |
|  ...                                           |
+------------------------------------------------+

또한, AWS Bedrock 콘솔의 Model catalog 메뉴에서 아래와 같이 각 리전에서 사용 가능한 모델 리스트를 조회할 수 있습니다.

[참고] Anthropic의 Claude 모델은 최초 1회에 한해 사용 목적을 기재한 양식을 제출해야 하며, 제출 즉시 액세스할 수 있습니다.

3. 추론 프로파일 및 크로스 리전 프리픽스

Bedrock의 추론 프로파일(inference profile) 은 모델 호출을 어느 리전으로 라우팅할지 정의하는 AWS 리소스입니다. 모델 ID 앞에 us. eu. apac. global. 과 같은 크로스 리전 프리픽스가 붙게 되고, 이를 통해 모델 호출의 부하를 AWS 전세계 리전으로 분산 시켜 처리량과 가용성을 높여 줍니다.

프리픽스 라우팅 범위
us. 미국 리전들 (us-east-1, us-west-2 등)
eu. 유럽 리전들 (eu-west-1, eu-central-1 등)
apac. 아시아 태평양 리전들
global. 전 세계 상용 리전

예를 들어 서울 리전에서 호출 가능한 모델 리스트는 다음과 같은 AWS CLI 명령어를 이용해서 조회할 수 있습니다.

aws bedrock list-inference-profiles \
  --region ap-northeast-2 \
  --query "inferenceProfileSummaries[*].[inferenceProfileId,status]" \
  --output table

----------------------------------------------------------------
|                     ListInferenceProfiles                    |
+----------------------------------------------------+---------+
|  apac.anthropic.claude-3-sonnet-20240229-v1:0      |  ACTIVE |
|  apac.anthropic.claude-3-5-sonnet-20240620-v1:0    |  ACTIVE |
|  apac.anthropic.claude-3-haiku-20240307-v1:0       |  ACTIVE |
|  apac.anthropic.claude-3-5-sonnet-20241022-v2:0    |  ACTIVE |
|  apac.amazon.nova-micro-v1:0                       |  ACTIVE |
|  apac.amazon.nova-lite-v1:0                        |  ACTIVE |
|  apac.amazon.nova-pro-v1:0                         |  ACTIVE |
|  apac.anthropic.claude-sonnet-4-20250514-v1:0      |  ACTIVE |
|  apac.twelvelabs.pegasus-1-2-v1:0                  |  ACTIVE |
|  apac.twelvelabs.marengo-embed-2-7-v1:0            |  ACTIVE |
|  global.cohere.embed-v4:0                          |  ACTIVE |
|  global.twelvelabs.pegasus-1-2-v1:0                |  ACTIVE |
|  ...                                               |         | 
+----------------------------------------------------+---------+

아래 명령어를 사용하여 위에서 조회된 모델에 대한 테스트를 직접 수행할 수 있습니다.

aws bedrock-runtime invoke-model \
  --model-id global.anthropic.claude-opus-4-8 \
  --region ap-northeast-2 \
  --content-type application/json \
  --cli-binary-format raw-in-base64-out \
  --body '{"anthropic_version":"bedrock-2023-05-31","max_tokens":100,"messages":[{"role":"user","content":"hello"}]}' \
  /tmp/response.json

{
    "contentType": "application/json"
}

Amazon Bedrock 모델 성능 평가 하기

1. promptfoo 설치

promptfoo를 사용하려면 먼저 Node.js를 설치해야 합니다. 그다음 npm(Node Package Manager)으로 promptfoo와 Amazon Bedrock SDK를 설치합니다.

brew install node
node --version

npm install promptfoo
npm install @aws-sdk/client-bedrock-runtime

2. 프로젝트 초기화

작업 디렉터리를 만들고 promptfoo를 초기화합니다. 참고로 npx 는 Node.js 용 패키지 실행 도구입니다.

mkdir llm-eval && cd llm-eval
npx promptfoo init

init 를 실행한 후, Compare prompts and models 메뉴을 선택합니다.

[AWS Bedrock] Claude, Llama, Titan, ... 메뉴를 선택합니다.

프로젝트 디렉토리에 README.md 파일과 promptfootconfig.yaml 파일이 생성된 것을 확인할 수 있습니다.

3. 테스트 시나리오 작성하기

Bedrock 이 제공하는 모델들을 평가하기 위해서 promptfooconfig.yaml 파일의 내용을 아래 시나리오로 대체 합니다.

description: AWS Bedrock 모델 비교 평가

prompts:
  - |
    다음 질문에 한국어로 답변해줘.
    정확하고 간결하게 답변할 것.

    질문: {{question}}

*# 모델 프로바이더 설정*
providers:
  *# Sonnet 4.6 (global. 프리픽스)*
  - id: bedrock:global.anthropic.claude-sonnet-4-6
    config: { region: ap-northeast-2, temperature: 0, max_tokens: 1024 }
  *# 3 Haiku (apac. 프리픽스)*
  - id: bedrock:apac.anthropic.claude-3-haiku-20240307-v1:0
    config: { region: ap-northeast-2, temperature: 0, max_tokens: 1024 }
  *# Nova Pro (apac. 프리픽스)*
  - id: bedrock:apac.amazon.nova-pro-v1:0
    config:
      region: ap-northeast-2
      interfaceConfig: { temperature: 0, max_new_tokens: 1024 }

*# 평가 모델 설정*
defaultTest:
  options:
    provider:
      id: bedrock:global.anthropic.claude-opus-4-8
      config: { region: ap-northeast-2, temperature: 0 }
  assert:
    *# bedrock은 cost API를 지원하지 않으므로 주석처리.* 
    #- type: cost
    #  threshold: 0.05
    - type: latency
      threshold: 10000
      metric: 응답속도
    *# 한국어로 답변했는가*
    - type: llm-rubric
      value: "답변이 한국어로 작성되어 있는가?"
      metric: 언어
    *# 기술적 정확성*
    - type: llm-rubric
      value: "기술적으로 틀린 내용이 없는가?"
      metric: 정확성
    *# 구조화된 답변인가*
    - type: llm-rubric
      value: "답변이 논리적으로 구조화되어 있고, 핵심을 먼저 말하는가?"
      metric: 구조
    *# 간결성 (너무 길지 않은가)*
    - type: javascript
      value: "output.length < 3000 ? 1.0 : Math.max(0, 1 - (output.length - 3000) / 5000)"
      metric: 간결성

tests:
  - vars:
      question: "쿠버네티스에서 파드와 디플로이먼트의 차이점은?"
    threshold: 0.7
    assert: # 아래 기준 1개 + defaultTest 5개에 대해서 대해서 test 를 수행한다.
      - type: llm-rubric
        value: "파드와 디플로이먼트의 차이를 정확히 설명하고, 실무 관점의 예시가 포함되어 있는가?"
        metric: 완전성 

  - vars:
      question: "REST API와 GraphQL의 장단점을 비교해줘"
    threshold: 0.7
    assert:
      - type: llm-rubric
        value: "두 기술의 장단점이 균형 있게 설명되어 있는가?"
        metric: 균형성
      - type: contains
        value: "GraphQL"
        metric: 문자열포함

위의 promptfoo 설정은 크게 4가지 부분으로 이뤄져 있습니다.

가장 먼저 나오는 prompts에는 모든 모델에게 똑같이 주어지는 질문 틀이 담겨 있습니다. “한국어로, 정확하고 간결하게 답하라”는 지시와 함께 {{question}} 빈칸이 있고, 이 자리에 tests 섹션에 정의된 실제 질문이 하나씩 채워집니다. 덕분에 질문만 바꿔 가며 모든 모델을 동일한 조건에서 시험할 수 있습니다.

providers에서는 평가 대상 모델을 정의합니다. 여기서는 고성능 모델인 Claude Sonnet 4.6, 빠르고 가벼운 Claude 3 Haiku, 그리고 아마존이 만든 Nova Pro를 사용했습니다. 세 모델 모두 서울 리전(ap-northeast-2)에서 실행하며, 답변이 매번 들쭉날쭉하지 않도록 temperature를 0으로 두어 무작위성을 없앴습니다. 한 가지 눈여겨볼 점은, Nova는 답변 길이를 정하는 설정 키가 max_new_tokens로 Claude 계열의 max_tokens와 다르다는 것입니다.

세 번째 설정은 defaultTest로, 모든 질문에 공통으로 적용되는 채점 기준이 모여 있습니다. 채점 항목은 다섯 가지입니다. 정한 시간 안에 답했는지를 보는 응답 속도, 지정한 언어(한국어)로 답했는지를 보는 언어, 기술적으로 틀린 내용이 없는지를 보는 정확성, 논리적으로 풀어냈는지를 보는 구조, 답변이 너무 길지 않은지를 보는 간결성입니다.

이 항목들은 채점 방식이 둘로 나뉩니다. 언어/정확성/구조처럼 정답이 딱 떨어지지 않는 주관적 기준은, 사람이 아니라 또 다른 모델인 Claude Opus 4.8이 답변을 읽고 판단합니다. AI가 다른 AI의 답변을 평가하는 이른바 LLM-as-a-judge 방식입니다. 반면 응답 속도와 간결성은 평가 모델 없이 promptfoo가 직접 처리합니다. 걸린 시간을 재거나 글자 수를 세는 일은 코드만으로 객관적으로 판정할 수 있기 때문입니다. 특히 간결성은 답변이 3000자를 넘어가면 점수가 점점 깎이도록 계산식을 넣어 두었습니다. 참고로 비용을 재는 항목도 있었지만, 지원되지 않아 주석으로 막아 둔 상태입니다.

채점 항목마다 붙어 있는 metric 태그는 각 결과에 이름표를 달아 주는 역할을 합니다. 평가가 끝나면 promptfoo가 이 이름표별로 점수를 묶어 집계해 주기 때문에, 모델마다 정확성, 간결성, 응답 속도 같은 항목에서 어느 쪽이 강하고 약한지를 한눈에 비교할 수 있습니다. 합격 여부를 좌우하는 설정이 아니라, 결과를 분류해 보여 주기 위한 이름표라고 이해하면 됩니다

마지막 tests 에는 모델에게 실제로 던질 질문이 들어 있습니다. 각 질문에는 앞서 정한 공통 기준 다섯 개가 그대로 적용되고, 거기에 더해 그 질문에만 적용되는 기준이 하나씩 덧붙습니다. 예를 들어 ‘쿠버네티스의 파드와 디플로이먼트의 차이‘를 묻는 질문에는 차이를 정확히 설명하면서 실무 예시까지 들었는지를 보는 완전성 기준이 추가되고, REST API와 GraphQL을 비교하라는 질문에는 양쪽 장단점을 어느 한쪽에 치우치지 않고 균형 있게 다뤘는지를 보는 기준과 함께 답변에 “GraphQL”이라는 단어가 실제로 들어 있는지를 확인하는 기준이 함께 붙습니다. 각 질문에 적힌 0.7이라는 숫자는 합격선으로, 전체 채점 점수 평균이 0.7를 넘어야 통과로 인정됩니다.

왜 Threshold는 0.7 인가?

0.7이라는 합격선은 어떤 절대적인 공식이 있어서가 아니라, 경험적으로 적당하다고 여겨지는 균형점이라 자주 쓰입니다. 기준을 1.0이나 0.9로 너무 높게 잡으면 사소한 흠 하나에도 멀쩡한 답변이 탈락하고, 반대로 0.5처럼 낮게 잡으면 절반만 맞아도 통과해 변별력이 사라집니다. 0.7은 “대부분의 기준을 만족하되 약간의 부족함은 눈감아 주는” 정도의 선이어서, 지나치게 깐깐하지도 너무 느슨하지도 않은 현실적인 합격 기준으로 통용됩니다. 그래서 평가 초기에 별다른 근거가 없을 때는 무난한 출발점으로 0.7을 두고, 이후 결과를 보며 더 높이거나 낮추는 경우가 많습니다.

이 밖의 assert 연산자들

이 글에서는 llm-rubric, latency, javascript, contains 정도의 연산자만 다뤘지만, promptfoo는 이 밖에도 다양한 assert 연산자를 제공합니다. 코드로 검증하는 결정적 연산자로는 출력값이 지정한 값과 정확히 일치하는지 보는 equals, 정규식 패턴에 맞는지 보는 regex,유효한 JSON인지 확인하는 is-json, 호출 비용이 기준 이하인지 보는 cost 등이 있습니다. 채점용 LLM이 활용하는 연산자로는 답변의 사실관계를 따지는 factuality, 질문과의 관련성을 보는 answer-relevance, 여러 기준을 단계적으로 평가하는 g-eval 등이 있습니다. 각 연산자의 정확한 동작과 설정 방법은 promptfoo 공식 문서 에서 확인할 수 있습니다.

4. 테스트 실행하기

다음과 같이 eval 모드로 테스트를 실행합니다. @latest를 붙여 최신 버전의 promptfoo를 사용했으며, —no-cache 옵션을 주어 이전 실행 결과를 재사용하지 않고 모든 요청을 모델에 새로 보내도록 했습니다.

npx promptfoo@latest eval --no-cache

콘솔에 모델별 pass/fail 테이블이 출력됩니다. 어떤 케이스가 통과했고 어떤 케이스가 실패했는지 한눈에 확인할 수 있습니다.

세 개의 Bedrock 모델(Claude Sonnet 4.6, Claude 3 Haiku, Nova Pro)에 동일한 프롬프트로 두 가지 질문을 던져 비교한 평가입니다.

테스트 케이스 수 성공 케이스 수 총 토큰 사용량 답변 생성에 사용된 토큰량 채점에 사용된 토큰량
6 6 22,742 2,960 19,782

이번 평가에서 특히 눈여겨볼 점은 채점에 들어간 토큰량입니다. Grading 항목의 19,782 토큰은 모두 채점을 담당한 Opus(LLM judge)가 사용한 것으로, 답변 생성에 쓰인 2,960 토큰의 6배가 넘습니다. 단순히 점수만 매기는 작업치고는 많아 보이지만, 구조를 들여다보면 이유가 분명합니다.

채점은 응답 하나당 한 번이 아니라 설정한 rubric 개수만큼 반복됩니다. 이번 설정에는 공통 기준(언어·정확성·구조)에 질문별 추가 기준(완전성·균형성)까지 더해져, 6개의 응답에 스무 번이 넘는 채점 요청이 발생했습니다. 게다가 채점할 때마다 “채점 지시문 + 평가기준 + 모델이 생성한 답변 전문”이 통째로 입력되기 때문에, 답변이 길수록 채점 때마다 judge의 입력으로 다시 들어가 토큰이 빠르게 불어납니다. 여기에 judge로 Opus 같은 대형 모델을 쓰면 토큰 단가까지 높아져 비용 부담이 한층 커집니다.

이 비용은 몇 가지 방법으로 줄일 수 있습니다. 먼저 채점 모델을 Sonnet이나 Haiku처럼 더 가벼운 모델로 바꾸면 토큰 단가를 크게 낮출 수 있습니다. 다만 채점의 정확도는 judge 모델의 성능에 좌우되므로, 평가의 신뢰도가 중요하다면 비용이 들더라도 Opus 같은 우수한 모델을 유지하고, 그 외의 방법으로 비용을 줄이는 편이 낫습니다. 예컨대 rubric을 꼭 필요한 것만 남기거나 여러 기준을 하나로 합치면 채점 호출 횟수 자체가 줄어듭니다. 또 평가 대상 답변의 max_tokens를 제한하면 judge가 읽어야 할 입력이 짧아지고, 규칙으로 판단할 수 있는 항목은 llm-rubric 대신 javascript나 contains 같은 비(非)-LLM 검증으로 대체할 수 있습니다.

정리하면, LLM judge는 “응답 수 × rubric 수”만큼 답변 전문을 반복해서 읽기 때문에 채점 토큰이 답변 생성 토큰보다 훨씬 많아지는 것이 일반적입니다. judge 모델을 가볍게 바꾸고 rubric을 정리하는 것만으로도 토큰과 비용을 크게 아낄 수 있습니다.

마지막으로, eval 모드에서는 드러나지 않는 모델 간의 세부 차이를 확인하기 위해 아래와 같이 view 모드로 promptfoo를 다시 실행합니다.

npx promptfoo@latest view

터미널의 eval 결과와 달리, promptfoo view로 띄운 웹 UI는 평가 결과를 한층 더 깊이 있게 보여줍니다. 각 셀 헤더에는 지표(metric)별 점수가 별도로 표시되고 모델들이 완전성, 구조, 언어, 정확성, 간결성 같은 항목에서 각각 몇 점을 받았는지 나란히 비교할 수 있습니다. 여기서 어떤 모델이 어느 부분에서 강하고 또 어디가 약한지를 세분화해서 살펴볼 수 있습니다.

여기서 눈에 띄는 빨간색 글씨는 채점을 담당한 LLM(이번 평가에서는 Opus)이 남긴 평가 코멘트입니다. llm-rubric 기준으로 점수를 매기면서 “왜 이런 점수를 줬는지” 그 이유를 함께 적어 주는데요. 예를 들어 “결론을 먼저 말하지 않고 정의부터 나열했다”처럼 구체적인 감점 사유가 그대로 드러납니다. 단순히 점수만 확인하는 데서 그치지 않고, 그 점수가 나온 채점 논리까지 들여다볼 수 있다는 점이 이 화면의 가장 큰 장점입니다.

마무리

지금까지 오픈소스 LLM 평가 프레임워크인 promptfooAmazon Bedrock의 모델 3종(Claude Sonnet 4.6, Claude 3 Haiku, Amazon Nova Pro)을 동일한 시나리오에서 비교 평가하는 방법을 살펴보았습니다. 같은 질문을 여러 모델에 동시에 던지고 일관된 기준으로 채점한 덕분에, 모델 선택을 막연한 느낌이 아니라 점수와 비용, 속도라는 숫자를 근거로 판단할 수 있었습니다. 이렇게 만들어 둔 평가는 프롬프트를 수정하거나 새로운 모델이 등장할 때마다 그대로 다시 실행해 품질이 유지되는지 확인하는 회귀 테스트로도 계속 활용할 수 있습니다.

물론 LLM이 LLM을 채점하는 방식에 한계가 없는 것은 아닙니다. 채점 모델의 판단이 늘 완벽하지는 않기에, 평가 기준을 어떻게 설계하고 threshold를 어디에 둘지는 여전히 사람의 몫으로 남습니다. 그럼에도 promptfoo는 여러 모델과 여러 테스트, 여러 기준의 조합을 한 번의 실행으로 자동 비교할 수 있는 틀을 제공함으로써, LLM을 다루는 일을 추측이 아닌 측정의 영역으로 끌어올려 줍니다.

이 모든 과정을 부담 없이 시작할 수 있었던 바탕에는 여러 모델을 하나의 API로 다룰 수 있게 해준 Amazon Bedrock이 있었습니다. promptfoo와 함께 활용해 여러분의 서비스에 가장 잘 맞는 모델을 데이터로 찾아보시기 바랍니다.

SoonBeom Kwon

SoonBeom Kwon

권순범 솔루션즈 아키텍트는 AWS Compute Specialist로서, 고객이 EC2, EKS 등 AWS 컴퓨팅 서비스를 효과적으로 활용하도록 돕습니다. AWS Graviton을 중심으로 웹 서비스·컨테이너·데이터 분석·HPC 등 다양한 워크로드에서 최적의 인스턴스를 선정·검증하고, GPU 기반의 분산 훈련과 추론, 나아가 Agentic AI 워크로드에 대한 기술 지원을 제공하여 고객이 대규모 AI/ML 환경을 효율적으로 구축·운영할 수 있도록 지원합니다

Yongjin Lee

Yongjin Lee

이용진 Solutions Architect는, AWS에서 고객들의 워크로드 최적화 및 현대화 업무를 지원하고 있습니다. AWS 서비스를 활용한 아키텍처 설계와 AWS Silicon 인 Graviton 기반의 비용 최적화를 주력하며, 레거시 시스템의 클라우드 현대화를 통해 고객의 비즈니스 민첩성과 운영 효율성 향상을 지원하고 있습니다.