feat(rag): fb-41 PR-3b-i dynamic decide loop + helpers #169

Merged
altair823 merged 1 commits from feat/fb-41-pr-3b-decide-loop into main 2026-05-25 07:32:27 +00:00

1 Commits

Author SHA1 Message Date
12c7dc9efb feat(rag): fb-41 PR-3b-i — dynamic decide loop + helpers + format! named arg
PR-3b 의 분할 첫 PR. ask_multi_hop 의 fixed depth=2 → dynamic N-hop.
ScriptedLm helper + 5+ integration tests (happy-path 통합 검증) 는
PR-3b-ii 분리. 본 PR 의 회귀 핀 = 기존 PR-2 의 2 integration test
통과 (decompose garbage refusal + multi_hop=false single-pass keep).

- `RagPipeline::multi_hop_decompose` 시그니처 변경 — `Result<
  (Option<Vec<String>>, u32)>` (parsed result + LLM call latency_ms).
  caller (`ask_multi_hop`) 가 hop trace 의 `llm_call_ms` stamp.
- `RagPipeline::multi_hop_decide` helper 신규. decide LLM call →
  `parse_decompose_response` 으로 `Option<Vec<String>>` 반환. None
  또는 empty array 가 stop signal (refusal 아닌 graceful degrade).
- `MULTI_HOP_DECIDE_SYSTEM_PROMPT` const 신규.
- `MULTI_HOP_DECOMPOSE_USER_TEMPLATE` const 제거 + `format!` named
  arg 사용 (PR-2 회차 1 carry-over fix). compile-time substitution
  check — 사용자 query 안에 `{max_sub_queries}` literal 있어도
  mis-replace 회피.
- `ask_multi_hop` 의 §1 (Decompose) + §2 (Retrieve) 영역을 dynamic
  loop 으로 재작성:
  - iter 0 = decompose, HopRecord 추가 (kind=Decompose).
  - iter 1..=max_depth = retrieve current_sub_queries → pool dedup
    → decide LLM call (forced_stop / pool_cap_hit 시 skip).
    HopRecord 추가 (kind=Decide, sub_queries=new_sub_queries,
    context_chunks_added, forced_stop, llm_call_ms).
  - `max_pool_chunks` 도달 시 `pool_cap_hit = true` → 그 iter 의
    HopRecord 가 `forced_stop = true` + decide LLM call skip.
  - depth 도달 (`iter >= max_depth`) 시 동일하게 forced_stop.
  - decide parse failure 또는 empty array → loop break (early
    synthesize, NOT refusal — spec §9 graceful degrade).
- §6 (Generate) 시작 시 `synthesize_started: Instant::now()` 별
  stamp → §8 Build Answer 직전 `HopRecord { kind=Synthesize,
  llm_call_ms = synth_ms }` 추가. happy path 의 Answer literal
  `hops: Some(hops)` 채움 (`hops: None` → `Some(...)` 변경).
- doc comment 갱신: "PR-2 scope (fixed depth=2)" → "PR-3b-i
  scope (dynamic N-hop)". refusal path 의 hops trace 손실 caveat
  명시 (PR-3b-ii / follow-up 에서 helper signature 확장 시 해결).

기존 회귀 핀 (PR-2 의 2 integration test):
- `ask_multi_hop_dispatches_and_decompose_garbage_refuses`:
  decompose garbage → RefusalReason::MultiHopDecomposeFailed +
  정확히 1 LLM call. PR-3b-i 의 시그니처 변경 후도 통과.
- `ask_with_multi_hop_false_keeps_single_pass_path`: 영향 없음.

56 unit + integration test 모두 통과 (kebab-rag).

Wire 영향: `Answer.hops` 가 multi-hop happy path 에서 emit. JSON
Schema additionalProperties default `true` 라 wire breaking 아님
(PR-3a 의 review 확인). schema.json 명시 갱신은 별 PR
(PR-3b-ii 또는 PR-4 의 schema sweep).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:29:46 +00:00