from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.output_parsers import StrOutputParser, CommaSeparatedListOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.document_loaders import PyPDFLoader
####################
####### RAG 챗봇 구축
###################
# 1. LLM 모델 불러오기
llm = ChatOpenAI(model="gpt-4o-mini") # GPT-4o-mini 모델을 사용하여 LLM을 초기화합니다.
# 임베딩 모델 설정
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") # 텍스트 임베딩 모델을 설정합니다.
# 저장된 데이터 로드
vectorstore = FAISS.load_local(
folder_path="/Users/baeminkyung/Desktop/github/UNO_BWMovie_recsys/Langchain/faiss_db", # FAISS 데이터베이스가 저장된 폴더 경로
index_name="index", # 사용할 인덱스 이름
embeddings=embeddings, # 임베딩 모델
allow_dangerous_deserialization=True, # 위험한 역직렬화를 허용합니다.
)
db = vectorstore # 데이터베이스 객체를 설정합니다.
def genre_weighted_mmr_search(db, query, preferred_genre, k=20):
# MMR 검색을 위한 리트리버 설정
retriever = db.as_retriever(
search_type="mmr", # MMR 검색 방식
search_kwargs={
"k": k, # 검색할 문서 수
"fetch_k": 10, # 추가로 가져올 문서 수
"lambda_mult": 0.1 # MMR의 람다 값
}
)
# 선호장르(60%)와 일반장르(40%) 문서 수 계산
preferred_count = int(k * 0.6) # 선호 장르 문서 수
general_count = k - preferred_count # 일반 장르 문서 수
# 선호 장르 필터 설정
preferred_filter = {
"genre": {"$eq": preferred_genre} # 선호 장르에 대한 필터
}
# 선호 장르 문서 검색
preferred_docs = retriever.get_relevant_documents(
query,
filter=preferred_filter,
k=preferred_count # 선호 장르 문서 수만큼 검색
)
# 일반 장르 문서 검색: 필터 없이 호출 후 직접 필터링
all_docs = retriever.get_relevant_documents(
query,
k=general_count + 10 # 여유를 두고 추가 문서를 가져옵니다.
)
# 일반 장르 문서 필터링
general_docs = [doc for doc in all_docs if doc.metadata.get("genre") != preferred_genre][:general_count]
return preferred_docs + general_docs # 선호 장르와 일반 장르 문서 결합하여 반환
# 7. 프롬프트 템플릿 구축하기 (물어온 데이터로)
prompt = ChatPromptTemplate.from_template("""
넌 영화를 추천하는 AI야. 너는 안성재 셰프와 백종원 사업가 2명의 입장에서 각각 1개의 영화를 추천해야해.
먼저, 안성재 셰프는 다양성이 높은 예술적인 영화를 좋아하는 성격이야.
안성재 셰프의 입장에서 예술적이고 새로운 영화를 1개 추천해줘.
말투에 영화 관련 특성의 익힘정도가 완벽하다는 내용을 포함해줘.
또, 영화 내용과 관련해서 동일한 점이 있으면 이븐하게 되었다는 표현을 자주 사용해줘.
마지막에 백종원씨에게 '오늘 영화 메뉴는 무엇인가요?" 라고 대화를 마무리해줘.
두번째로, 백종원 사업가는 대중적이고 인기가 많은 영화를 좋아하는 스타일이야.
백종원 사업가의 입장에서 대중적이고 인기가 많은 영화를 1개 추천해줘.
영화를 소개할 때, 중간에 "조보아씨 이리 내려와서 이것좀 봐봐유"라는 내용을 추가해줘줘
fewshot 예시의 답변형태로 답변을 만들어줘.
나의 말에 대답하도록 해. \
오로지 아래의 context 기반으로 질문에 fewshot 형태로로 대답하세요
fewshot:
1. 질문: "커플이 함께 볼 로맨스 영화를 추천해줘"
대답: 안성재 셰프: "음~ 저는 영화의 예술성 익힘 정도가 완벽한 '노트북'을 추천해드릴게요.
두 주인공의 사랑이 이븐하게 느껴지는군요. 커플과 함께 예술성이 높은 SF 로맨스 이야기 한번 맛보세요.
백종원씨, 오늘 영화 메뉴는 무엇인가요?"
백종원 사업가:"오늘 영화 메뉴는 맛있는 '라라랜드'지 말이에유. 전세계 로맨스중 인기는 탑이어유. 낭만적인 음악과 댄스, 아주 좋구만유.
조보아씨 이리 내려와서 이것좀 봐봐유, 아주 기가막히쥬.
오늘 백종원의 영화 메뉴 추천은 음악과 낭만의 영화 '라라랜드'에유."
2. 질문: "친구가 함께 볼 액션 영화를 추천해줘"
대답: 안성재 셰프: "음~ 저는 영화의 예술성 익힘 정도가 완벽한 '본레거시'를 추천해드릴게요.
멧 데이먼의 촬영 기법과 액션 디렉팅이 정말 예술적이네요. 친구와 함께 예술성이 높은 액션 이야기 한번 맛보세요.
백종원씨, 오늘 영화 메뉴는 무엇인가요?"
백종원 사업가:"오늘 영화 메뉴는 맛있는 '미션 임파서블'이지 말이에유. 전세계 액션영화 중 인기는 탑이어유. 유명한 BGM과 말도안되는 액션, 아주 신나구만유.
조보아씨 이리 내려와서 이것좀 봐봐유, 아주 기가막히쥬.
오늘 백종원의 영화 메뉴 추천은 액션과 스릴의 영화 '미션 임파서블'이에유."
{context}
질문:
{question} """)
# 영화 평론가의 성격 설정
question = "친구랑 함께볼 영화 추천해줘줘" # 질문 설정
# 8. 1~7의 요소들을 chain으로 조합하여 RAG 구축 완료
def format_docs(docs):
# 필요에 따라 docs의 데이터 구조 검사 후 포맷팅
return "\n\n".join(doc.page_content for doc in docs) # 문서 내용을 포맷팅하여 반환
def get_retrieval_chain(query):
# 문서 검색 및 포맷팅
retrieved_docs = genre_weighted_mmr_search(db, query, "action") # 장르 문서 검색
formatted_docs = format_docs(retrieved_docs) # 검색된 문서 포맷팅
# 체인 입력 설정
chain_inputs = {
"context": formatted_docs, # 포맷팅된 문서
"question": query, # 질문
}
result = llm.invoke(prompt.format(**chain_inputs)) # LLM에 입력하여 결과 얻기
return result.content # 결과 반환
####################
####### 구축한 RAG 챗봇 실행
###################
response = get_retrieval_chain(question) # 챗봇 실행
print(response) # 결과 출력
from langchain.memory import ConversationBufferMemory # 민경 추가
# 🐰 메모리 설정
# ConversationBufferMemory를 사용하여 대화 기록을 저장합니다.
memory = ConversationBufferMemory(memory_key="history", input_key="question")
def get_retrieval_chain(query):
# 문서 검색 및 포맷팅
retrieved_docs = genre_weighted_mmr_search(db, query, "action") # 장르 문서 검색
formatted_docs = format_docs(retrieved_docs) # 검색된 문서 포맷팅
# 민경 추가
# 이전 대화 내역 가져오기
history = memory.load_memory_variables({})["history"]
# 체인 입력 설정
chain_inputs = {
"context": formatted_docs, # 포맷팅된 문서
"question": query, # 질문
"history": history # 대화 기록 # 민경 추가}
result = llm.invoke(prompt.format(**chain_inputs)) # LLM에 입력하여 결과 얻기
# 민경 추가
# 현재 대화 내용을 메모리에 저장
memory.save_context({"question": query}, {"answer": result.content})
return result.content # 결과 반환
# 민경 추가
print("!! 종료하려면 '끝'이라고 입력하세요 !!") # 안내 문구 추가
while True:
question = input() # 질문 설정
response = get_retrieval_chain(question) # 챗봇 실행
print(response) # 결과 출력
if question == "끝":
print("안성재 셰프 : 음~ 여기서 끝내도록 할게요.\n\n백종원 사업가 : 다음에 또 봐유.")
break
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.output_parsers import StrOutputParser, CommaSeparatedListOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory # 민경 추가
from langchain.document_loaders import PyPDFLoader
####################
####### RAG 챗봇 구축
###################
# LLM 모델 불러오기
llm = ChatOpenAI(model="gpt-4o-mini") # GPT-4o-mini 모델을 사용하여 LLM을 초기화합니다.
# 임베딩 모델 설정
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") # 텍스트 임베딩 모델을 설정합니다.
# 저장된 데이터 로드
vectorstore = FAISS.load_local(
folder_path="/Users/baeminkyung/Desktop/github/UNO_BWMovie_recsys/Langchain/faiss_db", # FAISS 데이터베이스가 저장된 폴더 경로
index_name="index", # 사용할 인덱스 이름
embeddings=embeddings, # 임베딩 모델
allow_dangerous_deserialization=True, # 위험한 역직렬화를 허용합니다.
)
db = vectorstore # 데이터베이스 객체를 설정합니다.
def genre_weighted_mmr_search(db, query, preferred_genre, k=20):
# MMR 검색을 위한 리트리버 설정
retriever = db.as_retriever(
search_type="mmr", # MMR 검색 방식
search_kwargs={
"k": k, # 검색할 문서 수
"fetch_k": 10, # 추가로 가져올 문서 수
"lambda_mult": 0.1 # MMR의 람다 값
}
)
# 선호장르(60%)와 일반장르(40%) 문서 수 계산
preferred_count = int(k * 0.6) # 선호 장르 문서 수
general_count = k - preferred_count # 일반 장르 문서 수
# 선호 장르 필터 설정
preferred_filter = {
"genre": {"$eq": preferred_genre} # 선호 장르에 대한 필터
}
# 선호 장르 문서 검색
preferred_docs = retriever.get_relevant_documents(
query,
filter=preferred_filter,
k=preferred_count # 선호 장르 문서 수만큼 검색
)
# 일반 장르 문서 검색: 필터 없이 호출 후 직접 필터링
all_docs = retriever.get_relevant_documents(
query,
k=general_count + 10 # 여유를 두고 추가 문서를 가져옵니다.
)
# 일반 장르 문서 필터링
general_docs = [doc for doc in all_docs if doc.metadata.get("genre") != preferred_genre][:general_count]
return preferred_docs + general_docs # 선호 장르와 일반 장르 문서 결합하여 반환
# 프롬프트 템플릿 구축하기 (물어온 데이터로)
prompt = ChatPromptTemplate.from_template("""
넌 영화를 추천하는 AI야. 너는 안성재 셰프와 백종원 사업가 2명의 입장에서 각각 1개의 영화를 추천해야해.
먼저, 안성재 셰프는 다양성이 높은 예술적인 영화를 좋아하는 성격이야.
안성재 셰프의 입장에서 예술적이고 새로운 영화를 1개 추천해줘.
말투에 영화 관련 특성의 익힘정도가 완벽하다는 내용을 포함해줘.
또, 영화 내용과 관련해서 동일한 점이 있으면 이븐하게 되었다는 표현을 자주 사용해줘.
마지막에 백종원씨에게 '오늘 영화 메뉴는 무엇인가요?" 라고 대화를 마무리해줘.
두번째로, 백종원 사업가는 대중적이고 인기가 많은 영화를 좋아하는 스타일이야.
백종원 사업가의 입장에서 대중적이고 인기가 많은 영화를 1개 추천해줘.
영화를 소개할 때, 중간에 "조보아씨 이리 내려와서 이것좀 봐봐유"라는 내용을 추가해줘줘
fewshot 예시의 답변형태로 답변을 만들어줘.
나의 말에 대답하도록 해. \
오로지 아래의 context 기반으로 질문에 fewshot 형태로로 대답하세요
fewshot:
1. 질문: "커플이 함께 볼 로맨스 영화를 추천해줘"
대답: 안성재 셰프: "음~ 저는 영화의 예술성 익힘 정도가 완벽한 '노트북'을 추천해드릴게요.
두 주인공의 사랑이 이븐하게 느껴지는군요. 커플과 함께 예술성이 높은 SF 로맨스 이야기 한번 맛보세요.
백종원씨, 오늘 영화 메뉴는 무엇인가요?"
백종원 사업가:"오늘 영화 메뉴는 맛있는 '라라랜드'지 말이에유. 전세계 로맨스중 인기는 탑이어유. 낭만적인 음악과 댄스, 아주 좋구만유.
조보아씨 이리 내려와서 이것좀 봐봐유, 아주 기가막히쥬.
오늘 백종원의 영화 메뉴 추천은 음악과 낭만의 영화 '라라랜드'에유."
2. 질문: "친구가 함께 볼 액션 영화를 추천해줘"
대답: 안성재 셰프: "음~ 저는 영화의 예술성 익힘 정도가 완벽한 '본레거시'를 추천해드릴게요.
멧 데이먼의 촬영 기법과 액션 디렉팅이 정말 예술적이네요. 친구와 함께 예술성이 높은 액션 이야기 한번 맛보세요.
백종원씨, 오늘 영화 메뉴는 무엇인가요?"
백종원 사업가:"오늘 영화 메뉴는 맛있는 '미션 임파서블'이지 말이에유. 전세계 액션영화 중 인기는 탑이어유. 유명한 BGM과 말도안되는 액션, 아주 신나구만유.
조보아씨 이리 내려와서 이것좀 봐봐유, 아주 기가막히쥬.
오늘 백종원의 영화 메뉴 추천은 액션과 스릴의 영화 '미션 임파서블'이에유."
{context}
{history}
질문:
{question} """)
# 영화 평론가의 성격 설정
# 🐰 메모리 설정
# ConversationBufferMemory를 사용하여 대화 기록을 저장합니다.
memory = ConversationBufferMemory(memory_key="history", input_key="question")
# chain으로 조합하여 RAG 구축 완료
def format_docs(docs):
# 필요에 따라 docs의 데이터 구조 검사 후 포맷팅
return "\n\n".join(doc.page_content for doc in docs) # 문서 내용을 포맷팅하여 반환
def get_retrieval_chain(query):
# 문서 검색 및 포맷팅
retrieved_docs = genre_weighted_mmr_search(db, query, "action") # 장르 문서 검색
formatted_docs = format_docs(retrieved_docs) # 검색된 문서 포맷팅
# 민경 추가
# 이전 대화 내역 가져오기
history = memory.load_memory_variables({})["history"]
# 체인 입력 설정
chain_inputs = {
"context": formatted_docs, # 포맷팅된 문서
"question": query, # 질문
"history": history # 대화 기록 # 민경 추가
}
result = llm.invoke(prompt.format(**chain_inputs)) # LLM에 입력하여 결과 얻기
# 민경 추가
# 현재 대화 내용을 메모리에 저장
memory.save_context({"question": query}, {"answer": result.content})
return result.content # 결과 반환
####################
####### 구축한 RAG 챗봇 실행
###################
# 민경 추가
print("!! 종료하려면 '끝'이라고 입력하세요 !!") # 안내 문구 추가
while True:
question = input() # 질문 설정
response = get_retrieval_chain(question) # 챗봇 실행
print(response) # 결과 출력
if question == "끝":
print("안성재 셰프 : 음~ 여기서 끝내도록 할게요.\n\n백종원 사업가 : 다음에 또 봐유.")
break