1. 리트리버와 RAG는 왜 사용하는가?
- LLM는 뛰어나고 많은 정보를 제공해주나, 정확하지 않는 답변을 제공해준다는 단점이 있습니다.
- LLM에게 필요로 하는 정보를 제공해준 다면 더 정확하고 신뢰성 높은 결과를 제공, 그리고 단점을 해소 할 수 있기 때문입니다.
2. 리트리버
- 단어 뜻 : 사용자가 원하는 정보를 물어다준다는 의미
- 사용 의도 : 랭체인에서 자연어로 검색하기 위한 인터페이스
- 사용 방법 : 벡터스토어와 랭체인에서 제공하는 클래스를 이용하여 사용
- 특이 사항
- 벡터스토어에는 Runnable 인터페이스를 구현하지 않았음.
(as_retriever() 함수 이용하여 리트리버로 변경)
- 리트리버는 Runnable 인터페이스를 구현했기 때문에 LCEL 문법을 이용할 수 있음.
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.schema import Document
from langchain.text_splitter import CharacterTextSplitter
from langchain.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
load_dotenv()
# 임베딩 모델과 텍스트 분할기 준비
embeddings = OpenAIEmbeddings(model="text-embedding-3-large", api_key=os.getenv("OPENAI_API_KEY"))
text_splitter = CharacterTextSplitter(separator=".", chunk_size=50, chunk_overlap=20)
# 샘플
documents = [
Document(
page_content="파이썬은 읽기 쉽고 배우기 쉬운 프로그래밍 언어입니다."
"다양한 분야에서 활용되며, 특히 데이터 과학과 AI 개발에 인기가 높습니다.",
metadata={"source": "python_intro.txt", "topic": "programming"}
),
Document(
page_content="자바스크립트는 웹브라우저에서 실행되는 프로그래밍 언어로 시작했지만, "
"현재는 서버 사이드 개발에도 널리 사용됩니다. Node.js가 대표적입니다.",
metadata={"source": "js_guide.txt", "topic": "programming"}
),
Document(
page_content="머신러닝은 데이터에서 패턴을 학습하는 AI의 한 분야입니다."
"지도 학습, 비지도 학습, 강화 학습 등 다양한 방법론이 있습니다.",
metadata={"source": "ml_basics.txt", "topic": "ai"}
)
]
# 문서 분할
split_docs = text_splitter.split_documents(documents)
for doc in split_docs:
print(f"문서: {doc.page_content[:50]}... | 출처: {doc.metadata["source"]} | 주제: {doc.metadata["topic"]}")
# FAISS 벡터 스토어 생성
retriever = FAISS.from_documents(split_docs, embeddings).as_retriever(search_type="similarity", search_kwargs={"k": 1},)
# 책에서는 get_revevant_documents 이용했으나, 버전으로 인해 사양된 함수이므로 invoke 사용할 것을 권함
query = "초보자가 배우기 좋은 프로그래밍 언어는?"
results = retriever.invoke(query)
for i, doc in enumerate(results, 1):
print(f"{i}. {doc.page_content[:100]}... | 출처: {doc.metadata["source"]} | 주제: {doc.metadata["topic"]}")
# 리트리버를 사용한 LLM 호출
llm = ChatOpenAI(model_name="gpt-5-mini", api_key=os.getenv("OPENAI_API_KEY"))
message = """
질문에 대한 답변을 작성할 때, 리트리버에서 가져온 문서를 참고하여 답변을 작성하세요.
질문:
{question}
참고:
{context}
"""
print(retriever)
prompt = ChatPromptTemplate.from_messages([("human", message)])
chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | llm
response = chain.invoke("초보자가 배우기 좋은 프로그래밍 언어는?")
print("\nLLM 응답: ")
print(response.content)
3. RAG (검색 증강 생성)
- 의도 : 검색과 생성을 결합하여 좋은 답변을 만들어 내는 패턴
- 단어 뜻
- RAG (Retriever Argumented Generation) → 사용자 질문에 대한 컨텍스트를 프롬프트에 포함하여 답변 개선
import os, time
from dotenv import load_dotenv
from langchain_community.tools import DuckDuckGoSearchResults
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
load_dotenv()
# RealtimeWebRAG : 실시간 웹 검색 활용 RAG
class RealtimeWebRAG:
"""
실시간 웹 검색을 활용하는 RAG
"""
def __init__(self):
self.search = DuckDuckGoSearchResults()
self.llm = ChatOpenAI(model="gpt-5-mini", api_key=os.getenv("OPENAI_API_KEY"), temperature=0)
message = """웹에서 검색한 최신 정보를 바탕으로 답변하세요.
검색 결과 :
{search_results}
질문: {question}
중요: 검색 결과에 있는 정보만 사용하여 답변하세요.
답변: """
self.qa_prompt = ChatPromptTemplate.from_messages(
[
("human", message)
]
)
# 답변 생성
def answer(self, question):
"""실시간 검색 후 답변 생성"""
# 1. 웹 검색
print(f"검색 중: {question}")
search_results = self.search.run(question)
time.sleep(5)
# 2. LLM 답변 생성
qa_chain = self.qa_prompt | self.llm
answer = qa_chain.invoke({"search_results": search_results, "question": question})
return answer
web_rag = RealtimeWebRAG()
# 최신 정보 질문
questions = ["오늘 주요 뉴스는?", "오늘 야구 순위는?", "최신 AI 기술 동향은?"]
for q in questions:
print(f"\n질문: {q}")
answer = web_rag.answer(q)
print(f"답변: {answer.content}\n")