Post

[LLM 앱 실전] Day 5: 평가와 가드레일 - 신뢰할 수 있는 LLM 앱 운영하기

[LLM 앱 실전] Day 5: 평가와 가드레일 - 신뢰할 수 있는 LLM 앱 운영하기

이 글은 AI(Claude)의 도움을 받아 작성하고, 작성자가 검토·편집했습니다.

서론: “잘 되는 것 같다”를 넘어서

Day 1~4에서 프롬프트·RAG·도구·에이전트로 LLM 앱을 만들었다. 그런데 LLM은 비결정적이다. 같은 입력에도 다른 답을 내고, 프롬프트를 한 줄 바꾸면 예상치 못한 곳이 망가진다. “데모는 잘 됐는데 프로덕션에서 터지는” 전형적 함정이다. 마지막 편은 품질을 측정하고(평가) 안전하게 지키는(가드레일) 법이다. 이것이 데모와 제품을 가른다.

1. 왜 평가가 어려운가

1
2
3
4
5
전통 소프트웨어: 입력 X → 출력은 정확히 Y (단정적 테스트)
LLM 앱: 입력 X → 출력이 매번 조금씩 다름, "정답"이 여럿일 수 있음

→ assert output == expected 가 안 통한다
→ "얼마나 좋은가"를 측정하는 새로운 방법이 필요

핵심 전환: 통과/실패가 아니라 점수와 분포로 본다(대규모 시스템 Day 5의 백분위수 사고와 같다).

2. 평가 데이터셋 만들기

평가의 출발점은 대표적인 입력-기대 모음이다.

1
2
3
4
5
6
7
구성:
  - 실제 사용 사례를 반영한 입력 (로그에서 추출)
  - 엣지 케이스: 모호한 질문, 긴 입력, 적대적 입력
  - 각 입력에 기대 답변 또는 평가 기준

규모: 처음엔 20~50개로 시작해도 충분. 점진적으로 확장.
원칙: 데이터셋이 곧 품질의 정의. 실패 사례를 발견할 때마다 추가한다.

3. 평가 방법 세 가지

1
2
3
4
5
6
7
8
9
10
11
12
1. 코드 기반 (결정적):
   형식 검증(JSON 파싱 성공?), 키워드 포함, 길이, 정규식
   ✅ 빠르고 싸고 객관적  ❌ 의미·품질은 못 봄

2. LLM-as-Judge (모델이 채점):
   강한 모델에게 "이 답변이 기준을 충족하나" 평가시킴
   ✅ 의미·품질 평가 가능, 확장성  ❌ 비용, 심판 모델 편향
   → 평가 기준(rubric)을 명확히 줘야 일관됨

3. 인간 평가:
   ✅ 가장 정확한 기준  ❌ 느리고 비쌈
   → 소량의 골든셋·LLM-judge 보정에 사용
1
2
3
4
5
6
7
8
9
10
# LLM-as-Judge 예시
judge_prompt = f"""다음 답변을 평가하라. 0~5점.
기준: 정확성, 근거 인용, 한국어 자연스러움.

[질문] {question}
[제공 근거] {context}
[답변] {answer}

JSON으로: score"""
# 점수를 JSON으로 강제 (Day 1의 구조화 출력)

4. 무엇을 측정하는가 (RAG/에이전트별)

1
2
3
4
5
6
7
8
9
10
11
RAG (Day 2):
  - 검색 품질: 관련 문서를 찾았나 (recall, precision)
  - 충실성(faithfulness): 답이 근거에 기반하나 (환각 여부)
  - 관련성: 질문에 실제로 답했나

에이전트 (Day 4):
  - 작업 완료율: 목표를 달성했나
  - 효율성: 몇 단계·얼마 비용으로 (불필요한 루프?)
  - 도구 사용 정확도: 맞는 도구를 맞게 불렀나

공통: 정확성, 지연, 비용(토큰), 사용자 만족도

5. 회귀 방지: CI에 평가 붙이기

프롬프트·모델·코드를 바꿀 때마다 평가를 돌려 품질 회귀를 잡는다.

1
2
3
4
5
6
7
8
9
워크플로:
  프롬프트 수정 → 평가셋 실행 → 점수 비교
  → 이전보다 떨어진 케이스가 있나? → 있으면 배포 보류

A/B 비교:
  새 프롬프트 vs 기존을 같은 평가셋으로 점수 비교
  → "느낌"이 아니라 숫자로 판단

이것이 LLM 앱의 단위 테스트다 — 비결정성 때문에 통과/실패 대신 점수 임계로.

6. 가드레일: 입력과 출력을 지키기

평가가 “얼마나 좋은가”라면, 가드레일은 “나쁜 것을 막는” 실시간 방어다.

1
2
3
4
5
6
7
8
9
10
입력 가드레일:
  - 프롬프트 인젝션 탐지: "이전 지시를 무시하고..." 같은 공격 차단
  - 범위 외 질문 거부, 유해 입력 필터
  - PII(개인정보) 탐지·마스킹

출력 가드레일:
  - 환각 검증: 출력이 근거(RAG)에 있는 내용인지 확인
  - 형식 검증: 스키마 준수 (Day 1)
  - 유해성·민감정보 필터, 금지 주제 차단
  - 신뢰도 낮으면 "모르겠다"로 폴백
1
2
3
4
5
6
7
def guarded_generate(user_input):
    if detect_injection(user_input):           # 입력 방어
        return "처리할 수 없는 요청입니다."
    output = llm_generate(user_input)
    if not is_grounded(output, context):       # 출력 방어 (환각 체크)
        return "확실한 답변을 드릴 수 없습니다."
    return mask_pii(output)

프롬프트 인젝션은 본질적으로 완전 차단이 어렵다(WebAssembly Day 3의 보안 사고처럼, 신뢰 경계를 명확히 하고 권한을 좁히는 것이 근본 대책). 도구 권한 최소화·인간 승인과 함께 다층 방어한다.

7. 운영: 비용·지연·모니터링

1
2
3
4
5
6
7
8
9
10
비용 관리:
  - 작업별 적정 모델 선택 (분류는 작은 모델, 추론은 큰 모델)
  - 캐싱 (대규모 시스템 Day 1): 동일/유사 질의 결과 재사용
  - 프롬프트 캐싱: 반복되는 긴 시스템 프롬프트 캐시
  - 컨텍스트 최소화 (Day 1): 토큰이 곧 비용

모니터링 (대규모 시스템 Day 5):
  - 토큰 사용량·비용·지연(p99)·에러율 추적
  - 사용자 피드백(좋아요/싫어요) 수집 → 평가셋으로 환류
  - 프로덕션 로그에서 실패 사례 발굴 → 데이터셋 보강

8. 시리즈 종합 체크리스트

  1. 프롬프트·컨텍스트·구조화 출력으로 모델을 제어했다. (Day 1)
  2. RAG로 외부 지식을 검색해 환각을 줄였다. (Day 2)
  3. 함수 호출로 모델에 도구를 쥐여줬다. (Day 3)
  4. 에이전트로 자율적 다단계 작업과 그 통제를 구현했다. (Day 4)
  5. 평가·가드레일·비용관리로 신뢰할 수 있는 운영 체계를 세웠다. (Day 5)

시리즈 마무리

LLM 애플리케이션 개발의 핵심 교훈은 명확하다. 모델은 강력하지만 비결정적이고, 그럴듯하게 틀리며, 신뢰할 수 없는 입력을 만든다. 좋은 LLM 앱은 이를 부정하지 않고 설계로 감싼다 — RAG로 근거를 주고, 도구로 능력을 확장하되 권한을 좁히고, 에이전트에 상한을 두고, 평가로 품질을 측정하고, 가드레일로 방어한다.

프롬프트→RAG→도구→에이전트→평가 다섯 단계는 능력을 더하는 순서이자, 동시에 통제를 더하는 순서다. 능력과 통제가 함께 자랄 때 데모가 제품이 된다. 그리고 그 모든 토대에는 지난 시리즈에서 다룬 시스템 설계 원칙 — 캐싱, 관측성, 멱등성, 보안 경계 — 이 그대로 살아 있다. LLM 앱은 새로운 마법이 아니라, 비결정적 컴포넌트를 품은 또 하나의 분산 시스템이다.

This post is licensed under CC BY 4.0 by the author.