feat(rag): fb-41 PR-9c-2 — pipeline integration + mock test + SKILL.md (★ NLI 실 활성화) #179
Reference in New Issue
Block a user
Delete Branch "feat/fb-41-pr-9c-2-pipeline-integration"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
PR-9c-1 의 wire surface 위에 behavior 활성화 —
ask_multi_hop의 step 8.5 hook 가[rag] nli_threshold > 0일 때 NLI 검증 실 수행. 첫 user-visible behavior change in PR-9 — config 설정 시 multi-hop ask 가 mDeBERTa-v3 XNLI 로 답변 검증.설계: docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md (§3 PR-9c-2, §2.3)
계획: docs/superpowers/plans/2026-05-25-p9-fb-41-finalize-plan.md (§5)
변경 사항
kebab-rag (pipeline integration)
crates/kebab-rag/src/pipeline.rs::ask_multi_hop의 step 8.5 NLI hook:!acc.trim().is_empty() && nli_threshold > 0) — synthesize bail 시 별 path 의 refusal 보존.verifier.as_ref().expect(...)— facade invariant (App::open_with_config 가 enforce, 도달 불가능 panic-safe).truncate_for_nli(&packed_text, &acc)호출 →verifier.score→ entailment 채널 vsnli_threshold비교 →VerificationSummary생성.Err(e)→refuse_nli_model_unavailable(tracing::warn!+ RefusalReason).verification.nli_passed == false→refuse_nli_verification(verification stamp 포함 wire).refuse_nli_verification/refuse_nli_model_unavailablehelpers — 기존refuse_*패턴 일관.truncate_for_nli(premise: &str, _hypothesis: &str) -> (String, bool)module-level pub fn —MAX_NLI_PREMISE_CHARS = 4 * 400 = 1600 chars(chars-based budget — KR 같은 multi-byte 안전)._hypothesis미사용 placeholder (v0.18.1 token-budget 갱신 candidate).#[allow(dead_code)]두 곳 제거 (verifierfield +with_verifierbuilder) — round-1 PR-9c-1 review N1 carry-forward closure. doc 의 transitional sentence 도 cleanup.kebab-app (facade integration)
crates/kebab-app/src/app.rs::open_with_config가config.rag.nli_threshold > 0일 때OnnxNliVerifier::new(&config)?호출 +Arc::newwrap + 후속RagPipeline초기화 시with_verifier호출.Result<Self>unchanged —?전파만 (caller cascading 0).crates/kebab-app/Cargo.toml에kebab-nlipath 의존 추가.Mock tests (5 multi-hop)
crates/kebab-rag/tests/common/mod.rs에MockNliVerifier(pass / fail / err 생성자 + scorecall_countinstrumented).crates/kebab-rag/tests/multi_hop.rs:multi_hop_nli_pass_keeps_grounded— entailment 0.9 → grounded=true, verification.nli_passed=true.multi_hop_nli_fail_refuses— entailment 0.1 → refusal=NliVerificationFailed.multi_hop_nli_disabled_skip_verify— threshold 0.0 → verify skip, verification=None.multi_hop_nli_model_unavailable_refuses— verifier Err → refusal=NliModelUnavailable.multi_hop_truncate_for_nli_preserves_hypothesis— long premise truncation + hypothesis 보전 unit.SKILL.md
integrations/claude-code/kebab/SKILL.md의mcp__kebab__ask절에 NLI 안내 한 단락 —answer.v1.verification.nli_passed의미 + threshold tuning (0.5 default, 0.9 strict) +nli_verification_failed/nli_model_unavailablerefusal handling (사용자 임시 우회:[rag] nli_threshold = 0).검증
cargo test -p kebab-rag -j 1 --test multi_hop→ 15 passed / 0 failed (5 신규 NLI tests 포함).cargo clippy --workspace --all-targets -j 1 -- -D warnings→ clean (4m 31s,#[allow(dead_code)]제거 후unused_attributes회피).cargo test --workspace --no-fail-fast -j 1→ 전체 통과 + pre-existing kebab-mcp::tools_call_ask_multi_hop::ask_tool_routes_multi_hop_true_to_decompose_first 1 flaky (main baseline 동일, no_chunks short-circuit, PR-9c-2 무관, HOTFIXES candidate).비범위
truncate_for_nli의 v0.18.1 token-budget 갱신 (현재 char-based).시험 항목 (Test Plan)
사용자 시점 동작 (★)
본 PR 머지 +
[rag] nli_threshold = 0.5(config.toml 또는KEBAB_RAG_NLI_THRESHOLD=0.5env) 설정 시:kebab ask --multi-hop호출 시 mDeBERTa-v3 XNLI 모델 (~280 MB) HF 자동 다운로드 (lazy, ~30-60s).verification.nli_passed=true), 미만이면refusal_reason="nli_verification_failed".refusal_reason="nli_model_unavailable"— 사용자 우회:[rag] nli_threshold = 0임시 disable.PR-9d 가 dogfood retest 로 실측값 + threshold tuning iteration trigger 결정.
Assisted-by: Claude Code
회차 1 — ★ NLI 실 활성화 PR 검토.
칭찬 (산문, inline 안 함):
step 8.5 hook 의 facade invariant 명시 —
verifier.as_ref().expect("verifier must be Some when nli_threshold > 0.0 (kebab-app's open_with_config enforces this invariant)"). expect 메시지가 어느 코드 경로가 invariant 유지 책임자 명시 — panic 발생 시 root cause 진단 즉각.empty answer 가드의 short-circuit 순서 정확 —
nli_threshold > 0.0 && !acc.trim().is_empty()—acc.trim().is_empty()가 RHS 라 threshold disabled 경우 trim 비용도 회피. 좋음.App::build_pipeline헬퍼 도입 — App 의 모든 ask path (ask/ask_with_session/ 등) 가 같은 helper 거쳐서with_verifier일관 적용. PR-9d 의 dogfood retest 시 어느 ask path 든 NLI 검증 동일 동작 보장.eager
pipeline_verifier구성 결정 —open_with_config의?propagation 이 NLI model construction 실패를 App boot 시점에 user-facing 으로 surface. 매 ask 시점이 아닌 한 번 검증 — config 잘못된 경우 사용자가 즉각 알 수 있음.truncate_for_nli의chars().count()사용 —len()(byte count) 대신 char count — multi-byte (Korean) 안전. spec §2.2.3 의 "KR SentencePiece 1-2 char/token + tokenizer OnlyFirst backup" 의 first-line defense.두 refuse helper 의
verificationfield 차별 —refuse_nli_verification은Some(VerificationSummary)(wire 의 nli_score 측정값 보존),refuse_nli_model_unavailable은None(verification 자체 미수행). spec §2.4 의 semantic accuracy.PR-9c-1 의
#[allow(dead_code)]두 곳 cleanup 정확 —verifierfield +with_verifierbuilder 모두 제거 + doc 의 transitional sentence ("Currently unused — PR-9c-2 wires the read site") 도 정리. round-1 PR-9c-1 review N1 carry-forward closure.MockNliVerifier 의 enum-based mode — pass/fail/err 3 생성자가 동일 struct + MockMode enum 으로 표현. test 의 명료 + 후속 v0.18.1 mode 추가 (예: latency simulate) 의 확장 용이.
minor informational inline 1건 —
MockNliVerifier::calls()accessor 의 v0.18 unused (carry-forward informational, 본 PR 변경 없음).추가 actionable 없음. PR-9d 가 dogfood retest 로 실측 NLI score + threshold tuning iteration trigger 결정할 자연스러운 baseline.
머지 OK. 머지 후
[rag] nli_threshold = 0.5설정 시 ★ multi-hop ask 의 NLI 검증이 실 동작.N1 (informational):
MockNliVerifier::calls()accessor 가 추가됐지만 5 multi_hop tests 안에서 직접 호출 안 됨 (verifier 호출 횟수 검증은 implicit — pass/fail/skip path 의 wire 결과만 assert). 의���된 instrumentation 일 텐데 v0.18 scope 에서는 unused public API surface. 다음 중 선택:multi_hop_nli_disabled_skip_verify에verifier.calls() == 0직접 assert 추가 (verifier=None 일 땐 helper 자체 호출 불가 — MockNliVerifier 가 verifier 로 주입된 케이스만 적용 가능. instead 가multi_hop_nli_pass_keeps_grounded에calls() == 1assert 가능).calls()제거 후 각 test 가 필요할 때 추가 (YAGNI).v0.18.1 의 stress test (multi-hop 시 step 8.5 중복 호출 방지 회귀 검증 등) 에서 활용 가능 — carry-forward informational. 본 PR 변경 안 함.