diff --git a/Cargo.lock b/Cargo.lock index a0bb5de..5530765 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4127,7 +4127,7 @@ dependencies = [ [[package]] name = "kebab-app" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -4173,7 +4173,7 @@ dependencies = [ [[package]] name = "kebab-chunk" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4190,7 +4190,7 @@ dependencies = [ [[package]] name = "kebab-cli" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "clap", @@ -4211,7 +4211,7 @@ dependencies = [ [[package]] name = "kebab-config" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "dirs 5.0.1", @@ -4226,7 +4226,7 @@ dependencies = [ [[package]] name = "kebab-core" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4240,7 +4240,7 @@ dependencies = [ [[package]] name = "kebab-embed" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4254,7 +4254,7 @@ dependencies = [ [[package]] name = "kebab-embed-local" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "fastembed", @@ -4267,7 +4267,7 @@ dependencies = [ [[package]] name = "kebab-eval" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "kebab-app", @@ -4286,7 +4286,7 @@ dependencies = [ [[package]] name = "kebab-llm" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "kebab-core", @@ -4295,7 +4295,7 @@ dependencies = [ [[package]] name = "kebab-llm-local" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "kebab-config", @@ -4312,7 +4312,7 @@ dependencies = [ [[package]] name = "kebab-mcp" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "kebab-app", @@ -4330,7 +4330,7 @@ dependencies = [ [[package]] name = "kebab-nli" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "hf-hub", @@ -4345,7 +4345,7 @@ dependencies = [ [[package]] name = "kebab-normalize" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "kebab-core", @@ -4360,7 +4360,7 @@ dependencies = [ [[package]] name = "kebab-parse-code" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "gix", @@ -4383,7 +4383,7 @@ dependencies = [ [[package]] name = "kebab-parse-image" -version = "0.17.2" +version = "0.18.0" dependencies = [ "ab_glyph", "anyhow", @@ -4407,7 +4407,7 @@ dependencies = [ [[package]] name = "kebab-parse-md" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "kebab-core", @@ -4424,7 +4424,7 @@ dependencies = [ [[package]] name = "kebab-parse-pdf" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4437,7 +4437,7 @@ dependencies = [ [[package]] name = "kebab-parse-types" -version = "0.17.2" +version = "0.18.0" dependencies = [ "kebab-core", "serde", @@ -4445,7 +4445,7 @@ dependencies = [ [[package]] name = "kebab-rag" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4467,7 +4467,7 @@ dependencies = [ [[package]] name = "kebab-search" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "globset", @@ -4486,7 +4486,7 @@ dependencies = [ [[package]] name = "kebab-source-fs" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4505,7 +4505,7 @@ dependencies = [ [[package]] name = "kebab-store-sqlite" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "blake3", @@ -4526,7 +4526,7 @@ dependencies = [ [[package]] name = "kebab-store-vector" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "arrow", @@ -4550,7 +4550,7 @@ dependencies = [ [[package]] name = "kebab-tui" -version = "0.17.2" +version = "0.18.0" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 6542cca..e1e7c83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ edition = "2024" rust-version = "1.85" license = "MIT OR Apache-2.0" repository = "https://github.com/altair823/kebab" -version = "0.17.2" +version = "0.18.0" # pre-v0.18 workspace-wide cleanup: enable clippy::pedantic group with # intentional allow-list. The allowed lints are either cosmetic (doc style), diff --git a/HANDOFF.md b/HANDOFF.md index b47bbd8..0e01339 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -4,7 +4,7 @@ ## 한 줄 요약 -P0–P5 + P6 + P7 + P9-1/2/3/4 (Library / Search / Ask / Inspect) + P10 전체 머지 완료 (현재 **v0.17.2**). `kebab ingest` 가 markdown / image / PDF / 소스코드 (Rust / Python / TS / JS / Go / Java / Kotlin / C / C++) / Tier 2 리소스 파일 (yaml/k8s / dockerfile / toml / json / xml / groovy / go-mod) + Tier 3 paragraph fallback (shell / 비-k8s YAML / AST 실패 케이스) 처리. `kebab search` / `kebab ask` 가 매체 가로질러 결과 + page / code citation 반환. `kebab tui` 가 4 패널 (Library + Search + Ask + Inspect) 제공. **v0.17.0 cut (2026-05-24)**: 한국어 trigram FTS5 tokenizer (PR #159) + C typedef alias unit (PR #160) + `code_lang_chunk_breakdown` additive (PR #161). **v0.17.1 cut (2026-05-25)**: 확장 도그푸딩 후 `[models.llm] request_timeout_secs` config 노브 (PR #162) + sudo 없이 ollama 설치 + `kebab ask --stream` UX 권장 docs (PR #163). **v0.17.2 cut (2026-05-25)**: v0.17.1 post-dogfood polish — `[image.ocr] request_timeout_secs` 별 노브 (PR #164, v0.17.1 미진행 closure) + `heading_path` FTS5 column filter 로 text-only 매칭 + raw-mode escape hatch (PR #165, 2026-05-24 v0.17.0 trigram entry 의 JSON 노이즈 closure). 자세한 영향은 [v0.17.0 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.17.0) + [v0.17.1 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.17.1) + [v0.17.2 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.17.2). 구조적으로 남은 component 는 P9-5 (desktop tauri) 하나뿐, P8 (audio) 는 사용자 보류. +P0–P5 + P6 + P7 + P9-1/2/3/4 (Library / Search / Ask / Inspect) + P10 전체 머지 완료 (현재 **v0.18.0**). `kebab ingest` 가 markdown / image / PDF / 소스코드 (Rust / Python / TS / JS / Go / Java / Kotlin / C / C++) / Tier 2 리소스 파일 (yaml/k8s / dockerfile / toml / json / xml / groovy / go-mod) + Tier 3 paragraph fallback (shell / 비-k8s YAML / AST 실패 케이스) 처리. `kebab search` / `kebab ask` 가 매체 가로질러 결과 + page / code citation 반환. `kebab tui` 가 4 패널 (Library + Search + Ask + Inspect) 제공. **v0.17.0 cut (2026-05-24)**: 한국어 trigram FTS5 tokenizer (PR #159) + C typedef alias unit (PR #160) + `code_lang_chunk_breakdown` additive (PR #161). **v0.17.1 cut (2026-05-25)**: 확장 도그푸딩 후 `[models.llm] request_timeout_secs` config 노브 (PR #162) + sudo 없이 ollama 설치 + `kebab ask --stream` UX 권장 docs (PR #163). **v0.17.2 cut (2026-05-25)**: v0.17.1 post-dogfood polish — `[image.ocr] request_timeout_secs` 별 노브 (PR #164, v0.17.1 미진행 closure) + `heading_path` FTS5 column filter 로 text-only 매칭 + raw-mode escape hatch (PR #165, 2026-05-24 v0.17.0 trigram entry 의 JSON 노이즈 closure). **v0.18.0 cut (2026-05-26)**: fb-41 multi-hop RAG + NLI verification ship (PR #176-180) — `kebab ask --multi-hop` 의 decompose → decide → synthesize loop + mDeBERTa-v3 XNLI ONNX post-synthesize entailment 검사. dogfood S7 caffeine hallucination 의 silent LLM-self-judge ceiling 해결 (nli_score 0.0035 graceful refuse). 추가 `chore: workspace-wide cleanup + post-PR9 refactor` (PR #181) — clippy::pedantic baseline + H1 config wiring + 9 new tests. 자세한 영향은 [v0.17.0 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.17.0) + [v0.17.1 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.17.1) + [v0.17.2 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.17.2) + [v0.18.0 release notes](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.18.0). 구조적으로 남은 component 는 P9-5 (desktop tauri) 하나뿐, P8 (audio) 는 사용자 보류. ## Phase 로드맵 @@ -26,12 +26,13 @@ P0~P5 직렬. P6~P9 P5 이후 병렬 가능. ## Component 카운트 -총 33 component task — spec 시점 31 개 + 후속 wiring task 3 (P3-5 / P6-4 / P7-3) 가 머지 시점에 추가됨. per-component 진행 + status 는 [tasks/INDEX.md](tasks/INDEX.md). +총 33 component task — spec 시점 31 개 + 후속 wiring task 3 (P3-5 / P6-4 / P7-3) 가 머지 시점에 추가됨. v0.18.0 cut 시점에 fb-41 multi-hop RAG + NLI verification (PR-9 5 sub-PRs) 가 P9 추가 component 로 ship — `kebab-nli` 신규 crate (mDeBERTa-v3 XNLI ONNX verifier) + `kebab-rag::ask_multi_hop` (decompose/decide/synthesize loop + step 8.5 NLI hook). per-component 진행 + status 는 [tasks/INDEX.md](tasks/INDEX.md). ## 머지 후 발견된 버그 / 결정 (요약) 머지 후 발견된 모든 deviation / hotfix 의 dated 로그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md). 본 요약은 \"누군가가 인수받을 때 알아두면 시간을 많이 절약하는\" 항목만: +- **2026-05-26 v0.18.0 fb-41 multi-hop RAG + NLI verification ship (PR #176-180) + post-PR9 cleanup (PR #181)** — pre-v0.18.0 dogfood (`/build/cache/dogfood-v018/`, 33 assets / 205 chunks, gemma3:4b CPU only / 16 GB RAM) 에서 발견된 S7 caffeine hallucination 의 root cause = LLM-self-judge ceiling (synthesize 가 chunks 와 무관한 Adam optimizer gradient 식을 silent emit, self-judge 가 reject 못함). 학계 표준 (Self-RAG, CRAG, Auto-GDA, MedTrust-RAG) 결론 = deterministic post-synthesis verification. mDeBERTa-v3 XNLI ONNX (280 MB, Xenova HF) 가 `(packed_chunks, answer)` entailment 검사 — `[rag] nli_threshold > 0` (default 0.0 = disabled, production 권장 0.5) 일 때 활성. dogfood retest 측정 — S7 PR-8 baseline `grounded=true + Adam hallucination` → PR-9 `nli_verification_failed, nli_score 0.0035`. wire additive minor — `answer.v1.verification` field + `refusal_reason` 의 `nli_verification_failed` / `nli_model_unavailable` 추가, pre-v0.18 reader 무영향. 5 sub-PR 시퀀스 + cleanup PR (clippy::pedantic baseline + 의도적 30+ allow + H1 `[models.nli].model` config wiring + 9 new tests). post-refactor retest = PR-9d byte-identical (deterministic 확인). 자세한 내용: `tasks/HOTFIXES.md` (2026-05-25 fb-41 PR-9 closure entry + S3 follow-up). - **2026-05-25 v0.17.2 post-v0.17.1 polish (PR #164 + #165)** — v0.17.1 의 두 follow-up closure. (1) `[image.ocr] request_timeout_secs` 별 노브 — `crates/kebab-parse-image/src/ocr.rs::REQUEST_TIMEOUT` hard 300s 제거, LLM 쪽 패턴 (PR #162) 을 OCR 어댑터에 동일 적용. 사용자 결정으로 별 노브 분리 (OCR vs LLM 의 cold start 패턴이 달라 독립 조절). v0.17.1 미진행 항목 closure. (2) `chunks_fts` 의 `heading_path` 컬럼이 JSON 표기 + path 세그먼트 까지 trigram 색인 → query false positive 가능 문제 closure. `lexical.rs::build_match_string` 가 non-raw 분기 결과를 `text : ()` 로 wrap — heading 색인 V007 verbatim 유지, 매칭만 text 한정. 사용자가 명시 heading 검색 하려면 raw mode `'heading_path : '` escape hatch (SKILL.md 갱신). 둘 다 additive (옛 config 호환) / re-ingest 불필요. 자세한 내용: `tasks/HOTFIXES.md` (2026-05-25 v0.17.2 두 entry). - **2026-05-25 v0.17.1 post-dogfood (PR #162 + #163)** — 확장 도그푸딩 (16 GB CPU only, gemma4:e4b 시도) 에서 발견된 두 follow-up 한 묶음. (1) `crates/kebab-llm-local/src/ollama.rs::REQUEST_TIMEOUT` hard 300s → `[models.llm] request_timeout_secs` config + env override (additive, default 300, `=0` 은 disable 아닌 "즉시 timeout" 이라 doc 명시). (2) README + SMOKE 에 sudo / systemd 없이 ollama 설치 + ≤4B Q4 권장 모델 + `kebab ask --stream` UX 권장 docs. additive only — 옛 config / wire 호환. 자세한 내용: `tasks/HOTFIXES.md` (2026-05-25). - **2026-05-24 v0.17.0 PR-C `code_lang_chunk_breakdown` additive (closure of 2026-05-22 LOW)** — `schema.v1.stats` 에 chunk 수 집계 신규 키. 기존 `code_lang_breakdown` (doc count) 와 sister. 또 기존 두 필드 JSON schema description 의 "chunk count" 오기재 → "doc count" 로 정정. wire additive — schema_version bump 불필요. 자세한 내용: `tasks/HOTFIXES.md` (2026-05-24 PR-C). diff --git a/README.md b/README.md index 5c709e9..f431b2c 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ kebab doctor | `kebab list docs` | 색인된 문서 목록 | | `kebab inspect doc ` / `kebab inspect chunk ` | raw record 보기 | | `kebab fetch chunk [--context N]` / `kebab fetch doc [--max-tokens N]` / `kebab fetch span [--max-tokens N]` | (p9-fb-35) verbatim text fetch from indexed corpus. wire = `fetch_result.v1` (kind discriminator). chunk: target + ±N ordinal-context chunks. doc: full normalized markdown. span: 1-based line range (PDF/audio rejected as `error.v1.code = span_not_supported`). chars/4 budget on doc/span. | -| `kebab ask "" [--show-citations / --hide-citations] [--session ] [--stream]` | RAG 답변 + 근거 인용. 답변 후 `근거:` block 으로 full path / line range / score 한 줄씩 (default ON — `--hide-citations` 로 끄기, pipe 시 유용). 근거 부족 시 거절. Ollama 필요. `--session ` 로 multi-turn — 첫 호출에서 SQLite `chat_sessions` 에 자동 생성, 이후 호출은 prior turns 를 history 로 받아 follow-up. session id 는 사용자 지정 (e.g. `kb-rust-async-2026-05`) — `kebab reset --data-only` 로 모든 session wipe. **`--stream` (p9-fb-33)** 로 ndjson `answer_event.v1` event (retrieval_done → token* → final) 를 stderr 에 흘리고 stdout 마지막 줄에 기존 `answer.v1` — agent 가 token 즉시 소비 가능 | +| `kebab ask "" [--show-citations / --hide-citations] [--session ] [--stream] [--multi-hop]` | RAG 답변 + 근거 인용. 답변 후 `근거:` block 으로 full path / line range / score 한 줄씩 (default ON — `--hide-citations` 로 끄기, pipe 시 유용). 근거 부족 시 거절. Ollama 필요. `--session ` 로 multi-turn — 첫 호출에서 SQLite `chat_sessions` 에 자동 생성, 이후 호출은 prior turns 를 history 로 받아 follow-up. session id 는 사용자 지정 (e.g. `kb-rust-async-2026-05`) — `kebab reset --data-only` 로 모든 session wipe. **`--stream` (p9-fb-33)** 로 ndjson `answer_event.v1` event (retrieval_done → token* → final) 를 stderr 에 흘리고 stdout 마지막 줄에 기존 `answer.v1` — agent 가 token 즉시 소비 가능. **`--multi-hop` (v0.18.0 fb-41)** — single-pass 대신 decompose → decide → synthesize 의 N-hop loop. compound 질문 (cross-doc / prereq chain) 에 효과적. 최종 답변 후 mDeBERTa-v3 XNLI 가 `(packed_chunks, generated_answer)` entailment 검사 — `[rag] nli_threshold > 0` (default 0.0 = disabled, production 권장 0.5) 일 때 활성. entailment < threshold → `refusal_reason = "nli_verification_failed"` (LLM-self-judge ceiling 극복, S7 caffeine hallucination 같은 케이스 catch). 첫 호출 시 ~280 MB ONNX model 자동 다운로드 + RAM peak ~7-8 GB (gemma3:4b 기준). model unavailable 시 `refusal_reason = "nli_model_unavailable"`, 우회는 `[rag] nli_threshold = 0` 임시 disable. | | `kebab doctor` | 설정/모델/DB 헬스 체크 | | `kebab tui` | Ratatui 셸 (Library + Search + Ask + Inspect 패널, desktop 진행 중). Library 에서 `r` 키로 background ingest 시작 — 화면 하단 status bar 가 진행 표시, 완료/abort 시 final 라인 잠시 유지 후 자동 hide. ingest 진행 중 `Esc` / `Ctrl-C` 가 cancel signal (그 외에는 quit). vim-style mode (header 우측 `-- NORMAL --` / `-- INSERT --`) — Library/Inspect 는 자동 NORMAL, Search/Ask 는 자동 INSERT. `i` 로 Normal→Insert (모든 pane — p9-fb-21), `Esc` 로 Insert→Normal 어디서나. mode-authoritative dispatch — Search 의 `j/k/o/g`, Ask 의 `e/j/k` 는 NORMAL 모드에서만 명령으로 동작, INSERT 에서는 입력 문자로 typing. (Search 의 chunk inspect 키는 `i`→`o` 로 rebind — `i` 가 universal Insert toggle.) **`F1` 로 cheatsheet popup** (현재 pane 의 키 매핑 + global 토글 표) — `Esc` / `F1` 로 닫기. Search 패널은 200ms debounce 후 background worker 가 검색 — 키 입력으로 UI freeze 안 됨, 사용자가 계속 타이핑하면 stale 결과 자동 폐기 (generation counter). Ask 패널은 multi-turn — 같은 conversation 안에서 Q1/A1, Q2/A2 transcript 누적, 다음 질문이 이전 턴을 history 로 받아 답변. 답변 본문은 markdown 렌더 (bold/italic/inline code/heading/list/code fence/table/blockquote, raw `**bold**` 가 실제 굵게 표시). `Ctrl-L` 로 새 conversation 시작. Search 의 `g` 키가 `$EDITOR` (기본 `vi`) 로 hit 의 citation 위치 열기 — 종료 후 TUI 화면이 자동으로 깨끗이 redraw. CLI `kebab ask` 는 raw markdown 그대로 (terminal 호환성 위해). Library 의 doc-list 가 한글 / 일본어 / 중국어 (CJK) 제목을 wide-char 정확한 column width 로 truncate — 한글 제목이 한 줄을 넘기지 않음 (CJK 1 자 = 2 col). Search/Ask/Filter 입력의 cursor 가 wide char 위에서 column 단위로 정렬 — 한글 입력 시 caret 이 글자 옆에 정확히 놓임. `← / →` 로 입력 문자열 중간 cursor 이동 (한글 한 글자 = 2 column 이라도 한 번에 이동), `Home / End` 로 양 끝 점프, `Delete` 로 cursor 위치 char 삭제 — 모든 input pane (Ask / Search / Library filter overlay) 동일 (p9-fb-22). Ask 트랜스크립트는 새 답변이 viewport 아래로 누적될 때 자동으로 tail 을 따라감 (auto-scroll); `j` / `k` 로 위로 스크롤하면 freeze, `Shift-G` 로 다시 bottom + auto-tail 재개. 화면 하단 hint line 은 한국어 동사구로 (`"위로"` / `"아래로"` / `"필터"` / `"타이핑 검색어"` / `"Esc 로 NORMAL 모드"` / `"i 입력모드"` 등) + 현재 (pane, mode) 조합에 맞춰 자동 분기, **첫 fragment 가 항상 `F1 도움말`** (cheatsheet 발견성 보장). 모든 모드에서 항상 떠 있는 상태바 — `kebab v docs │ ` (state: streaming/searching/indexing/idle, ingest 진행 중에는 progress 가 같은 자리에 흡수됨). Ask 진입 시 conversation id 8 자 prefix 도 함께 표시. Ask 트랜스크립트와 Inspect 양쪽에서 `PgUp / PgDn` 으로 10 줄씩 페이지 스크롤. Library 의 doc list 위에는 `TITLE / TAGS / UPDATED / CHUNKS` 컬럼 헤더 행 표시 (display-width 정렬, Hangul / CJK 안전). | | `kebab reset [--all / --data-only / --vector-only / --config-only] [--yes]` | XDG 데이터 wipe. **Irreversible.** TTY 면 confirm prompt, 아니면 `--yes` 필수. `--vector-only` 는 SQLite `embedding_records` 도 함께 truncate (orphan 방지) | diff --git a/docs/SMOKE.md b/docs/SMOKE.md index 72866a8..55cbc3b 100644 --- a/docs/SMOKE.md +++ b/docs/SMOKE.md @@ -134,6 +134,12 @@ prompt_template_version = "rag-v1" score_gate = 0.05 # RRF 정규화 후 [0, 1] 범위라 default 그대로 OK explain_default = false max_context_tokens = 6000 +# v0.18.0 fb-41 multi-hop NLI gate (default 0.0 = disabled). +# `kebab ask --multi-hop` 사용 시 0.5 권장 — entailment < 0.5 면 refuse. +# 첫 호출 시 mDeBERTa-v3 XNLI ONNX 모델 자동 다운로드 (~280 MB, ~30-60s), +# RAM peak ~7-8 GB (gemma3:4b 기준, 16 GB 환경 안전). model 실패 시 +# `refusal_reason = "nli_model_unavailable"` — `nli_threshold = 0` 으로 disable. +nli_threshold = 0.0 [ui] theme = "dark" # p9-fb-14 — TUI palette ("dark" / "light", default "dark") diff --git a/docs/superpowers/plans/2026-05-25-p9-fb-41-finalize-plan.md b/docs/superpowers/plans/2026-05-25-p9-fb-41-finalize-plan.md index 79be574..1851944 100644 --- a/docs/superpowers/plans/2026-05-25-p9-fb-41-finalize-plan.md +++ b/docs/superpowers/plans/2026-05-25-p9-fb-41-finalize-plan.md @@ -3,11 +3,11 @@ title: "p9-fb-41 finalize implementation plan v4 — NLI verification + v0.18.0 date: 2026-05-25 task_id: p9-fb-41-finalize phase: P9 -status: approved-by-team +status: completed target_version: 0.18.0 design: ../specs/2026-05-25-p9-fb-41-finalize-spec.md spec_review_round: 5 -spec_status: approved-by-team +spec_status: completed plan_review_round: 3 plan_review_outcome: | All 4 OMC team reviewers APPROVE (plan v4 round 3, FINAL convergence). diff --git a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md index c0c33f9..35aea1c 100644 --- a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md +++ b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md @@ -803,6 +803,19 @@ pub enum RefusalReason { /// answer 가 채워져 있을 수 있음 (사용자가 본 부분까지). RAG /// retrieval 자체는 정상 — 모델 generation 단계에서만 중단. LlmStreamAborted, + /// p9-fb-22: multi-hop ask 의 decompose 단계 실패 (LLM 가 sub-query + /// 추출 불가 — JSON parse fail / 0 sub-query / 시간 초과 등). retrieval + /// 단계 도달 전에 graceful refuse. + MultiHopDecomposeFailed, + /// p9-fb-41 PR-9c-1: NLI groundedness gate 가 reject. `cfg.rag.nli_threshold > 0` + /// 일 때 multi-hop synthesize 직후 mDeBERTa-v3 XNLI 가 (packed_chunks, answer) + /// entailment 검사 → entailment < threshold 면 본 variant 로 refuse + Answer + /// 의 `verification` field 가 measured score 보존. single-pass `ask` 는 적용 + /// 안 함 (LLM self-judge 가 single-pass 의 verification path). + NliVerificationFailed, + /// p9-fb-41 PR-9c-1: NLI model 자체가 unavailable (download / inference 실패). + /// fail-closed — 사용자 우회는 `[rag] nli_threshold = 0` 임시 disable. + NliModelUnavailable, } pub struct ModelRef { @@ -865,6 +878,31 @@ prompt 빌드 priority (token budget = `cfg.rag.max_context_tokens`): V1 은 legacy backwards-compat 으로 보존 — user TOML 에 `prompt_template_version = "rag-v1"` 명시 시 그대로. +**Multi-hop RAG + NLI verification** (도그푸딩 후 추가 — 2026-05-26, fb-41 v0.18.0 ship): + +`kebab-rag` facade 의 세 번째 entry — `ask_multi_hop(cfg, question, ...)`: + +- compound 질문 (cross-doc reasoning, prereq chain) 의 N-hop loop. **decompose → decide → synthesize** 의 3 단계: + 1. **decompose**: 원 질문을 5 sub-query 까지 분해 (LLM JSON 응답). 실패 시 `RefusalReason::MultiHopDecomposeFailed`. + 2. **decide**: pool 의 chunks (probe gate 통과한 candidates) 가 답변에 충분한지 결정. forced_stop 또는 `kind: "stop"` 이면 synthesize 진입. 그 외엔 추가 sub-query 로 N-hop 확장 (max_depth 제한, default 3). + 3. **synthesize**: 누적 chunks 로 최종 답변 생성. `rag-multi-hop-v1` prompt template — self-check rule 포함. +- **step 8.5 NLI verification** (★ v0.18.0 신규): `cfg.rag.nli_threshold > 0` (default 0.0 = disabled, production 권장 0.5) 일 때 synthesize 답변에 대해 mDeBERTa-v3 XNLI ONNX 가 `(packed_chunks, answer)` entailment 검사. entailment < threshold → `RefusalReason::NliVerificationFailed` (Answer 의 `verification` field 가 `nli_score / nli_threshold / nli_passed` 보존). model unavailable 시 `NliModelUnavailable`. +- LLM-self-judge 의 *probabilistic ceiling* 을 NLI 의 *deterministic external verifier* 가 극복 — dogfood S7 caffeine hallucination 같은 silent fail 케이스 catch. spec: `docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md`. + +`HopRecord` (`Answer.hops: Option>` field — multi-hop only) 가 매 hop 의 `kind / iter / sub_queries / context_chunks_added / llm_call_ms / forced_stop` 를 보존 — agent 가 trace 분석 가능. + +`VerificationSummary` (`Answer.verification: Option` field — multi-hop NLI gate 통과 또는 NliVerificationFailed refusal 시 stamped): + +```rust +pub struct VerificationSummary { + pub nli_score: f32, // measured entailment channel + pub nli_threshold: f32, // gate threshold (cfg.rag.nli_threshold) + pub nli_passed: bool, // nli_score >= nli_threshold +} +``` + +wire `answer.v1` 의 `hops` / `verification` 둘 다 additive minor (skip_serializing_if = None) — pre-v0.18 reader 무영향. + --- ## 4. ID 생성 recipe @@ -1467,6 +1505,7 @@ kebab-cli, kebab-tui, kebab-desktop | `index_version` | retrieval 형상 변화 | bump | | `corpus_revision` | ingest commit 발생 (ANY new/updated) | 모노토닉 u64, SQLite `kv['corpus_revision']` 에 영속. p9-fb-19 의 in-process LRU search cache 가 cache-key 에 snapshot 으로 포함 → 다음 lookup 에서 자동 무효화. | | `prompt_template_version` | template 변경 | 코드 상수 (`rag-v2`) | +| `nli_model_version` | NLI 모델 교체 (fb-41 v0.18.0+) | `[models.nli].model` 의 HuggingFace repo id (예: `Xenova/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7`). 모델 교체 = cache_dir 다른 sanitized path. wire 미surface — v0.19+ 의 second adapter 도입 시 `answer.v1.verification` 에 `nli_model_version` field 추가 candidate. | | DB `schema_version` | DDL 변경 | 마이그레이션 정수 증가 | | wire schema (`*.v1`) | 깨는 변경 시 | `*.v2` 신설, v1 additive only | | internal Rust struct | 자유 진화 | wire 분리되어 외부 영향 0 | diff --git a/docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md b/docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md index a8dda60..69f87d9 100644 --- a/docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md +++ b/docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md @@ -3,7 +3,7 @@ title: "p9-fb-41 finalize — multi-hop RAG post-dogfood safety hardening + v0.1 date: 2026-05-25 task_id: p9-fb-41-finalize phase: P9 -status: approved-by-team +status: completed target_version: 0.18.0 contract_source: ./2026-04-27-kebab-final-form-design.md contract_sections: [§3.8 RAG, §7 RAG pipeline] diff --git a/tasks/INDEX.md b/tasks/INDEX.md index f84e3ab..b98ef46 100644 --- a/tasks/INDEX.md +++ b/tasks/INDEX.md @@ -135,7 +135,7 @@ P0~P5 는 직렬. P6~P9 는 P5 이후 병렬 가능. - [p9-fb-40 fact-grounded answer](p9/p9-fb-40-fact-grounded-answer.md) — ✅ 머지 (2026-05-10) ### 🎯 0.6.0 또는 P+ — reasoning - - [p9-fb-41 multi-hop reasoning](p9/p9-fb-41-multi-hop-reasoning.md) — ⏳ 미구현, brainstorm 필요 (XL, eval 인프라 선행) + - [p9-fb-41 multi-hop reasoning](p9/p9-fb-41-multi-hop-reasoning.md) — ✅ 머지 (v0.18.0, 2026-05-26). 5 sub-PR (PR #176-180) + NLI verification (mDeBERTa-v3 XNLI ONNX). spec: `docs/superpowers/specs/2026-05-25-p9-fb-41-finalize-spec.md`. plan: `docs/superpowers/plans/2026-05-25-p9-fb-41-finalize-plan.md`. - [p9-fb-42 bulk multi-query + re-rank hint](p9/p9-fb-42-bulk-multi-query-rerank.md) — ✅ 머지 (2026-05-10) — bulk only, rerank hint deferred - P10 — [p10/](p10/) — code ingest (multi-task, sub-indexed in [p10/INDEX.md](p10/INDEX.md)) @@ -160,6 +160,14 @@ P0~P5 는 직렬. P6~P9 는 P5 이후 병렬 가능. - **PR #162 `[models.llm] request_timeout_secs` config + 권장 모델 가이드** — ✅ 머지 (2026-05-25). 8B+ 모델 CPU 추론 시 5분 hard timeout 회피용 노브. additive serde default + env override + 0-edge doc. README + SMOKE 에 CPU only / ≤16GB RAM ⇒ ≤4B Q4 모델 권장 한 단락. - **PR #163 sudo 없이 ollama 설치 + ask --stream 권장 (docs only)** — ✅ 머지 (2026-05-25). README + SMOKE 에 tarball + OLLAMA_MODELS env 설치 패턴 + cold start 긴 모델은 progressive 토큰 권고 (p9-fb-33 surface). + **v0.18.0 fb-41 multi-hop RAG + NLI verification ship** (release: [v0.18.0](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.18.0)): + - **PR #176 PR-9a kebab-nli crate skeleton** — ✅ 머지 (2026-05-25). `NliVerifier` trait + `NliScores` struct (XNLI 3-channel: entailment / neutral / contradiction) + `OnnxNliVerifier` placeholder. workspace.dependencies 에 ort 2.0-rc.9, tokenizers 0.21 (default-features=false, onig), hf-hub 0.4, ndarray 0.16. + - **PR #177 PR-9b OnnxNliVerifier ONNX inference + model download** — ✅ 머지 (2026-05-25). hf-hub lazy download (XDG `model_dir/nli/`) + ort `Session::commit_from_file` + tokenizers `OnlyFirst` truncation (max_length=512, premise 끝부터 잘림 — hypothesis 보전). `--ignored` integration test 5 cases manual smoke (EN self-entailment / EN unrelated / KR entailment / long premise truncation / empty hypothesis err). + - **PR #178 PR-9c-1 core types + wire scaffolding** — ✅ 머지 (2026-05-26). `RefusalReason::NliVerificationFailed` + `NliModelUnavailable` (serde rename_all snake_case, wire = identical strings). `Answer.verification: Option` additive minor wire. `NliCfg` + `RagCfg.nli_threshold` (default 0.0) + env override. `RagPipeline.verifier` field + `with_verifier` builder. wire schemas + `docs/ARCHITECTURE.md` Mermaid 갱신. + - **PR #179 PR-9c-2 pipeline integration + mock test + SKILL.md** — ✅ 머지 (2026-05-26). ★ 첫 user-visible behavior. `ask_multi_hop` step 8.5 NLI hook (empty answer 가드 + `truncate_for_nli` + verifier.score + verification field + refusal 분기) + `App::open_with_config` 의 NliVerifier construction + 5 mock multi-hop tests + SKILL.md NLI 안내 한 단락. + - **PR #180 PR-9d dogfood retest + HOTFIXES closure + corpus 보존** — ✅ 머지 (2026-05-26). 동일 dogfood corpus 의 S7/S1/S3/S10 multi-hop retest — S7 PR-8 baseline `grounded=true + Adam hallucination` → PR-9 `nli_verification_failed, nli_score 0.0035` (HALLUCINATION FIXED 확정). `docs/dogfood/v0.18.0/` 신규 — sanitized SUMMARY + 4 sample wire JSON 보존. + - **PR #181 chore: workspace-wide cleanup + post-PR9 refactor** — ✅ 머지 (2026-05-26). v0.18.0 cut 전 마지막 정리. `[workspace.lints.clippy] pedantic = warn` + 의도적 30+ allow (각 rationale inline). 128 files mechanical clippy --fix. OMC team `post-pr9-refactor` 가 추가 H1 (`[models.nli].model` config wiring — `DEFAULT_MODEL_ID` 제거 + provider 분기) + H2 (`truncate_for_nli` stub `_hypothesis` 제거) + H3 (`was_truncated` tracing::debug! surface) + D (MCP test flake fix) + E (HOTFIXES cross-link) + 9 new tests (T1-T4). post-refactor dogfood = PR-9d byte-identical (deterministic 확인). system-architect 의 component-level review 결론 = pre-cut nothing, all v0.18.1+ defer (kebab-normalize 흡수, Extractor dispatch unification, kebab-source-fs dep lightening 등). + ## Post-merge 핫픽스 머지 후 발견된 버그들과 그 follow-up PR들은 [HOTFIXES.md](HOTFIXES.md)에 dated 로그로 기록한다. 원래 task spec은 frozen 상태로 두고, post-merge 동작 변경은 HOTFIXES.md를 source of truth로 본다.