RAG2026-5일15차시 · 기본 RAG 앱 만들기10 / 19← 이전목차다음 →MetaContents

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 (터미널 버전)」과 이어집니다.

Python
# ==============================================================
# 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: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.

파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.

← 이전 절15차시 · 6. 예외 처리 — 앱이 죽지 않게다음 절 →15차시 · 8. 실습 ⑤ — Streamlit 웹 화면 (선택)