지난 시간까지 진행한 <Streamlit으로 데모 RAG 서비스 구축> 프로젝트를 더 디벨롭하며, 관련 지식들을 정리해보고자 한다.
제공된 데이터에는 특정 시점의 수치나 거래량 등이 빈번하여, 해당 수치가 몇년도의 수치인지,, 등을 LLM이 판단하고 출력하는 것이 필요했다..
현재 “9월 전력량은?” 같은 질문을 리라이팅할 때 문제는:
- LLM이 현재 시점(2025년 7월) 을 기준으로 “2025년 9월” 전력량” 을 묻는 질문으로 잘못 해석할 수도 있다
- 하지만 사용자 데이터에는 2024년 9월 데이터만 있음 → 연도 불일치 문제 발생 가능성.
문서 제공 시점: 2025년 초
- 데이터 내용: 주로 2024년까지지만, 일부 2025년 초 데이터도 포함
- 현재/미래 시점 정보: 거의 없음
😎 즉, 데이터는 “과거 중심” + “현재(2025 초반 일부)”
➡ “2024로 고정”은 ❌
➡ “2025 현재/미래” 질문도 다룰 수 있게 유연한 리라이팅 필요.
🤐 그래서 현재 시간과 날짜를 출력하고, 이를 기반으로 LLM이 해당 시점 이후의 데이터(미래)는 존재하지 않으며, 데이터가 2024년~2025년 초라는 것을 명시하고자 하였다.
datetime
modulefrom 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}
추가
📌 (2) {now_date}
로 구체화된 미래 데이터 제한
now_date
(예: "2025-07"
)를 써서 “2025년 7월 이후 데이터 없음” 처럼 더 정확한 cut-off point 를 알려줌.📌 (3) 프롬프트 구조 강화
➡ LLM이 질문 리라이팅 시 해야 할 일, 하면 안 되는 일을 명확히 알게 됨.
✅ (1) 질문 해석 정확도↑
✅ (2) 미래 질문 처리 개선
✅ (3) 데이터 신뢰성 유지
🚀 정리
- 이전과 달라진 점 →
현재 날짜(now)
와now_date
(연월)를 넣어 리라이팅 기준점을 명확히 제공- 얻는 효과 →
- ✔ 질문이 어느 시점을 가리키는지 LLM이 더 잘 판단
- ✔ 미래 데이터에 대해 허위 정보 생성 방지
- ✔ “연도 없는 질문”도 문맥에 맞게 2024 or 2025로 보정 가능
👉 이렇게 하면 “시간에 민감한 질문” (예: 월별 전력량, 연도별 통계) 을 리라이팅할 때 LLM이 헷갈리지 않도록 안전장치가 생기는 것.
➡ 즉, 질문 리라이팅의 “시간 정확도”가 올라간다.
사용자가 쿼리 입력 시 다수의 질문을 제공할 때, 현재는 하나의 질문으로 취급 및 임베딩하여 올바른 답변을 제공하지 못한다는 한계가 있었다.
이를 해결하기 위해서는 LLM이 질문을 읽고, 다수의 질문이라면 질문의 분해 여부를 판단하고, 각 질문에 대해 리라이팅하여 질문별로 답변을 생성하는 과정이 필요하다.
질문 분해 여부 판단 → 리라이팅 → 질문별 답변 생성
1️⃣ 질문 분해 여부 판단
"9월 전력량은? 정부가 최근 10년간 신약개발 투자비용은?"
같은 복수 질문을 하면, LLM이 혼동해서 한꺼번에 답변하거나 답변이 섞이는 문제 발생.detect_and_split_questions()
함수 구현json.loads()
사용 → 응답을 파이썬 리스트(list) 로 변환["질문1", "질문2", ...]
형태로 리턴되므로 for loop 에서 바로 사용 가능.2️⃣ 질문 리라이팅 (Question Rewriting)
rewrite_question()
함수 추가now
)와 “데이터는 2024~2025 초반까지만 있음” 같은 맥락을 prompt에 넣어 질문을 더 구체적으로 만들도록 유도.3️⃣ 질문별 답변 생성
generate_answer_stream()
호출 시 전체 질문(question) 대신 리라이팅된 질문(rewritten_question) 을 넘김 → 다른 질문이 섞여 들어가는 현상 방지.사용자 입력
↓
detect_and_split_questions() → ["질문1", "질문2", ...]
↓
for 질문 in 리스트:
↳ rewrite_question() → 질문 명확화
↳ get_embeddings() → 질문을 1536차원 벡터로 변환
↳ search_milvus() → 벡터 유사도 검색으로 관련 문맥 찾기
↳ generate_answer_stream() → 문맥 기반 GPT 답변 스트리밍 생성
↳ 질문별 답변 출력 (문맥/답변/처리시간)
RAG 검색 방법 비교(희소 검색, 밀집검색-Sparse Retriever vs Dense Retriever)
1️⃣ Dense Search (밀집 검색)
👉 문서와 쿼리를 “벡터(embedding)”로 변환해 유사도를 계산하는 방식
text-embedding-3-small
)로 고차원 벡터로 변환2️⃣ Sparse Search (희소 검색)
👉 문서와 쿼리를 “토큰 단위(단어 단위)”로 분해해, 토큰 빈도 기반으로 검색하는 방식
→ 전통적인 TF-IDF나 BM25가 대표적인 Sparse Search 기법
3️⃣ Hybrid Search (하이브리드 검색)
👉 Dense + Sparse Search를 합쳐 장점만 살린 방식
🌈 왜 Hybrid 방식이 필요할까?
✅ Dense만 쓰면?
✅ Sparse만 쓰면?
✅ Hybrid → 둘의 장점을 결합
👉 RAG 시스템에서 가장 이상적인 방식
✅ 한 줄 요약
- Dense Search → 의미 검색(semantic), 벡터 기반
- Sparse Search → 정확한 키워드 검색, TF-IDF/BM25 기반
- Hybrid Search → 둘 다 쓰고 가중치로 조합
📌 실무에서는?
- FAQ, 법률, 내부 문서 검색 → Hybrid Search 선호
- 간단한 문서 검색, 뉴스, 블로그 → Dense Search로 충분
- 단순 키워드 필터링(상품 코드, ID) → Sparse Search로도 충분