refactor: audit-tail 정리 — FusionPolicy inline + dual-YAML 통일 + NLI required

ponytail-audit 의 작은 over-engineering 잔여 3건 (output/동작 불변):

1. FusionPolicy 1-arm enum 제거 — kebab-search 내부 전용(외부 importer 0)이던
   `enum FusionPolicy { Rrf { k_rrf } }` 를 `HybridRetriever.k_rrf: u32` 로 inline.
   parse_fusion 은 u32 반환. RRF 수식 `Σ 1/(k_rrf+rank)` byte-identical.
2. dual YAML 통일 — serde_yaml(0.9) 를 maintained fork serde_yaml_ng(0.10) 로 일원화.
   k8s manifest chunker + eval golden loader 2 site 마이그레이션(API drop-in),
   workspace dep 에서 serde_yaml 제거. parse 결과 불변.
3. NliVerifier::hypothesis_token_count 의 `Ok(0)` default 제거 → required.
   prod OnnxNliVerifier 가 이미 override 라 동작 불변, MockNliVerifier 에 explicit 추가.

문서 동기화: search/eval/parse component README + ARCHITECTURE 의 FusionPolicy/
serde_yaml 참조를 현 상태로 갱신. net −37줄. 적대적 검증 2렌즈(output-identity +
completeness-build) 통과, 영향 crate 테스트 + clippy -D warnings 클린.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
This commit is contained in:
2026-06-27 02:44:03 +00:00
parent 2f02058f63
commit 3ac3e91421
18 changed files with 48 additions and 85 deletions

View File

@@ -105,7 +105,7 @@ flowchart LR
## 주요 type / trait / 함수
**Loader** (`kebab-eval::loader`):
- `load_golden_set(path: &Path) -> Result<Vec<GoldenQuery>>``serde_yaml` 위 YAML 파싱.
- `load_golden_set(path: &Path) -> Result<Vec<GoldenQuery>>``serde_yaml_ng` 위 YAML 파싱.
- `GoldenQuery { id, query, mode, k, must_contain, expected_doc_ids, ask }` — fixture 한 entry. `must_contain` 가 case-sensitive substring 검사.
**Runner** (`kebab-eval::runner`):
@@ -133,7 +133,7 @@ flowchart LR
## 외부 의존
- crate dep: `kebab-core` + `kebab-config` + `kebab-app` (facade only) + `kebab-store-sqlite` (SQLite 직접 read/write — `eval_runs` / `eval_query_results` 측). retrieval / embedding / LLM crate 직접 import **금지**.
- 외부 lib: `serde_yaml` (golden YAML), `serde_json`, `uuid` (v7), `time`, `tracing`, `anyhow`.
- 외부 lib: `serde_yaml_ng` (golden YAML), `serde_json`, `uuid` (v7), `time`, `tracing`, `anyhow`.
- 외부 서비스: 없음 (facade 가 가져옴).
## 핵심 결정

View File

@@ -114,7 +114,7 @@ flowchart LR
- crate dep:
- 모든 parser → `kebab-core` (`Extractor` trait, `Block`, `Metadata`, `id_for_*`).
- `kebab-parse-md``kebab-parse-types` (`ParsedBlock`/`ParsedPayload`/`Warning`), `pulldown-cmark`, `serde_yaml`.
- `kebab-parse-md``kebab-parse-types` (`ParsedBlock`/`ParsedPayload`/`Warning`), `pulldown-cmark`, `serde_yaml_ng`.
- `kebab-parse-pdf``lopdf`.
- `kebab-parse-image``image` (decode), `kamadak-exif` (EXIF), `kebab-core::LanguageModel` (caption).
- 외부 서비스:

View File

@@ -30,16 +30,12 @@ classDiagram
}
class HybridRetriever {
+new(cfg, lexical, vector) Self
+with_policy(lex, vec, FusionPolicy, k)
+with_policy(lex, vec, k_rrf, default_k)
-lexical: Arc~dyn Retriever~
-vector: Arc~dyn Retriever~
-fusion: FusionPolicy
-k_rrf: u32
-default_k: usize
}
class FusionPolicy {
<<enum>>
Rrf{k_rrf}
}
class SearchMode {
<<enum>>
Lexical
@@ -51,7 +47,6 @@ classDiagram
Retriever <|.. HybridRetriever
HybridRetriever --> LexicalRetriever
HybridRetriever --> VectorRetriever
HybridRetriever ..> FusionPolicy
HybridRetriever ..> SearchMode : dispatch
```
@@ -95,7 +90,7 @@ flowchart LR
**HybridRetriever** (`kebab-search::hybrid`):
- `HybridRetriever::new(&Config, Arc<dyn Retriever> lex, Arc<dyn Retriever> vec) -> Self``config.search.hybrid_fusion` (`"rrf"`) + `config.search.rrf_k` 읽음. 두 retriever 의 `index_version` 가 다르면 `tracing::warn`.
- `FusionPolicy::Rrf { k_rrf }` — default 60. `with_policy` 헬퍼로 explicit 지정 가능.
- `k_rrf: u32` — default 60. `with_policy` 헬퍼로 explicit 지정 가능.
- 상수: `DEFAULT_K = 10` (query.k == 0 fallback), `DEFAULT_K_RRF = 60`, `HYBRID_FANOUT_MULTIPLIER = 2`.
- merge rule: 양측 등장 chunk 의 `snippet` / `citation` / `heading_path` 는 lexical 측에서 가져옴 (FTS5 highlight 가 vector 의 truncated text 보다 user-relevant).