feat(rag): fb-41 PR-2 ask_multi_hop skeleton (fixed depth=2) #167

Merged
altair823 merged 2 commits from feat/fb-41-pr-2-ask-multi-hop-skeleton into main 2026-05-25 06:50:05 +00:00
Owner

요약

fb-41 multi-hop RAG 의 PR-2. RagPipeline::ask_multi_hop skeleton — decompose LLM call → 각 sub-query retrieve + chunk_id dedup pool → synthesize LLM call. Dynamic decide loop (LLM 의 "추가 필요?" signal + max_depth cap) 는 PR-3 에서.

설계: docs/superpowers/specs/2026-05-25-p9-fb-41-multi-hop-rag-design.md
계획: docs/superpowers/plans/2026-05-25-p9-fb-41-multi-hop-rag.md (PR-2 단락)

변경

  • AskOpts.multi_hop: bool 신규 + impl Default for AskOpts — HOTFIXES 2026-05-07 의 known limitation (모든 caller 가 9 field 명시 init) 해소. 9 explicit init site 갱신 (kebab-rag tests 3 + kebab-cli 2 + kebab-mcp + kebab-tui + kebab-eval + kebab-app/tests).
  • RagPipeline::ask entry 에 dispatcher 한 줄. opts.multi_hop=trueask_multi_hop route.
  • RagPipeline::ask_multi_hop skeleton — decompose + retrieve + synthesize 3 stage. PR-2 는 fixed depth=2 (decide loop 없음). prompt_template_version = "rag-multi-hop-v1" stamp.
  • RefusalReason::MultiHopDecomposeFailed variant 신규. Cascade: kebab-store-sqlite::refusal_reason_label + kebab-tui::ask refusal render exhaustive match 갱신.
  • 5 prompt / version const 신규: MULTI_HOP_DECOMPOSE_SYSTEM_PROMPT + MULTI_HOP_DECOMPOSE_USER_TEMPLATE + MULTI_HOP_SYNTHESIZE_SYSTEM_PROMPT + PROMPT_TEMPLATE_VERSION_MULTI_HOP + MULTI_HOP_MAX_SUB_QUERIES_DEFAULT = 5.
  • parse_decompose_response + strip_markdown_json_fence helper — markdown code fence (``` json) strip + JSON array of strings parse + trim + drop empty + cap.

회귀 핀 (55 test 통과, 8 신규)

  • 6 unit (parse_decompose_response_*): bare array / json fence / bare fence / garbage / cap / trim.
  • 2 integration:
    • ask_multi_hop_dispatches_and_decompose_garbage_refusesmulti_hop=true 시 garbage decompose 응답이 RefusalReason::MultiHopDecomposeFailed + 정확히 1 LLM call (synthesize 까지 진입 안 함) + prompt_template_version = rag-multi-hop-v1.
    • ask_with_multi_hop_false_keeps_single_pass_path — 회귀 핀, 기존 single-pass caller 자동 backwards-compat. Default route + config 의 prompt_template_version 그대로 stamp.

의도된 비범위 (PR-3+ 책임)

  • Dynamic iter (LLM 의 continue / stop signal + max_depth / max_sub_queries_per_iter / max_pool_chunks cap) — PR-3.
  • Answer.hops wire field 노출 — PR-3 가 trace 채우기 시작.
  • Happy-path multi-hop (decompose 성공 + synthesize) integration test — ScriptedLm helper (per-call 다른 response 반환) 가 PR-3 와 함께 도입.
  • CLI --multi-hop flag — PR-4.
  • MCP multi_hop argument + SKILL.md — PR-5.
  • TUI Ask Multi-hop toggle — PR-6.

검증

  • cargo test -p kebab-rag -j 1 — 55 test 모두 녹색 (30 unit + 19 pipeline + 3 prompt + 3 streaming + 0 doc).
  • cargo clippy -p kebab-rag -p kebab-core -p kebab-store-sqlite -p kebab-tui -p kebab-app --all-targets -j 1 -- -D warnings — clean.
  • 16 GB RAM 직렬 build 규약 준수 (-j 1).

시험 항목 (Test Plan)

  • parse_decompose_response_parses_bare_json_array
  • parse_decompose_response_strips_markdown_json_fence
  • parse_decompose_response_strips_bare_markdown_fence
  • parse_decompose_response_returns_none_for_garbage
  • parse_decompose_response_caps_at_max_sub_queries
  • parse_decompose_response_trims_each_entry
  • ask_multi_hop_dispatches_and_decompose_garbage_refuses
  • ask_with_multi_hop_false_keeps_single_pass_path

다음 단계

PR-3 — dynamic decide loop + Answer.hops wire additive + max_depth 등 cfg 노브. ScriptedLm helper 도입으로 happy-path integration test.

Assisted-by: Claude Code

## 요약 fb-41 multi-hop RAG 의 PR-2. `RagPipeline::ask_multi_hop` skeleton — decompose LLM call → 각 sub-query retrieve + chunk_id dedup pool → synthesize LLM call. Dynamic decide loop (LLM 의 "추가 필요?" signal + max_depth cap) 는 PR-3 에서. 설계: docs/superpowers/specs/2026-05-25-p9-fb-41-multi-hop-rag-design.md 계획: docs/superpowers/plans/2026-05-25-p9-fb-41-multi-hop-rag.md (PR-2 단락) ## 변경 - `AskOpts.multi_hop: bool` 신규 + `impl Default for AskOpts` — HOTFIXES 2026-05-07 의 known limitation (모든 caller 가 9 field 명시 init) 해소. 9 explicit init site 갱신 (kebab-rag tests 3 + kebab-cli 2 + kebab-mcp + kebab-tui + kebab-eval + kebab-app/tests). - `RagPipeline::ask` entry 에 dispatcher 한 줄. `opts.multi_hop=true` → `ask_multi_hop` route. - `RagPipeline::ask_multi_hop` skeleton — decompose + retrieve + synthesize 3 stage. PR-2 는 fixed depth=2 (decide loop 없음). `prompt_template_version = "rag-multi-hop-v1"` stamp. - `RefusalReason::MultiHopDecomposeFailed` variant 신규. Cascade: `kebab-store-sqlite::refusal_reason_label` + `kebab-tui::ask refusal render` exhaustive match 갱신. - 5 prompt / version const 신규: `MULTI_HOP_DECOMPOSE_SYSTEM_PROMPT` + `MULTI_HOP_DECOMPOSE_USER_TEMPLATE` + `MULTI_HOP_SYNTHESIZE_SYSTEM_PROMPT` + `PROMPT_TEMPLATE_VERSION_MULTI_HOP` + `MULTI_HOP_MAX_SUB_QUERIES_DEFAULT = 5`. - `parse_decompose_response` + `strip_markdown_json_fence` helper — markdown code fence (`` ``` json ``) strip + JSON array of strings parse + trim + drop empty + cap. ## 회귀 핀 (55 test 통과, 8 신규) - 6 unit (`parse_decompose_response_*`): bare array / json fence / bare fence / garbage / cap / trim. - 2 integration: - `ask_multi_hop_dispatches_and_decompose_garbage_refuses` — `multi_hop=true` 시 garbage decompose 응답이 `RefusalReason::MultiHopDecomposeFailed` + 정확히 1 LLM call (synthesize 까지 진입 안 함) + `prompt_template_version = rag-multi-hop-v1`. - `ask_with_multi_hop_false_keeps_single_pass_path` — 회귀 핀, 기존 single-pass caller 자동 backwards-compat. Default route + config 의 `prompt_template_version` 그대로 stamp. ## 의도된 비범위 (PR-3+ 책임) - Dynamic iter (LLM 의 `continue` / `stop` signal + `max_depth` / `max_sub_queries_per_iter` / `max_pool_chunks` cap) — PR-3. - `Answer.hops` wire field 노출 — PR-3 가 trace 채우기 시작. - Happy-path multi-hop (decompose 성공 + synthesize) integration test — `ScriptedLm` helper (per-call 다른 response 반환) 가 PR-3 와 함께 도입. - CLI `--multi-hop` flag — PR-4. - MCP `multi_hop` argument + SKILL.md — PR-5. - TUI Ask Multi-hop toggle — PR-6. ## 검증 - `cargo test -p kebab-rag -j 1` — 55 test 모두 녹색 (30 unit + 19 pipeline + 3 prompt + 3 streaming + 0 doc). - `cargo clippy -p kebab-rag -p kebab-core -p kebab-store-sqlite -p kebab-tui -p kebab-app --all-targets -j 1 -- -D warnings` — clean. - 16 GB RAM 직렬 build 규약 준수 (`-j 1`). ## 시험 항목 (Test Plan) - [x] `parse_decompose_response_parses_bare_json_array` - [x] `parse_decompose_response_strips_markdown_json_fence` - [x] `parse_decompose_response_strips_bare_markdown_fence` - [x] `parse_decompose_response_returns_none_for_garbage` - [x] `parse_decompose_response_caps_at_max_sub_queries` - [x] `parse_decompose_response_trims_each_entry` - [x] `ask_multi_hop_dispatches_and_decompose_garbage_refuses` - [x] `ask_with_multi_hop_false_keeps_single_pass_path` ## 다음 단계 PR-3 — dynamic decide loop + `Answer.hops` wire additive + `max_depth` 등 cfg 노브. ScriptedLm helper 도입으로 happy-path integration test. Assisted-by: Claude Code
altair823 added 1 commit 2026-05-25 06:46:14 +00:00
PR-2 of fb-41 multi-hop RAG. Decompose + retrieve + synthesize 3-stage
pipeline가 `opts.multi_hop=true` 일 때 dispatch. Dynamic decide loop
는 PR-3.

- `AskOpts.multi_hop: bool` 필드 추가 + `impl Default for AskOpts`
  도입 (HOTFIXES 2026-05-07 의 known limitation 해소). 9 explicit
  init site 모두 `multi_hop: false` 추가 — Default 도입으로 향후
  `..Default::default()` 점진 migrate 가능.
- `RagPipeline::ask` 의 entry 에 dispatcher 한 줄
  (`if opts.multi_hop { return self.ask_multi_hop(...) }`).
- `RagPipeline::ask_multi_hop` 신규 method. 1) decompose LLM call
  → JSON array of strings parse, 2) 각 sub-query 로 retrieve +
  chunk_id dedup pool, 3) score gate / no-chunks 가드, 4)
  pack_context (single-pass 와 helper 공유), 5) synthesize LLM
  call w/ MULTI_HOP_SYNTHESIZE_SYSTEM_PROMPT, 6) citation extract
  + Answer build. `prompt_template_version` = "rag-multi-hop-v1"
  로 stamp — eval `compare` 가 single-pass vs multi-hop 분리.
- Prompt const 신규: MULTI_HOP_DECOMPOSE_SYSTEM_PROMPT +
  MULTI_HOP_DECOMPOSE_USER_TEMPLATE + MULTI_HOP_SYNTHESIZE_SYSTEM_PROMPT
  + PROMPT_TEMPLATE_VERSION_MULTI_HOP + MULTI_HOP_MAX_SUB_QUERIES_DEFAULT.
- `kebab_core::RefusalReason::MultiHopDecomposeFailed` variant 신규.
  Cascade: kebab-store-sqlite `refusal_reason_label` + kebab-tui `ask
  refusal render` exhaustive match 갱신.
- `parse_decompose_response` + `strip_markdown_json_fence` helper —
  markdown code fence (```json / ```) strip + JSON array of strings
  parse + trim + drop empty + cap at MULTI_HOP_MAX_SUB_QUERIES_DEFAULT.
  None 반환 시 caller 가 `MultiHopDecomposeFailed` refusal.

Tests (55 passing total, 8 신규):
- 6 unit (parse_decompose_response 의 bare array / fence variants /
  garbage / cap / trim 회귀 핀).
- 2 integration: `ask_multi_hop_dispatches_and_decompose_garbage_refuses`
  (decompose garbage → MultiHopDecomposeFailed + 정확히 1 LLM call) +
  `ask_with_multi_hop_false_keeps_single_pass_path` (회귀 핀, 기존
  caller 자동 backwards-compat).

Happy-path multi-hop (decompose 성공 → synthesize) 의 integration
test 는 ScriptedLm helper 가 PR-3 의 decide loop 와 함께 도입될
때 같이 추가. 현 `MockLanguageModel` 는 canned single response 라
2-LLM-call sequence 핀 불가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-25 06:48:10 +00:00
claude-reviewer-01 left a comment
Member

회차 1 — PR-2 scope 정확히 ship (Default + dispatch + decompose-failure refusal + cascade). RefusalReason cascade build/clippy clean. actionable 1 + suggestion 3 + question 1 + 칭찬 1:

actionable:

  • parse_decompose_response_drops_partial_empty_keeps_valid 회귀 핀 추가 — 현재 모두 empty 만 핀, partial-empty 케이스 가드 부족

suggestion:

  • decompose user template 의 {max_sub_queries} substitution corner case (사용자 query 안에 literal 가능)
  • ask + ask_multi_hop 의 §4-§9 mirror — PR-3 refactor 합리적 timing
  • history block 처리도 양쪽 path 중복, drift 위험

question:

  • decompose stop: Vec::new() 의 의도 — instruction-following 약한 모델 prose 추가 대응 policy 확인

칭찬: exhaustive match cascade 가 Rust 자동 guard 로 missing site 0.

회차 1 — PR-2 scope 정확히 ship (Default + dispatch + decompose-failure refusal + cascade). RefusalReason cascade build/clippy clean. actionable 1 + suggestion 3 + question 1 + 칭찬 1: actionable: - `parse_decompose_response_drops_partial_empty_keeps_valid` 회귀 핀 추가 — 현재 모두 empty 만 핀, partial-empty 케이스 가드 부족 suggestion: - decompose user template 의 `{max_sub_queries}` substitution corner case (사용자 query 안에 literal 가능) - ask + ask_multi_hop 의 §4-§9 mirror — PR-3 refactor 합리적 timing - history block 처리도 양쪽 path 중복, drift 위험 question: - decompose `stop: Vec::new()` 의 의도 — instruction-following 약한 모델 prose 추가 대응 policy 확인 칭찬: exhaustive match cascade 가 Rust 자동 guard 로 missing site 0.
@@ -69,3 +74,4 @@
MultiHopDecomposeFailed,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]

칭찬RefusalReason::MultiHopDecomposeFailed variant 추가 후 모든 exhaustive match cascade (kebab-store-sqlite::refusal_reason_label + kebab-tui::ask refusal render) 가 build + clippy clean — Rust 의 exhaustive match 가 자동 guard. cascade missing 한 곳 없음. 또 wire code 의 snake_case 매핑 (multi_hop_decompose_failed) 이 design §10.1 error.v1 의 future-extensions 절 패턴과 일관.

**칭찬** — `RefusalReason::MultiHopDecomposeFailed` variant 추가 후 모든 exhaustive match cascade (kebab-store-sqlite::refusal_reason_label + kebab-tui::ask refusal render) 가 build + clippy clean — Rust 의 exhaustive match 가 자동 guard. cascade missing 한 곳 없음. 또 wire code 의 snake_case 매핑 (`multi_hop_decompose_failed`) 이 design §10.1 error.v1 의 future-extensions 절 패턴과 일관.
@@ -546,2 +587,4 @@
}
/// p9-fb-41: multi-hop ask. Decompose the user query into independent
/// sub-questions, retrieve each separately, then synthesize a single

Refactor opportunity (PR-3 합리적, 본 PR scope 외)ask_multi_hop 의 §4-§9 (pack_context → synthesize prompt → generate stream → citation extract → Answer build → persist) 가 ask 의 동일 단계 mirror. ~150 lines 중복.

PR-3 가 dynamic iter 도입하면 자연스러운 refactor 지점 — 공통 helper (예: synthesize_with_packed_context(query, system, packed_text, packed_entries, opts, started, top_score, prompt_template_version) -> Result<Answer>) 추출. 본 PR 의 working state 가 우선이라 즉시 refactor 안 하는 게 의도된 staging.

history block 처리 (line 850+ history_block = serialize_history(...)) 도 양쪽 path 에서 같은 코드 — 같은 PR-3 refactor 에 묶임. drift 위험 (한쪽만 patch 시 단발 검색 결과 다름) → 명시적 helper 화 가치 ↑.

Suggestion 만 — 본 PR 머지 후 PR-3 plan 단락에 "helper 추출" 의 명시 추가 권장.

**Refactor opportunity (PR-3 합리적, 본 PR scope 외)** — `ask_multi_hop` 의 §4-§9 (pack_context → synthesize prompt → generate stream → citation extract → Answer build → persist) 가 `ask` 의 동일 단계 mirror. ~150 lines 중복. PR-3 가 dynamic iter 도입하면 자연스러운 refactor 지점 — 공통 helper (예: `synthesize_with_packed_context(query, system, packed_text, packed_entries, opts, started, top_score, prompt_template_version) -> Result<Answer>`) 추출. 본 PR 의 working state 가 우선이라 즉시 refactor 안 하는 게 의도된 staging. history block 처리 (line 850+ `history_block = serialize_history(...)`) 도 양쪽 path 에서 같은 코드 — 같은 PR-3 refactor 에 묶임. drift 위험 (한쪽만 patch 시 단발 검색 결과 다름) → 명시적 helper 화 가치 ↑. Suggestion 만 — 본 PR 머지 후 PR-3 plan 단락에 "helper 추출" 의 명시 추가 권장.
@@ -548,0 +890,4 @@
Ok(answer)
}
/// Run a single decompose LLM call and parse the response into a

Question — decompose stop vec 비어 있는 의도 확인:

let req = GenerateRequest {
    system: MULTI_HOP_DECOMPOSE_SYSTEM_PROMPT.to_string(),
    user,
    stop: Vec::new(),  // ← 비어 있음
    max_tokens: 512,
    ...
};

synthesize 는 stop: vec!["\n\n[원본 질문]"] 로 stop sequence 있음. decompose 는 비어 있음. instruction-following 약한 모델 (gemma3:4b CPU 환경, README 권장) 이 JSON array ] 다음에 prose 추가 ("..."]\n\n참고: ...) 시 parse_decompose_response 가 fence strip 만으로 처리 못 함 → refusal.

2 옵션:

  1. stop vec 그대로 (현재) — refusal 이 의도된 결과. instruction-following 좋은 모델 (gemma4:e4b+, Claude, GPT-4) 은 prose 안 붙임. 안 좋은 모델은 refusal 로 surface — "이 LLM 으로는 multi-hop 못 함" 신호.
  2. stop = ["]\n\n", "```\n"] — array 끝 + fence 끝 기점 stop. parse 강인성 ↑, 단 LLM 이 마지막 ] 직전 stop 가능성 (truncated array → parse fail).

현재 채택은 (1) 로 보이는데 명시적 의도라면 OK, 미고려라면 (2) 검토. 답변에 따라 회차 2 에서 doc comment 한 줄 추가 또는 stop vec 보강.

**Question — decompose stop vec 비어 있는 의도 확인**: ```rust let req = GenerateRequest { system: MULTI_HOP_DECOMPOSE_SYSTEM_PROMPT.to_string(), user, stop: Vec::new(), // ← 비어 있음 max_tokens: 512, ... }; ``` synthesize 는 `stop: vec!["\n\n[원본 질문]"]` 로 stop sequence 있음. decompose 는 비어 있음. instruction-following 약한 모델 (gemma3:4b CPU 환경, README 권장) 이 JSON array `]` 다음에 prose 추가 (`"..."]\n\n참고: ...`) 시 `parse_decompose_response` 가 fence strip 만으로 처리 못 함 → refusal. 2 옵션: 1. **stop vec 그대로 (현재)** — refusal 이 의도된 결과. instruction-following 좋은 모델 (gemma4:e4b+, Claude, GPT-4) 은 prose 안 붙임. 안 좋은 모델은 refusal 로 surface — "이 LLM 으로는 multi-hop 못 함" 신호. 2. **stop = `["]\n\n", "```\n"]`** — array 끝 + fence 끝 기점 stop. parse 강인성 ↑, 단 LLM 이 마지막 `]` 직전 stop 가능성 (truncated array → parse fail). 현재 채택은 (1) 로 보이는데 명시적 의도라면 OK, 미고려라면 (2) 검토. 답변에 따라 회차 2 에서 doc comment 한 줄 추가 또는 stop vec 보강.
@@ -548,0 +898,4 @@
let user = MULTI_HOP_DECOMPOSE_USER_TEMPLATE
.replace("{query}", query)
.replace(
"{max_sub_queries}",

Corner case (nit)MULTI_HOP_DECOMPOSE_USER_TEMPLATE substitution order:

let user = MULTI_HOP_DECOMPOSE_USER_TEMPLATE
    .replace("{query}", query)
    .replace("{max_sub_queries}", &MULTI_HOP_MAX_SUB_QUERIES_DEFAULT.to_string());

사용자 query 안에 "{max_sub_queries}" literal 이 들어 있으면 두 번째 replace 가 의도치 않게 fire (예: "여기 {max_sub_queries} 의 의미는?" 같은 메타 질문). 실용적 corner case 빈도 ↓ — kebab 사용자가 LLM template variable 을 query 에 일부러 넣을 가능성 매우 낮음.

Fix 옵션 (회차 2 또는 PR-3):

  • order 뒤집기: {max_sub_queries} 먼저 → {query} 두 번째. 사용자 query 안의 {query} literal 도 동일 문제 — 완전 해결은 안 됨.
  • format! 매크로의 {query} named arg 사용 — compile-time check, mis-replace 없음.
  • handlebars / minijinja crate — overkill.

Fb-41 의 multi-hop 만 영향이라 PR-2 의 명시 nit, fix 시점은 PR-3 합리적.

**Corner case (nit)** — `MULTI_HOP_DECOMPOSE_USER_TEMPLATE` substitution order: ```rust let user = MULTI_HOP_DECOMPOSE_USER_TEMPLATE .replace("{query}", query) .replace("{max_sub_queries}", &MULTI_HOP_MAX_SUB_QUERIES_DEFAULT.to_string()); ``` 사용자 query 안에 `"{max_sub_queries}"` literal 이 들어 있으면 두 번째 replace 가 의도치 않게 fire (예: `"여기 {max_sub_queries} 의 의미는?"` 같은 메타 질문). 실용적 corner case 빈도 ↓ — kebab 사용자가 LLM template variable 을 query 에 일부러 넣을 가능성 매우 낮음. Fix 옵션 (회차 2 또는 PR-3): - order 뒤집기: `{max_sub_queries}` 먼저 → `{query}` 두 번째. 사용자 query 안의 `{query}` literal 도 동일 문제 — 완전 해결은 안 됨. - `format!` 매크로의 `{query}` named arg 사용 — compile-time check, mis-replace 없음. - handlebars / minijinja crate — overkill. Fb-41 의 multi-hop 만 영향이라 PR-2 의 명시 nit, fix 시점은 PR-3 합리적.
@@ -912,0 +1379,4 @@
/// Returns `None` when:
/// - parse fails outright (not a JSON array of strings),
/// - the array deserializes but is empty after trim/drop,
///

회귀 핀 보강 권장: partial-empty 케이스. 현재 parse_decompose_response_returns_none_for_garbage모두 empty 인 [" ", ""] → None 검증. ["", "valid"]Some(vec!["valid"]) (empty drop, 나머지 유지) 케이스의 회귀 핀 없음.

#[test]
fn parse_decompose_response_drops_partial_empty_keeps_valid() {
    let out = parse_decompose_response(r#"["", "valid q", "  "]"#).unwrap();
    assert_eq!(out, vec!["valid q"]);
}

현재 동작이 그렇게 작동 (filter + trim + take chain) — pin 해두면 향후 fence strip / order 변경 시 회귀 자동 ���견.

회귀 핀 보강 권장: partial-empty 케이스. 현재 `parse_decompose_response_returns_none_for_garbage` 는 *모두* empty 인 `[" ", ""]` → None 검증. **`["", "valid"]` → `Some(vec!["valid"])`** (empty drop, 나머지 유지) 케이스의 회귀 핀 없음. ```rust #[test] fn parse_decompose_response_drops_partial_empty_keeps_valid() { let out = parse_decompose_response(r#"["", "valid q", " "]"#).unwrap(); assert_eq!(out, vec!["valid q"]); } ``` 현재 동작이 그렇게 작동 (filter + trim + take chain) — pin 해두면 향후 fence strip / order 변경 시 회귀 자동 ���견.
altair823 added 1 commit 2026-05-25 06:49:26 +00:00
- `parse_decompose_response_drops_partial_empty_keeps_valid` 신규
  회귀 핀 — `["", "valid q", "  "]` → `["valid q"]` (trim+filter
  chain 동작 pin).
- `multi_hop_decompose` 의 `stop: Vec::new()` 옆 doc comment 추가 —
  의도 명시 (instruction-following 모델 기대 + prose 추가 시
  MultiHopDecomposeFailed refusal 가 policy). 회차 1 question 의
  답변.
- plan 의 PR-3 implementation order 에 회차 1 carry-over 추가:
  1) ask + ask_multi_hop 의 §4-§9 mirror → 공통 helper 추출,
  2) decompose template 의 substitution corner case → format!
     named arg 으로 교체.

회차 1 의 다른 suggestion (mirror refactor, substitution corner
case, history block helper) 는 PR-3 합리적 timing 으로 plan 에
명시 — 회차 2 reply 에 정리.

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

회차 2 — 회차 1 의 actionable 1 (partial-empty pin) + question 답변 (stop intent doc) 잘 반영. 잔여 actionable 0건. APPROVE.

회차 1 의 다른 3 suggestion 의 PR-3 carry-over 명시:

  • ask + ask_multi_hop 의 §4-§9 mirror (~150 줄 중복) → 공통 helper 추출
  • history block 처리 양쪽 path 중복 → 같이 helper 화
  • MULTI_HOP_DECOMPOSE_USER_TEMPLATE substitution corner case → format! named arg 으로 교체

plan 의 PR-3 implementation order 에 7번 항목으로 추가됨 — PR-3 가 자연스러운 timing (decide loop 도입하면서 본격 refactor). 본 PR scope 외 잘 분리.

cadence 자연 (5 → 0) 수렴. PR-3 (dynamic iter + decide loop + Answer.hops wire + cfg 노브 + ScriptedLm helper) 진행 권장.

회차 2 — 회차 1 의 actionable 1 (partial-empty pin) + question 답변 (stop intent doc) 잘 반영. 잔여 actionable 0건. APPROVE. 회차 1 의 다른 3 suggestion 의 PR-3 carry-over 명시: - ask + ask_multi_hop 의 §4-§9 mirror (~150 줄 중복) → 공통 helper 추출 - history block 처리 양쪽 path 중복 → 같이 helper 화 - `MULTI_HOP_DECOMPOSE_USER_TEMPLATE` substitution corner case → `format!` named arg 으로 교체 plan 의 PR-3 implementation order 에 7번 항목으로 추가됨 — PR-3 가 자연스러운 timing (decide loop 도입하면서 본격 refactor). 본 PR scope 외 잘 분리. cadence 자연 (5 → 0) 수렴. PR-3 (dynamic iter + decide loop + Answer.hops wire + cfg 노브 + ScriptedLm helper) 진행 권장.
@@ -548,0 +899,4 @@
.replace("{query}", query)
.replace(
"{max_sub_queries}",
&MULTI_HOP_MAX_SUB_QUERIES_DEFAULT.to_string(),

칭찬stop: Vec::new() 옆 doc comment 가 (1) instruction-following 모델 가정 + (2) prose-after-array 시 의도된 refusal policy + (3) 잘못된 stop sequence (]) 의 truncation 위험까지 명시. 후속 reviewer / archeology 가 Vec::new() 의도 즉시 파악 + alternative trade-off 까지 추적 가능. 회차 1 question 의 답변이 코드 + doc 양쪽에 etched 됨.

**칭찬** — `stop: Vec::new()` 옆 doc comment 가 (1) instruction-following 모델 가정 + (2) prose-after-array 시 의도된 refusal policy + (3) 잘못된 stop sequence (`]`) 의 truncation 위험까지 명시. 후속 reviewer / archeology 가 `Vec::new()` 의도 즉시 파악 + alternative trade-off 까지 추적 가능. 회차 1 question 의 답변이 코드 + doc 양쪽에 etched 됨.
@@ -912,0 +1392,4 @@
///
/// in which case the caller surfaces `RefusalReason::MultiHopDecomposeFailed`.
fn parse_decompose_response(raw: &str) -> Option<Vec<String>> {
let stripped = strip_markdown_json_fence(raw.trim());

칭찬parse_decompose_response_drops_partial_empty_keeps_valid 핀이 정확히 trim+filter chain 의 invariant ("["", "valid q", " "]["valid q"]") 를 표현. 향후 refactor (예: PR-3 의 helper 추출 시 step 재정렬) 가 partial-empty 케이스를 silently 깨트리면 즉시 fail.

**칭찬** — `parse_decompose_response_drops_partial_empty_keeps_valid` 핀이 정확히 trim+filter chain 의 invariant ("`["", "valid q", " "]` → `["valid q"]`") 를 표현. 향후 refactor (예: PR-3 의 helper 추출 시 step 재정렬) 가 partial-empty 케이스를 silently 깨트리면 즉시 fail.
altair823 merged commit 6280abf2df into main 2026-05-25 06:50:05 +00:00
altair823 deleted branch feat/fb-41-pr-2-ask-multi-hop-skeleton 2026-05-25 06:50:06 +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#167