hyungi
aeb9290cbd
feat(documents): hier 절 char_start offset (Path B) — md_content 점프 builder offset
...
플랜 ds-outline-anchor-b5 (g1~g6 코드). 핵심 ASME/법령 windowed 절의 0% 점프를
서버계산 char_start(builder offset)로 100% deterministic 점프로 전환.
- g1 migration 318: document_chunks.char_start INTEGER NULL (단일 statement, 멱등)
- g2 builder: char_start emit = FE 라인/offset 모델 미러(split('\n')+UTF-16 code unit+코드펜스 skip).
window-child=NULL, split-parent=heading offset, preamble=NULL, CR 미strip, NFC=telemetry.
node.text 보존(라인모델 hash-neutral) → hash_stable doc 보존. 단위테스트 7건.
- g3 persist+backfill 하이브리드:
* persist INSERT char_start
* update-char-start (g3-tU): hash_stable doc 비파괴 — 100% jump-target VERIFY(NEW-1) +
position-aligned PK UPDATE(NEW-2), 미달 doc DEMOTE → re-decompose 합류(NEW-4)
* --reprocess (g3-t2): md_content 출처(g0-t1) + jump-target-set 완료마커(B1) + B_jumptarget>=1(B3),
--doc 필수 else REFUSE. self-heal sweep(g3-t3).
- g4 /sections: char_start inner+outer SELECT + split-parent 노출(is_leaf OR %_split)
- g5 FE: resolveAnchorMap(BE-first, NEW-5 jump-target-candidate-scoped 폴백, C1 OR-exclude),
per-render-site basis guard(C3), endsWith('_split') 정정 + collapseWindows split-parent 흡수(C2).
단위테스트 25건(NEW-5/B4/C1/C2 포함).
- g6 hier_outline_quality_gate.py: read-only g-measure(verdict/B_jumptarget/hash_stable/dup/fence)
배포(g7: --no-deps, 스냅샷, UPDATE-only 32 + re-decompose 230∪demote, 정확도 게이트)는 별 ops 단계.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-09 10:12:26 +09:00
hyungi
daf6a0ade9
feat(documents): S1 dedup·office-md·storage scaffold (B/C/D/E)
...
plan ds-s1-backend-1 잔여 구현 (A·C-1 은 16b0fe1 ):
- B 중복검사: services/dedup.py (OFF-list law_monitor 공용) + 업로드 채움(B-1)
+ GET /documents/duplicates(B-2) + post-upload near-dup 비동기(B-3)
+ backfill_dedup.py(B-4) + 야간 dedup_reconcile 잡(03:30 KST 멱등 재계산)
- C MD-first: marker_worker office/hwp 분기 _process_office(C-2) + md_status
상태머신 postcondition success|failed(C-5) + backfill_nonpdf_markdown.py(C-4)
+ requirements markitdown
- D 스토리지: services/storage ABC+Range 계약 / LocalBackend / NasApiBackend 503
(D-1) + /file resolver 경유, 로컬 동작 불변(D-2)
- E 운영: pre-change pg_dump + rollback_287.sql + apply runbook(E-3) + 테스트(E-1)
비파괴 불변식 유지(기존 응답 shape 무변경, md_status success→completed read-time 매핑).
어드버서리얼 리뷰 확정 1건(soft-delete canonical 승격 시 stale duplicate_of) → B-1
승격 정규화 + 야간 재계산으로 정합.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-08 03:05:30 +00:00
hyungi
68e2d7ea04
feat(documents): S1-ADD dedup·원본명 3컬럼 + md_status success→completed 매핑 (A) + office→md PoC (C-1)
...
plan ds-s1-backend-1 (r5 수렴). 코드만 스테이징 — migration 미적용(restart 보류, E-2 Soft Lock 예외창).
A (앱 v1 디코딩 비파괴 최소선):
- A-1 migrations/287_documents_dedup_fields.sql: original_filename TEXT / duplicate_of BIGINT FK ON DELETE SET NULL
/ duplicate_count INTEGER NOT NULL DEFAULT 0. 단일 statement·PG16 fast-path·BEGIN/COMMIT 금지. backfill 미포함(B-4).
- A-2 app/models/document.py: 1계층 블록에 3 mapped_column (+ ForeignKey import). md_* 는 기존.
- A-3 app/api/documents.py: DocumentResponse 3필드(duplicate_count=0 non-opt) + DocumentDetailResponse
field_validator(success→completed, mode=before) — read-time DB→API 단방향, write(ORM) 미적용.
- A-4 tests/test_s1_dedup_shape.py: success→completed 동작 + 비-success 통과 + 3필드 디폴트/roundtrip
+ ds-app contract fixture 디코드(skip-if-absent). py_compile OK. ★ backend 절반 — 전체 비파괴는 S3 render 테스트와 AND.
C-1 PoC (워커 미연결 — C-2 에서 marker_worker 분기 연결):
- app/workers/office_md.py: OOXML=markitdown(신규 dep, lazy) / hwp·hwpx=LibreOffice headless→HTML→markdownify(기존 dep).
실패·빈출력·타임아웃·dep부재 → OfficeMdError raise (success+빈md 금지 = C-5 postcondition 의 변환기 계약).
- scripts/poc_office_md.py: 표 fidelity 측정 하니스. E-1 = prod LibreOffice 버전핀 안전컨텍스트 실행(hwpx 필터 버전 의존).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-08 03:05:30 +00:00
hyungi
6a85087b83
feat(eid): 이드 persona substrate W2~W4 — DS compose·약점진단·egress 코드층 박탈
...
전 로컬 LLM 관통 '이드' persona substrate 의 Document Server 측 빌드(W2~W4).
설계 = PKM eid-persona-substrate(r1~r3 수렴) / impl = eid-persona-impl.
W2 — compose + 표면 배선:
- app/eid/compose.py: persona→rules→overlay→task 단일 system 문자열 + 정적 ROUTE_MAP
(런타임 sniffing 아님) + rules 부재 fail-loud · persona 부재 quiet · overflow fail-loud.
- 자유-prose 3 표면(react_ask·study_subject_note·study_question_explanation) 중복 정체성·
generic 정책 trim + compose 배선(AIClient 에 additive system 파라미터). 도메인 calibration 보존.
- STRICT JSON 기계류(briefing_comparative·digest_topic)는 persona-ZERO 동결(불변식 #3 ).
- app/prompts/substrate/: persona(외부 컴파일 산출물 vendor) + rules(생성 가드 서브셋) + overlay 5.
W3 — migration + 워커 + study_diagnosis:
- migration 301~305: eid_* append-only 원장(약점/복습초안/회고) + approval_requests(가변 큐) + 일정 파생뷰 2.
- app/workers/study_weakness.py: study_question_progress.pattern_state 집계로 약점 derived 산출
(LLM 0) + bounded tier(watch/review/focus). nightly cron.
- study_diagnosis 표면: 최신 스냅샷을 코치 언어로 번역(약점 판정은 코드, LLM 은 블록 값만 인용).
W4-1 — egress 코드층 박탈:
- app/eid/ai.py EidAIClient: 이드 표면 = call_primary(내부 MLX) only. 외부 LLM fallback 경로
구조적 봉쇄(call_fallback raise · 자동 fallback 제거 · 외부 endpoint 차단). egress 워커는 분리 유지.
load-bearing 정정 3(환경 grounding 강제, 설계 회귀 아님):
- rules = 운영 ruleset 전체 → 생성 가드 서브셋(HTML 산출물 룰이 study task 와 충돌).
- append-only = REVOKE → CREATE RULE DO INSTEAD NOTHING(단일 owner role 은 REVOKE 무효 +
migration 검증기가 plpgsql BEGIN 거부) + actor/source_* NOT NULL 스탬프.
- 이드 LLM 봉쇄 = path discipline → EidAIClient 구조화.
검증: eid 순수 단위테스트 30 통과 + py_compile + migration 검증기 모사 + egress 적대감사 COMPLETE.
DB/LLM/httpx 의존 테스트(append-only RULE·EidAIClient·E2E)는 staging(Docker) 가동.
W4-2 네트워크 belt 은 조건부 보류(코드층 1차 충분, P0-3② 원격 실측 후 hard-gate 시 승격).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-07 15:13:20 +09:00
hyungi
e1da984e08
refactor(study): SR 산술 sr_schedule.py 공용추출 (B1 — 카드 SR 토대)
...
문제 SR과 카드 SR이 같은 간격 상수·산술을 참조하도록 순수함수 추출. 운영 동작 무변경.
- app/services/study/sr_schedule.py: REVIEW_INTERVAL_DAYS{1:3,2:7,3:14}/MASTERED=4/FIRST_DUE=1
+ advance(stage,outcome,now)→(new_stage,new_due) | None(skipped) + first_due(now).
진입 게이트(due_at IS NOT NULL/최초 due/skipped 불변)는 호출부 잔류(finalize vs review-complete 정책 차이).
- session_finalize.py: 상수·advance 분기 → sr_schedule import + sr_advance() (re-export 유지).
- study_question_progress.py: DEFAULT_FIRST_DUE_DAYS → sr_schedule import.
- 회귀 테스트 7/7: 전진 1·3·7·14·졸업·리셋·skipped불변·상수 + 전 stage×outcome 구 로직 바이트 동등.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-07 10:11:38 +09:00
hyungi
19f544fb5e
feat(study): 공부 암기노트 Phase 1 — 정정/삭제 훅 + needs_review 큐 + 알람 재료 (HR/A)
...
추출 파이프라인(287~298, 별 커밋) 위 HR/A. 신규 마이그레이션 0 (DDL은 295~298 재사용).
- HR 정정/삭제 훅: PATCH 본문 수정 → 파생 study_memo_cards needs_review=auto(source_changed),
soft-DELETE → source_deleted. flag_cards_for_source 헬퍼(임시 플래그, 최종정리는 워커 supersede).
- HR needs_review: PATCH set/clear(flagged_by='user' 서버강제) + GET /study-questions/needs-review
목록·count(부분인덱스 술어 일치, 동적 {id} 라우트보다 먼저 등록해 int 파싱 충돌 회피).
- A 알람 재료: study_topics.focused_at 공부중 토글 + study_reminder cron(09/13/19 KST, due 술어
quiz_selection SQL 재현·시간슬롯 truncate 멱등·LLM 0) + GET /api/study-reminders/latest(없으면 204).
- 테스트: 가드/정규화 18/18 (정량=evidence 원문·cue/cloze 누출·dedup·배치).
검증: 앱 부팅 import+mapper OK · 가드 18/18 PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-07 08:08:55 +09:00
hyungi
cd33ded7a8
docs(search): passage-RAG go/no-go = NO-GO (hier evidence 동등, diagnose c4+c5)
...
PR-DocSrv-Hier-PassageRAG-Diagnose-1 c4+c5. 조건부 N=12(retrieval 통제) blind pairwise
(hypothesis-blind subagent, 익명 3-file split). 결과 4-way 수렴 = 동등:
pairwise prehier4/hier3/tie5(no edge) + axis ±0.08 + objective 동일(halluc36/36) +
variance~0(byte-identical 재생성). verbosity artifact 없음(prehier 더 길었으나 승+1).
=> NO-GO: hier-leaf evidence 무이득. hier leaf = section-outline UI 전용 완전 확정
(UI yes / doc-search NO-GO / passage-RAG NO-GO 3영역 종결). 2026-06-21 freeze input only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 07:02:46 +00:00
hyungi
9c039139ef
feat(search): passage-RAG capture runner + raw JSONL (diagnose c3)
...
PR-DocSrv-Hier-PassageRAG-Diagnose-1 c3. 22Q x {prehier,hier_sim_clean} /ask?debug=true
exact_knn capture (44 rec). ai_answer/evidence/target_doc_present/target_span_used/
objective signals(hallucination/grounding/completeness/refused) 박제.
관찰: hier 일부 타깃 retrieval 실패(exam_005/006,cl_007=doc-search NO-GO 일관) + 일부 gain
(cl_001/002). empty-answer 케이스(cl_005/cl_007 prehier, cl_006/exam_004 skipped) 존재.
JWT 15min 만료로 1차 부분실패 → cache-warm 재실행 완주.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 06:53:11 +00:00
hyungi
698510bc0e
feat(search): passage-RAG answer-seeking question subset (diagnose c2)
...
PR-DocSrv-Hier-PassageRAG-Diagnose-1 c2. queries.yaml v0.2 의 answer-seeking 22문항
(exam 7 + korean_only 7 + mixed 8, decomposed-target 필터). targets_g2/g3 = 조건부 subset
산출용. broad seed (조건부 ~65-70% → N≥12 확보). 신규 authoring 0 (기존 graded 재사용).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 06:20:20 +00:00
hyungi
6e9d73278f
docs(search): pin hier measurement views as EVAL-ONLY (replace-diagnose)
...
COMMENT ON VIEW + header — corpus_chunks_{prehier,hier_sim_raw,hier_sim_clean} 은
?corpus_variant= eval dispatch 전용. production retrieval default-path 는 corpus_chunks
(partial ivfflat) 만. 재측정/passage-RAG 재평가 자산으로 보존, 오용 방지 박제.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 05:53:04 +00:00
hyungi
6a9142a2e5
docs(search): hier vs legacy go/no-go = NO-GO (replace-diagnose c6)
...
PR-DocSrv-Hier-Replace-Diagnose-1 c6 측정+결정. prehier exact vs hier_sim exact, dedup 0/51.
결정權(분해-subset n=41): prehier 0.748 -> hier_sim_clean 0.675 (-0.074 회귀). raw 0.673 (robust).
카테고리: standards(법령, hier 최적가설) flat -0.002 / exam -0.183 / korean -0.109 / english -0.088.
법령 제N조조차 개선 없음 + 대체로 회귀 → 짧은 절 leaf 가 맥락 손실. dedup clean = 실제값.
=> NO-GO: 검색 코퍼스 hier 교체 안 함. Apply PR 미진입. hier leaf 는 in_corpus=false 잔존
(section-outline UI 재료, doc-level 검색 무관). 측정은 doc-level NDCG 한정.
산출물: decision md + 4 eval csv(sanity/prehier/clean/raw exact) + subset analysis script.
in_corpus 634 전 구간 불변. default 검색 path 회귀 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 05:46:14 +00:00
hyungi
100aaa3b0c
feat(search): corpus_variant + exact_knn measurement dispatch (replace-diagnose c4+c5)
...
PR-DocSrv-Hier-Replace-Diagnose-1 c4+c5. hier vs prehier(legacy) go/no-go 비파괴 측정 hook.
- 측정 뷰 3종 (hier_measure_views.sql, additive/droppable): corpus_chunks_prehier
(legacy+null-source 375 포함) / hier_sim_raw / hier_sim_clean (childless-tiny<30 제외,
all-tiny doc 은 legacy fallback 정합).
- retrieval_service: _resolve_corpus_variant + CORPUS_VARIANT_MAP + _VALID_CHUNKS_TABLE
3 뷰 추가 + exact_knn(SET LOCAL enable_indexscan/bitmapscan=off, eval 전용).
chunk leg 만 영향 (doc-level + fts/trgm = documents 무관). baseline/None path 회귀 0.
- search_pipeline.run_search + search.py: corpus_variant/exact_knn 전달, unknown→400,
embedding_backend cand 와 동시 사용 금지(400).
- run_eval: --corpus-variant + --exact-knn flag.
- tests/test_corpus_variant.py 22 PASS (resolver/map/allowlist + SQL injection 거부).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 05:37:15 +00:00
hyungi
e860baa179
ops(hier): Phase A law/library decompose + snapshot freeze (replace-diagnose c3)
...
47 eval-target undecomposed non-news docs (law21+library24+document2) 분해+임베딩
(--skip-analysis, additive). 1005 leaf 생성 fail0, in_corpus 634 무손상 검증.
snapshot doc_id_max=25912 chunk_id_max=71164 docs_decomposed 301->348. 측정 drift 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-25 05:23:38 +00:00
hyungi
a7b16b63db
feat(search): doc-level atomic corpus replace + isolation test (Hier-Decomp-1 c5)
...
replace_doc_corpus(dry_run): G5 precond(doc-local embed 100% + parent 무결성 + leaf>0) 검증 후
단일 트랜잭션 atomic 교체(legacy in_corpus=false / hier leaf in_corpus=true,
predicate=is_leaf AND embedding NOT NULL, node_type 미사용). 물리삭제 없음. rollback_doc_corpus 역토글.
precond 미충족 시 변경 0(legacy 유지).
tests/hier_decomp/test_corpus_isolation.py: in_corpus=false leaf 가 corpus_chunks 누출 0 단언
(부분 ivfflat + 뷰 이중 choke point 회귀 가드).
c5: dry-run 3 pilot precond_ok(5140 158L→271leaf / 5186 381→199 / 5225 18→164), 격리 테스트 PASS.
실제 replace 는 c6(1-doc-first).
plan: hierarchical-decomposition-tiered-nesting-marmot.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 13:14:36 +00:00
hyungi
3b753f18d6
fix(search): Phase 2Q result dedup — apply_diversity unlimited path doc_id inflation 차단
...
PR-2Q-Search-Result-Dedup. measurement chain 의 마지막 cleanup. plan inline.
root cause: apply_diversity 의 top_score ≥ 0.90 → unlimited path (diversity 제약 해제)
→ 같은 doc 의 N chunks 가 results 에 박제 → returned_ids 에 doc.id 중복 → 모든 graded
metric inflation. multi-query 의 reranker score 가 자주 0.90+ → 다수 case 영향.
변경 (baseline path 영향 0, multi-query 전용 invariant):
- app/services/search/search_pipeline.py:
· _dedup_results_by_doc_id() helper 신규 (doc.id first-only, top score 보존)
· search_with_rewrite() 의 rerank path 에 apply_diversity(top_score_threshold=2.0)
강제 + 후속 _dedup_results_by_doc_id 적용
· rerank=False path 도 _dedup_results_by_doc_id(unified_docs) 적용
- tests/test_query_rewriter.py — 신규 4 test (55/55 PASS)
🎯 진짜 측정값 (모든 dedup layer 적용, 51 case gemma):
cold: NDCG 0.663 / Recall t≥2 0.729 / Recall t≥3 0.761 / p50 3692ms / p95 9992ms
warm: NDCG 0.659 / Recall t≥2 0.721 / Recall t≥3 0.739 / p50 1588ms / p95 3514ms
baseline (rewrite_backend=null): NDCG 0.644 / Recall t≥2 0.699 / Recall t≥3 0.761 / p50 378ms
Dedup audit: gemma 0/51 ✓ 정상 (fix 작동, eval-dedup 42/51 → 0/51 회복)
Δ vs baseline (진짜 multi-query 효과):
NDCG +0.019 (cold) / +0.015 (warm) — sub-noise level
Recall t≥2 +0.030 (cold) / +0.022 (warm) — 소량 개선
Recall t≥3 0.000 / -0.022 — 동등~약간 회귀
latency p50 +876% (cold) / +320% (warm) — major cost
category: english/standards/mixed 약간 우세 / exam/korean 약간 회귀
measurement chain 정정 history:
Phase 3 (a41adb6 ) 0.927 — chunk_id 중복 inflation
Rerank-Fix (b734fc5 ) 0.876 — doc_id 중복 잔재
Eval-Dedup (3553573 ) 0.641 — eval layer 만 dedup
Result-Dedup (본 PR) 0.663 — production + eval 둘 다 dedup ← 정확값
사용자 결정 필요 (3 path, json 박제):
(a) rollback — marginal 개선이 latency cost 정당화 X
(b) opt-in 유지 + PR-2Q-Cache-Prewarm 진입 (warm path 만 노출)
(c) 1주 관찰 종료 후 (2026-05-31) 재결정 (현 상태 유지)
산출물:
reports/v0_2_phase2q_result_dedup_gemma_{cold,warm}_2026-05-24.csv
tests/search_eval/baselines/v0_2_phase2q_result_dedup_2026-05-24.json (요약 + 사용자 결정 옵션)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 04:48:50 +00:00
hyungi
9dad5e6289
chore(eval): graded NDCG dedup + warning + audit stats (Phase 2Q inflation 정정)
...
PR-Eval-GradedNDCG-Dedup. [[feedback_graded_ndcg_dedup_invariant]] cleanup.
plan pr-eval-graded-ndcg-dedup-stormy-tide.md.
변경:
- tests/search_eval/run_eval.py:
· _dedup_returned_ids() helper — returned[:k] 첫 등장 순서 보존 dedup + count 반환
· count_dedup() wrapper (audit 용)
· ndcg_at_k + graded_ndcg_at_k 진입 시 dedup (NDCG > 1.0 invariant 강제)
· QueryResult.dedup_count 필드 + csv schema 신규 column
· evaluate() 에서 dedup_count > 0 시 stderr WARNING
· print_summary 에 dedup audit stats (cases/total chunks + 정상/⚠️ flag)
- tests/search_eval/test_eval_graded_ndcg_dedup.py 신규 — 13 test:
· _dedup_returned_ids 6 (empty / no-dup / dup-first / k-limit / count helper / Phase 2Q kw_001)
· graded_ndcg invariant 5 (baseline 회귀 0 / dup 차단 / all-dup / exam_001 regression / empty grades)
· ndcg_at_k binary dedup 1 + graded_recall set 변환 1
51/51 test PASS (13 신규 + 38 기존 회귀 0).
🚨 CRITICAL 측정 발견:
dedup audit baseline = 0/51 정상 (single-query path 의 retrieval 가 doc unique 박제)
dedup audit gemma = 42/51 (totaling 81 chunks dedup) ⚠️
→ _rrf_fuse_variants 의 representative 보존 logic 이 같은 doc_id 의 여러 SearchResult
를 unique 가정. chunk_id dedup (Rerank-Fix) 이후에도 doc_id 중복 잔재.
정정값 (이번이 가장 정확):
baseline NDCG 0.644 (이전 0.659 와 noise level diff)
gemma NDCG 0.641 → Δ vs baseline = -0.003 (사실상 동일, multi-query 실제 net 효과 ≈ 0)
latency p50 +1005ms (+266%) — 회귀
Recall t≥3 -0.033 (회귀)
이전 박제값 (모두 inflation):
Phase 3 (a41adb6 ) NDCG 0.927 — chunk_id 중복
Rerank-Fix (b734fc5 ) NDCG 0.876 — doc_id 중복 잔재
Category-Analysis (b00d9f5 ) NDCG 0.876 정정 박제 — 위와 동일
산출물:
reports/v0_2_phase2q_eval_dedup_baseline_2026-05-24.csv (baseline 회귀 verify)
reports/v0_2_phase2q_eval_dedup_gemma_2026-05-24.csv (실제 효과 측정)
tests/search_eval/baselines/v0_2_phase2q_eval_dedup_2026-05-24.json (요약 + critical 권고)
권고 (사용자 결정 필요):
1. Apply rollback 검토 — multi-query 의 실제 net 효과 ≈ 0 + latency 4x 회귀
2. 또는 PR-2Q-Search-Result-Dedup 진입 (real fix _rrf_fuse_variants representative)
후 재측정 → 실제 multi-query 효과 측정 후 Apply 결정
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 04:35:33 +00:00
hyungi
b00d9f5e15
docs(eval): Phase 2Q Category-Analysis — standards/exam 회귀 진단 (inflation 정정)
...
Apply rollout 후속 read-only 진단. Phase 3 측정 (commit a41adb6 ) 의 NDCG 0.927 + standards 1.441 + exam 1.109 = **측정 artifact (top-N doc 중복 박제 → graded NDCG inflation)**.
진단 path:
- script category_analysis_phase2q.py (csv parse + queries.yaml graded lookup + standards/exam 18 case 3-way top-5 박제)
- 회귀 큰 case top: kw_004/kw_009/kw_010 = Phase 3 inflation 1.631 → Rerank-Fix 정상 1.000 (baseline 동일, 회귀 0)
- kw_001/exam_004 = Rerank-Fix 가 baseline 대비도 회귀 (reranker chunk-level relevance 우선 → doc grade 3 가 rank 5 밀림)
정정값 박제:
- Phase 3 NDCG 0.927 → **Rerank-Fix 0.876 (정확값)**
- Δ vs baseline: +0.268 (inflated) → **+0.217 (실제 multi-query 효과)**
- standards 1.441 → 1.157 (vs baseline 0.873, +0.284)
- exam 1.109 → 0.918 (vs baseline 0.738, +0.180)
결론:
- **Apply rollout 결정 = 정정값 기준 invariant 유지** — +0.217 vs baseline = 유의미 net 개선
- standards -0.28 / exam -0.19 회귀 = false alarm (inflation 정정)
- 실제 회귀 case (kw_001/exam_004) = Apply 후 telemetry 박제 항목
산출물:
- tests/search_eval/baselines/v0_2_phase2q_category_analysis_2026-05-24.md (180+ lines, §1~8)
- tests/search_eval/scripts/category_analysis_phase2q.py (read-only csv parse script, reproducibility)
신규 feedback memory: graded-ndcg-dedup-invariant (NDCG > 1.0 = inflation 의심 invariant + dedup audit 필수)
후속 별 chore 후보:
- PR-Eval-GradedNDCG-Dedup — run_eval.py 의 graded NDCG 계산 dedup + NDCG > 1.0 warning
- PR-2Q-Search-Result-Dedup — _rrf_fuse_variants 의 representative doc_id 중복 audit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 04:23:58 +00:00
hyungi
59bde9a399
feat(search): phase-2q apply opt-in — production rollout 시작, 1주 관찰 (gemma-4)
...
plan pr-2q-apply-query-rewrite-1-bright-meadow.md. Phase 2Q Diagnose closure +
Rerank-Payload-Fix (main 0257a5d ) 완료 후 Apply rollout. opt-in path 가 Phase 1B/2
부터 이미 production 가동 중 → 본 PR 의 production 영향 0 (marker PR).
rollout 정책:
· default = rewrite_backend null (single-query path, baseline 회귀 0 invariant)
· 명시 opt-in = ?rewrite_backend=cand_multi_query_macmini (추천 gemma-4)
· 대안 = cand_multi_query_macbook (qwen3.6, mixed/english 강점, MacBook 가동 시)
· 1주 관찰 (2026-05-24 ~ 2026-05-31) → metric 정상 시 default ON 별 PR
변경 (production 영향 0):
- docs/phase_2q_apply_opt_in.md 신규 — 사용자 가시화:
· 사용 방법 (query param + SvelteKit fetch 예시)
· 1주 관찰 metric 목표 (cache hit ≥ 50% / LLM warm p50 ≤ 1500 / 503 ≤ 5/day / Recall t≥3 ≥ 0.74)
· 추천 LLM 사유 (decision md §4 4-factor) + 대안 명시
· Phase 2 QueryAnalyzer sequencing 박제 (영향 0, ask_events 0건 운영 관찰 후 확정)
· Follow-up PR 5건 명시 (Telemetry / Alert / Default-ON / Cache-Prewarm / Category-Analysis)
- app/api/search.py — rewrite_backend query param description 갱신.
Apply 진입 박제 + 추천 LLM 표시 + docs 링크. 동작 변경 0.
- tests/search_eval/baselines/v0_2_phase2q_apply_smoke_2026-05-24.json — production smoke:
· opt-in path HTTP 200 + total_ms 957 (cache hit) + rerank_ms 109 (정상 호출) + fallback 0
· baseline path HTTP 200 + total_ms 207 + rerank_ms 19 + fallback 0 (회귀 0 확정)
38/38 unit test PASS (회귀 0). main HEAD 0257a5d 위 branch.
Closure gate PASS:
· docs 가시화 / search.py description / smoke json 박제
· production smoke 양쪽 path 정상 + 회귀 0 verify
· 메모리 갱신 + 1주 관찰 종료일 2026-05-31 박제
Follow-up: 1주 후 PR-2Q-Apply-Default-ON-1 (metric 정상 시) 또는 fix PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 04:01:49 +00:00
hyungi
b734fc54af
fix(search): Phase 2Q rerank payload — chunk_id dedup + cap 60 + TEI batch 64 (Apply prereq)
...
plan pr-2q-rerank-payload-fix-resolute-haven.md. Phase 2Q multi-query path 의 reranker
413 Payload Too Large root cause = TEI 의 MAX_CLIENT_BATCH_SIZE=32 default (batch entries
한도) + multi-query 의 chunks 누적이 32 초과. MAX_BATCH_TOKENS 와 별개 (token sum 한도).
4 iteration 진단 history (json 박제):
1) cap 60 + dedup = 413 다수 (batch 54 > 32)
2) cap 30 + chunks_per_doc=1 = 413 0건 + NDCG 0.666 catastrophic (-0.261)
3) cap 60 + dedup + TEI 16384 only = 413 46건 (batch size 한도 별개)
4) cap 60 + dedup + TEI 16384/64 = 413 1건 + NDCG 0.876 (FINAL)
변경:
- app/services/search/search_pipeline.py:
· _dedup_chunks_by_id() 신규 helper — chunk_id (None 시 doc.id) 기준 first-only.
variant 별 same chunk 중복 누적 회피, 첫 등장 variant 보존.
· PHASE2Q_RERANK_INPUT_CAP=60 + PHASE2Q_CHUNKS_PER_DOC=2 신규 상수 (baseline
MAX_RERANK_INPUT=200 / MAX_CHUNKS_PER_DOC=2 와 별도).
· search_with_rewrite() merge 후 dedup wire-up + rerank input cap swap.
- docker-compose.yml reranker env (사용자 결정, plan out-of-scope 정정):
· MAX_BATCH_TOKENS 8192 → 16384 (token sum 한도)
· MAX_CLIENT_BATCH_SIZE 32 → 64 신규 추가 (batch entries 한도 — root cause)
· GPU VRAM free 6199MiB 충분 사전 verify.
- tests/test_query_rewriter.py: _dedup_chunks_by_id 5 test + PHASE2Q_* constants test.
38/38 PASS (기존 32 + 신규 6).
측정 결과 (51 case, gemma backend, snapshot 25180/56526):
vs Phase 3 (commit a41adb6 NDCG 0.927, 413 다수):
· NDCG 0.876 (-0.051 acceptable, plan 변수 격리 invariant 충족)
· Recall t≥2 0.721 (+0.034 회복)
· Recall t≥3 0.739 (+0.011)
· latency p50 1421ms (-1336ms, -48%) / p95 3392ms (-6292ms, -65%) major win
· 413 fallback 1/51 (98%↓ from 다수) + reranker batch error 0
· 카테고리 english_only +0.34 / standards -0.28 / exam -0.19 (Apply 후 분석 항목)
closure gate PASS:
· unit test 38/38, production smoke 413 0
· 51 case 413 < 5/51 (1건만)
· latency 대폭 개선
· NDCG threshold 0.92 미달 단 plan invariant (production 평가 단일 변수) 충족
· Apply PR-2Q-Apply-Query-Rewrite-1 진입 ready
산출물:
· reports/v0_2_phase2q_rerank_fix_2026-05-24.csv (raw)
· tests/search_eval/baselines/v0_2_phase2q_rerank_fix_2026-05-24.json (4 iter 진단 박제)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 03:54:59 +00:00
hyungi
c57e4c52dc
docs(eval): Phase 2Q Diagnose Phase 4 — decision tree md + Apply PR 백로그
...
phase-2q-query-rewrite-diagnose.md v6 plan §7 Phase 4 closure.
Phase 3 commit a41adb6 의 3 측정 결과 + 4 factor weighted decision.
decision = H1 (both backends NDCG net 개선 ≥ +0.26):
- 추천 Apply LLM = cand_multi_query_macmini (gemma-4)
- 사유: F3 ⭐ 24/7 가동 + F1 NDCG 0.927 dominant + F4 cold latency 우세
- 대안: qwen (mixed/english 강점 + MacBook always-on 의향 시)
산출물:
- tests/search_eval/baselines/v0_2_phase2q_decision_2026-05-24.md (180 lines)
· §1 결정 요약 / §2 측정 표 / §3 카테고리 회복 / §4 4-factor weighted
· §5 분석 노트 5건 (multi-query 효과 / variants 구성 / cache hit / Recall 회귀 / Phase 3 incident)
· §6 closure gate (branch close 사용자 결정 보류)
· §7 follow-up PR 백로그: Apply 1 + 별 chore 2 + Extended 4 + Cloud 1 + Cleanup 1
· §9 사용자 검토 항목 5건
Phase 2Q Diagnose closure 완료. Apply PR 진입 = 사용자 LLM 선택 + sequencing 결정 후.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 00:57:48 +00:00
hyungi
a41adb63a0
fix(search): Phase 2Q variants bug fix + Phase 3 3 measurement 박제
...
Phase 3 cold 측정 1차에서 NDCG 0.033 catastrophic 발견 — 모든 query 에 동일 variants
반환. root cause = _call_llm 이 user 메시지 1개에 prompt template 전체 박음. LLM 이
actual query 인식 못 함. fixture request_body 형식 (system=prompt / user=query) 과
mismatch. fixture-first invariant 위반.
fix:
- app/services/search/query_rewriter.py _call_llm — system/user 메시지 분리.
fixture request_body 와 단일 source-of-truth. _render_prompt 는 [deprecated] 유지.
- tests/test_query_rewriter.py — Phase 3 regression test 2:
· _call_llm 가 system + user 분리 호출 verify (httpx.AsyncClient monkeypatch)
· qwen backend = response_format 미사용 verify
- 32/32 unit test PASS.
Phase 3 측정 (fix 후 재측정, 51 case × 3 candidate × cold/warm = 5 run):
- baseline_rebaseline (rewrite_backend=null): NDCG 0.659 = Phase 2A 0.659, diff 0.000 PASS
- cand_multi_query_macmini cold: NDCG 0.927 (Δ +0.268), p50 2757ms / p95 9684ms
- cand_multi_query_macmini warm: NDCG 0.927 동일, p50 998ms (cache hit -64%)
- cand_multi_query_macbook cold: NDCG 0.919 (Δ +0.260), p50 3647ms / p95 5202ms
- cand_multi_query_macbook warm: NDCG 0.919 동일, p50 873ms (cache hit -76%)
핵심 약점 회복 (gemma / qwen):
- mixed 0.39 → 0.57 / 0.65
- korean_only 0.51 → 0.71 / 0.67
- standards 0.87 → 1.44 / 1.31
- exam 0.74 → 1.11 / 1.04
decision = H1 (both backends 유의미 net 개선). LLM 선택 = Phase 4 decision md 별 step.
산출물:
- reports/v0_2_phase2q_*.csv (5 raw run_eval output)
- tests/search_eval/baselines/v0_2_phase2q_results_2026-05-24.json (요약 + incident 박제)
follow-up:
- rerank 413 Payload Too Large 다수 관찰 (RRF fallback 작동, NDCG 영향 없음). Apply PR 전 별 chore — chunk dedup 또는 reranker batch cap 검토.
- p95 cold 9684ms 매우 큼. production rollout 시 cache prewarm 정책 필수.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-24 00:51:56 +00:00
hyungi
ecd2350c15
feat(search): Phase 2Q Diagnose Phase 2 — multi-query retrieval fusion
...
phase-2q-query-rewrite-diagnose.md v6 plan §5.5 + §7 Phase 2.
Phase 1B 3e6866b (scaffold + dispatcher) 위 retrieval 합성 wire-up.
신규:
- search_pipeline._rrf_fuse_variants() — N variant ranked list RRF 합성.
fusion_service.RRFOnly 알고리즘 동일 (k=60), 첫 등장 variant representative 보존.
- search_pipeline.search_with_rewrite() — variant N 별 retrieval+fusion 후
unified RRF (cap 60) → reranker 1회 (query=원본 q) → diversity+freshness+display.
· per-variant K = 50//3 = 16 (PHASE2Q_PRODUCTION_TOPK//N, A1 채택)
· variant 별 retrieval asyncio.gather 병렬
· chunks_by_doc merge (variant 무관 unified reranker input)
· production fusion_service.get_strategy() + rerank_chunks() 재사용
- 상수: PHASE2Q_PRODUCTION_TOPK=50, PHASE2Q_UNIFIED_CAP=60, PHASE2Q_RRF_K=60.
수정:
- search_pipeline.run_search() — rewrite_backend param 추가. hybrid + cand_<slug> 시
search_with_rewrite() 위임. baseline/None 시 기존 single-query path 그대로 (invariant).
- app/api/search.py — Phase 1B scaffold discard call 제거. run_search 에 rewrite_backend
전달. ValueError → 400 (unknown_rewrite_backend 우선 분기) / RuntimeError → 503
(rewrite_llm_unavailable).
- tests/test_query_rewriter.py — Phase 2 test 9개 추가:
· _rrf_fuse_variants 6 (single / overlap accumulation / representative / cap limit /
empty / rank position)
· search_pipeline import + run_search rewrite_backend default=None signature 1
· PHASE2Q_* constants 1
· DATABASE_URL dummy 주입 (api.search import → SQLAlchemy engine init 회피)
30/30 unit test PASS (Phase 1B 21 + Phase 2 9).
baseline 회귀 0 invariant:
- run_search(rewrite_backend=None) → 기존 path 100% 그대로 (분기 first line guard)
- run_search(rewrite_backend=baseline) → 동일
- mode != hybrid → multi-query path 비활성 (text-only/vector-only/trgm 영향 0)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 22:41:50 +00:00
hyungi
3e6866b4ae
feat(search): Phase 2Q Diagnose Phase 1B — scaffold + dispatcher
...
phase-2q-query-rewrite-diagnose.md v6 plan Phase 1 의 fixture 외 잔여.
Phase 1A 446ba82 위 dispatcher + cache + LLM call + API param + eval flag + 21 unit test.
retrieval 합성 (search_with_rewrite) 은 Phase 2 별 commit.
신규:
- app/services/search/query_rewriter.py — LLM_BACKEND_MAP + _resolve + cache + rewrite()
· slug-based allowlist (no silent fallback), httpx 직접, Priority.FOREGROUND semaphore
· sampling 박제 (gemma response_format json_object / qwen prompt rule only — Phase 0 inspect 9)
· manual TTL cache (query_analyzer 패턴 1:1, sha256[:32] NFKC key, LLM_REWRITE_TIMEOUT_MS=15000)
- tests/test_query_rewriter.py — 21 test PASS (resolve / cache key / parser / cache TTL / constants)
수정:
- app/api/search.py — ?rewrite_backend= query param + 400 unknown / 503 unavailable.
scaffold = call but discard variants (retrieval path 영향 0). Phase 2 에서 합성.
- tests/search_eval/run_eval.py — --rewrite-backend flag + 4 hot spot wire-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 22:25:03 +00:00
hyungi
446ba82c91
feat(eval): Phase 2Q Diagnose Phase 1A — fixture (4 카테고리 × 2 LLM) + prompt v1
...
phase-2q-query-rewrite-diagnose.md v6 plan 의 Phase 1 fixture 박제 (G0-1 + G0-2).
산출물:
- app/prompts/query_rewrite.txt — multi-query rewrite prompt v1 (3 variants: 원본 + 한국어 rephrase + 영어 번역)
- tests/fixtures/macmini_gemma4_query_rewrite_response.json — 4 카테고리 (korean_only/mixed/english_only/exam)
- tests/fixtures/macbook_qwen_query_rewrite_response.json — 4 카테고리 동일
inspect 9 결과 (2026-05-24):
- Mac mini gemma-4-26B-A4B :8801 = response_format json_object 지원
- MacBook qwen3.6-27B-8bit :8810 = response_format json_object 미지원 (120s hang) — prompt rule only
- prompt rule \"no markdown, no code fence\" 강제 시 둘 다 strict JSON (gemma 도 fence wrap 없음)
- parser fallback (markdown fence regex) 유지 — 첫 호출 prompt 없을 때 wrap 관찰 사례
8 호출 측정:
- gemma 1.16~1.36s / qwen 1.93~2.24s (warm)
- variants 의미 일관 + 도메인 용어 (ASME/Section VIII/압력용기/가스기사) verbatim preserve
- 한국어→영어 cross-lingual translation 자연
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 22:09:29 +00:00
hyungi
076c0e1802
feat(eval): Phase 2B Reranker Diagnose — dispatcher + gte 측정 + decision (H3 bge-reranker-v2-m3 유지)
...
round-2-review-mighty-starfish.md v2.1 (Phase 2B Reranker Diagnose) plan 실행.
Phase 2A 의 CANDIDATE_BACKEND_MAP 패턴 재사용 + RERANKER_BACKEND_MAP 신규.
코드 변경 (4 파일):
- app/services/search/rerank_service.py:
- RERANKER_BACKEND_MAP allowlist (baseline / cand_gte_ml_base, slug-based resolve)
- _resolve_reranker(slug) → endpoint URL or None
- _rerank_via_candidate_endpoint() — 후보 TEI POST /rerank
- rerank_chunks() 시그니처에 reranker_backend + snapshot_*_id_max 추가 + dispatch log
- app/services/search/search_pipeline.py: run_search() threading
- app/api/search.py: reranker_backend Query parameter + 400 unknown_reranker_backend 에러 매핑
- tests/search_eval/run_eval.py: --reranker-backend flag + call_search/evaluate threading
infra:
- docker-compose.override.rerank-cand.yml: 3 후보 service (gte_ml_base / mxbai_large / bge_v2_gemma_2b),
profile 'rerank-cand' 격리, restart=unless-stopped
측정 산출물 (51 case, scored=46, failure=5):
- reports/v0_2_phase2b_baseline_snapshot_2026-05-23.csv (NDCG 0.659, Phase 2A 와 일치 = 재현성 PASS)
- reports/v0_2_phase2b_gte_ml_base_2026-05-23.csv
- tests/search_eval/baselines/v0_2_phase2b_{baseline_snapshot,gte_ml_base}_2026-05-23.json
- reports/phase_2b_reranker_decision_2026-05-23.md
- tests/fixtures/tei_rerank_response.json (G0-1 한국어+영어 mixed sample sanity PASS)
후보 TEI 1.7 호환성 (Phase 1 smoke gate):
- cand_gte_ml_base : ✅ PASS (xlm-roberta-based, TEI 호환)
- cand_mxbai_large : ❌ deberta-v2 미지원 → Phase 2B-Extended (sentence-transformers wrapper)
- cand_bge_v2_gemma_2b : ❌ LLM-based reranker, 1_Pooling/config.json 부재 → Phase 2B-Extended (FlagEmbedding wrapper)
결과 (1 후보 측정 + baseline rebaseline):
| Candidate | NDCG | Δ baseline | mixed | korean | exam | p50 ms |
|------------------------------------|------:|-----------:|------:|-------:|------:|-------:|
| bge-reranker-v2-m3 (baseline) | 0.659 | — | 0.39 | 0.51 | 0.74 | 454 |
| cand_gte_ml_base | 0.604 | -0.055 | 0.38 | 0.41 | 0.62 | 345 |
Decision (H3): bge-reranker-v2-m3 유지. gte 의 reranker quality 가 production 보다 약함 (korean_only -0.10, exam -0.12, overall -0.055).
후속 PR 백로그 (6건):
- PR-Search-Query-Rewrite-1 (Phase 2Q, korean_only/mixed 보완 권고)
- PR-2B-Extended-Mxbai-Large (sentence-transformers wrapper)
- PR-2B-Extended-Bge-V2-Gemma (FlagEmbedding LayerwiseReranker wrapper)
- PR-2B-Extended-Jina-V2-ML (license 결정 후, 개인 비영리 가정)
- PR-2B-Cloud-Reranker-Scaffold-1 (Cohere scaffold-only, 선택)
- PR-2B-Rerank-Cand-Cleanup-1 (1주 후 cand 컨테이너 정리)
production 영향:
- production reranker (bge-reranker-v2-m3) 변경 0
- config.yaml ai.models.rerank.endpoint 변경 0
- embedding (bge-m3 ollama) 변경 0 (Phase 2A 결정 보존)
- documents / document_chunks 변경 0 (21365 docs / 30605 chunks 그대로)
- 4 smoke PASS (baseline / baseline+snapshot / cand_gte_ml_base / cand_invalid → 400)
- dispatch log 박제 verify (endpoint + snapshot id)
closure gate: 16 항목 PASS (flex closure 조항 적용 — 1 후보 측정, 2 후보 TEI 호환 탈락 사유 명시).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 08:37:42 +00:00
hyungi
3092e3009d
feat(eval): Phase 2A Diagnose Phase 3+4 — dispatcher + 3 측정 + decision (H3 bge-m3 유지)
...
phase-2a-embedding-diagnose.md v4 § 6 (dispatcher) + § 7 Phase 3 (51 case 측정) + § 7 Phase 4 (decision)
Round 2 review: round-2-review-mighty-starfish.md (R2-2 + R2-B1 페어 invariant + slug-based resolve)
코드 변경:
- app/services/search/retrieval_service.py:
- CANDIDATE_BACKEND_MAP allowlist (baseline / cand_me5_large_inst / cand_snowflake_l_v2)
- _resolve_backend(slug) → docs_table/chunks_table/embed_endpoint or None
- _embed_query_via_tei() — candidate TEI 엔드포인트 호출 (cache 미사용)
- _VALID_DOCS_TABLE + _VALID_CHUNKS_TABLE regex (R2-B1 2단계 gate)
- _search_vector_docs / _search_vector_chunks: docs_table/chunks_table + snapshot_*_id_max 파라미터
- search_vector + search_vector_multilingual: embedding_backend + snapshot_*_id_max 파라미터 + dispatch log
- app/services/search/search_pipeline.py: run_search() 시그니처 + 4 search_vector* 호출 threading
- app/api/search.py: 3 Query parameter + ValueError → HTTP 400 (allowed list 응답)
- tests/search_eval/run_eval.py: --embedding-backend + --snapshot-doc-id-max + --snapshot-chunk-id-max
+ call_search/call_search_full/evaluate threading + main 3 asyncio.run threading
측정 산출물 (51 case, scored=46, failure=5):
- reports/v0_2_phase2a_baseline_snapshot_2026-05-23.csv (snapshot filter 적용 production path)
- reports/v0_2_phase2a_me5_large_inst_2026-05-23.csv
- reports/v0_2_phase2a_snowflake_l_v2_2026-05-23.csv
- tests/search_eval/baselines/v0_2_phase2a_{baseline_snapshot,me5_large_inst,snowflake_l_v2}_2026-05-23.json (3개)
결과:
| Candidate | NDCG | Δ vs baseline | mixed | korean_only | p50 ms |
|------------------------------------|-----:|--------------:|------:|------------:|-------:|
| bge-m3 (baseline snapshot) | 0.659| — | 0.39 | 0.51 | 464 |
| cand_me5_large_inst | 0.477| -0.182 | 0.17 | 0.47 | 194 |
| cand_snowflake_l_v2 | 0.616| -0.043 | 0.35 | 0.52 | 254 |
Decision (H3): bge-m3 유지. 둘 다 net 회귀.
- mE5-large-instruct: 전 카테고리 회귀 (-0.182). prefix 미적용 변수 — 별 PR PR-2A-mE5-Prefix-Retry 후보.
- snowflake_l_v2: 가벼운 회귀 (-0.043). korean_only +0.01 미세 개선 신호.
- korean_only/mixed 약점 보완은 Phase 2B (Reranker) 또는 Phase 2Q (Query rewrite) 권고.
Decision report: reports/phase_2a_embedding_decision_2026-05-23.md (§ 1~8 포함, Closure gate 16 항목 모두 PASS).
후속 PR 백로그:
- PR-2A-mE5-Prefix-Retry (별 PR)
- PR-2A-Extended-Bge-Mgemma2 (별 PR, v3 결정)
- PR-2A-Cloud-Embedding-Scaffold-1 (Cohere/Voyage scaffold-only, 선택)
- PR-Search-Query-Rewrite-1 (Phase 2Q)
- PR-Search-Reranker-V2-Diagnose (Phase 2B)
- PR-2A-Chunks-Cand-Cleanup-1 (1주 후 cand 테이블 DROP)
production 영향:
- documents / document_chunks 컬럼/row 변경 0
- config.yaml 변경 0 (ollama bge-m3 unchanged)
- 추가된 endpoint = query parameter opt-in (미지정 시 production path 회귀 0)
- smoke 4건 PASS (baseline / baseline+snapshot / cand_me5 / cand_invalid → HTTP 400)
- dispatch log 박제 verify (snapshot_doc/chunk_id_max 박제)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 06:55:13 +00:00
hyungi
a67df0a10b
feat(eval): Phase 2A Diagnose Phase 2 — candidate reindex (me5 + snowflake 페어)
...
phase-2a-embedding-diagnose.md v4 § 7 Phase 2 산출.
페어 invariant (R2-2): documents_cand + document_chunks_cand 동기 swap, 부분 swap 금지.
- snapshot 박제 (R2-D): v0_2_phase2a_snapshot_2026-05-23.json
- SNAPSHOT_DOC_ID_MAX=25180 / SNAPSHOT_CHUNK_ID_MAX=56526
- documents_n=21365 (embedded, active) / chunks_n=30605
- production ingest 정지 0, 모든 candidate reindex + baseline rebaseline 측정이 id<=snapshot 한정
- reindex_candidate.py 신규 (R2-5):
- reindex_documents(): production _build_embed_input() import 재사용
- reindex_chunks(): document_chunks.text 그대로 (재 chunking 0)
- TEI batch=8 (1.7 internal queue overflow 회피) + truncate=true (mE5 512 context)
- retry-8 exponential backoff (10/20/40/80/90s) — TEI SIGSEGV 자동 복구
- idempotent ON CONFLICT DO NOTHING (cancellation/resume 안전)
- docker-compose.override.cand.yml: restart=unless-stopped (TEI 1.7 panic 자동 복구)
DB 산출물 (4 테이블):
- documents_cand_me5_large_inst : 21365 rows (dim 1024) + ivfflat lists=100
- document_chunks_cand_me5_large_inst : 30605 rows (dim 1024) + ivfflat lists=100
- documents_cand_snowflake_l_v2 : 21365 rows (dim 1024) + ivfflat lists=100
- document_chunks_cand_snowflake_l_v2 : 30605 rows (dim 1024) + ivfflat lists=100
- ivfflat.probes=20 (production 동일) 보존
- smoke retrieval (nearest neighbor SQL) PASS 후보 2종
production 영향:
- documents / document_chunks 컬럼/row 변경 0
- config.yaml 변경 0 (ollama bge-m3 unchanged)
- production fastapi/postgres/reranker 변경 0 (profile embed-cand 격리)
다음 단계: Phase 3 (DS API + retrieval_service slug-based dispatcher 추가, baseline rebaseline + 2 후보 51 case 측정).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 06:26:14 +00:00
hyungi
943ac5f59c
feat(eval): Phase 2A Diagnose Phase 1 — TEI candidate compose override + fixture G0
...
Phase 2A Embedding Diagnose 본 PR 의 Phase 1 산출물.
- docker-compose.override.cand.yml: 4 후보 service, profile 'embed-cand' 격리
- active: me5_large_inst (intfloat/multilingual-e5-large-instruct, smoke PASS)
- active: snowflake_l_v2 (Snowflake/snowflake-arctic-embed-l-v2.0, smoke PASS)
- 비활성 (extended profile): bge_mgemma2 (9B FP16 OOM risk → 별 PR 이관)
- 비활성 (disabled profile): me5_ko (HF 401 → 폐기)
- tests/fixtures/: G0 fixture 3건 박제
- ollama_bge_m3_embedding_response.json (G0-2: dim 1024, flat dict shape)
- tei_embedding_response.json (G0-1: me5_large_inst, dim 1024, nested array)
- tei_embedding_snowflake_l_v2_response.json (G0-1: snowflake, dim 1024, nested array)
운영 변경 0 (profile 격리, default up 시 미기동). production 9 컨테이너 영향 없음.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 05:04:21 +00:00
hyungi
4d14ab69d9
feat(eval): v0.2 28 신규 case + 2026-05-23 baseline + analysis
...
PR-1 (725a4e1 ) v0.2 schema + harness 위에 신규 28 case 추가 → 51 case
완성 + 현재 모델로 baseline 박제 + 약점 카테고리 analysis md.
신규 28 case 분포 (계획 +28 = standards +6 / english_only +8 / mixed +5
/ exam +7 / failure_expected +2 / ocr_derived 0):
- standards 5 → 11 (KGS FP111/FU551 + 산안기준 후반 편 + 고압가스법)
- english_only 1 → 9 (Pressure Vessel Design Manual + ASME VIII/IX +
Hydrogen ASME + Industrial Safety 영문 교재 + Structural Analysis)
- mixed 5 → 10 (한↔영 ASME / KGS-영문 / 양언어 압력용기)
- exam 0 → 7 (가스기사 study_questions → library 개념 docs 매핑)
- failure_expected 3 → 5 (KGS AC999 / 초전도 안전 관리법)
- ocr_derived 0 (TBD-O FAILED: extract_meta NULL 21385, chunks.source
= RSS feed 명. OCR 식별 컬럼 부재 → +4 case 재배분, analysis 명시)
baseline 측정 결과 (corpus 21,385, hybrid mode, bge-m3 + bge-reranker-v2-m3):
- v0.1 Recall@10 0.646, MRR 0.724, NDCG 0.606, Top-3 0.891
- v0.2 graded NDCG 0.659, Recall@10 g≥2 0.695, g≥3 0.761
- latency p50 528ms / p95 1,664ms
- failure precision 0/5 (DS confidence threshold 미적용)
약점 top 3 (analysis md):
- mixed crosslingual 0.39 graded NDCG — TOP weakness, bge-m3
multilingual 한계 추정
- korean_only natural language 0.51 — query rewrite 부재 추정
- failure_expected 0/5 — confidence cutoff 부재
Phase 2 dispatch 권고 (analysis md):
- 2A Embedding bge-m3 — 즉시 진입 (mixed/korean 동시 타격)
- 2B Reranker — M (2A 이후)
- 2C OCR-Marker — 선행 chore (OCR 식별 컬럼 추가) 필요
- 2D STT — 본 평가셋 외 (별 평가셋 필요)
Query rewrite 는 Phase 2Q/Search-PR 로 별도 분리.
영향 받는 파일:
- tests/search_eval/queries.yaml: 23 → 51 case (기존 23 변경 0, append only)
- tests/search_eval/baselines/v0_2_baseline_2026-05-23.json: 신규
- tests/search_eval/baselines/v0_2_baseline_2026-05-23_analysis.md: 신규
PR plan: ~/.claude/plans/pr-2-serialized-hummingbird.md
Phase 1 plan: ~/.claude/plans/phase-1-graded-eval-v0-2.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 03:32:55 +00:00
hyungi
725a4e1f1d
feat(eval): v0.2 graded relevance schema + harness
...
queries.yaml v0.1 23 case → v0.2 schema swap:
- 7 카테고리 (standards / korean_only / english_only / mixed / exam /
ocr_derived / failure_expected)
- language / ocr_derived / failure_expected / graded_relevance 컬럼 추가
- v0.1 호환 보존 (legacy_category + relevant_ids + top3_ids)
- 신규 28 case (50+ 목표) 는 후속 PR-Eval-V0_2-Baseline-Analysis
run_eval.py 확장:
- graded_ndcg_at_k / graded_recall_at_k 함수 추가
- Query / QueryResult dataclass 확장 (v0.2 컬럼)
- load_queries v0.1 fallback (top3 → grade 3, 나머지 → grade 2)
- --eval-version v0.1/v0.2/both flag (default both)
- print_summary 의 by_language / by_ocr_derived 집계 추가
- write_csv 의 graded 컬럼 추가
README.md 신규:
- graded 등급 정의 (0~3) + 카테고리 정의 (7개)
- v0.2 schema 컬럼 + 신규 case 작성 가이드
- v0.1 호환성 + CLI 사용 예 + baseline 박제 정책
Phase 1 plan: ~/.claude/plans/phase-1-graded-eval-v0-2.md
Parent: ~/.claude/plans/peppy-hugging-nest.md § Phase 1
본 PR closure: schema + harness + README. 신규 28 case + baseline 박제 +
약점 분석 (embedding-sensitive failure pattern 4 카테고리 식별) 은 후속 PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-23 01:21:06 +00:00
hyungi
51c3f6df10
feat(search): /ask/react endpoint with Qwen native tool calling ReAct loop
...
PR-DocSrv-Ask-ToolCalling-ReAct-1 — Qwen3.6-27B-8bit 의 native tool calling
으로 ReAct loop 도입. 기존 /api/search/ask 무수정. 트랙 B (frontend /ask SSE)
와 파일 단위 충돌 0 (search.py 의 ask() 함수 line diff = 0, 순수 추가).
핵심 invariant:
- 별 endpoint /api/search/ask/react (qwen-macbook only, implicit opt-in)
- MacBook unavailable 시 HTTP 503 + error_reason=macbook_unavailable.
Gemma 자동 fallback X (정정 4 의 연장)
G0 (구현 전 hard gate, plan b-velvety-hare.md):
- G0-1 fixture (tests/fixtures/qwen_tool_call_response.json): 실제 mlx-vlm
응답 박제. shape = OpenAI 표준 호환 (choices[0].message.tool_calls +
function.arguments JSON string). generate_with_tools() 가 본 shape 기준 구현.
- G0-2 counter semantics: max_tool_rounds=2 + max_llm_calls=3 + search_exec_max=2.
마지막 LLM 호출은 tool_choice="none" + system instruction 으로 final 강제.
- G0-3 trace exposure: default response 의 debug_trace=null. debug=true 시만
채움. server log 에는 항상 round 기록.
backends.py (193 → 261줄):
- QwenMacBookBackend.generate_with_tools(messages, tools, tool_choice)
신규 method. 기존 generate() 무수정. BackendUnavailable 처리 동일.
react_loop.py 신규 (275줄):
- agentic_ask_loop(session, query, *, backend, max_tool_rounds, debug)
- tool round 안에서 run_search 호출, results dedup by id, final round 강제,
partial=True 조건 (final content 빈 경우)
search.py (+82줄):
- POST /api/search/ask/react + AskReactRequest/Response schema
- BackendUnavailable → JSONResponse(503, error_reason=macbook_unavailable)
config.yaml + config.py:
- search.ask.react: { enabled, max_tool_rounds=2, search_tool_limit=5,
search_tool_mode=hybrid }
tests (566줄, 18 신규 + 23 회귀 모두 PASS):
- test_react_loop.py 13건: G0-1 fixture shape / G0-2 counter cap / G0-3 trace
exposure / BackendUnavailable propagation / sources dedup
- test_search_ask_react_endpoint.py 5건: 503 + run_search 호출 0 / 정상 200 /
debug=true trace 노출 / max rounds partial
- 회귀 (test_ask_eval_auth 9 + test_search_ask_macbook_503 5 +
test_backend_dispatcher 9) 모두 PASS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-22 13:43:47 +00:00
hyungi
a7b8f15870
feat(search): /ask backend dispatcher (qwen-macbook opt-in, no silent fallback)
...
PR-MacBook-RAG-Backend-1 — /api/search/ask 의 명시 backend 선택 진입점.
핵심 invariant (정정 4):
- backend 미지정 = Gemma Mac mini default, 응답 contract 변동 0
- backend="qwen-macbook" 명시 opt-in 만 MacBook M5 Max mlx-vlm.server 호출
- MacBook unavailable 시 HTTP 503 + error_reason=macbook_unavailable
- 자동 fallback 절대 금지 — 실패 path 에서 Gemma backend.generate() 호출 0
backend dispatcher (services/llm/):
- BackendBase / GemmaMacMiniBackend / QwenMacBookBackend / BackendUnavailable
- Qwen backend 는 Mac mini llm_gate 점유 X, 별 Semaphore(1) — llm_gate
docstring 의 single-inference 영구 룰은 같은 endpoint 한정으로 scope 명시
- httpx Connect/Read/Pool/Timeout/5xx → BackendUnavailable, 4xx 전파
synthesis_service.py:
- backend 인자 추가, status="backend_unavailable" 신규
- cache key 에 backend_name 포함 (qwen ↔ gemma 캐시 충돌 차단)
config:
- search.ask.backend.{macmini_url, macbook_url, macbook_model,
timeout_connect_s=1, timeout_read_s=30}
- MacBook endpoint = http://100.118.112.84:8810 (M5 Max Tailscale bind)
tests (14 신규):
- tests/services/test_backend_dispatcher.py (9): dispatcher 정합성 + Qwen
generate path (mock 200 / dead port / 5xx / 4xx) + cache identity
- tests/api/test_search_ask_macbook_503.py (5): 정정 4 핵심 invariant.
backend=qwen-macbook 비가용 시 gemma.generate.assert_not_called()
기존 ask 회귀 0 (test_ask_eval_auth 9건 등 85건 모두 PASS).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-22 13:10:44 +00:00
Hyungi Ahn
eae1f48d62
feat(worker-pool): Registry-1C cap 1MB + deterministic compaction
...
사용자 결정 2026-05-19: 100KB cap 이 운영 7d 데이터 1.36MB 대비 부족 →
cap 상향만으로 raw 비대화 위험. cap 1MB + payload compaction 병행.
fetch_recap_context() 변경:
- memo payload item field 축소 = id/title/ai_tldr/ai_event_kind/created_at (5 필드)
(ai_bullets/file_type/source_channel/category/extracted_text 등 제외)
- memo top-N = RECAP_MEMO_TOP_N env (default 200) — 초과분은 aggregate 로
- aggregate = memos_by_day + memos_by_kind + omitted_memos
- payload_compacted flag = aggregate fallback 발현 여부
- events 는 raw (운영 7d 데이터에서 통상 0~소량)
internal_worker.py:
- PAYLOAD_MAX_BYTES → _payload_max_bytes() env override
(WORKER_RECAP_PAYLOAD_MAX_BYTES default 1_000_000)
- JobsRecapResponse 에 payload_compacted / omitted_memos 노출
- 413 detail 에 "after compaction" 명시 + RECAP_MEMO_TOP_N 조정 안내
테스트 3 항목 신규 + 기존 endpoint 413 test 업데이트:
- 700 memo → 200 kept + 500 omitted + compacted=true + < 1MB
- 10 memo → compacted=false + omitted=0
- 비정상 큰 title (compaction 후에도 cap 초과) → 413 유지
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-19 12:55:51 +09:00
Hyungi Ahn
0ea72c1aa6
feat(worker-pool): Registry-1C recap context + /jobs/recap + 100KB guard
...
- app/services/worker_recap_context.py — fetch_recap_context(user_id, days)
documents file_type='note' 7d (single-user invariant) + events 7d
(user_id 매칭 + cancelled 제외) JOIN. timezone Asia/Seoul.
- /internal/worker/jobs/recap POST — 일반 user JWT 인증 + context 조립
+ worker_jobs INSERT. job_type='recap' + payload JSONB.
- payload 100KB guard — JSON 직렬화 100_000 bytes 초과 시 413.
- 회귀 위험 0: memos/events API select 절 touch 0, read-only 쿼리만.
worker-pool-policy §B.2 invariant 보존: ProcessingQueue 무변경, 운영 자동
분기 변경 0, canonical promote 0 (worker_jobs.payload JSONB only).
Notebook-Pilot-1 entry condition 4항목 모두 충족 가능:
manual recap E2E / payload <100KB guard / residue 0 / 권한 분리 403.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-19 12:44:07 +09:00
Hyungi Ahn
0cbd97fcba
refactor(worker-pool): Registry-1B test fixture — NullPool helper standalone
...
각 helper 가 자체 engine + NullPool 사용 (connection 격리). fixture chain 의
asyncpg "another operation in progress" race 회피. 호출 site 단순화.
같은 파일 sequential 실행 시 module-level app + global engine pool 충돌은
별 follow-up `PR-Worker-Pool-Test-Fixture-Isolation` (P3) 영역.
단독 PASS 검증: auth 5/5 + smoke 3/3 + ownership 1/1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-19 12:43:53 +09:00
Hyungi Ahn
f60d6e52fc
feat(worker-pool): Registry-1B Pull 활성화 (auth + worker_jobs + 5 endpoint)
...
worker-pool-policy §B 1B 영역 완료. 1A scaffold (mig 270~274 + 503 stub) 위에:
- mig 275/276: worker_jobs (status CHECK + user_id=owner) + pending partial index
- create_laptop_worker_bot_token + require_worker_user dependency (voice-memo 동형)
- /internal/worker/{register,heartbeat,claim,result,drain} 5 endpoint 실 구현
- /claim FOR UPDATE SKIP LOCKED + 204 body 0
- /result 소유권 검증 (worker_id 매칭, 404) + failed 재시도 (attempts/max)
- explicit failure 시 request.result 무시 (DB result NULL 유지)
- 테스트 22 항목 7 파일
policy §B.2 5 invariant 보존: voice-memo wrapper 변경 0, drain advisory,
result raw JSONB, ProcessingQueue 무변경, 운영 자동 분기 변경 0.
활용처 (recap context + /jobs/recap + payload 100KB guard) = Registry-1C 영역.
stale recovery / 노트북 client / canonical promote = Notebook-Pilot-1 영역.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-19 08:54:07 +09:00
Hyungi Ahn
bbd92a840a
feat(worker-pool): Registry-1A scaffold — worker_capabilities/heartbeats + /internal/worker/* 5 endpoint 503 stub
...
PR-Worker-Pool-Registry-1A (scaffold only, no runtime activation).
신규:
- migrations/270~274 (1 statement/1 file 강제): worker_capabilities + 2 idx + worker_heartbeats + 1 idx
- app/models/worker_pool.py: WorkerCapability + WorkerHeartbeat ORM (queue.py 패턴)
- app/api/internal_worker.py: 5 endpoint 모두 _stub_503() — register/heartbeat/claim/result/drain
- tests/test_internal_worker_stub.py: 503 응답 smoke (inline ASGI client, DB 의존 0)
수정:
- app/main.py: import + include_router 각 1줄 (prefix=/internal/worker, internal_study 일관)
scaffold-first + phase-gate-material-first 강제 (worker-pool-policy §1, §12):
- 인증 dependency 0 (1B 에서 JWT + require_worker_user)
- ProcessingQueue 변경 0 (방향 b: worker_jobs 별 table = 1B)
- LLM 호출 0 / canonical DB 변경 0 / 운영 자동 분기 0
회귀 0 (1주 안전망 = app/main.py.pre-registry-1a.20260518).
plan: ~/.claude/plans/floofy-exploring-mitten.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-18 20:24:59 +09:00
Hyungi Ahn
7c9aff393a
feat(search): MLX priority gate (B-1, Priority.FOREGROUND vs BACKGROUND)
...
DS-Mac-mini-26B-Priority-Gate-1 — Mac mini 26B single-inference gate 를
FIFO Semaphore → 우선순위 기반 heap dispatch 로 교체. concurrency 1 유지,
queue ordering 만 foreground 우선.
API:
- Priority(IntEnum): FOREGROUND=0, BACKGROUND=100
- acquire_mlx_gate(priority=DEFAULT_PRIORITY) async context manager
- DEFAULT_PRIORITY = BACKGROUND (안전 default, foreground 짓밟지 않음)
- get_mlx_gate() legacy wrapper — context-manager only 호환
구현:
- _inflight: bool + _waiters heap [(priority, seq, future, enqueue_ts)]
- fast-path: not inflight and not waiters → 즉시 inflight, Future 생성 X
- _dispatch_next_locked: cancelled/done Future skip (heap 잔재 risk 회피)
- release: lock 안에서 pop, set_result 는 loop.call_soon (lock 밖) reentry deadlock 회피
- dispatch / enqueue / release / WARN log (observability)
- BACKGROUND wait_ms > 300_000 (5분) 시 starvation WARN — aging 은 Phase 2 deferred
Tests (tests/test_priority_gate.py, 6 scenario):
1. FIFO within same priority
2. Foreground jumps queue (bg5 대기 중 fg 들어오면 즉시 다음 슬롯)
3. Long-running background blocks foreground (preemption X, intended)
4. Mixed concurrent enqueue (FG fifo 먼저, BG fifo 후)
5. Backward compat (legacy get_mlx_gate() = BACKGROUND 매핑)
6. Cancelled waiter skip (heap 의 죽은 Future 건너뜀, gate stuck X)
Site 교체는 별 commit (refactor(search): swap 10 call sites).
plan: ~/.claude/plans/hermes-polymorphic-rossum.md
2026-05-17 08:42:58 +09:00
Hyungi Ahn
98ee7dffe2
ops(gpu-health): GPU 서비스 health/smoke 표준화 + synthetic VRAM 피크 가드
...
PR-GPU-Health-1. 운영 준비성 표준화 PR (모델 성능 개선 아님).
- OCR /smoke endpoint 추가 (160x60 OK PNG in-memory, 200/503 분기, Docker healthcheck 미사용)
- marker /health endpoint 추가 (stt/ocr 동일 시그니처)
- reranker docker-compose healthcheck 추가 (TEI :80/health)
- scripts/gpu_service_smoke.sh: docker exec 표준 점검 (OCR/STT expose-only)
- scripts/gpu_vram_fixture.sh: Mode A sequential + Mode B light overlap + --stress 옵션
- tests/load/fixtures/: synthetic ocr_ok.png / sine_30s.wav / lorem_1p.pdf
OCR 빈 응답 false negative — root cause: ports 미매핑.
결정: ocr-service / stt-service 는 expose-only 유지, 운영 점검은 docker exec 내부 curl 표준.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-14 09:42:07 +09:00
Hyungi Ahn
6966be9cf6
fix(briefing): backfill country_perspectives[].article_ids from cluster members
...
LLM 이 article_ids 를 자율적으로 비워두는 케이스 (2026-05-12 첫 briefing 6
topics 모두 빈 list) 를 서버에서 보정.
후처리 정책 (_resolve_article_ids):
1. LLM 이 준 id ∩ cluster member id (엉뚱한 id 차단, hallucination 방어)
2. 비어있으면 같은 country cluster member top weight N 개 자동 주입
3. cluster 안 country 매칭 멤버 0 → []
per-country cap = MAX_ARTICLE_IDS_PER_COUNTRY = 5. weight 내림차순.
API 계약 강화: country_perspectives 가 있는 topic 은 article_ids ≥ 1 보장
(같은 country cluster member 존재 시). frontend / 외부 채널 / archive UI
모두 신뢰 가능.
tests 3 케이스 추가.
2026-05-12 13:15:26 +09:00
Hyungi Ahn
431d4fe010
feat(briefing): add morning briefing schema + services + api (historical off)
...
야간 수집 뉴스 (KST 00:00~05:00) topic×country 비교 분석 1페이지 카드.
Phase 4 Global Digest 와 코드/로직/테이블 분리, 알고리즘만 services/clustering_common 공유.
Backend 신규:
- migrations/255_morning_briefings.sql: morning_briefings + briefing_topics
(briefing_date UNIQUE, UNIQUE(briefing_id,topic_rank), FK CASCADE,
historical_* 3컬럼 nullable, cluster_members JSONB, country_perspectives
JSONB, status 4-state success|partial|failed|empty)
- app/models/briefing.py: SQLAlchemy ORM
- app/services/briefing/loader.py: KST 5h 윈도우 + news_sources prefix
fallback (Phase 4 패턴 미러) + historical candidate pool 로더
- app/services/briefing/clustering.py: cluster_global topic-first
(LAMBDA=ln(2)/2h, MIN_COUNTRIES_PER_TOPIC=2, MAX_TOPICS=7)
- app/services/briefing/comparator.py: call_primary 26B + JSON envelope
sanitize (cap perspectives 10 / divergences 3 / convergences 2 /
quotes 5) + fallback row 고정 형태 + retrieve_historical cosine top-K
- app/services/briefing/pipeline.py: load→cluster→select(K=7,λ=0.6)
→historical→compare→status 4-state→delete+insert transaction
- app/workers/briefing_worker.py: APScheduler/수동 호출 공용 진입점,
600s hard cap
- app/prompts/briefing_comparative.txt: 한국어 비교 분석 JSON 프롬프트,
{articles_block} + {historical_block} 2섹션, 인용 금지 라벨
- app/api/briefing.py: GET /latest, GET ?date=, POST /regenerate?date=
(admin, sync delete+insert tx, regenerated:true)
Backend 수정:
- app/main.py: briefing_router 등록 (/api/briefing prefix). scheduler
등록은 PR-3 에서.
- app/services/digest/selection.py: select_for_llm 매개변수화 (K, λ
caller 주입). Phase 4 동작은 default 값으로 보존.
Historical 정책:
- BRIEFING_HISTORICAL_ENABLED env flag, default off.
- flag off → historical_* 컬럼 모두 NULL, prompt {historical_block} 빈
라벨, retrieval 호출 안 함.
- flag on (PR-1b 에서 enable) → cluster centroid 와 과거 30일 doc
embedding cosine top-K 5 (sim≥0.70), prompt 에 주입.
Country canonical (실측 확인 후):
- documents.country 컬럼 부재 확정
- document_chunks.country 매칭률 0% (chunks 자체가 뉴스에 안 만들어짐)
- 유일 country 신호 = news_sources prefix 매핑 (Phase 4 와 동일)
Tests:
- tests/test_briefing_historical.py: 3 경로 회귀 (flag off/on with
fixture/on zero match) + sanitize cap + fallback row 형태.
Verification: PR-1.8 에서 GPU 컨테이너 pytest + 수동 regenerate.
2026-05-12 12:58:50 +09:00
Hyungi Ahn
68fa86ea52
feat(markdown): persist extracted images with auth routes
...
Markdown Canonical Phase 1B.5 — marker 가 추출하던 이미지를 NAS 에 영구 저장하고
DB 메타 + 인증 라우트 + 프론트 swap 까지 wiring.
핵심 변경:
- marker-service /convert 응답에 base64 image 리스트 포함 (stateless 유지, NAS write 권한 X)
- marker_worker 가 NAS `/documents/extracted_images/{doc_id}/` 에 persist + UPSERT +
고아 row DELETE + md_content ref 를 `docimg:img_NNN` stable scheme 으로 정규화
- /api/documents/{id}/images/{key}/raw 인증 라우트 (Cache-Control private + ETag = content_hash)
- frontend MarkdownDoc 가 placeholder card 안의 docimg ref 를 실제 <img> 로 swap
원칙:
- 이미지 binary = NAS, metadata = Postgres (학습 섹션 패턴 동일)
- image_key sequence 기반 결정적 → 재변환 idempotent
- MARKDOWN_IMAGE_PERSIST=false env 로 rollback 가능 (placeholder card 폴백 자연 유지)
기존 28건 marker success 문서는 본 PR 에서 건드리지 않음 — deploy + 신규 업로드 1건 +
sample 5건 검증 후 scripts/marker_reprocess_existing_success.py 로 targeted reprocess.
plan: ~/.claude/plans/piped-humming-crystal.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-10 14:05:41 +09:00
Hyungi Ahn
5185501bbd
feat(search): PR-RAG-Time-1 freshness decay (news/law_monitor)
...
뉴스/법령 알림 retrieval 결과에 시간 가중치 soft multiplier 적용.
reranker 이후 final score 합성 단계에서 운영 정책 단계로 분리.
- news (source_channel='news'): half-life 90일
- law_monitor (source_channel='law_monitor'): half-life 365일
- 비적용: manual / drive_sync / inbox_route / memo / Manual / Reference /
Academic_Paper / Checklist / KGS Code / Study / content_origin='ai_drafted'
- formula: decay = exp(-ln(2) * age / HL); final = base * (0.7 + 0.3 * decay)
- floor 0.7 (완전 demote 금지)
- 가드: missing date / future date / unknown source 모두 no-op
- 임시 date source: documents.created_at (published_date 컬럼 부재 — 후속 PR)
debug 메타 (?debug=true 응답 + logs/search.log):
base_score / age_days / decay_factor / freshness_adjusted_score /
freshness_policy / freshness_date_source
신규: app/services/search/freshness_decay.py
hook: app/services/search/search_pipeline.py:303 (apply_diversity 직후, normalize 직전)
schema: app/api/search.py SearchResult.freshness_debug (Optional[dict])
tests: tests/test_freshness_decay.py 24 case (정책 디스패처 9 + age/decay/score 11 + apply integration 6 — guard 1~6 all)
Episode/Fact layer 와 contradiction detection 은 본 PR 스코프 외.
plan: ~/.claude/plans/pr-rag-time-1-freshness-decay.md
2026-05-03 08:38:09 +09:00
Hyungi Ahn
dfc5913c5e
fix(tests): explanation cap test setup — 한글 chunk 길이 부족 보정
...
case 3/4 의 setup 이 EXPLANATION_MAX_CHARS (1200) 보다 작은 text 를 만들어
assert 실패. 한글 chunk 반복 횟수 늘려 1200 자 이상 보장.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 08:35:34 +09:00
Hyungi Ahn
6b52d57bac
feat(study): Phase 4-A explanation_md 길이 cap + prompt 강화
...
운영 데이터에서 ready 박힌 풀이가 793/838/866자 — 권장 200~400 대비 큰 편.
1차 운영 후 결과 화면 가독성 + 토큰 사용량 통제 위해 prompt 강화 + 저장 전 cap.
Prompt (study_explanation_envelope.txt):
- explanation_md 권장 300~600자, 최대 900자 명시
- 핵심 개념 + 정답 근거 + 헷갈리는 1~2개 오답만 — 모든 오답 풀이 X
- explanation_md 안 줄바꿈 최소화 (parse_json fix 와 결합 — invalid escape 줄임)
- LaTeX 수식 자제 — \\circ/\\text/\\, 매크로 가능하면 평문 ('0°C', 'C')
- 출력은 raw JSON 한 객체만 — 코드 펜스/thinking/메타 X 강조
Worker (study_explanation_worker.py):
- _cap_explanation_md(text, max_chars=1200) 헬퍼 신규
· 1200자 이하 passthrough
· 초과 시 마지막 200자 안에서 \\n\\n / \\n / '. ' / '다.' / '요.' 경계 탐색
· 경계에서 자르기 + '…' (단어 중간 자르기 회피)
· 경계 못 찾으면 단순 자르기 + '…'
- save 전 cap 적용. ai_explanation_status='ready' 유지 (cap 됐다고 failed X)
- payload 에 운영 분석 metadata: explanation_len_original / _saved / capped 플래그
검증:
- tests/test_explanation_cap.py (6 케이스)
· short passthrough / exact at limit / paragraph boundary / sentence boundary
· no boundary fallback / empty input
- scripts/phase4_health.sql 섹션 8/9 추가
· ai_explanation 길이 p50/p95/max (study_questions.ready)
· cap 작동 빈도 (job.payload 의 explanation_capped/_original/_saved)
cap 1200 = 800 (4-B summary_md) 보다 여유 — 기사시험 풀이는 공식+오답+개념 묶이면
800 빡빡함. 운영 후 800~1000 으로 조정 검토.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 08:33:18 +09:00
Hyungi Ahn
8074be6b6d
feat(study): Phase 4-D 운영 관찰 + confidence calibration
...
Phase 4-B v1 첫 검증 결과 자료 부족 토픽인데도 모델이 confidence='high'
박는 케이스 발견. 정의 (high = 자료 + 다른 ai_explanation 으로 패턴 명확)
보다 과신 — UX 신뢰도 위험. 자동 cap 보정 + 운영 관찰 SQL 추가.
confidence calibration (services/study/session_summary_guard):
- calibrate_confidence(c, ctx_docs_count, ready_explanation_count) 신규
· ctx_docs_count == 0 AND ready_explanation_count == 0 → 'low' cap
· ctx_docs_count == 0 (ready 만 있음) → 'medium' cap
· ctx_docs_count >= 1 → 모델 값 그대로
- 모델이 정의보다 더 보수적인 값 박은 경우 (모델 'low' + cap 'medium') 는
보존 — 더 보수적인 값을 절대 올리지 않음
worker 적용 (study_session_analysis_worker):
- ctx_docs_count = len(ctx_docs)
- ready_explanation_count = sum(1 for a in prompt_attempts if a.get('ai_explanation'))
- calibrate_confidence 호출 → study_quiz_session_analysis.confidence 박힘
- job.payload 에 운영 분석 metadata 보존:
· ctx_docs_count / ready_explanation_count
· model_confidence_raw (모델 응답) vs calibrated_confidence (cap 후)
· prompt_attempts / valid_attempts_total / summary_len
→ SQL 4 번 쿼리가 cap 작동 빈도 측정
scripts/phase4_health.sql (신규 운영 점검 SQL 7 섹션):
1. 4-A study_question_jobs status × error_code 분포
2. 4-B study_quiz_session_jobs status × error_code 분포
3. 4-B confidence 분포 (calibrated)
4. 4-B model_confidence_raw vs calibrated 차이 (cap 작동 빈도)
5. 4-A/4-B 최근 7일 처리 지연 p50/p95/max/avg
6. 4-A/4-B skipped 사유 분포
7. 4-B guard_fail / parse_fail / llm_timeout 비율
ship gate (단위 테스트):
- test_calibrate_confidence_no_evidence_caps_to_low (3 케이스)
- test_calibrate_confidence_only_explanations_caps_to_medium (3 케이스)
- test_calibrate_confidence_with_documents_passthrough (3 케이스)
- test_calibrate_confidence_normalizes_invalid_first (2 케이스)
Plan: ~/.claude/plans/nifty-sparking-spindle.md (Phase 4-B v1 후속)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 07:33:57 +09:00
Hyungi Ahn
6785d53d3d
feat(study): Phase 4-B v1 세션 단위 종합 분석 (자유 마크다운)
...
Phase 4-A 가 wrong/unsure 한 문제씩 풀이 캐시. 4-B 는 세션 전체 wrong/unsure
5~30건을 묶어 200~400자 자연어 요약 1건 생성. 결과 화면 헤더 카드.
큐 인프라는 4-A study_question_jobs 와 분리 — FK 단일 의미 + 운영 SQL 명확성
+ 4-A/4-B 가드/payload/재시도 정책 차이. 신규 study_quiz_session_jobs (큐) +
study_quiz_session_analysis (결과 캐시 PK=session_id, UPSERT) + 전용 consumer.
Backend:
- migrations/233 — study_quiz_session_jobs (FK study_quiz_sessions NOT NULL,
status pending/processing/completed/failed/skipped, max_attempts=2)
- migrations/234 — partial unique idx (session_id) WHERE pending/processing
- migrations/235 — study_quiz_session_analysis (session_id PK, summary_md,
confidence, model_name, generated_at, is_stale)
- models/study_quiz_session_job — ORM + enqueue_session_analysis_job() (멱등)
- models/study_quiz_session_analysis — ORM (PK = session_id)
- services/study/session_summary_guard — GUARD_PATTERN (정규식) +
normalize_confidence() 단일 source, worker + tests 가 import 공유
- services/study/session_summary_rag — gather_session_summary_context()
documents 만 (PR-3 _gather_document_evidence 재사용). evidence 없어도 호출
허용 (4-A 와 다른 정책 — 세션 기록 자체가 evidence)
- services/study/session_analysis_enqueue — auto (finalize/fallback) +
request_session_analysis_regenerate (manual). manual 은 wrong/unsure < 5
즉시 차단, active job 차단, 기존 analysis 있으면 is_stale=true 박기
- prompts/study_session_summary_envelope.txt — envelope JSON
{summary_md, confidence}. 정량 정수만 인용 가능, 비율/추세/범위/날짜 금지
- workers/study_session_analysis_worker — terminal status 분기:
· wrong/unsure < 5 → status=skipped, error_code=insufficient_attempts
· question_text/outcome 부족 → skipped, evidence_missing
· GUARD_PATTERN match → failed, guard_fail
· 800자 hard cap + confidence normalize
· timeout/parse/unknown → 재시도 후보
· UPSERT study_quiz_session_analysis ON CONFLICT DO UPDATE (PK session_id)
- workers/study_session_queue_consumer — 4-A consumer 패턴 복제. BATCH_SIZE=1
+ STALE_MINUTES=10. MLX gate 4-A 와 공유 (Semaphore(1))
- main.py — APScheduler add_job(consume_study_session_queue, ..., 1분 주기)
- session_finalize — 끝에서 enqueue_session_analysis_auto (best-effort)
- api/study_topics:
· QuizSessionAnalysisOut + ai_session_analysis 응답 필드 (analysis row +
최신 job status/error_code)
· GET fallback enqueue (기존 analysis 또는 active job 없으면만, non-blocking)
· POST /quiz-sessions/{sid}/regenerate-summary — manual 트리거
Frontend (quiz-sessions/[sid]/+page.svelte):
- 결과 헤더에 세션 요약 카드 (AI 풀이 indicator 직후, 바로 할 일 직전)
- summary_md 박혔으면 markdown 렌더, 없으면 job_status / error_code 분기:
· pending/processing → "AI 가 세션 분석 중"
· insufficient_attempts → "오답·모르겠음 5건 미만"
· evidence_missing → "자료 부족"
· guard_fail → "환각 검증 차단" + 재생성 링크
- confidence='low' 배지 + is_stale "재생성 중" 배지
- 재생성 버튼 + regenerateSummary() — reason 별 toast 분기
ship gate:
- tests/test_session_summary_guard_pattern.py — 허용 5 + 차단 7 케이스 +
normalize_confidence 표준/비표준 검증. python3 직접 실행 패스.
Plan: ~/.claude/plans/nifty-sparking-spindle.md (Phase 4-B v1)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 07:20:29 +09:00
Hyungi Ahn
99672292d3
fix(policy): use container-compatible imports (drop app. prefix)
...
프로덕션 컨테이너는 /app 을 cwd 로 실행하고 import 는 `from api...`,
`from core...`, `from workers...` 처럼 무접두 스타일을 사용한다.
PR-A 내부 import 가 `from app.policy...`, `from app.ai.envelope` 로
되어 있어서 컨테이너에서 ModuleNotFoundError 발생.
변경:
- app/policy/*.py: `from app.policy.X` → `from policy.X`
- app/services/prompt_versions.py: lazy import 도 `from policy.prompt_render`
- app/ai/envelope.py: 영향 없음 (내부 import 없음)
- tests/policy/*.py: 모두 `from policy.X` / `from ai.envelope` 로 통일
- tests/policy/conftest.py: 로컬 pytest 용 sys.path.insert(app/) 추가
(MacBook 에서 repo-root 기준 실행 시 app/ 를 package root 로 취급)
CI: pytest tests/policy/ -q → 98 passed (로컬, 동일 결과)
프로덕션: docker exec fastapi python -c "from policy.loader import load_policy" → OK
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-24 09:42:24 +09:00
Hyungi Ahn
ba97766d45
feat(policy): INV-1~6 테스트 + loader/audit/envelope/shadow 검증
...
tests/policy/ 7개 테스트 파일 + conftest + __init__. 98 tests passed.
커버:
- test_policy_loader_schema.py (9) — yaml 로드, cross-reference,
unknown flag reject, invalid UI category reject, synthesis_directive
500 chars 초과 reject
- test_self_declare_add_only.py (4) — INV-1 invariant 엄격 검증
- test_routing_decisions.py (27) — INV-2~6 + low_confidence +
도메인 × 시나리오 parametrize (9 도메인 x 기본 시나리오)
- test_audit_patterns.py (11) — detection_patterns 양성/음성,
도메인 미스매치, 빈 텍스트 엣지
- test_envelope_contract.py (6) — JSON round-trip, invalid
from_stage reject, tuple 강제
- test_prompt_render.py (16) — 모든 템플릿 렌더, placeholder 치환,
policy_version deterministic/yaml-sensitive hash
- test_shadow_logger_inmem.py (5) — record/clear/multiple/extra/
Protocol 호환
conftest.py: autouse _clear_policy_cache fixture — lru_cache 로 인한
테스트 간 오염 방지. policy fixture 는 repo root domain_policy.yaml 로드.
plan: ~/.claude/plans/wise-gliding-hippo.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-24 09:34:49 +09:00
Hyungi Ahn
51a6f7c9af
feat(eval): 발주건 단위 baseline 평가 경로 추가
...
- run_eval.py: --queries-order / --order-groups / --output-order / --debug
옵션 추가. 기존 legacy CSV 스키마/값 불변 (출력 소비자 보호).
- Tier 1A/1B/2 지표 구현: cross_format_link_success (top-10 공식 +
top-5 보조, eligible/success 분수), top_5_document_match (guardrail +
절대 건수), manual_refind_flag (v0 heuristic), chunk_idx_stddev,
range/page_citation_available capability flags.
- order_groups.yaml: 발주건 3건 매핑 (TKP-26-0114/0132/0112, 10 docs).
- queries_order_baseline.yaml: 12개 질문 (A:4 B:4 C:3 D:1).
plan: ~/.claude/plans/merry-yawning-owl.md
2026-04-20 15:04:39 +09:00