feat(chunk): Phase 1.2-G embedding 입력 강화 (title + section + text)

Phase 1.2-G hybrid retrieval 측정 결과 Recall 0.66 정체 + 진단:

직접 nl 쿼리 시도 결과 일부 정답 doc(3854, 3981, 3982, 3920, 3921)이
top-100에도 못 들어옴. doc은 corpus + chunks + embedding 모두 정상.

진짜 원인: 자연어 query ↔ 법령 조항 의미 거리 + 짧은 본문 embedding signal 약함.
- query: '유해화학물질을 다루는 회사가 지켜야 할 안전 의무'
- 본문: '화학물질관리법 제4장 유해화학물질 영업자'
- bge-m3 입장: chunk text만으로는 같은 의미인지 못 알아봄

해결: chunks embedding 입력에 doc.title + section_title 포함.
- before: embed(c['text'])
- after:  embed('[제목] {title}\n[섹션] {section}\n[본문] {text}')

기대 효과:
- 짧은 조항 문서 매칭 회복 (3920/3921 등 300자대)
- 자연어 query → 법령 조항 의미 매칭 개선
- Recall 0.66 → 0.72~0.78

영향: chunks embedding 차원/구조 변경 X — 입력 텍스트 prefix만 다름.
재인덱싱 1회로 모든 chunks 재생성 필요.
This commit is contained in:
Hyungi Ahn
2026-04-08 13:08:23 +09:00
parent 2ca67dacea
commit 25ef3996ec

View File

@@ -313,8 +313,16 @@ async def process(document_id: int, session: AsyncSession) -> None:
client = AIClient() client = AIClient()
try: try:
for idx, c in enumerate(chunk_dicts): for idx, c in enumerate(chunk_dicts):
# Phase 1.2-G: embedding 입력 강화 (자연어 query ↔ 법령 조항 의미 매칭 개선)
# 짧은 본문이나 segment-only chunk는 임베딩 signal이 약함 → title/section 포함.
section = c.get("section_title") or ""
embed_input = (
f"[제목] {doc.title or ''}\n"
f"[섹션] {section}\n"
f"[본문] {c['text']}"
)
try: try:
embedding = await client.embed(c["text"]) embedding = await client.embed(embed_input)
except Exception as e: except Exception as e:
logger.warning(f"[chunk] document_id={document_id} chunk {idx} 임베딩 실패: {e}") logger.warning(f"[chunk] document_id={document_id} chunk {idx} 임베딩 실패: {e}")
embedding = None embedding = None