Skip to content

검색 신호 확장(P48) — 0단계 완료, 1단계 실측 인계 #113

Description

@tpals0409

요약

검색 신호 확장(P48)의 0단계를 끝내고 1단계 코드를 오프라인 검증까지 마쳤습니다.
남은 것은 실데이터 실측 하나이며, 측정 DB 접근이 없어 여기서 멈췄습니다.
artifact 값을 추정하거나 생성하지 않았습니다.

  • 브랜치: leo (base dev)
  • 커밋: 34c4595 · 252c187 · fcb397c
  • app/ 0줄 · 응답 계약 불변 · core.* 미접근

PR 은 아직 열지 않았습니다 — CI 의 Validate PR titleS15P11A705-N 을 강제하는데
티켓이 미발급이라, 키가 나온 뒤 열어야 처음부터 CI 가 초록입니다. 코드는 브랜치로 바로
받을 수 있습니다.

git fetch origin leo && git switch leo

왜 했나

현행 검색은 판단 근거가 질의–Context 코사인 하나입니다. 순위를 만드는 것도 그것이고
그 순위에서 무엇을 남길지 정하는 것도 그것이라, 원인이 여러 개인데 조절 지점이 컷밖에
없습니다. S15P11A705-266τ_abs 를 질의 길이로 가른 것이 그 한계의 표현입니다.

S15P11A705-255 가 가른 한 줄이 근거입니다.

본문에 그대로 있는 짧은 질의가, 본문에 없는 같은 길이의 질의보다 낮은 유사도를 받는다.

「본문에 있음」이라는 정보가 현행 신호에 반영되지 않습니다. 컷의 문제도 임계값의
문제도 아니고, 그 정보를 담은 신호가 없다는 문제입니다.

무엇이 들어 있나

파일 역할 호출
tools/search_cut/rank_score.py 순위 지표 baseline (Hit·Recall·MRR·nDCG) DB 0 · GMS 0
tools/search_cut/fusion.py keyword 신호 순수 로직 (앱·파일 비의존) 없음
tools/search_cut/keyword_matrix.py artifact 생성 GMS 배치 1 + DB 읽기
tools/search_cut/fusion_sweep.py 오프라인 비교 DB 0 · GMS 0
word_matrix.py · recall_probe.py results 행에 context_id 추가
tests/test_search_fusion.py 픽스처 24종 없음
docs/proposals/P48-…md 제안 (2차 개정)
docs/implements/2026-08-05-…md 0단계 리포트 (I51)

0단계 실측 결과

Record 42건 · 소유자 3명 · tau_abs=0.30 · tau_word=0.24 · r=0.60 · limit=20

세그먼트 n hit@1 hit@3 mrr recall@3 ndcg@3 zero 반환
문장형(정답) 컷후 12 0.8333 1.0000 0.9028 1.0000 0.9276 0.0000 4.08
단어형(정답) 컷후 66 0.8636 0.9394 0.9015 0.9205 0.9007 0.0152 2.77
무관-문장형 15 0.7333 0.33
무관-단어형 45 0.4222 1.89
타인소유-단어형 96 0.3646 1.74

재구성이 맞다는 독립 증거 2건

무관-문장형 침묵률 0.7333 = 11/15   →  -213 이 기록한 값과 일치
스팟 정답 유사도 0.2438            →  config.py 주석의 마진 0.0038 과 일치

사례 4건 — 판정 하나가 바뀝니다

질의 정답 top1 tau_word r·top1 컷 후 판정
그네 0.2671 (3위) 0.2871 0.24 0.1723 3위 생존 해소됨
스팟 0.2438 (1위) 0.2438 0.24 0.1463 1위 생존 마진 0.0038
신한 0.2301 (6위) 0.3889 0.24 0.2333 잘림 ③ 유지
부캠 0.2105 (8위) 0.4102 0.24 0.2461 잘림 ③ 유지

-255 의 ① 판정은 -266 의 단어형 분기로 이미 닫혀 있었습니다.
신한·부캠 은 두 컷 모두 아래이고, r 은 top1 이 높을수록 세게 자르므로
무관한 1위가 강할수록 정답이 확실히 잘립니다. 컷으로는 닿지 않고 순위가 올라와야
합니다
— 1단계의 표적입니다.

담당자 인계 — 실측 재개 절차

필요한 환경

비고
Docker 데몬
측정 DB :15432 가드가 이 포트를 요구합니다(T33). 실사용자 기록 포함 42건
GMS_API_KEY .env (이 저장소에 커밋되지 않음)
venv pytest 실행용

실행

# ① 행렬 재생성 — context_id 가 추가됐으므로 기존 3종 중 둘을 다시 뜹니다
DATABASE_URL=<:15432> python tools/search_cut/word_matrix.py     # GMS 배치 1회
DATABASE_URL=<:15432> python tools/search_cut/recall_probe.py    # GMS 배치 1회

# ② keyword 신호 artifact
DATABASE_URL=<:15432> python tools/search_cut/keyword_matrix.py  # GMS 배치 1회 + DB

# ③ 비교 — 여기서부터는 GMS·DB 를 부르지 않습니다
python tools/search_cut/fusion_sweep.py --rrf-cutoff-grid "0,0.008,0.02" \
       --json .search/fusion_result.json

fusion_sweep 의 조절 축: --top-k · --floor · --null-policy · --null-fill ·
--tau · --tau-word · --ratio · --rrf-cutoff · --rrf-cutoff-grid · --rrf-k

가드 — 어긋나면 수치를 내지 않고 멈춥니다

DATABASE_URL 이 :15432 가 아님        데이터 없는 DB 를 재면 「컷이 아무것도 자르지 않는다」가 결론이 됩니다
행렬 profile 불일치
행렬에 context_id 없음                 ① 을 안 돌린 경우
낡은 artifact (record_count 변동)
질의에 user_id 없음
Preset version 이 섞임

채택 조건

단어형과 문장형을 합산한 평균만으로 채택하지 않는다
기존 정답의 Hit@3 · Recall@3 퇴행이 없어야 한다
최소 한 세그먼트에서 MRR 또는 nDCG 가 개선되어야 한다
무관 질의 통과율이 유의미하게 나빠지면 기각한다
신한 · 부캠 · 그네 · 스팟 을 개별 사례로 결과표에 남긴다
Preset 으로 표현할 수 없는 질의는 별도 세그먼트로 보고한다   (sweep 이 자동 집계)

RRF 는 cutoff=0 결과만으로 채택하지 않습니다 — 표에 ⚠ 잠정 으로 표시되고
JSON 에 adoptable_alone: false 로 기록됩니다. 양수 후보를 실측해 비교하고, 채택 시
측정된 양수 값 또는 0 유지 중 하나를 근거와 함께 명시해야 합니다. rrf_k
바꾸면 점수 스케일이 바뀌므로 cutoff 를 다시 측정합니다.

Record 42건 규모이므로 결과를 일반화하지 않고 퇴행 탐지 근거로만 사용합니다.
idf 는 df 가 불안정해 참고용이며 주 채택 근거로 쓰지 않습니다.

검증 현황

픽스처 24/24 통과                 python tests/test_search_fusion.py
0단계 baseline 결정성              두 번 실행 JSON 바이트 일치
가드 6종                          전부 exit 1 확인
ruff check .                      All checks passed!
python -m compileall app tools    통과
문서 색인 정합                     통과
합성 픽스처 end-to-end sweep       4세그먼트 × 6방식 정상

② 확장 artifact = 원본 DB/GMS 계산 일치    미실행 (환경 필요)
③ sweep 반복 실행 결과 일치                미실행 (확장 artifact 필요)

pytest 는 이 머신에 venv 가 없어 미실행이며, 단독 실행으로 24/24 확인했습니다.
tests/test_search_fusion.py 는 pytest 로도 수집됩니다.

픽스처가 실데이터 전에 잡은 결함 3건

실측을 미루고 순수 로직을 먼저 검증한 것이 유효했습니다.

  1. user_id 조인이 조용히 비었습니다. recall_probe.jsonuser_id 를 최상위에만
    두는데 질의 행에서 읽고 있었습니다. 사례 4건이 전 방식에서 순위 불변이었고, 실데이터로
    먼저 돌렸다면 「keyword 신호는 효과 없다」로 방식 자체를 기각했을 오판입니다.
  2. 모집단에 embedding_status='COMPLETED' 가 빠져 검색 후보가 아닌 Context 가 유령
    후보로 올라왔고, 그것이 코사인 없는 행(sim=None)으로 나타났습니다. similarity
    cosine float 계약이 깨지는 경로였습니다.
  3. rrf_k 가 하드코딩돼 기록되지 않았습니다. 값이 바뀌어도 흔적이 남지 않아 낡은
    cutoff 를 그대로 쓰게 됩니다.

셋 다 픽스처로 회귀를 고정했습니다.

미완

  • fusion 실측 — 이 이슈의 목적
  • tools/search_cut/README.md 갱신 — 실측으로 artifact 스키마가 확정된 뒤 함께 쓰는 편이
    낫다고 판단해 미뤘습니다
  • PR — Jira 키 발급 후. 브랜치명도 {type}/{jira-key}-{summary} 로 변경 필요
  • docs/WORKLOG.md 한 줄 — 병합 시점에 추가

참고

  • 제안: docs/proposals/P48-search-signal-expansion.md
  • 0단계 리포트: docs/implements/2026-08-05-search-rank-baseline.md (I51)
  • 선행: S15P11A705-213 · S15P11A705-255 · S15P11A705-266

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions