🧠 LangChain + FAISS + Gradio로 만드는 RAG 기반 지식 챗봇
문서를 검색해 대화형으로 응답해 주는 RAG 기반 지식 챗봇을 만들어보는 프로젝트입니다.
OpenAI의 LLM을 기반으로 벡터 검색, 대화 메모리, 웹 인터페이스까지 연결하며 실습하였습니다.
5. 🎯 FAISS 벡터 추출 및 시각화 준비
이 코드는 FAISS 벡터 저장소에서 벡터와 메타데이터를 꺼내어 시각화를 준비하는 부분입니다.
# 🎯 시각화를 위한 사전 준비 단계 (FAISS 저장소에서 벡터 및 메타데이터 추출)
vectors = [] # 벡터들을 담을 리스트
documents = [] # 문서 원문을 담을 리스트
doc_types = [] # 문서 유형(폴더 이름 등)을 담을 리스트
colors = [] # 시각화용 색상 정보를 담을 리스트
# 🎨 각 문서 유형별로 색상을 지정 (시각화에서 색상 구분용)
color_map = {
'products': 'blue',
'employees': 'green',
'contracts': 'red',
'company': 'orange'
}
# 🔁 벡터 저장소에서 벡터 및 관련 정보 추출
for i in range(total_vectors):
vectors.append(vectorstore.index.reconstruct(i)) # 🎯 i번째 벡터를 복원해서 리스트에 저장
doc_id = vectorstore.index_to_docstore_id[i] # 🆔 FAISS 인덱스에서 문서 ID 조회
document = vectorstore.docstore.search(doc_id) # 🔍 해당 ID로 문서 정보 조회
documents.append(document.page_content) # 📄 문서 내용 저장
doc_type = document.metadata['doc_type'] # 🏷️ 문서 유형(metadata)
doc_types.append(doc_type) # 📋 문서 유형 리스트에 추가
colors.append(color_map[doc_type]) # 🎨 시각화용 색상 정보 추가
# 📐 벡터 리스트를 NumPy 배열로 변환 (시각화 및 수치 계산에 사용)
vectors = np.array(vectors)
💡 이 데이터를 기반으로 2D나 3D 임베딩 시각화(tsne)를 할 수 있습니다.
🔹 2차원 시각화 (t-SNE)
이 코드는 FAISS에서 추출한 벡터를 2차원으로 축소해서 시각화하는 핵심 부분입니다.
# 👀 우리는 2D로 시각화할 때 가장 직관적으로 이해할 수 있어!
# ✂️ t-SNE (t-distributed stochastic neighbor embedding) 알고리즘을 이용해
# 고차원 벡터를 2차원으로 줄여서 시각화에 적합하게 변환
tsne = TSNE(n_components=2, random_state=42) # 2차원으로 줄이기, 결과 재현성을 위한 seed 설정
reduced_vectors = tsne.fit_transform(vectors) # 📉 벡터 차원 축소 실행
# 🖼️ 2D 산점도(Scatter plot) 생성
fig = go.Figure(data=[go.Scatter(
x=reduced_vectors[:, 0], # ➡️ x축 좌표 (1번째 차원)
y=reduced_vectors[:, 1], # ⬆️ y축 좌표 (2번째 차원)
mode='markers', # 점(marker) 형태로 시각화
marker=dict(
size=5, # 점 크기
color=colors, # 문서 유형에 따라 색상 지정
opacity=0.8 # 투명도 설정
),
# 🧾 마우스를 올렸을 때 보여줄 텍스트 설정
text=[f"Type: {t}<br>Text: {d[:100]}..." for t, d in zip(doc_types, documents)],
hoverinfo='text' # 마우스 오버 시 텍스트만 표시
)])
# 🧩 그래프 레이아웃 설정
fig.update_layout(
title='FAISS 벡터 저장소의 2차원 시각화', # 제목
scene=dict(xaxis_title='x', yaxis_title='y'), # x, y 축 라벨
width=800,
height=600,
margin=dict(r=20, b=10, l=10, t=40) # 여백 설정
)
fig.show() # 📊 그래프 표시
💡 이 그래프를 통해 벡터들이 문서 유형별로 어떻게 모이거나 흩어져 있는지,
즉 의미상 유사한 문서들이 가까이 위치하는지를 직관적으로 확인할 수 있어요.
🔹 3차원 시각화 (t-SNE)
이번엔 2D가 아닌 3D로 벡터를 시각화하는 코드입니다.
# 🧭 3D로 시도해보자!
# - 3차원으로 축소하면 더 입체적으로 벡터 간 관계를 시각적으로 확인할 수 있음
tsne = TSNE(n_components=3, random_state=42) # 📉 t-SNE로 벡터 차원을 3D로 축소
reduced_vectors = tsne.fit_transform(vectors) # 🔄 차원 축소 실행
# 📊 3D 산점도(Scatter plot) 생성
fig = go.Figure(data=[go.Scatter3d(
x=reduced_vectors[:, 0], # ➡️ x축 값
y=reduced_vectors[:, 1], # ⬆️ y축 값
z=reduced_vectors[:, 2], # 🔁 z축 값 (3번째 차원)
mode='markers', # 마커(점) 형태로 표시
marker=dict(
size=5, # 점 크기
color=colors, # 문서 유형별 색상
opacity=0.8 # 투명도 조절
),
# 🧾 마우스 오버 시 보여줄 정보 설정
text=[f"Type: {t}<br>Text: {d[:100]}..." for t, d in zip(doc_types, documents)],
hoverinfo='text' # hover 시 텍스트만 보여줌
)])
# 🧩 그래프 레이아웃 구성
fig.update_layout(
title='FAISS 벡터 저장소의 3차원 시각화', # 제목
scene=dict(
xaxis_title='x',
yaxis_title='y',
zaxis_title='z' # 각 축 라벨
),
width=900, # 그래프 너비
height=700, # 그래프 높이
margin=dict(r=20, b=10, l=10, t=40) # 여백 설정
)
fig.show() # 🌐 그래프 표시
💡 3D 시각화는 Plotly의 마우스 회전/줌 기능을 통해
벡터들이 공간상에서 어떻게 군집을 이루는지 더 직관적으로 탐색할 수 있다는 장점이 있습니다.
6. 💬 대화형 RAG 시스템 구성
이 코드는 LangChain을 사용해 RAG 기반 대화형 질문 응답 시스템을 구축하는 핵심 부분이에요.
# 🤖 OpenAI의 Chat 모델로 새로운 챗봇 생성
llm = ChatOpenAI(
temperature=0.7, # 🎲 창의성 조절 (0.0은 일관된 응답, 1.0은 다양성 강조)
model_name=MODEL # 사용하려는 모델 이름 (예: "gpt-4o-mini", "gpt-3.5-turbo" 등)
)
# 💬 대화 메모리 구성: 이전 채팅 내용을 저장하고 기억하기 위한 설정
memory = ConversationBufferMemory(
memory_key='chat_history', # 메모리 내용을 저장할 키 이름
return_messages=True # 이전 대화를 message 형식으로 반환
)
# 🔍 검색기(retriever) 구성: 벡터 저장소에서 관련 문서를 찾아주는 역할
retriever = vectorstore.as_retriever()
# - vectorstore(Faiss/Chroma 등)에 기반한 문서 검색 기능을 추상화한 객체
# 🧩 모든 구성요소를 하나로 연결
# - LLM (GPT 모델), 문서 검색기(retriever), 대화 메모리(memory)를 합쳐서
# - RAG 기반 대화형 응답 시스템을 구성함
conversation_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
memory=memory
)
⚠️ 경고 메시지 해석
LangChainDeprecationWarning:
Please see the migration guide at: https://python.langchain.com/docs/versions/migrating_memory/
이 경고는 LangChain의 최근 업데이트로 인해 ConversationBufferMemory 사용 방식이 바뀌었기 때문입니다.
이 말은:
"지금 쓰고 있는 ConversationBufferMemory 방식이 곧 폐지될 예정이니까, 새로운 방식으로 바꿔 써줘!"
라는 뜻이에요.
✅ 해결 방법: 최신 방식으로 메모리 정의하기
LangChain 0.1.0 이후부터는 ConversationBufferMemory를 아래처럼 구성해 주는 것이 권장됩니다:
🔁 새로운 방식 예시
from langchain.memory import ConversationBufferMemory
from langchain_core.messages import AIMessage, HumanMessage
memory = ConversationBufferMemory(
return_messages=True,
memory_key="chat_history",
input_key="question" # 또는 "input" (사용할 입력 필드에 따라 다름)
)
💡 input_key는 conversation_chain에서 사용되는 프롬프트의 입력 이름과 맞춰줘야 해요.
예를 들어 conversation_chain.run(question="...")처럼 쓰면 "question"이고,
그냥 run("...")이면 "input"입니다.
🔍 전체 최신 버전 코드 예시
llm = ChatOpenAI(temperature=0.7, model_name=MODEL)
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
input_key="question" # 중요!
)
retriever = vectorstore.as_retriever()
conversation_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=retriever,
memory=memory
)
7. 💬 사용자 질문 → 응답 흐름 처리
# ❓ 사용자가 던질 질문 정의
query = "SecureAI는 언제 설립되었고, 어떤 회사인가요?"
# 🧠 conversation_chain에 질문을 전달하고 응답 받기
# - 딕셔너리 형식으로 key를 "question"으로 전달 (input_key와 맞춰야 함!)
result = conversation_chain.invoke({"question": query})
# 💬 모델의 답변 출력
print(result["answer"])
# 예: "SecureAI는 2017년에 Harper Yoon에 의해 설립된 차세대..."
💡 참고 팁
만약 input_key="input"으로 설정했다면, 아래처럼 호출해야 합니다:
result = conversation_chain.invoke({"input": query})
지금 구조는 input_key="question"으로 잘 맞춰져 있으니 그대로 사용하면 됩니다.
🔹 대화 메모리 및 RAG 체인 구성
# 💬 이전 대화를 기억하기 위한 '대화 메모리' 설정
memory = ConversationBufferMemory(
memory_key='chat_history', # 🔑 대화 내용을 저장할 키 이름 (프롬프트 내부에서 사용됨)
return_messages=True # 🧾 이전 대화를 message 형식으로 반환 → 더 자연스러운 이어말하기 가능
)
# 🧠 RAG 기반 대화 시스템 구성
# - LLM(OpenAI), 문서 검색기(Retriever), 대화 메모리(Memory)를 하나로 연결
# - 사용자의 질문을 이해하고 관련 문서를 찾아 답변을 생성함
conversation_chain = ConversationalRetrievalChain.from_llm(
llm=llm, # 🤖 사용할 LLM (예: GPT-4, GPT-3.5)
retriever=retriever, # 🔍 관련 문서를 찾을 수 있는 벡터 검색기
memory=memory # 💬 이전 대화 내용을 기억해서 대화의 연속성 유지
)
💡 이 설정이 완료되면, conversation_chain.invoke({...}) 또는 run(...)으로 질문을 보내고
벡터 검색 + LLM 응답을 한 번에 받아볼 수 있게 됩니다.
8. 🧵 Gradio로 챗봇 함수 래핑하기
이 함수는 Gradio 챗봇 인터페이스에 연결하기 위해 대화 기능을 래핑(wrapping)한 부분입니다.
🔹 chat 함수 정의 및 연결
# 🧵 LLM 대화 기능을 하나의 함수로 래핑
# - Gradio의 채팅 인터페이스와 연결하기 위한 구조
# - 사용자 입력(message)과 대화 이력(history)을 인자로 받음
def chat(message, history):
# 🤖 conversation_chain을 통해 사용자 메시지를 LLM에게 전달
result = conversation_chain.invoke({"question": message})
# 💬 LLM의 응답 중 'answer' 필드만 반환
return result["answer"]
💡 Gradio에서는 이 chat() 함수를 gr.ChatInterface()에 연결해서
실제 웹 UI로 입력 → 응답 흐름을 자동으로 처리해줍니다.
🔹 ChatInterface 실행
마지막 Gradio를 통해 만든 chat 함수를 웹 기반 챗봇 인터페이스로 실행하는 부분입니다.
# 💬 Gradio의 Chat 인터페이스에 앞서 정의한 chat 함수를 연결
# - 사용자가 입력한 메시지를 chat() 함수로 전달하고
# - LLM의 응답을 인터페이스에 표시함
view = gr.ChatInterface(chat).launch()
# 🚀 launch(): 웹 브라우저에서 챗봇 인터페이스를 실행
# - 로컬 서버가 열리고, 대화형 챗봇을 바로 사용할 수 있음
📌 이렇게 하면 http://localhost:7860 같은 주소로 브라우저가 열리고,
OpenAI LLM + FAISS 벡터 검색 기반의 실시간 챗봇을 웹에서 바로 체험할 수 있습니다 😄
'🧠 LLM 엔지니어링' 카테고리의 다른 글
자연어 처리 기반 가격 예측 모델 개발💸 (BOW vs Word2Vec vs RF) 01 (1) | 2025.05.01 |
---|---|
📘 제품 가격 예측 AI 만들기: Amazon 리뷰 데이터로 LLM 학습하기 01 (0) | 2025.04.30 |
문서 기반 챗봇 만들기: RAG 실습 with OpenAI, FAISS, Gradio 01 (1) | 2025.04.28 |
LangChain과 Chroma를 활용한 문서 임베딩 시각화 실습 02 (0) | 2025.04.27 |
LangChain과 Chroma를 활용한 문서 임베딩 시각화 실습 01 (1) | 2025.04.26 |