scvcoder commited on
Commit
8863f87
·
verified ·
1 Parent(s): ca27852

Retriever: RRF로 키워드+원본 질문 결합 — LLM이 핵심 주제어 누락(e.g. '처방전 보관기간' → ['보관기간']) 해도 원본 질문이 안전망. cases·guides 동일 적용

Browse files
Files changed (1) hide show
  1. src/kpaa/retrieval/retriever.py +35 -5
src/kpaa/retrieval/retriever.py CHANGED
@@ -211,9 +211,23 @@ async def _fetch_cases(
211
  if on_progress:
212
  await on_progress("fetch_done", {"source": "case", "count": 0, "keyword": ""})
213
  return []
214
- # 매칭된 키워드 1~3개를 공백으로 합쳐 전달 search 내부에서 OR 결합
215
- query = " ".join((plan.search_keywords or [plan.query])[:3])
216
- hits = idx.search(query, k=k)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  out: list[Excerpt] = []
218
  for h in hits:
219
  category = " > ".join(filter(None, (h.category1, h.category2, h.category3)))
@@ -260,8 +274,24 @@ async def _fetch_guides(
260
  if on_progress:
261
  await on_progress("fetch_done", {"source": "guide", "count": 0, "keyword": ""})
262
  return []
263
- query = " ".join((plan.search_keywords or [plan.query])[:3])
264
- hits = idx.search(query, k=k)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  out: list[Excerpt] = []
266
  for h in hits:
267
  # doc_date "YYYY.MM" → 연도만 추출해 recency 점수에 사용
 
211
  if on_progress:
212
  await on_progress("fetch_done", {"source": "case", "count": 0, "keyword": ""})
213
  return []
214
+ # 키워드 + 원본 질문 쿼리로 검색 RRF(Reciprocal Rank Fusion)로 결합
215
+ # LLM 추출 키워드가 핵심 주제어 누락 시 원본 질문이 안전망. 단순 concat은
216
+ # BM25 토큰 가중치 차이로 한쪽이 독점 가능하므로 rank 기반 결합 필요.
217
+ _RRF_K = 60
218
+ queries: list[str] = []
219
+ if plan.search_keywords:
220
+ queries.append(" ".join(plan.search_keywords[:3]))
221
+ if plan.query and plan.query not in queries:
222
+ queries.append(plan.query)
223
+ rrf_scores: dict = {}
224
+ hit_map: dict = {}
225
+ for q in queries:
226
+ for rank, h in enumerate(idx.search(q, k=k)):
227
+ rrf_scores[h.ntt_id] = rrf_scores.get(h.ntt_id, 0.0) + 1.0 / (_RRF_K + rank)
228
+ hit_map.setdefault(h.ntt_id, h)
229
+ top_ids = sorted(rrf_scores, key=lambda i: -rrf_scores[i])[:k]
230
+ hits = [hit_map[i] for i in top_ids]
231
  out: list[Excerpt] = []
232
  for h in hits:
233
  category = " > ".join(filter(None, (h.category1, h.category2, h.category3)))
 
274
  if on_progress:
275
  await on_progress("fetch_done", {"source": "guide", "count": 0, "keyword": ""})
276
  return []
277
+ # 키워드 + 원본 질문 두 쿼리로 검색 후 RRF로 결합 — LLM 추출 키워드가 핵심
278
+ # 주제어를 누락해도 원본 질문이 안전망 (e.g. "처방전 보관기간" → ["보관기간"]만
279
+ # 추출돼도 원본 query에서 "처방전" 토큰 hit 가능). 단순 concat은 BM25 가중치
280
+ # 차이로 한쪽이 독점하므로 rank 기반 union 필요.
281
+ _RRF_K = 60
282
+ queries: list[str] = []
283
+ if plan.search_keywords:
284
+ queries.append(" ".join(plan.search_keywords[:3]))
285
+ if plan.query and plan.query not in queries:
286
+ queries.append(plan.query)
287
+ rrf_scores: dict = {}
288
+ hit_map: dict = {}
289
+ for q in queries:
290
+ for rank, h in enumerate(idx.search(q, k=k)):
291
+ rrf_scores[h.chunk_id] = rrf_scores.get(h.chunk_id, 0.0) + 1.0 / (_RRF_K + rank)
292
+ hit_map.setdefault(h.chunk_id, h)
293
+ top_ids = sorted(rrf_scores, key=lambda i: -rrf_scores[i])[:k]
294
+ hits = [hit_map[i] for i in top_ids]
295
  out: list[Excerpt] = []
296
  for h in hits:
297
  # doc_date "YYYY.MM" → 연도만 추출해 recency 점수에 사용