34차시. RAG 평가 개요
실습 1 — RAG 평가 개요 실습 따라 하기
관련 수업 자료: 원본 PDF 34차시_RAG평가개요.pdf · 실습 코드 32/test_regression.py
이 실습은 교안 해설(보충_평가설계.md)의 실습 순서를 옮긴 것입니다. OpenAI 임베딩이나 채팅 모델을 부르는 파일은 .env 에 API 키가 있어야 돌아갑니다. 키가 없으면 실행 결과 샘플로 출력 모양을 확인합니다.
수업 자료와 이어지는 곳
| 실습 코드 | 원본 PDF | 이어지는 내용 |
|---|---|---|
32/test_regression.py | - | 원본 PDF 에 짝이 되는 쪽이 없어 이 페이지 아래에 실었습니다 |
실행 흐름
질문 세트 → 정답 조각이 k안에
정답 조각이 k안에 → 검색 수정 (아니오)
정답 조각이 k안에 → 키워드와 무환각 (예)
키워드와 무환각 → 생성 검증 수정 (아니오)
키워드와 무환각 → 통과 (예)실습 순서
질문 8개를 검색 성공이 목표인 것과 답변 문장이 목표인 것으로 나눕니다. test_regression.py가 키워드를 어떻게 검사하는지 읽고, 없는 질문을 성공으로 세지 않는지 확인합니다.
스스로 확인
- 검색 실패를 답변 점수로만 보면 왜 진단이 꼬이는가?
- 없는 질문의 정답이 거절인 이유를 아는가?
- 한 세트에서 100퍼센트여도 새 질문에는 실패할 수 있다는 점을 말하는가?
이어지는 실습 코드
실습 코드 · 32/test_regression.py
실습 코드: 실습/src/32/test_regression.py · 원본 PDF 에 같은 이름의 쪽이 없어 여기에 실었습니다.
# ==============================================================
# 32차시. PPT 32차시_고도화ModularRAG. 코드 폴더는 src/32
# PPT: 32차시 경로 점검과 회귀 테스트
# 역할: graph32.ask로 기존 질문을 다시 물어 키워드, pass 수, 재생성 수, 재검색 수를 집계합니다.
# 읽는 키: answer, grade, retries, rewrites
# 쓰는 키: 화면의 질문별 표시와 마지막 집계
# 그래프:
# 각 질문 --> ask --> 키워드 포함 여부
# 문서에 없는 대표이사 질문은 안내 문장 키워드가 있으면 정답
# --------------------------------------------------------------
# 파이썬만 아는 분을 위한 말
# State는 노드들이 함께 쓰는 딕셔너리입니다. 함수는 state를 읽고, 바꿀 키만 return 합니다.
# return에 적지 않은 키는 이전 값이 남습니다.
# log와 tried_queries는 리스트라서, 새로 return한 항목이 뒤에 이어 붙습니다.
# Retriever: 검색 담당. 질문과 비슷한 문서 조각만 고릅니다. 답 문장은 만들지 않습니다.
# Generator: 고른 문서를 근거로 답 문장을 만듭니다. 문서가 없으면 안내 문장만 돌려줍니다.
# Verifier: 답이 근거와 맞는지 검사하고 grade를 적습니다.
# grade=pass 답을 확정하고 그래프를 끝냅니다.
# grade=retry 같은 문서로 답 문장만 다시 씁니다. retries가 1 늘어납니다.
# grade=research 문서가 부족합니다. 검색어를 바꾼 뒤 다시 찾습니다. rewrites가 늘어납니다.
# grade=giveup 더 진행하지 않고 fallback으로 안내 문장만 돌려줍니다.
# fallback: 실패했을 때 답을 지어내지 않고 안내 문장을 반환하는 노드입니다.
# intent: 질문 종류. greeting 인사, calc 계산, scope 문서 밖, document 문서 검색.
# rewrite: 사용자가 친 question은 유지하고, 검색에 쓰는 query만 바꿉니다.
# safe_node: 노드를 try로 감쌉니다. 오류가 나도 그래프는 멈추지 않고 node_error에 적습니다.
# check_answer는 keys 중 하나라도 답 문자열에 있으면 참입니다.
# 정답률, 검증통과 수, 재생성 건수, 재검색 건수는 실행 결과라 기록에 없는 값은 적지 않습니다.
# ==============================================================
# ============================================================
# test_regression.py
# graph32가 기존 질문에 여전히 올바르게 답하는지 검사합니다.
# 각 답변에 기대한 핵심 키워드가 포함되어 있는지 확인합니다.
# ============================================================
from graph32 import ask
# ============================================================
# 테스트 질문
# ============================================================
# q : RAG에 입력할 질문
# keys : 정답으로 인정할 핵심 키워드
TESTS = [
{
"q": "환불은 며칠 이내인가요?",
"keys": ["7일"],
},
{
"q": "교환 기간은 얼마인가요?",
"keys": ["14일"],
},
{
"q": "고객센터 운영 시간은?",
"keys": ["09:00", "18:00", "9시", "18시"],
},
{
"q": "반품하고 싶은데 언제까지?",
"keys": ["7일"],
},
{
"q": "물건 바꾸려면 택배비 누가?",
"keys": ["배송비", "택배비", "고객"],
},
{
"q": "환불 방법과 수수료는?",
"keys": ["환불", "수수료"],
},
{
"q": "대표이사가 누구인가요?",
"keys": [
"관련 자료를 찾지 못했습니다",
"확인할 수 없습니다",
"정보가 없습니다",
],
},
]
# ============================================================
# 답변 확인 함수
# ============================================================
def check_answer(answer, keys):
"""답변에 기대한 키워드가 하나라도 있는지 확인합니다."""
# 키워드를 하나씩 확인
for key in keys:
# 답변에 키워드가 있으면 정답
if key in answer:
return True
# 모든 키워드가 없으면 오답
return False
# ============================================================
# 통계 변수 초기화
# ============================================================
# 정답 개수
correct = 0
# Verifier를 통과한 질문 개수
pass_count = 0
# 답변을 재생성한 질문 개수
retry_count = 0
# 질문을 고쳐 재검색한 질문 개수
rewrite_count = 0
# ============================================================
# 회귀 테스트 실행
# ============================================================
for test in TESTS:
# 테스트 질문과 정답 키워드 가져오기
question = test["q"]
keys = test["keys"]
# --------------------------------------------------------
# 1. Modular RAG 실행
# --------------------------------------------------------
result = ask(question)
# --------------------------------------------------------
# 2. 최종 답변 가져오기
# --------------------------------------------------------
answer = result.get("answer", "")
# --------------------------------------------------------
# 3. 답변이 맞는지 확인
# --------------------------------------------------------
ok = check_answer(
answer,
keys
)
if ok:
correct += 1
mark = "✓"
else:
mark = "✗"
# --------------------------------------------------------
# 4. Verifier 통과 여부 확인
# --------------------------------------------------------
grade = result.get("grade", "")
if grade == "pass":
pass_count += 1
# --------------------------------------------------------
# 5. 재생성 여부 확인
# --------------------------------------------------------
retries = result.get("retries", 0)
if retries > 0:
retry_count += 1
# --------------------------------------------------------
# 6. 재검색 여부 확인
# --------------------------------------------------------
rewrites = result.get("rewrites", 0)
if rewrites > 0:
rewrite_count += 1
# --------------------------------------------------------
# 7. 질문별 결과 출력
# --------------------------------------------------------
print(
f"{mark} {question} | "
f"판정={grade} | "
f"재생성={retries} | "
f"재검색={rewrites}"
)
# ============================================================
# 전체 테스트 결과
# ============================================================
# 전체 질문 개수
count = len(TESTS)
print()
print("=" * 50)
# 정답률 계산
accuracy = correct / count * 100
print(
f"정답률 : "
f"{correct}/{count} "
f"({accuracy:.0f}%)"
)
print(
f"검증통과 : "
f"{pass_count}/{count}"
)
print(
f"재생성 : "
f"{retry_count}건"
)
print(
f"재검색 : "
f"{rewrite_count}건"
)
print("=" * 50)실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
경로: classify → retrieve → generate → verify → END
예시 출력 (API 키 필요, 실행하지 않음)
일곱 질문의 정답률과 건수 집계는 기록에 없습니다.
기록과 맞는 질문만 적습니다.
환불은 며칠 이내에 신청해야 하나요? (테스트 문구는 '환불은 며칠 이내인가요?') grade=pass retries=0 rewrites=0 elapsed=6.69
다른 실행 elapsed=2.39 n_docs=2 top_score=0.056
대표이사가 누구인가요? grade=giveup rewrites=2 elapsed=3.41 fail_reason=low_score top_score=-0.219코드 해설
graph32.ask로 기존 질문을 다시 물어 키워드, pass 수, 재생성 수, 재검색 수를 집계합니다.
코드 앞에서 고정해 두는 말입니다.
- 읽는 키: answer, grade, retries, rewrites
- 쓰는 키: 화면의 질문별 표시와 마지막 집계
- 그래프:
- 각 질문 --> ask --> 키워드 포함 여부
- 문서에 없는 대표이사 질문은 안내 문장 키워드가 있으면 정답
- 파이썬만 아는 분을 위한 말
- State는 노드들이 함께 쓰는 딕셔너리입니다. 함수는 state를 읽고, 바꿀 키만 return 합니다.
- return에 적지 않은 키는 이전 값이 남습니다.
함수와 클래스로 나누어 읽습니다.
check_answer: 답변에 기대한 키워드가 하나라도 있는지 확인합니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
코드 설명
check_answer(answer, keys)는 키워드가 하나라도 들어 있으면 True 입니다. 1-1 절에서 본 키워드 채점과 같은 방식이라, "7일이 아니라 30일입니다" 같은 답도 정답으로 셉니다. 오늘 배운 한계를 이 코드에서 직접 확인할 수 있습니다.- 대표이사 질문의 keys 는 "관련 자료를 찾지 못했습니다" 같은 안내 문장입니다. 문서에 없는 질문은 거절하는 것이 정답이라서, 지어낸 이름이 나오면 오답이 됩니다.
correct와pass_count는 다른 숫자입니다. correct 는 키워드로 본 최종 답의 정답 수이고, pass_count 는 Verifier 가 pass 를 준 수입니다. 둘을 같은 것으로 읽는 분이 많습니다.retry_count와rewrite_count는 횟수의 합이 아니라, 재생성이나 재검색이 한 번이라도 일어난 질문의 수입니다.if retries > 0:으로 질문당 1만 더하기 때문입니다.from graph32 import ask가 FAISS 와 OpenAI 를 준비하므로 API 키가 있어야 돕니다. 여기서 나온 정답률도 이 일곱 질문에서의 결과일 뿐입니다.