15차시. 기본 RAG 앱 만들기
7. 실습 ④ — app.py (터미널 버전)
출처: 원본 PDF 15차시_기본RAG앱만들기.pdf 14쪽 · 쪽 그림 14쪽
📝 app.py
from rag import ask
BANNER = (
"╔══════════════════════════════════════════╗\n"
"║ 문서 기반 질의응답 시스템 ║\n"
"║ 종료하려면 'q' 또는 'exit' 입력 ║\n"
"╚══════════════════════════════════════════╝"
)
def main():
print(BANNER)
while True:
try:
q = input("\n질문 > ").strip()
except (KeyboardInterrupt, EOFError):
print("\n종료합니다.")
break
if q.lower() in ("q", "exit", "quit", "종료"):
print("종료합니다.")
break
if not q:
continue
result = ask(q)
print("\n" + "─" * 50)
print(result["answer"])
if result["sources"]:
print("\n📎 참고 자료")
seen = set()
for s in result["sources"]:
key = (s["file"], s["page"])
if key not in seen:
seen.add(key)
print(f" · {s['file']} {s['page']}페이지")
if result.get("cited") is False:
print("\n⚠ 인용 표시가 확인되지 않았습니다.")
print("─" * 50)
if __name__ == "__main__":
main()
실행 화면
──────────────────────────────────────────
╔══════════════════════════════════════════╗
║ 문서 기반 질의응답 시스템 ║
║ 종료하려면 'q' 또는 'exit' 입력 ║
╚══════════════════════════════════════════╝
질문 > 환불은 며칠 이내인가요?
──────────────────────────────────────────
환불은 상품 수령 후 7일 이내에 신청하실 수 있습니다[1].
단순 변심의 경우 왕복 배송비는 고객 부담입니다[1].
※ 참고 자료
· manual.pdf 2페이지
──────────────────────────────────────────
질문 >실습 코드 · rag_app/app.py
실습 코드: 실습/src/rag_app/app.py · 위의 원본 PDF 15차시 14쪽 「7️⃣ 실습 ④ — app.py (터미널 버전)」과 이어집니다.
# ==============================================================
# 15차시. PPT 15차시_기본RAG앱만들기. 코드 폴더는 src/rag_app
# PPT: 15차시 문서 질의응답
# 역할: 콘솔에서 질문을 반복 입력받아 rag.ask 결과에 답과 출처를 출력합니다.
# 읽는 키: ask() 반환의 answer, sources, cited
# 쓰는 키: 화면 출력
# 그래프:
# 배너
# |
# v
# 질문 입력 -- q/exit/quit/종료 --> 종료
# |
# v
# rag.ask --> 답변 --> 참고 자료(파일, 페이지) --> 인용 경고
# --------------------------------------------------------------
# while True 루프입니다. 빈 줄은 continue로 다시 입력을 받습니다.
# 같은 파일의 같은 페이지는 set으로 한 번만 출력합니다.
# cited가 False이면 인용 표시 경고를 한 줄 더 출력합니다.
# input()이 있어 실행하지 않았습니다. API와 인덱스도 필요합니다.
# ==============================================================
# ============================================================
# 콘솔에서 사용자의 질문을 반복해서 입력받고,
# RAG 시스템에 질문을 전달한 뒤 답변과 출처를 출력합니다.
# q, exit, quit, 종료를 입력하면 프로그램을 종료합니다.
# ============================================================
import warnings
from rag import ask
# 불필요한 경고 메시지를 화면에 표시하지 않습니다.
warnings.filterwarnings("ignore")
# ============================================================
# 프로그램 시작 시 보여줄 안내 문구입니다.
# ============================================================
BANNER = (
"╔══════════════════════════════════════════╗\n"
"║ 문서 기반 질의응답 시스템 ║\n"
"║ 종료하려면 'q' 또는 'exit' 입력 ║\n"
"╚══════════════════════════════════════════╝"
)
# ============================================================
# 사용자에게 질문을 입력받고 답변을 출력합니다.
# ============================================================
def main():
# 프로그램 시작 안내문을 출력합니다.
print(BANNER)
# 사용자가 종료할 때까지 계속 질문을 받습니다.
while True:
# ----------------------------------------------------
# 질문 입력
# ----------------------------------------------------
try:
question = input("\n질문 > ").strip()
# Ctrl+C 또는 Ctrl+Z 등이 입력되면 종료합니다.
except (KeyboardInterrupt, EOFError):
print("\n종료합니다.")
break
# ----------------------------------------------------
# 종료 명령 확인
# ----------------------------------------------------
# 종료 명령이면 반복문을 끝냅니다.
if question.lower() in ("q", "exit", "quit", "종료"):
print("종료합니다.")
break
# ----------------------------------------------------
# 빈 질문 확인
# ----------------------------------------------------
# 아무것도 입력하지 않았다면 다시 질문을 받습니다.
if not question:
continue
# ----------------------------------------------------
# RAG 시스템에 질문 전달
# ----------------------------------------------------
result = ask(question)
# ----------------------------------------------------
# 답변 출력
# ----------------------------------------------------
print("\n" + "─" * 50)
print(result["answer"])
# ----------------------------------------------------
# 참고 자료 출력
# ----------------------------------------------------
sources = result["sources"]
# 검색된 출처가 있다면 출력합니다.
if sources:
print("\n참고 자료")
# 이미 출력한 출처를 저장합니다.
seen = set()
# 출처를 하나씩 확인합니다.
for source in sources:
filename = source["file"]
page = source["page"]
# 파일명과 페이지 번호를 하나의 값으로 묶습니다.
key = (filename, page)
# 아직 출력하지 않은 출처만 출력합니다.
if key not in seen:
seen.add(key)
print(
f" · {filename} {page}페이지"
)
# ----------------------------------------------------
# 인용 여부 확인
# ----------------------------------------------------
# 답변에 올바른 인용 표시가 없으면 경고합니다.
if result.get("cited") is False:
print("\n⚠ 인용 표시가 확인되지 않았습니다.")
# 한 번의 질문 처리가 끝났음을 표시합니다.
print("─" * 50)
# ============================================================
# 이 파일을 직접 실행했을 때만 main()을 실행합니다.
# ============================================================
if __name__ == "__main__":
main()실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
예시 콘솔 (API 키 필요, 실행하지 않음)
문서 기반 질의응답 시스템
종료하려면 'q' 또는 'exit' 입력
질문 > 환불은 며칠 이내에 신청해야 하나요?
──────────────────────────────────────────────────
(답 문장은 API가 만듭니다. 기록된 원문이 없어 적지 않습니다.)
참고 자료
· 파일명과 페이지는 인덱스 결과에 따라 달라 숫자를 만들지 않습니다.
──────────────────────────────────────────────────
같은 질문의 이후 그래프 기록: grade=pass retries=0 rewrites=0 elapsed=6.69
다른 실행: elapsed=2.39 n_docs=2 top_score=0.056코드 해설
콘솔에서 질문을 반복 입력받아 rag.ask 결과에 답과 출처를 출력합니다.
코드 앞에서 고정해 두는 말입니다.
- 읽는 키: ask() 반환의 answer, sources, cited
- 쓰는 키: 화면 출력
- 그래프:
- 배너
- |
- v
- 질문 입력 -- q/exit/quit/종료 --> 종료
- |
함수와 클래스로 나누어 읽습니다.
main: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.