feat(rag): fb-41 PR-9c-2 — pipeline integration + mock test + SKILL.md (★ NLI 실 활성화) #179

Merged
altair823 merged 1 commits from feat/fb-41-pr-9c-2-pipeline-integration into main 2026-05-26 01:03:20 +00:00
Owner

요약

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:
    • empty answer 가드 (!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 채널 vs nli_threshold 비교 → VerificationSummary 생성.
    • Err(e)refuse_nli_model_unavailable (tracing::warn! + RefusalReason).
    • verification.nli_passed == falserefuse_nli_verification (verification stamp 포함 wire).
  • refuse_nli_verification / refuse_nli_model_unavailable helpers — 기존 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)] 두 곳 제거 (verifier field + with_verifier builder) — 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_configconfig.rag.nli_threshold > 0 일 때 OnnxNliVerifier::new(&config)? 호출 + Arc::new wrap + 후속 RagPipeline 초기화 시 with_verifier 호출.
  • 시그니처 Result<Self> unchanged? 전파만 (caller cascading 0).
  • crates/kebab-app/Cargo.tomlkebab-nli path 의존 추가.

Mock tests (5 multi-hop)

  • crates/kebab-rag/tests/common/mod.rsMockNliVerifier (pass / fail / err 생성자 + score call_count instrumented).
  • 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.mdmcp__kebab__ask 절에 NLI 안내 한 단락 — answer.v1.verification.nli_passed 의미 + threshold tuning (0.5 default, 0.9 strict) + nli_verification_failed / nli_model_unavailable refusal handling (사용자 임시 우회: [rag] nli_threshold = 0).

검증

  • cargo test -p kebab-rag -j 1 --test multi_hop15 passed / 0 failed (5 신규 NLI tests 포함).
  • cargo clippy --workspace --all-targets -j 1 -- -D warningsclean (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).

비범위

  • Dogfood retest (PR-9d).
  • v0.18.0 cut PR (chore: cut).
  • truncate_for_nli 의 v0.18.1 token-budget 갱신 (현재 char-based).

시험 항목 (Test Plan)

  • cargo test -p kebab-rag --test multi_hop -j 1 — 15/15 (5 신규 NLI pass).
  • cargo clippy --workspace --all-targets -j 1 -- -D warnings clean (#[allow(dead_code)] 제거 후 cleanup).
  • cargo test --workspace -j 1 회귀 0 (pre-existing 1 flaky = main baseline 동일).
  • App::open_with_config 의 NliVerifier construction — config.rag.nli_threshold > 0 시 OnnxNliVerifier::new + Arc::new + with_verifier.
  • ask_multi_hop step 8.5 hook + 두 refuse helpers + truncate_for_nli.
  • SKILL.md NLI 안내 한 단락.

사용자 시점 동작 (★)

본 PR 머지 + [rag] nli_threshold = 0.5 (config.toml 또는 KEBAB_RAG_NLI_THRESHOLD=0.5 env) 설정 시:

  1. kebab ask --multi-hop 호출 시 mDeBERTa-v3 XNLI 모델 (~280 MB) HF 자동 다운로드 (lazy, ~30-60s).
  2. multi-hop synthesize 후 step 8.5 NLI 검증 — entailment >= 0.5 이면 통과 (verification.nli_passed=true), 미만이면 refusal_reason="nli_verification_failed".
  3. NLI 모델 다운로드 실패 시 refusal_reason="nli_model_unavailable" — 사용자 우회: [rag] nli_threshold = 0 임시 disable.

PR-9d 가 dogfood retest 로 실측값 + threshold tuning iteration trigger 결정.

Assisted-by: Claude Code

## 요약 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: - empty answer 가드 (`!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 채널 vs `nli_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_unavailable` helpers — 기존 `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)]` 두 곳 제거** (`verifier` field + `with_verifier` builder) — 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::new` wrap + 후속 `RagPipeline` 초기화 시 `with_verifier` 호출. - 시그니처 `Result<Self>` *unchanged* — `?` 전파만 (caller cascading 0). - `crates/kebab-app/Cargo.toml` 에 `kebab-nli` path 의존 추가. ### Mock tests (5 multi-hop) - `crates/kebab-rag/tests/common/mod.rs` 에 `MockNliVerifier` (pass / fail / err 생성자 + score `call_count` instrumented). - `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_unavailable` refusal 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). ## 비범위 - Dogfood retest (PR-9d). - v0.18.0 cut PR (chore: cut). - `truncate_for_nli` 의 v0.18.1 token-budget 갱신 (현재 char-based). ## 시험 항목 (Test Plan) - [x] cargo test -p kebab-rag --test multi_hop -j 1 — 15/15 (5 신규 NLI pass). - [x] cargo clippy --workspace --all-targets -j 1 -- -D warnings clean (#[allow(dead_code)] 제거 후 cleanup). - [x] cargo test --workspace -j 1 회귀 0 (pre-existing 1 flaky = main baseline 동일). - [x] App::open_with_config 의 NliVerifier construction — config.rag.nli_threshold > 0 시 OnnxNliVerifier::new + Arc::new + with_verifier. - [x] ask_multi_hop step 8.5 hook + 두 refuse helpers + truncate_for_nli. - [x] SKILL.md NLI 안내 한 단락. ## 사용자 시점 동작 (★) 본 PR 머지 + `[rag] nli_threshold = 0.5` (config.toml 또는 `KEBAB_RAG_NLI_THRESHOLD=0.5` env) 설정 시: 1. 첫 `kebab ask --multi-hop` 호출 시 mDeBERTa-v3 XNLI 모델 (~280 MB) HF 자동 다운로드 (lazy, ~30-60s). 2. multi-hop synthesize 후 step 8.5 NLI 검증 — entailment >= 0.5 이면 통과 (`verification.nli_passed=true`), 미만이면 `refusal_reason="nli_verification_failed"`. 3. NLI 모델 다운로드 실패 시 `refusal_reason="nli_model_unavailable"` — 사용자 우회: `[rag] nli_threshold = 0` 임시 disable. PR-9d 가 dogfood retest 로 실측값 + threshold tuning iteration trigger 결정. Assisted-by: Claude Code
altair823 added 1 commit 2026-05-26 00:55:51 +00:00
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.

- crates/kebab-rag/src/pipeline.rs:
  - ask_multi_hop step 8.5 NLI hook (empty answer 가드 + truncate_for_nli + verifier.score + verification field + refusal 분기).
  - refuse_nli_verification helper (verification: Some(...) + RefusalReason::NliVerificationFailed).
  - refuse_nli_model_unavailable helper (verification: None + RefusalReason::NliModelUnavailable).
  - truncate_for_nli helper (module-level pub fn, MAX_NLI_PREMISE_CHARS = 4 * 400 = 1600 chars 의 chars-based budget, _hypothesis 미사용 placeholder — v0.18.1 token-budget 갱신 candidate).
  - PR-9c-1 의 #[allow(dead_code)] 두 곳 제거 (verifier field + with_verifier builder; doc 의 transitional sentence 도 정리). round-1 PR-9c-1 review N1 carry-forward closure.
- crates/kebab-app/src/app.rs:
  - App::open_with_config 의 NliVerifier construction — config.rag.nli_threshold > 0 → OnnxNliVerifier::new + Arc::new wrap + 후속 RagPipeline 초기화 시 with_verifier 호출. 실패 시 ? 전파 (시그니처 Result<Self> 그대로 — caller cascading 0).
  - kebab-app/Cargo.toml 에 kebab-nli path 의존 추가.
- crates/kebab-rag/tests/multi_hop.rs + tests/common/mod.rs:
  - MockNliVerifier (pass / fail / err 생성자 + score call_count instrumented).
  - 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 보전.
- integrations/claude-code/kebab/SKILL.md: mcp__kebab__ask 절에 NLI 안내 한 단락 (verification.nli_passed 의미 + threshold tuning + nli_verification_failed/nli_model_unavailable refusal handling).

검증: cargo test --workspace -j 1 — 5 신규 multi-hop pass + 회귀 0 (pre-existing kebab-mcp::tools_call_ask_multi_hop 동일 flaky). cargo clippy --workspace --all-targets -j 1 -- -D warnings clean.
Wire 영향: PR-9c-1 의 schema 변경에 *behavior wiring* — answer.v1.verification field 가 multi-hop happy path + refuse path 양쪽에서 채움.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 approved these changes 2026-05-26 00:57:24 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — ★ NLI 실 활성화 PR 검토.

칭찬 (산문, inline 안 함):

  1. 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 진단 즉각.

  2. empty answer 가드의 short-circuit 순서 정확nli_threshold > 0.0 && !acc.trim().is_empty()acc.trim().is_empty()RHS 라 threshold disabled 경우 trim 비용도 회피. 좋음.

  3. App::build_pipeline 헬퍼 도입 — App 의 모든 ask path (ask / ask_with_session / 등) 가 같은 helper 거쳐서 with_verifier 일관 적용. PR-9d 의 dogfood retest 시 어느 ask path 든 NLI 검증 동일 동작 보장.

  4. eager pipeline_verifier 구성 결정open_with_config? propagation 이 NLI model construction 실패를 App boot 시점에 user-facing 으로 surface. 매 ask 시점이 아닌 한 번 검증 — config 잘못된 경우 사용자가 즉각 알 수 있음.

  5. truncate_for_nlichars().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.

  6. 두 refuse helper 의 verification field 차별refuse_nli_verificationSome(VerificationSummary) (wire 의 nli_score 측정값 보존), refuse_nli_model_unavailableNone (verification 자체 미수행). spec §2.4 의 semantic accuracy.

  7. PR-9c-1 의 #[allow(dead_code)] 두 곳 cleanup 정확verifier field + with_verifier builder 모두 제거 + doc 의 transitional sentence ("Currently unused — PR-9c-2 wires the read site") 도 정리. round-1 PR-9c-1 review N1 carry-forward closure.

  8. 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 검증이 실 동작.

회차 1 — ★ NLI 실 활성화 PR 검토. 칭찬 (산문, inline 안 함): 1. **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 진단 즉각. 2. **empty answer 가드의 *short-circuit 순서* 정확** — `nli_threshold > 0.0 && !acc.trim().is_empty()` — `acc.trim().is_empty()` 가 *RHS* 라 threshold disabled 경우 trim 비용도 회피. 좋음. 3. **`App::build_pipeline` 헬퍼 도입** — App 의 모든 ask path (`ask` / `ask_with_session` / 등) 가 같은 helper 거쳐서 `with_verifier` 일관 적용. PR-9d 의 dogfood retest 시 *어느 ask path 든* NLI 검증 동일 동작 보장. 4. **eager `pipeline_verifier` 구성 결정** — `open_with_config` 의 `?` propagation 이 NLI model construction 실패를 *App boot* 시점에 user-facing 으로 surface. 매 ask 시점이 아닌 *한 번* 검증 — config 잘못된 경우 사용자가 즉각 알 수 있음. 5. **`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. 6. **두 refuse helper 의 `verification` field 차별** — `refuse_nli_verification` 은 `Some(VerificationSummary)` (wire 의 nli_score 측정값 보존), `refuse_nli_model_unavailable` 은 `None` (verification 자체 미수행). spec §2.4 의 semantic accuracy. 7. **PR-9c-1 의 `#[allow(dead_code)]` 두 곳 cleanup 정확** — `verifier` field + `with_verifier` builder 모두 제거 + doc 의 transitional sentence ("Currently unused — PR-9c-2 wires the read site") 도 정리. round-1 PR-9c-1 review N1 carry-forward closure. 8. **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_verifyverifier.calls() == 0 직접 assert 추가 (verifier=None 일 땐 helper 자체 호출 불가 — MockNliVerifier 가 verifier 로 주입된 케이스만 적용 가능. instead 가 multi_hop_nli_pass_keeps_groundedcalls() == 1 assert 가능).
  • calls() 제거 후 각 test 가 필요할 때 추가 (YAGNI).

v0.18.1 의 stress test (multi-hop 시 step 8.5 중복 호출 방지 회귀 검증 등) 에서 활용 가능 — carry-forward informational. 본 PR 변경 안 함.

**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() == 1` assert 가능). - `calls()` 제거 후 *각 test 가 필요할 때* 추가 (YAGNI). v0.18.1 의 stress test (multi-hop 시 step 8.5 중복 호출 방지 회귀 검증 등) 에서 활용 가능 — *carry-forward informational*. 본 PR 변경 안 함.
altair823 merged commit 772575d8f0 into main 2026-05-26 01:03:20 +00:00
altair823 deleted branch feat/fb-41-pr-9c-2-pipeline-integration 2026-05-26 01:03:21 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#179