Skip to content

feat: add metadata based retrieval reranking - #153

Merged
Yu-JeSeung merged 5 commits into
mainfrom
feat/chatbot-response-accuracy
Aug 4, 2026
Merged

feat: add metadata based retrieval reranking#153
Yu-JeSeung merged 5 commits into
mainfrom
feat/chatbot-response-accuracy

Conversation

@Yu-JeSeung

@Yu-JeSeung Yu-JeSeung commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🎯 배경

  • 챗봇의 하이브리드 검색에서 정답 문서가 top-3 안에 검색되지만 부서 연락처, 학과 소개, 학사 정책 문서보다 낮게 정렬되는 문제를 개선합니다.
  • 정정 문맥이 포함된 연락처 질의에서도 사용자가 마지막으로 요청한 학과의 공식 연락처를 우선하도록 보완합니다.

🔍 주요 내용

  • 부서명, leaf title, breadcrumb 및 문서 유형 기반 retrieval 재랭킹을 추가했습니다.
  • 공식 학과 페이지와 간접적으로 학과가 언급된 페이지를 구분해 가중치를 적용했습니다.
  • 연락처 요청이 아닌 학과 질의에서 연락처 문서를 감점하도록 보완했습니다.
  • 호텔관광학과 정정 문맥을 RAG 및 챗봇 회귀 케이스에 추가했습니다.
  • RAG 평가 34건과 구버전 인덱스 호환성 검사를 수행했습니다.

변경 요약

하이브리드 검색에 메타데이터 기반 재정렬을 추가했습니다. 부서 연락처와 부서 관련 문서의 검색 정확도를 개선합니다.

주요 변경점

  • 부서명, leaf 제목, breadcrumb, 문서 유형을 기준으로 점수를 보정합니다.
  • 공식 부서 페이지와 간접 언급 페이지에 다른 가중치를 적용합니다.
  • 일반 부서 검색에서는 연락처 문서에 감점을 적용합니다.
  • 정정 문맥에서는 요청한 부서의 공식 연락처를 우선합니다.
  • 호텔관광학과 관련 RAG·챗봇 회귀 테스트 4건을 추가했습니다.
  • 희소 메타데이터 환경에서 조직명 정규화와 연락처 보너스를 검증합니다.
  • 기존 색인과의 호환성을 확인하고 34개 RAG 평가를 실행했습니다.

주의/리스크

  • 검색 품질은 새 가중치 설정에 영향을 받습니다.
  • 색인 메타데이터 형식이 변경되면 검색 순위를 다시 확인해야 합니다.

다음 액션

  • 운영 환경에서 주요 부서 연락처 검색 결과를 모니터링합니다.
  • 새 부서명과 문서 유형을 추가할 때 회귀 테스트를 갱신합니다.

@Yu-JeSeung Yu-JeSeung self-assigned this Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 58c79efb-490a-4b72-b4ad-2c0662f561bb

📥 Commits

Reviewing files that changed from the base of the PR and between 5109b25 and 6491421.

📒 Files selected for processing (1)
  • tests/regression/chatbot/check_query_index_metadata_quality.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/regression/chatbot/check_query_index_metadata_quality.py

📝 Walkthrough

Walkthrough

연락처 및 조직 단위 질의의 정규화 규칙을 변경했습니다. 문서 메타데이터 기반 검색 점수 보정을 추가했습니다. 관련 회귀 테스트와 RAG 평가 사례를 확장했습니다.

Changes

검색 재순위 조정

Layer / File(s) Summary
조직 단위 정규화
LLM/sub_model/query_index.py, tests/regression/chatbot/check_query_index_metadata_quality.py
연락처 일반 용어를 제외하고 질의 뒤쪽의 조직 단위명을 선택합니다. 축약된 단위명은 UNIT_SUFFIX_RE 또는 색인 메타데이터로 검증합니다.
메타데이터 검색 점수 보정
LLM/sub_model/query_index.py
연락처·단위 일치도, 정규 출처, leaf 제목, 검색어 커버리지에 따라 문서 점수를 보정합니다. 일반 단위 질의에서는 연락처 문서에 감점을 적용합니다.
연락처 검색 회귀 검증
tests/regression/chatbot/chatbot_regression_cases.json, tests/regression/chatbot/rag_eval_cases.json, tests/regression/chatbot/check_query_index_metadata_quality.py
호텔관광학과, 학생성공지원팀, 컴퓨터공학부 관련 검색 결과와 조직 단위 정규화 및 보너스 조건을 검증합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant hybrid_search
  participant _metadata_retrieval_bonus
  participant 색인_메타데이터
  hybrid_search->>_metadata_retrieval_bonus: 질의와 문서 전달
  _metadata_retrieval_bonus->>색인_메타데이터: 조직·출처·제목·breadcrumb 확인
  색인_메타데이터-->>_metadata_retrieval_bonus: 메타데이터 반환
  _metadata_retrieval_bonus-->>hybrid_search: 문서별 보너스 반환
  hybrid_search->>hybrid_search: 최종 검색 점수 계산
Loading

Possibly related PRs

  • dongsooop/AI#110: 동일한 LLM/sub_model/query_index.py 검색 재순위 로직과 관련 RAG 평가 사례를 확장합니다.
  • dongsooop/AI#151: 연락처 단위 매칭과 검색 점수에 _unit_term_score 기반 변경을 적용합니다.
  • dongsooop/AI#152: 챗봇 연락처 검색 동작과 호텔 부서 관련 회귀 테스트를 변경합니다.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 메타데이터 기반 검색 재정렬이라는 PR의 주요 변경 사항을 간결하고 명확하게 설명합니다.
Description check ✅ Passed 배경과 주요 내용은 충실하지만 템플릿의 관련 이슈 항목과 이슈 번호가 누락되었습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chatbot-response-accuracy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
LLM/sub_model/query_index.py (1)

570-608: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

search_df.iterrows() 중복 및 성능 개선이 필요합니다.

570-577번째 줄과 593-600번째 줄은 동일한 단위 매칭 로직(unit, title, leaf_title에 대해 _unit_term_score 최댓값 계산)을 iterrows()로 중복 구현합니다. iterrows()는 매 행마다 Series를 생성하므로 느립니다. 이 파일의 다른 부분(614-632번째 줄)은 이미 .map()을 사용한 벡터화 패턴을 채택하고 있어 일관성이 떨어집니다.

이 함수는 연락처 키워드가 포함된 질의마다 호출되며, 연락처 질의는 이 챗봇의 핵심 사용 사례입니다. 색인 규모가 커지면 요청 경로에서 지연 시간이 늘어날 수 있습니다.

공통 헬퍼로 추출하고 .apply() 또는 .map() 기반으로 바꾸는 것을 권장합니다.

♻️ 제안하는 리팩터링 방향
+def _unit_match_scores(target_unit: str, df: pd.DataFrame) -> np.ndarray:
+    """unit/title/leaf_title 열에서 target_unit과의 최대 매칭 점수(0~1)를 계산합니다."""
+    def _row_score(row):
+        candidates = (row.get("unit", ""), row.get("title", ""), row.get("leaf_title", ""))
+        return max((_unit_term_score(target_unit, str(v)) for v in candidates), default=0)
+    return (df.apply(_row_score, axis=1) / 6.0).to_numpy()

두 분기 모두 이 헬퍼를 호출하도록 정리하면 중복이 사라지고 유지보수가 쉬워집니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@LLM/sub_model/query_index.py` around lines 570 - 608, Extract the duplicated
unit-score calculation into a shared helper near the surrounding ranking logic,
computing the maximum _unit_term_score across unit, title, and leaf_title for
one row. Replace both search_df.iterrows() loops that build unit_scores with a
.apply() or .map()-based Series calculation using that helper, while preserving
the existing normalization, canonical-page checks, contact-source matching, and
bonus behavior in each branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@LLM/sub_model/query_index.py`:
- Around line 112-130: Update _known_unit_alias so its cached result cannot
outlive the underlying search_df index: either remove `@lru_cache` and evaluate
against the current index on every call, or include a stable search_df
identity/version in the cache key. Preserve the existing alias-matching logic
and empty-index behavior.
- Around line 561-635: Restructure the branching in the metadata bonus logic so
contact_like queries no longer return before the shared leaf-title and
query-term coverage calculations. Keep contact-specific handling and the
target_unit-dependent general handling mutually exclusive, preserve the contact
path’s existing scoring behavior without NON_CONTACT_DOC_PENALTY, and let all
queries reach the common EXACT_LEAF_RETRIEVAL_BOOST and
QUERY_TERM_COVERAGE_BOOST logic before returning bonus.

---

Nitpick comments:
In `@LLM/sub_model/query_index.py`:
- Around line 570-608: Extract the duplicated unit-score calculation into a
shared helper near the surrounding ranking logic, computing the maximum
_unit_term_score across unit, title, and leaf_title for one row. Replace both
search_df.iterrows() loops that build unit_scores with a .apply() or
.map()-based Series calculation using that helper, while preserving the existing
normalization, canonical-page checks, contact-source matching, and bonus
behavior in each branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6da8c8f4-27ac-4cc9-b0eb-44ca469bb8da

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6f9e0 and 924ac10.

📒 Files selected for processing (4)
  • LLM/sub_model/query_index.py
  • tests/regression/chatbot/chatbot_regression_cases.json
  • tests/regression/chatbot/check_query_index_metadata_quality.py
  • tests/regression/chatbot/rag_eval_cases.json

Comment on lines +112 to +130
@lru_cache(maxsize=256)
def _known_unit_alias(candidate: str) -> bool:
"""Accept a bare '-과' abbreviation only when it matches indexed unit metadata."""
if not candidate.endswith("과") or len(_unit_root(candidate)) < 2:
return False

indexed_df = globals().get("search_df")
if indexed_df is None or indexed_df.empty:
return False

rows = indexed_df[indexed_df["doc_type"].isin(["contact", "department"])]
for column in ("unit", "leaf_title", "title"):
if column not in rows.columns:
continue
if rows[column].fillna("").astype(str).map(
lambda value: _unit_term_score(candidate, value) >= 4
).any():
return True
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: query_index.py 내에서 색인(search_df)을 런타임에 재적재하는 코드가 있는지 확인합니다.
set -euo pipefail

fd query_index.py LLM/sub_model --exec cat -n {}
rg -n 'search_df\s*=' LLM/sub_model/query_index.py
rg -n 'cache_clear|reload|_index_resources' LLM/sub_model/query_index.py

Repository: dongsooop/AI

Length of output: 43793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a '.*\.py$' LLM/sub_model | sed 's#^\./##'

echo "== query_index_loader.py =="
cat -n LLM/sub_model/query_index_loader.py

echo "== search_df/cache_clear/reload usages outside query_index.py =="
rg -n 'search_df\s*=|load_query_index_resources|query_index_loader|cache_clear|reload' LLM . --glob '*.py' | head -n 200

Repository: dongsooop/AI

Length of output: 8771


_known_unit_alias를 캐시하지 않거나 캐시 키에 색인 식별자를 포함하세요.

_known_unit_aliascandidate만으로 캐시하며 globals().get("search_df")를 기준으로 판정합니다. 색인 파일 기반의 search_df만 모듈 시작 시 로드하므로 런타임 내 재적재 로직은 현재 없지만, 색인 교체 후 같은 모듈을 사용하는 동시 요청에서는 이전 search_df의 결과를 계속 반환할 수 있습니다. 캐시는 제거하고 매번 색단을 평가하거나 search_df 식별자/버전을 캐시 키에 포함하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@LLM/sub_model/query_index.py` around lines 112 - 130, Update
_known_unit_alias so its cached result cannot outlive the underlying search_df
index: either remove `@lru_cache` and evaluate against the current index on every
call, or include a stable search_df identity/version in the cache key. Preserve
the existing alias-matching logic and empty-index behavior.

Comment thread LLM/sub_model/query_index.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/regression/chatbot/check_query_index_metadata_quality.py`:
- Around line 402-405: 테스트의 `_metadata_retrieval_bonus` 검증을 전체 배열의 `np.any` 확인에서
대상 행의 메타데이터 필터링으로 변경하세요. 일반 질의는 기대 URL과 `doc_type == "contact"`를 함께 만족하는 행의 보너스를
검증하고, 특정 질의는 여기에 `unit == "학생성공지원팀"`을 추가하세요. URL이 반복되는 경우에도 오답 조직 청크가 통과하지 않도록
하며, 재순위 의도라면 오답 조직 행과 비교해 대상 행의 보너스가 더 높은지도 확인하도록 `student_row` 및 관련 검증을 수정하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cdba12d-9fce-4bfb-8441-3f2a0756f57a

📥 Commits

Reviewing files that changed from the base of the PR and between 924ac10 and 481df63.

📒 Files selected for processing (2)
  • LLM/sub_model/query_index.py
  • tests/regression/chatbot/check_query_index_metadata_quality.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • LLM/sub_model/query_index.py

Comment thread tests/regression/chatbot/check_query_index_metadata_quality.py
@Yu-JeSeung Yu-JeSeung added run-rag-check check rag eval and removed run-rag-check check rag eval labels Aug 4, 2026
@Yu-JeSeung
Yu-JeSeung merged commit 0e06f07 into main Aug 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant