옵션 1: 다국어 RAG 시스템은 언어 교차 검색 기능을 통해 다양한 언어의 문서를 처리
옵션 2: 언어 감지와 자동번역 기능이 통합되어 매끄러운 다국어 처리가 가능
옵션 3: 벡터저장소 라우팅을 통해 각 언어별 최적화된 처리 경로를 구성
데이터는 자체적으로 보유중인 테슬라, 리비안에 관련된 데이터를 사용합니다.
언어 교차 검색은 서로 다른 언어 간의 정보 검색을 가능하게 하는 기술
질의어와 문서가 다른 언어여도 의미적 연관성을 기반으로 검색이 가능
다국어 임베딩을 활용하여 언어 간 의미적 매칭을 수행
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 데이터 로드
def load_text_files(txt_files):
data = []
for text_file in txt_files:
loader = TextLoader(text_file, encoding='utf-8')
data += loader.load()
return data
# 한국어 데이터 로드
korean_txt_files = glob(os.path.join('data', '*_KR.md'))
korean_data = load_text_files(korean_txt_files)
# 문장을 구분하여 분할 - 정규표현식 사용 (문장 구분자: 마침표, 느낌표, 물음표 다음에 공백이 오는 경우)
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", # TikToken 인코더 이름
separators=['\n\n', '\n', r'(?<=[.!?])\s+'], # 구분자
chunk_size=300, # 문서 분할 크기
chunk_overlap=50, # 문서 분할 중첩
is_separator_regex=True, # 구분자가 정규식인지 여부
keep_separator=True, # 구분자 유지 여부
)
korean_docs = text_splitter.split_documents(korean_data)
print("한국어 청크 수:", len(korean_docs))
- 출력
한국어 청크 수: 39
# 영어 데이터 로드
english_txt_files = glob(os.path.join('data', '*_EN.md'))
english_data = load_text_files(english_txt_files)
# 문장을 구분하여 분할 - 정규표현식 사용 (문장 구분자: 마침표, 느낌표, 물음표 다음에 공백이 오는 경우)
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", # TikToken 인코더 이름
separators=['\n\n', '\n', r'(?<=[.!?])\s+'], # 구분자
chunk_size=300, # 문서 분할 크기
chunk_overlap=50, # 문서 분할 중첩
is_separator_regex=True, # 구분자가 정규식인지 여부
keep_separator=True, # 구분자 유지 여부
)
english_docs = text_splitter.split_documents(english_data)
print("영어 청크 수:", len(english_docs))
- 출력
영어 청크 수: 19
from langchain_openai import OpenAIEmbeddings
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_ollama import OllamaEmbeddings
# OpenAI 임베딩 모델 생성
embeddings_openai = OpenAIEmbeddings(model="text-embedding-3-small")
# Hugoing Face 임베딩 모델 생성
embeddings_huggingface = HuggingFaceEmbeddings(model_name="BAAI/bge-m3", cache_folder='./embeddings_cache') # cahce_folder는 임베딩 캐시를 저장할 폴더
# Ollama 임베딩 모델 생성
embeddings_ollama = OllamaEmbeddings(model="nomic-embed-text")
# 다국어 벡터 저장소 구축
from langchain_chroma import Chroma
db_openai = Chroma.from_documents(
documents=korean_docs+english_docs,
embedding=embeddings_openai,
collection_name="db_openai",
persist_directory="./chroma_db",
)
db_huggingface = Chroma.from_documents(
documents=korean_docs+english_docs,
embedding=embeddings_huggingface,
collection_name="db_huggingface",
persist_directory="./chroma_db",
)
db_ollama = Chroma.from_documents(
documents=korean_docs+english_docs,
embedding=embeddings_ollama,
collection_name="db_ollama",
persist_directory="./chroma_db",
)
# 벡터 저장소에 저장된 문서 수
print(f"OpenAI: {db_openai._collection.count()}")
print(f"Hugging Face: {db_huggingface._collection.count()}")
print(f"Ollama: {db_ollama._collection.count()}")
- 출력
OpenAI: 58
Hugging Face: 58
Ollama: 58
# 다국어 벡터 저장소 로드
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_ollama import OllamaEmbeddings
# 임베딩 모델 생성
embeddings_openai = OpenAIEmbeddings(model="text-embedding-3-small")
embeddings_huggingface = HuggingFaceEmbeddings(model_name="BAAI/bge-m3")
embeddings_ollama = OllamaEmbeddings(model="nomic-embed-text")
db_openai = Chroma(
embedding_function=embeddings_openai,
collection_name="db_openai",
persist_directory="./chroma_db",
)
db_huggingface = Chroma(
embedding_function=embeddings_huggingface,
collection_name="db_huggingface",
persist_directory="./chroma_db",
)
db_ollama = Chroma(
embedding_function=embeddings_ollama,
collection_name="db_ollama",
persist_directory="./chroma_db",
)
# 벡터 저장소에 저장된 문서 수
print(f"OpenAI: {db_openai._collection.count()}")
print(f"Hugging Face: {db_huggingface._collection.count()}")
print(f"Ollama: {db_ollama._collection.count()}")
- 출력
OpenAI: 58
Hugging Face: 58
Ollama: 58
# RAG 체인 생성
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_openai import ChatOpenAI
# 질문 템플릿 정의
template = """Answer the question based only on the following context.
Do not use any external information or knowledge.
If the answer is not in the context, answer "I don't know".
Make sure to answer with the reference to the context.
[Context]
{context}
[Question]
{question}
[Answer]
"""
# 프롬프트 생성
prompt = ChatPromptTemplate.from_template(template)
# 문서 포맷터 함수
def format_docs(docs):
return "\n\n".join([d.page_content for d in docs])
# LLM 모델 생성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 체인 생성
def create_rag_chain(vectorstore):
retriever = vectorstore.as_retriever(search_kwargs={'k': 4})
return (
{"context": retriever | RunnableLambda(format_docs) , "question": RunnablePassthrough()}
# retriever 에서 검색한 문서를 format_docs 함수로 포맷팅하여 context 에 전달,
# question 에는 입력 질문 그대로 전달
| prompt
| llm
| StrOutputParser()
)
# 체인 생성
rag_chain_openai = create_rag_chain(db_openai)
rag_chain_huggingface = create_rag_chain(db_huggingface)
rag_chain_ollama = create_rag_chain(db_ollama)
# 한국어 쿼리에 대한 성능 평가
query_ko = "테슬라 창업자는 누구인가요?"
# OpenAI
output_openai = rag_chain_openai.invoke(query_ko)
print("OpenAI:", output_openai)
# Hugging Face
output_huggingface = rag_chain_huggingface.invoke(query_ko)
print("Hugging Face:", output_huggingface)
# Ollama
output_ollama = rag_chain_ollama.invoke(query_ko)
print("Ollama:", output_ollama)
- 출력
Ollama - nomic-embed-text
모델은 한국어에 있어 낮은 성능 OpenAI: 테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. 이들은 각각 CEO와 CFO를 역임했습니다. Ian Wright도 얼마 지나지 않아 합류하였고, Elon Musk는 2004년 2월에 시리즈 A 자금 조달을 주도하여 회장 겸 최대 주주가 되었습니다. J. B. Straubel은 2004년 5월 CTO로 합류했습니다. 다섯 명 모두 공동 설립자로 인정받고 있습니다. (출처: "창립 (2003–2004)")
Hugging Face: 테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. (참고: "Tesla Motors, Inc.는 2003년 7월 1일에 Martin Eberhard와 Marc Tarpenning에 의해 설립되었으며...")
Ollama: I don't know.
# 영어 쿼리에 대한 성능 평가
query_en = "Who is the founder of Tesla?"
# OpenAI
output_openai = rag_chain_openai.invoke(query_en)
print("OpenAI:", output_openai)
# Hugging Face
output_huggingface = rag_chain_huggingface.invoke(query_en)
print("Hugging Face:", output_huggingface)
# Ollama
output_ollama = rag_chain_ollama.invoke(query_en)
print("Ollama:", output_ollama)
- 출력
OpenAI: The founders of Tesla are Martin Eberhard and Marc Tarpenning. They are recognized as co-founders along with Ian Wright, Elon Musk, and J. B. Straubel.
Hugging Face: Tesla was founded by Martin Eberhard and Marc Tarpenning.
Ollama: The founders of Tesla are Martin Eberhard and Marc Tarpenning.
langdetect의 언어 감지 기능으로 입력 텍스트의 언어를 자동으로 식별함
DeepL을 통해 감지된 언어로 번역
deepl 설치: pip install deepl
/ poetry add deepl
langdetect 설치: pip install langdetect
/ poetry add langdetect
DeepL 번역을 위해 DeepL API Key 가 필요
로그인 후 Account → API Keys & Limits → Create key → 하단 API Key 에서 복사 가능 → 환경 변수 .env
에 저장
vectorstore = Chroma.from_documents(
documents=korean_docs,
embedding=embeddings_openai,
collection_name="db_korean_cosine_metadata",
persist_directory="./chroma_db",
collection_metadata={'hnsw:space': 'cosine'} # 코사인 유사도 사용
)
# 한국어 문서로 저장되어 있는 벡터 저장소 로드
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(
collection_name="db_korean_cosine_metadata",
embedding_function=embeddings,
persist_directory="./chroma_db",
)
print(f"벡터 저장소에 저장된 문서 수: {vectorstore._collection.count()}")
- 출력
벡터 저장소에 저장된 문서 수: 39
import deepl
from langdetect import detect
# Deepl 번역기 생성
translator = deepl.Translator(os.getenv('DEEPL_API_KEY'))
# 언어 감지 및 번역 함수
def detect_and_translate(text, target_lang='KO'):
""" 텍스트의 언어를 감지하고, 목표 언어로 번역합니다."""
# 언어 감지
detected_lang = detect(text)
# 언어가 목표 언어와 다른 경우 번역
if detected_lang.upper() != target_lang:
result = translator.translate_text(text, target_lang=target_lang)
return str(result), detected_lang
# 언어가 목표 언어와 같은 경우 원본 텍스트 반환
return text, detected_lang
# 문서 번역 테스트 (영어 -> 한국어)
text = "Who is the founder of Tesla?"
translated_text, detected_lang = detect_and_translate(text, target_lang='KO')
print(f"Detected language: {detected_lang}")
print(f"Translated text: {translated_text}")
- 출력
Detected language: en
Translated text: 테슬라의 창립자는 누구인가요?
# 문서 번역 테스트 (한국어 -> 영어)
text = "테슬라 창업자는 누구인가요?"
translated_text, detected_lang = detect_and_translate(text, target_lang='EN-US')
print(f"Detected language: {detected_lang}")
print(f"Translated text: {translated_text}")
- 출력
Detected language: ko
Translated text: Who is the founder of Tesla?
# RAG 체인 생성
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda, chain
from langchain_openai import ChatOpenAI
# 질문 템플릿 정의
template = """Answer the question based only on the following context.
Do not use any external information or knowledge.
If the answer is not in the context, answer "I don't know".
Make sure to answer with the reference to the context.
[Context]
{context}
[Question]
{question}
[Answer]
"""
# 프롬프트 생성
prompt = ChatPromptTemplate.from_template(template)
# 문서 포맷터 함수
def format_docs(docs):
return "\n\n".join([d.page_content for d in docs])
# LLM 모델 생성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 벡터저장소 문서를 검색하는 도구
retriever = vectorstore.as_retriever(search_kwargs={'k': 4})
# 문서를 검색하여 답변을 생성하는 RAG 체인 생성
lang_rag_chain = (
{"context": retriever | format_docs , "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 언어 감지에 기반한 RAG 실행 함수를 체인으로 변환 (@chain 데코레이터 사용)
@chain
def run_lang_rag_chain(query):
# 입력 쿼리의 언어 감지
original_lang = detect(query)
print(f"Original language: {original_lang}")
# 한국어가 아닌 경우 번역
if original_lang.upper() != 'KO':
translated_query, _ = detect_and_translate(query, target_lang='KO')
# 한국어인 경우 번역 없이 쿼리 사용
else:
translated_query = query
print(f"Translated query: {translated_query}")
# RAG 체인 실행
output = lang_rag_chain.invoke(translated_query)
print(f"Output: {output}")
# 번역된 경우 다시 번역 (영어로)
if original_lang.upper() != 'KO':
output = translator.translate_text(output, target_lang='EN-US')
return str(output)
# 한국어 쿼리에 대한 테스트 실행
query_ko = "테슬라 창업자는 누구인가요?"
output = run_lang_rag_chain.invoke(query_ko)
print(f"Final Output: {output}")
- 출력
Original language: ko
Translated query: 테슬라 창업자는 누구인가요?
Output: 테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. (참고: "Tesla Motors, Inc.는 2003년 7월 1일에 Martin Eberhard와 Marc Tarpenning에 의해 설립되었으며...")
Final Output: 테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. (참고: "Tesla Motors, Inc.는 2003년 7월 1일에 Martin Eberhard와 Marc Tarpenning에 의해 설립되었으며...")
# 영어 쿼리에 대한 테스트 실행 (영어가 섞인 경우 번역 오류 발생 가능)
query_en = "Who is the founder of Tesla?"
output = run_lang_rag_chain.invoke(query_en)
print(f"Final Output: {output}")
- 출력
Original language: en
Translated query: 테슬라의 창립자는 누구인가요?
Output: 테슬라의 창립자는 Martin Eberhard와 Marc Tarpenning입니다. (참고: "Tesla Motors, Inc.는 2003년 7월 1일에 Martin Eberhard와 Marc Tarpenning에 의해 설립되었으며...")
Final Output: Tesla's founders are Martin Eberhard and Marc Tarpenning. (See: "Tesla Motors, Inc. was founded on July 1, 2003, by Martin Eberhard and Marc Tarpenning...")
언어별 벡터저장소를 분리하여 한국어와 영어 문서를 독립적으로 관리
각 언어에 최적화된 저장소를 구성하여 검색 효율성을 향상
라우팅 시스템을 통해 언어를 감지하고 해당 벡터저장소로 자동 연결
# 한국어 문서로 저장되어 있는 벡터 저장소 로드
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
db_korean = Chroma(
collection_name="db_korean_cosine_metadata",
embedding_function=embeddings,
persist_directory="./chroma_db",
)
print(f"한국어 문서 수: {db_korean._collection.count()}")
- 출력
한국어 문서 수: 39
# 영어 문서를 저장하는 벡터 저장소 생성
db_english = Chroma.from_documents(
documents=english_docs,
embedding=embeddings_openai,
collection_name="eng_db_openai",
persist_directory="./chroma_db",
)
print(f"영어 문서 수: {db_english._collection.count()}")
- 출력
영어 문서 수: 19
# 영어 문서를 저장하는 벡터 저장소 로드
db_english = Chroma(
embedding_function=embeddings_openai,
collection_name="eng_db_openai",
persist_directory="./chroma_db",
)
print(f"영어 문서 수: {db_english._collection.count()}")
- 출력
영어 문서 수: 19
# RAG 체인 생성
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_openai import ChatOpenAI
# 질문 템플릿 정의
template = """Answer the question based only on the following context.
Do not use any external information or knowledge.
If the answer is not in the context, answer "I don't know".
Make sure to answer with the reference to the context.
[Context]
{context}
[Question]
{question}
[Answer]
"""
# 프롬프트 생성
prompt = ChatPromptTemplate.from_template(template)
# 문서 포맷터 함수
def format_docs(docs):
return "\n\n".join([d.page_content for d in docs])
# LLM 모델 생성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 체인 생성
def create_rag_chain(vectorstore):
retriever = vectorstore.as_retriever(search_kwargs={'k': 4})
return (
{"context": retriever | RunnableLambda(format_docs) , "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 각 언어별 RAG 체인 생성 (한국어, 영어)
rag_chain_korean = create_rag_chain(db_korean)
rag_chain_english = create_rag_chain(db_english)
# 언어 감지에 기반한 RAG 실행 함수를 체인으로 변환 (@chain 데코레이터 사용)
@chain
def run_route_rag_chain(query):
# 입력 쿼리의 언어 감지
original_lang = detect(query)
# 한국어인 경우 한국어 RAG 체인 실행 (한국어 문서 벡터 저장소 사용)
if original_lang.upper() == 'KO':
return rag_chain_korean.invoke(query)
# 영어인 경우 영어 RAG 체인 실행 (영어 문서 벡터 저장소 사용)
elif 'EN' in original_lang.upper():
return rag_chain_english.invoke(query)
# 한국어 또는 영어가 아닌 경우 에러 메시지 반환
else:
return "Unsupported language (Korean or English only)"
# 한국어 쿼리에 대한 테스트 실행
query_ko = "테슬라 창업자는 누구인가요?"
output = run_route_rag_chain.invoke(query_ko)
print(output)
- 출력
테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. (참고: "Tesla Motors, Inc.는 2003년 7월 1일에 Martin Eberhard와 Marc Tarpenning에 의해 설립되었으며...")
# 영어 쿼리에 대한 테스트 실행
query_en = "Who is the founder of Tesla?"
output = run_route_rag_chain.invoke(query_en)
print(output)
- 출력
The founders of Tesla are Martin Eberhard and Marc Tarpenning. They established Tesla Motors, Inc. on July 1, 2003.