RAG - Rewriting & Decomposition & Hybrid Search

wldbs._.·2025년 8월 6일
0

AI-LLM

목록 보기
14/21
post-thumbnail

지난 시간까지 진행한 <Streamlit으로 데모 RAG 서비스 구축> 프로젝트를 더 디벨롭하며, 관련 지식들을 정리해보고자 한다.


🕐 Considering Datetime

👻 문제점

제공된 데이터에는 특정 시점의 수치나 거래량 등이 빈번하여, 해당 수치가 몇년도의 수치인지,, 등을 LLM이 판단하고 출력하는 것이 필요했다..

현재 “9월 전력량은?” 같은 질문을 리라이팅할 때 문제는:

  • LLM이 현재 시점(2025년 7월) 을 기준으로 “2025년 9월” 전력량” 을 묻는 질문으로 잘못 해석할 수도 있다
  • 하지만 사용자 데이터에는 2024년 9월 데이터만 있음 → 연도 불일치 문제 발생 가능성.

문서 제공 시점: 2025년 초

  • 데이터 내용: 주로 2024년까지지만, 일부 2025년 초 데이터도 포함
  • 현재/미래 시점 정보: 거의 없음

😎 즉, 데이터는 “과거 중심” + “현재(2025 초반 일부)”
“2024로 고정”은 ❌
➡ “2025 현재/미래” 질문도 다룰 수 있게 유연한 리라이팅 필요.

👻 해결방안

🤐 그래서 현재 시간과 날짜를 출력하고, 이를 기반으로 LLM이 해당 시점 이후의 데이터(미래)는 존재하지 않으며, 데이터가 2024년~2025년 초라는 것을 명시하고자 하였다.

  • 파이썬의 datetime module
from datetime import datetime
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
...
now = datetime.now(ZoneInfo("Asia/Seoul")).strftime("%Y-%m-%d %H:%M:%S")

✅ 리라이팅 프롬프트에 “데이터 연도”를 명시

"You are rewriting questions for a search engine that ONLY has data from 2024.
If the user asks about an unspecified month, assume it refers to 2024 unless stated otherwise."

✅ 이를 활용하여 Streamlit과 프롬프트에도 해당 정보를 넣었다.

messages=[
    {"role": "system", "content": """You are a helpful and informative assistant..."""},
    {"role": "user", "content": f"Current date and time: {now}\n\nContext: {context}\n\nQuestion: {query}"},
]

👻 결과

📌 (1) 📅 Current date and time: {now} 추가

  • 현재 시점(날짜/시간) 을 명시적으로 프롬프트에 넣음
  • 이제 LLM이 오늘 날짜가 언제인지 정확히 알고 질문을 리라이팅.

📌 (2) {now_date} 로 구체화된 미래 데이터 제한

  • now_date(예: "2025-07")를 써서 “2025년 7월 이후 데이터 없음” 처럼 더 정확한 cut-off point 를 알려줌.

📌 (3) 프롬프트 구조 강화

  • Rules 블록 안에 세부 규칙이 들어감:
    • 연도 없는 질문 → 2024 또는 2025 초반으로 리라이팅
    • 미래 질문 → 데이터 없음 경고

➡ LLM이 질문 리라이팅 시 해야 할 일, 하면 안 되는 일을 명확히 알게 됨.

👻 개선점

✅ (1) 질문 해석 정확도↑

  • LLM이 현재 날짜를 인지 “9월 전력량은?” 같은 질문을 받을 때,
    • 현재 날짜(예: 2025-07) 기준 → “2024년 9월”로 리라이팅 가능.
    • 미래 달(예: 2025-09) → “미래 데이터 없음” 경고 가능.

✅ (2) 미래 질문 처리 개선

  • 사용자가 “2026년 전력 사용량?” 같은 질문을 하면,
  • LLM은 현재(now) 와 비교해서 “2026년은 미래”라는 걸 명확히 인식 → “데이터 없음” 메시지 또는 경고를 넣도록 유도 가능.

✅ (3) 데이터 신뢰성 유지

  • ‘현재 날짜’라는 기준점을 주니까,
  • LLM이 질문을 너무 최신 데이터 기준으로 오해하거나 미래 예측을 만들어내는 것(hallucination) 을 방지.

🚀 정리

  • 이전과 달라진 점현재 날짜(now)now_date(연월)를 넣어 리라이팅 기준점을 명확히 제공
  • 얻는 효과
    • ✔ 질문이 어느 시점을 가리키는지 LLM이 더 잘 판단
    • ✔ 미래 데이터에 대해 허위 정보 생성 방지
    • ✔ “연도 없는 질문”도 문맥에 맞게 2024 or 2025로 보정 가능

👉 이렇게 하면 “시간에 민감한 질문” (예: 월별 전력량, 연도별 통계) 을 리라이팅할 때 LLM이 헷갈리지 않도록 안전장치가 생기는 것.

즉, 질문 리라이팅의 “시간 정확도”가 올라간다.


🧬 Query Decomposition

사용자가 쿼리 입력 시 다수의 질문을 제공할 때, 현재는 하나의 질문으로 취급 및 임베딩하여 올바른 답변을 제공하지 못한다는 한계가 있었다.

이를 해결하기 위해서는 LLM이 질문을 읽고, 다수의 질문이라면 질문의 분해 여부를 판단하고, 각 질문에 대해 리라이팅하여 질문별로 답변을 생성하는 과정이 필요하다.

질문 분해 여부 판단 → 리라이팅 → 질문별 답변 생성

👽 문제점

1️⃣ 질문 분해 여부 판단

  • 문제점 인식 → 사용자가 "9월 전력량은? 정부가 최근 10년간 신약개발 투자비용은?" 같은 복수 질문을 하면, LLM이 혼동해서 한꺼번에 답변하거나 답변이 섞이는 문제 발생.
  • 해결 방식
    • detect_and_split_questions() 함수 구현
    • OpenAI API를 사용해 “사용자 입력에 몇 개의 질문이 있는지 판단하고, 여러 개라면 JSON 배열로 분리”하도록 지시.
  • 핵심 포인트
    • json.loads() 사용 → 응답을 파이썬 리스트(list) 로 변환
    • 항상 ["질문1", "질문2", ...] 형태로 리턴되므로 for loop 에서 바로 사용 가능.

2️⃣ 질문 리라이팅 (Question Rewriting)

  • 필요성
    • 원래 질문이 너무 모호하거나, 연도가 생략된 경우가 있음. (예: “9월 전력량은?” → “2024년 9월 전력량은?” 으로 명확화 필요)
  • 해결 방식
    • rewrite_question() 함수 추가
    • LLM에게 “질문의 의미는 바꾸지 말고, 더 명확하고 검색 가능한 형태로 바꿔 달라”고 요청.
    • 현재 날짜(now)와 “데이터는 2024~2025 초반까지만 있음” 같은 맥락을 prompt에 넣어 질문을 더 구체적으로 만들도록 유도.

3️⃣ 질문별 답변 생성

  • 구조 설계
    • 질문 분해 → for loop → 각 질문 처리 패턴으로 변경.
    • 질문마다 아래 순서대로 실행:
      1. 리라이팅
      2. 임베딩 (get_embeddings) → 질문을 1536차원 벡터로 변환
      3. Milvus 검색 (search_milvus) → 질문 벡터와 가장 가까운 문맥 검색
      4. 답변 생성 (generate_answer_stream) → 검색된 문맥을 기반으로 GPT가 스트리밍 답변

👽 개선 포인트

  • partial_answer, response_stream 변수를 loop 안에서 초기화 → 각 질문 답변이 서로 섞이지 않도록 함.
  • generate_answer_stream() 호출 시 전체 질문(question) 대신 리라이팅된 질문(rewritten_question) 을 넘김 → 다른 질문이 섞여 들어가는 현상 방지.
  • 검색 결과(context)가 없으면 해당 질문에 대해서만 ⚠️ 경고 출력 → “문맥 없음” 메시지가 아래에 중복해서 뜨지 않도록 처리.

👽 결과

👽 다이어그램

사용자 입력
   ↓
detect_and_split_questions()["질문1", "질문2", ...]for 질문 in 리스트:
   ↳ rewrite_question() → 질문 명확화
   ↳ get_embeddings() → 질문을 1536차원 벡터로 변환
   ↳ search_milvus() → 벡터 유사도 검색으로 관련 문맥 찾기
   ↳ generate_answer_stream() → 문맥 기반 GPT 답변 스트리밍 생성
   ↳ 질문별 답변 출력 (문맥/답변/처리시간)

📊 Hybrid Search

RAG 검색 방법 비교(희소 검색, 밀집검색-Sparse Retriever vs Dense Retriever)

1️⃣ Dense Search (밀집 검색)

👉 문서와 쿼리를 “벡터(embedding)”로 변환해 유사도를 계산하는 방식

  • 어떻게 동작?
    • 문서(또는 청크)와 사용자의 질문을 LLM용 임베딩 모델(예: OpenAI text-embedding-3-small)로 고차원 벡터로 변환
    • 코사인 유사도(Cosine Similarity) 또는 내적(Dot Product) 등으로 벡터 간 거리를 계산해 가장 가까운 결과를 찾음
  • 장점
    • 의미 기반 검색 가능 (정확히 같은 단어가 없어도, 의미적으로 비슷하면 검색 가능) → 예: “자동차 보험”과 “카 인슈어런스” 같은 표현도 매칭됨
    • 다국어 검색, 패러프레이즈(말 바꾸기) 상황에 강함
  • 단점
    • 임베딩 계산 비용이 있음 (모든 문서를 미리 벡터화해야 함)
    • 정확한 키워드 매칭(예: 특정 코드, 제품명 등)에는 약할 수 있음

2️⃣ Sparse Search (희소 검색)

👉 문서와 쿼리를 “토큰 단위(단어 단위)”로 분해해, 토큰 빈도 기반으로 검색하는 방식

→ 전통적인 TF-IDFBM25가 대표적인 Sparse Search 기법

  • 어떻게 동작?
    • 문서를 토큰(단어)으로 나누고, 각 단어의 빈도(TF)와 문서 내 중요도(IDF)를 계산
    • 사용자가 검색한 키워드가 몇 번, 얼마나 중요한 위치에 등장했는지로 점수를 매김
  • 장점
    • 정확한 키워드 매칭에 강함 (법률조항, 제품명, 코드 같은 정확한 텍스트 검색에 유리)
    • 오래된 정보 검색 방식이라 속도가 빠르고 구현이 간단 (Elasticsearch, Lucene 등)
  • 단점
    • 의미 이해 불가 (동의어나 문맥 이해 X) → 예: “car”와 “automobile”을 다르게 취급
    • 긴 문서에서 “단어 수 많음 = 점수 높음” 같은 부작용 발생 가능

🔍 Hybrid Search

3️⃣ Hybrid Search (하이브리드 검색)

👉 Dense + Sparse Search를 합쳐 장점만 살린 방식

  • 어떻게 동작?
    1. Sparse 검색 → 정확한 키워드 기반 필터링 (BM25)
    2. Dense 검색 → 의미 기반 유사도 점수 부여 (Embedding)
    3. 결과를 결합(Weighted Sum, RRF 등) → 최종 랭킹 산출
  • 장점
    • 정확한 키워드 매칭 + 의미적 확장 검색 모두 가능
    • “법률, 의학, 금융”처럼 정확성 + 의미 이해가 동시에 필요한 분야에서 강력
  • 단점
    • 구현 복잡 (두 시스템 통합 + 가중치 조정 필요)
    • 리소스 소모 ↑ (Sparse + Dense 모두 돌려야 함)

🔍 정리

🌈 왜 Hybrid 방식이 필요할까?

Dense만 쓰면?

  • 의미 검색은 잘 되지만, 정확한 키워드 매칭이 약함.

Sparse만 쓰면?

  • 키워드 검색은 잘 되지만, 동의어나 맥락 검색이 어려움.

Hybrid둘의 장점을 결합

  • Dense Search: 의미/유사도 기반 (ex. “자동차” vs. “승용차”)
  • Sparse Search: 키워드 기반 정확 매칭 (ex. “BMW X5”)

👉 RAG 시스템에서 가장 이상적인 방식

한 줄 요약

  • Dense Search의미 검색(semantic), 벡터 기반
  • Sparse Search정확한 키워드 검색, TF-IDF/BM25 기반
  • Hybrid Search둘 다 쓰고 가중치로 조합

📌 실무에서는?

  • FAQ, 법률, 내부 문서 검색 → Hybrid Search 선호
  • 간단한 문서 검색, 뉴스, 블로그 → Dense Search로 충분
  • 단순 키워드 필터링(상품 코드, ID) → Sparse Search로도 충분
profile
공부 기록용 24.08.05~ #LLM #RAG

0개의 댓글