에이전트형 RAG: 웹 검색 폴백
에이전트형 RAG: 웹 검색 폴백 (Conditional Routing)
RAG 애플리케이션이 데이터셋에서 답을 찾지 못하면 웹 검색으로 폴백하도록 만드는 방법을 살펴볼게요. 조건부 라우팅(ConditionalRouter)을 사용해 데이터 흐름을 제어하고, 조건에 따라 데이터 소스를 웹으로 전환하는 에이전트형 RAG 파이프라인을 만들어요.
출처: 공식문서
개요
RAG에서는 검색 단계가 LLM의 주요 정보 소스예요. 그런데 데이터베이스에 필요한 정보가 없으면 검색 단계의 효과가 제한돼요. 이런 상황에서 에이전트 행동을 더하고 웹을 폴백 데이터 소스로 쓰는 게 실용적일 수 있어요. 이 튜토리얼에서는 초기에 주어진 문서에서 답을 찾지 못하면 질문을 웹 기반 RAG 경로로 보내는 파이프라인을 만들어요.
설치와 환경
pip install haystack-ai serperdev-haystack
API 키를 입력받아 저장해요.
from getpass import getpass
import os
if "OPENAI_API_KEY" not in os.environ:
os.environ["OPENAI_API_KEY"] = getpass("Enter OpenAI API key:")
if "SERPERDEV_API_KEY" not in os.environ:
os.environ["SERPERDEV_API_KEY"] = getpass("Enter Serper Api key: ")
문서 스토어 채우기
뮌헨에 대한 Document를 만들어 InMemoryDocumentStore에 써요. 이 문서에서 문제의 답을 먼저 찾게 돼요.
from haystack.dataclasses import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
document_store = InMemoryDocumentStore()
documents = [
Document(
content="""Munich, the vibrant capital of Bavaria in southern Germany, exudes a perfect blend of rich cultural
heritage and modern urban sophistication. Nestled along the banks of the Isar River, Munich is renowned
for its splendid architecture, including the iconic Neues Rathaus (New Town Hall) at Marienplatz and
the grandeur of Nymphenburg Palace. The city is a haven for art enthusiasts, with world-class museums like the
Alte Pinakothek housing masterpieces by renowned artists. Munich is also famous for its lively beer gardens, where
locals and tourists gather to enjoy the city's famed beers and traditional Bavarian cuisine. The city's annual
Oktoberfest celebration, the world's largest beer festival, attracts millions of visitors from around the globe.
Beyond its cultural and culinary delights, Munich offers picturesque parks like the English Garden, providing a
serene escape within the heart of the bustling metropolis. Visitors are charmed by Munich's warm hospitality,
making it a must-visit destination for travelers seeking a taste of both old-world charm and contemporary allure."""
)
]
document_store.write_documents(documents)
초기 RAG 파이프라인 컴포넌트
주어진 문서로 질문에 답할 수 없으면 "no_answer"라는 텍스트로 응답하라고 LLM에 지시하는 프롬프트를 정의해요. 이 키워드가 웹 검색 폴백 경로로 보내는 신호가 돼요.
from haystack.components.builders import ChatPromptBuilder
from haystack.dataclasses import ChatMessage
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
retriever = InMemoryBM25Retriever(document_store)
prompt_template = [
ChatMessage.from_user(
"""
Answer the following query given the documents.
If the answer is not contained within the documents reply with 'no_answer'
Documents:
{% for document in documents %}
{{document.content}}
{% endfor %}
Query: {{query}}
"""
)
]
prompt_builder = ChatPromptBuilder(template=prompt_template, required_variables="*")
llm = OpenAIChatGenerator(model="gpt-4o-mini")
웹 RAG 컴포넌트
웹 기반 RAG용으로 SerperDevWebSearch와 전용 프롬프트 빌더, LLM을 준비해요.
from haystack_integrations.components.websearch.serperdev import SerperDevWebSearch
prompt_for_websearch = [
ChatMessage.from_user(
"""
Answer the following query given the documents retrieved from the web.
Your answer should indicate that your answer was generated from websearch.
Documents:
{% for document in documents %}
{{document.content}}
{% endfor %}
Query: {{query}}
"""
)
]
websearch = SerperDevWebSearch()
prompt_builder_for_websearch = ChatPromptBuilder(template=prompt_for_websearch, required_variables="*")
llm_for_websearch = OpenAIChatGenerator(model="gpt-4o-mini")
ConditionalRouter 만들기
ConditionalRouter는 에이전트 행동의 핵심으로, 특정 조건에 따라 데이터 라우팅을 담당해요. 각 경로마다 condition(조건), output(출력 값), output_name, output_type을 정의해요. 여기서는 두 경로를 만들어요.
- LLM이
"no_answer"라고 답하면 → 웹 검색을 수행. 원래query를 출력 값으로 넘기고 출력 이름은go_to_websearch. - 그 외에는 → 주어진 문서만으로 답이 되므로 여기서 종료. LLM 응답을
answer라는 출력으로 반환.
from haystack.components.routers import ConditionalRouter
routes = [
{
"condition": "{{'no_answer' in replies[0].text}}",
"output": "{{query}}",
"output_name": "go_to_websearch",
"output_type": str,
},
{
"condition": "{{'no_answer' not in replies[0].text}}",
"output": "{{replies[0].text}}",
"output_name": "answer",
"output_type": str,
},
]
router = ConditionalRouter(routes)
에이전트형 RAG 파이프라인 만들기
모든 컴포넌트를 파이프라인에 추가하고 연결해요. router의 go_to_websearch 출력은 웹에서 문서를 가져올 websearch와 프롬프트에 쓸 prompt_builder_for_websearch 양쪽에 연결돼요.
from haystack import Pipeline
agentic_rag_pipe = Pipeline()
agentic_rag_pipe.add_component("retriever", retriever)
agentic_rag_pipe.add_component("prompt_builder", prompt_builder)
agentic_rag_pipe.add_component("llm", llm)
agentic_rag_pipe.add_component("router", router)
agentic_rag_pipe.add_component("websearch", websearch)
agentic_rag_pipe.add_component("prompt_builder_for_websearch", prompt_builder_for_websearch)
agentic_rag_pipe.add_component("llm_for_websearch", llm_for_websearch)
agentic_rag_pipe.connect("retriever", "prompt_builder.documents")
agentic_rag_pipe.connect("prompt_builder.prompt", "llm.messages")
agentic_rag_pipe.connect("llm.replies", "router.replies")
agentic_rag_pipe.connect("router.go_to_websearch", "websearch.query")
agentic_rag_pipe.connect("router.go_to_websearch", "prompt_builder_for_websearch.query")
agentic_rag_pipe.connect("websearch.documents", "prompt_builder_for_websearch.documents")
agentic_rag_pipe.connect("prompt_builder_for_websearch", "llm_for_websearch")
파이프라인 실행
run()에서 질문을 retriever, prompt_builder, router 세 곳에 넘겨요. 첫 질문은 문서에 답이 있으므로, router의 answer 출력으로 곧바로 답이 나와요.
query = "What region of Germany is Munich in?"
result = agentic_rag_pipe.run(
{"retriever": {"query": query}, "prompt_builder": {"query": query}, "router": {"query": query}}
)
# Print the `answer` coming from the ConditionalRouter
print(result["router"]["answer"])
이번엔 문서에 답이 없는 질문으로 웹 검색이 동작하는지 확인해요. 이 질문은 router가 go_to_websearch로 보내고, 웹에서 가져온 문서로 llm_for_websearch가 답을 생성해요.
query = "How many people live in Munich?"
result = agentic_rag_pipe.run(
{"retriever": {"query": query}, "prompt_builder": {"query": query}, "router": {"query": query}}
)
# Print the `replies` generated using the web searched Documents
print(result["llm_for_websearch"]["replies"][0].text)
전체 결과를 보면 websearch 컴포넌트가 웹에서 가져온 문서들의 링크도 제공하는 것을 확인할 수 있어요.