모든 글

요즘 AI 에이전트 개발: 리트리버와 RAG

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")