반응형
💼 전문가 수준의 지식 기반 질문응답 챗봇 만들기
이번 글에서는 전자상거래 회사 Shopwise를 위한
지식 기반 질문-응답(Q&A) 에이전트를 만드는 과정을 정리합니다.
정확도와 신뢰성이 중요한 영역이기 때문에,
RAG(Retrieval Augmented Generation) 방식을 활용하며,
이를 통해 단순 LLM보다 훨씬 정확한 답변을 기대할 수 있습니다.
📂 1. 프로젝트 준비 - 코드 및 환경 설정
# 📁 운영체제 관련 기능을 다루는 기본 모듈
# - 환경 변수 설정, 파일 경로 조작 등에 사용됨
import os
# 📂 특정 경로에서 파일을 패턴(예: *.txt)으로 한꺼번에 가져올 때 사용
# - 예: 문서 디렉토리에서 모든 PDF 파일을 한 번에 불러오기
import glob
# 🔐 .env 파일에 저장된 환경 변수를 코드 내에서 사용할 수 있도록 불러오는 모듈
# - API 키, 비밀번호 등 민감한 정보 관리에 사용됨
from dotenv import load_dotenv
# 🖥️ Gradio는 웹 기반 인터페이스(UI)를 간단하게 만들어주는 프레임워크
# - 모델 입력/출력 테스트용으로 빠르게 UI를 만들 수 있음
import gradio as gr
# 🤖 OpenAI의 GPT 모델을 Python에서 사용할 수 있게 해주는 클라이언트
# - 텍스트 생성, 요약, 코드 변환 등 다양한 작업 가능
from openai import OpenAI
# 💰 비용이 중요한 요소이기 때문에, 저비용 LLM을 선택함
# - "gpt-4o-mini"는 OpenAI에서 제공하는 경량화 모델
# - 가격이 저렴하면서도 꽤 우수한 성능을 보여줌
# - 과도한 예산 소모 없이 Q&A, 요약, 검색 등 일반적인 업무에 적합
MODEL = "gpt-4o-mini"
# 🔐 .env 파일에서 환경변수를 불러와 현재 Python 실행 환경에 반영
# - override=True: 이미 설정된 환경 변수라도 덮어쓰기 허용
load_dotenv(override=True)
# 🔑 OPENAI_API_KEY 값을 환경변수에서 가져와 명시적으로 등록
# - .env 또는 시스템 환경에 OPENAI_API_KEY가 없으면 기본값 사용
# - (주의) 'your-key-if-not-using-env'은 예시용이며 실제 키가 필요함
os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY', 'your-key-if-not-using-env')
# 🤖 OpenAI 클라이언트 초기화
# - os.environ['OPENAI_API_KEY']를 자동으로 참조하여 인증
openai = OpenAI()
📚 2. 문서 불러오기 - 직원 및 제품 정보
# 📦 직원 문서를 저장할 딕셔너리 생성
context = {}
# 직원(staff) 문서 불러오기
staff_files = glob.glob("knowledge-base/staff/*")
# 🔄 각 파일에 대해 다음 작업 수행:
for staff in staff_files:
# 📛 파일명에서 직원 이름만 추출
# - 공백 기준으로 split → 마지막 조각 선택 (이름 부분)
# - 확장자 .txt 제거 ([:-3])
name = staff.split(os.sep)[-1][:-3]
# 📖 파일 내용 읽어서 문자열로 저장
with open(staff, "r", encoding="utf-8") as f:
context[name] = f.read()
# 제품 문서 불러오기
product_files = glob.glob("knowledge-base/products/*")
for product in product_files:
name = product.split(os.sep)[-1][:-3]
with open(product, "r", encoding="utf-8") as f:
context[name] = f.read()
이렇게 하면 context
라는 딕셔너리에 직원과 제품 이름을 key로,
해당 문서 내용을 value로 저장하게 됩니다.
🔍 3. 질문에 맞는 문맥 자동 선택
# 🔍 사용자의 질문(message)에서 관련된 문맥(context)을 찾아 반환하는 함수
# - context 딕셔너리에는 직원/제품 이름을 key로, 설명 텍스트를 value로 저장되어 있음
def get_relevant_context(message):
relevant_context = [] # 📦 관련 문맥(문서 텍스트)들을 저장할 리스트
# 🔁 context 딕셔너리의 모든 항목을 확인
for context_title, context_details in context.items():
# 📌 메시지에 key(이름/제품명)가 포함되어 있으면 관련 문맥으로 판단
if context_title.lower() in message.lower():
relevant_context.append(context_details)
# 📤 관련된 문맥 리스트 반환 (0개일 수도 있음)
return relevant_context
예시 실행 결과:
get_relevant_context("Shopwise에서 Jaeyoung은 어떤 역할을 하나요?")
["Jaeyoung은 Shopwise의 마케팅 팀장으로, 광고 전략과 신규 상품 런칭 캠페인을 총괄합니다."]
🧩 4. 관련 문맥을 질문에 자동으로 추가하기
# 🧩 사용자 질문에 문맥(context)을 자동으로 덧붙이는 함수
# - 질문 안에 특정 이름/제품명 등 키워드가 있다면,
# 해당 키워드에 대한 문서(context)를 뒤에 붙여서 LLM에게 더 정확한 답변을 유도함
def add_context(message):
# 🔍 질문과 관련된 문서들을 리스트로 가져옴
relevant_context = get_relevant_context(message)
# 📌 관련 문맥이 하나라도 있다면, 질문 메시지 뒤에 추가 문단 삽입
if relevant_context:
# 📎 안내 문구 추가 (LLM이 context를 참고하도록 유도)
message += "\n\n아래는 이 질문에 도움이 될 수 있는 참고 문맥입니다:\n\n"
# 📄 관련 문서들을 모두 추가 (각 문서는 줄바꿈으로 구분)
for relevant in relevant_context:
message += relevant + "\n\n"
# ✅ 문맥이 추가된 메시지를 반환 (LLM 프롬프트로 사용됨)
return message
실행 예:
print(add_context("Shopwise에서 Jaeyoung은 어떤 역할을 하나요?"))
Shopwise에서 Jaeyoung은 어떤 역할을 하나요?
아래는 이 질문에 도움이 될 수 있는 참고 문맥입니다:
Jaeyoung은 Shopwise의 마케팅 팀장으로, 광고 전략과 신규 상품 런칭 캠페인을 총괄합니다.
아래는 이 질문에 도움이 될 수 있는 참고 문맥입니다:
Jaeyoung은 Shopwise의 마케팅 팀장으로, 광고 전략과 신규 상품 런칭 캠페인을 총괄합니다.
🧠 5. 시스템 프롬프트: 역할 부여 및 언어 설정
# 🧠 시스템 프롬프트 설정: LLM에게 역할과 답변 원칙을 알려줌
# - 역할: 전자상거래 회사 Shopwise에 대한 전문적인 질문에 답변하는 에이전트
# - 답변 지침:
# 1. 간결하고 정확하게 답변할 것
# 2. 한국어로 질문받으면 한국어로 답변할 것
# 3. 문맥이 부족하거나 모르는 경우는 "모른다"고 명시
# 4. 제공된 정보 외에는 상상으로 답변을 만들어내지 말 것 (❌ 허구 금지)
# - 신뢰성과 비즈니스 적합성을 보장하는 핵심 설정
system_message = (
"당신은 전자상거래 회사 Shopwise에 대한 질문에 정확하게 답변하는 AI입니다. "
"질문자는 한국어로 질문할 수 있으며, 당신의 답변도 항상 한국어로 제공되어야 합니다. "
"답변은 간결하고 정확해야 하며, 제공된 문맥(context)에 기반하여 작성되어야 합니다. "
"문맥에 없는 내용은 절대 지어내지 말고, 모르면 모른다고 답하세요."
)
🤖 6. LLM과 연결되는 chat 함수 구현
# 💬 사용자 질문과 과거 대화 기록을 기반으로, LLM(OpenAI 모델)에 메시지를 보내고
# 문맥(context)을 덧붙인 상태에서 실시간으로 응답을 받아오는 함수
# - Gradio 같은 UI에서 실시간 스트리밍 응답을 처리할 때 사용됨
def chat(message, history):
# 🧠 시스템 메시지와 이전 대화 기록(history)을 묶어 초기 메시지 목록 구성
# - system_message: LLM에게 "Shopwise 전문 답변 에이전트" 역할 부여
# - history: 이전 사용자/AI 메시지 목록
messages = [{"role": "system", "content": system_message}] + history
# 🧩 사용자 질문에 관련 문맥(context)을 자동으로 추가
# - 직원/제품 이름이 포함되어 있으면 관련 설명을 질문에 덧붙임
message = add_context(message)
# 🙋 사용자 메시지를 대화에 추가
messages.append({"role": "user", "content": message})
# 🚀 OpenAI에 메시지 목록을 보내고, 스트리밍 방식으로 응답 받기 시작
# - model: "gpt-4o-mini" 등 저비용 모델
# - stream=True: 한 줄씩 응답을 실시간으로 받아옴
stream = openai.chat.completions.create(model=MODEL, messages=messages, stream=True)
# 📝 실시간 응답 내용을 저장할 변수
response = ""
# 🔄 모델이 생성한 텍스트를 한 줄씩 받아서 누적하며 전달
for chunk in stream:
response += chunk.choices[0].delta.content or '' # 새로 생성된 텍스트 조각 추가
yield response # 매번 누적된 결과를 실시간으로 출력 (Gradio 등에 연결됨)
🖥️ 7. Gradio로 챗봇 UI 실행
# 💬 Gradio의 ChatInterface를 통해 LLM 챗봇 UI 실행
# - chat: 사용자 입력을 처리하고 LLM 응답을 반환하는 핵심 함수 (앞서 정의한 chat(message, history))
# - type="messages": 대화 이력을 message 형태로 자동 관리 (역할별 정리: system/user/assistant 등)
# - launch(): 브라우저에서 대화형 웹 UI를 띄워줌 (로컬 또는 Colab 환경 모두 지원)
import gradio as gr
view = gr.ChatInterface(chat, type="messages").launch()
이 코드를 실행하면 브라우저에서 실시간 LLM 챗봇이 동작하는 인터페이스를 확인할 수 있습니다.
---📝 마무리
이번 프로젝트는 LLM에 전자상거래 회사 도메인의 지식을 효과적으로 연동해주는
RAG 기반 질문-응답 챗봇을 구현하는 실습 예제입니다.
이후에는 벡터 검색 기반 RAG로 발전시키거나,
요약, 분류, 추천 시스템으로 확장해볼 수 있습니다.
반응형
'🧠 LLM 엔지니어링' 카테고리의 다른 글
LangChain과 Chroma를 활용한 문서 임베딩 시각화 실습 01 (1) | 2025.04.26 |
---|---|
LLM 문서에서 특정 키워드 추출하는 방법 02 (Shopwise 지식기반 예제) (1) | 2025.04.25 |
LLM 문서에서 특정 키워드 추출하는 방법 01 (Shopwise 지식기반 예제) (1) | 2025.04.24 |
LangChain으로 시작하는 RAG 파이프라인 구축 여정 (1) | 2025.04.23 |
RAG와 벡터 임베딩 이해하기 (1) | 2025.04.22 |