Skip to main content

Multi-Query + Decomposition

하나의 질문에서 여러 쿼리를 생성하거나, 복잡한 질문을 단순한 하위 질문으로 분해하여 검색 범위를 넓히는 기법입니다.

Multi-Query (다중 쿼리)

원본 질문에서 여러 개의 다른 관점의 쿼리를 생성하여, 각각 검색한 결과를 합칩니다.

원리

구현

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

llm = init_chat_model("gpt-4o-mini", temperature=0)

multi_query_prompt = ChatPromptTemplate.from_messages([
    ("system", """사용자 질문에 대해 3개의 서로 다른 관점의 검색 쿼리를 생성하세요.
각 쿼리는 원본 질문의 다른 측면을 다뤄야 합니다.
한 줄에 하나씩, 번호 없이 작성하세요."""),
    ("human", "{question}"),
])

multi_query_chain = multi_query_prompt | llm | StrOutputParser()

def multi_query_retrieve(question: str, retriever):
    queries = multi_query_chain.invoke({"question": question})

    # 각 쿼리로 검색 후 결과 합치기 (중복 제거)
    all_docs = []
    seen = set()
    for query in queries.strip().split("\n"):
        query = query.strip()
        if not query:
            continue
        results = retriever.invoke(query)
        for doc in results:
            doc_id = hash(doc.page_content[:200])
            if doc_id not in seen:
                seen.add(doc_id)
                all_docs.append(doc)
    return all_docs

# 사용 예시
results = multi_query_retrieve(
    "RAG에서 검색 품질을 높이는 방법은?",
    retriever,
)

효과적인 쿼리 생성 프롬프트

# 관점 다양성을 강조한 프롬프트
diverse_prompt = ChatPromptTemplate.from_messages([
    ("system", """사용자 질문에 대해 3개의 검색 쿼리를 생성하세요.

각 쿼리는 다음 관점 중 하나를 취해야 합니다:
1. 개념/정의 관점: 핵심 개념과 정의를 다루는 쿼리
2. 구현/실전 관점: 실제 구현 방법과 코드를 다루는 쿼리
3. 비교/평가 관점: 다른 기법과의 비교나 장단점을 다루는 쿼리

한 줄에 하나씩, 번호 없이 작성하세요."""),
    ("human", "{question}"),
])

Sub-query Decomposition (하위 쿼리 분해)

복잡한 질문을 여러 개의 단순한 하위 질문으로 분해하여 각각 검색합니다.

원리

구현

decompose_prompt = ChatPromptTemplate.from_messages([
    ("system", """복잡한 질문을 답변에 필요한 하위 질문들로 분해하세요.
각 하위 질문은 독립적으로 검색 가능해야 합니다.
한 줄에 하나씩, 번호 없이 작성하세요."""),
    ("human", "{question}"),
])

decompose_chain = decompose_prompt | llm | StrOutputParser()

def decompose_and_retrieve(question: str, retriever):
    sub_queries = decompose_chain.invoke({"question": question})

    all_docs = []
    seen = set()
    for query in sub_queries.strip().split("\n"):
        query = query.strip()
        if not query:
            continue
        results = retriever.invoke(query)
        for doc in results:
            doc_id = hash(doc.page_content[:200])
            if doc_id not in seen:
                seen.add(doc_id)
                all_docs.append(doc)
    return all_docs

results = decompose_and_retrieve(
    "Self-RAG와 Corrective RAG의 차이점을 비교하고, Adaptive RAG가 이 둘을 어떻게 통합하는지 설명해줘",
    retriever,
)

Step-back Prompting

구체적인 질문을 더 넓은 범위의 상위 질문으로 변환하여 배경 지식을 보강합니다.

원리

구현

stepback_prompt = ChatPromptTemplate.from_messages([
    ("system", """주어진 질문에 대해 한 단계 뒤로 물러서서, 더 넓은 범위의 상위 질문을 생성하세요.
상위 질문은 원래 질문에 답하는 데 필요한 배경 지식을 포함해야 합니다."""),
    ("human", "원본 질문: {question}\n\n상위 질문:"),
])

stepback_chain = stepback_prompt | llm | StrOutputParser()

def stepback_retrieve(question: str, retriever):
    stepback_question = stepback_chain.invoke({"question": question})

    # 원본 질문 + 상위 질문 모두로 검색
    original_results = retriever.invoke(question)
    stepback_results = retriever.invoke(stepback_question)

    # 중복 제거 후 합산
    all_docs = []
    seen = set()
    for doc in original_results + stepback_results:
        doc_id = hash(doc.page_content[:200])
        if doc_id not in seen:
            seen.add(doc_id)
            all_docs.append(doc)
    return all_docs

results = stepback_retrieve(
    "Self-RAG에서 Reflection Token의 ISREL은 어떤 역할을 하나요?",
    retriever,
)

기법 조합 전략

이 기법들은 단독으로 사용할 수도 있지만, 조합하면 더 강력합니다.
조합효과적합한 경우
Multi-Query + Reranking다양한 관점 검색 + 정밀 정렬일반적인 품질 향상
Decomposition + Multi-Query분해된 각 질문에 다중 쿼리매우 복잡한 질문
Step-back + Query Rewriting배경 지식 + 최적화된 쿼리전문 도메인 질문
# 예시: Step-back + Multi-Query 조합
def combined_retrieve(question: str, retriever):
    # 1. Step-back으로 상위 질문 생성
    stepback_q = stepback_chain.invoke({"question": question})

    # 2. 원본 + 상위 질문 모두에 Multi-Query 적용
    original_results = multi_query_retrieve(question, retriever)
    stepback_results = multi_query_retrieve(stepback_q, retriever)

    # 3. 중복 제거 후 합산
    all_docs = []
    seen = set()
    for doc in original_results + stepback_results:
        doc_id = hash(doc.page_content[:200])
        if doc_id not in seen:
            seen.add(doc_id)
            all_docs.append(doc)
    return all_docs
시작 추천: Multi-Query로 시작하세요. 복잡한 비교/분석 질문이 많다면 Sub-query Decomposition을, 전문 도메인 질문이 많다면 Step-back Prompting을 추가하세요.

참고 논문

논문학회/연도링크
Take a Step Back: Evoking Reasoning via Abstraction (Zheng et al.)ICLR 2024arXiv 2310.06117
Least-to-Most Prompting Enables Complex Reasoning (Zhou et al.)ICLR 2023arXiv 2205.10625