10차시. 벡터저장소 이해
8. 실습 ⑤ — 8차시 코드 개선하기
출처: 원본 PDF 10차시_벡터저장소이해.pdf 17~18쪽 · 쪽 그림 17쪽 18쪽
이제 8차시의 RAG를 오늘 배운 것으로 개선합니다. 두 번째 실행부터 즉시 시작됩니다.
📝 src/rag_v2.py
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from indexer import get_store # 오늘 만든 함수
load_dotenv()
store = get_store() # 있으면 로드, 없으면 생성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
MIN_SCORE = 0.47 # 9차시에서 정한 임계값
def build_context(docs):
parts = []
for i, d in enumerate(docs, 1):
parts.append(f"[{i}] ({d.metadata['filename']} "
f"p.{d.metadata['page_no']})\n{d.page_content}")
return "\n\n".join(parts)
def ask(question, k=3):
# 점수와 함께 검색해서 기준 미달은 걸러낸다
pairs = store.similarity_search_with_relevance_scores(question, k=k)
# 기준(MIN_SCORE)을 넘는 것만 하나씩 골라 담기
good = []
for d, s in pairs:
if s >= MIN_SCORE:
good.append((d, s))
if not good:
print("Q:", question)
print("A: 관련 자료를 찾지 못했습니다.")
print(f" (최고 점수 {pairs[0][1]:.3f} < 기준 {MIN_SCORE})")
print("-" * 55)
return
docs = []
for d, s in good:
docs.append(d)
prompt = (
"아래 자료만 근거로 답하세요.\n"
"자료에 없는 내용은 '자료에서 확인할 수 없습니다'라고 답하세요.\n\n"
f"[자료]\n{build_context(docs)}\n\n[질문] {question}"
)
print("Q:", question)
print("A:", llm.invoke(prompt).content)
print("\n※ 근거:")
for d, s in good:
print(f" · {d.metadata['filename']} "
f"p.{d.metadata['page_no']} (관련도 {s:.3f})")
print("-" * 55)
if __name__ == "__main__":
ask("환불은 며칠 이내에 신청해야 하나요?")
ask("우리 회사 대표이사가 누구인가요?")
실행 결과
─────────────────────────────────────────────
→ 저장된 인덱스를 불러옵니다 (비용 0)
Q: 환불은 며칠 이내에 신청해야 하나요?
A: 상품 수령 후 7일 이내에 환불을 신청하실 수 있습니다.
근거:
· manual.pdf p.2 (관련도 0.812)
· manual.pdf p.2 (관련도 0.744)
─────────────────────────────────────────────
Q: 우리 회사 대표이사가 누구인가요?
A: 관련 자료를 찾지 못했습니다.
(최고 점수 0.284 < 기준 0.47)🎯 8차시 대비 좋아진 점 세 가지
① 두 번째 실행부터 즉시 시작합니다
인덱스를 불러오므로 임베딩 비용이 0입니다
② 관련 없으면 LLM을 부르지도 않습니다
임계값 미달이면 바로 종료 → 비용 절약 + 환각 차단
③ 근거의 신뢰도가 숫자로 보입니다
답이 이상할 때 관련도부터 확인하면 됩니다