review(p9-fb-18): 회차 1 nit 반영

- `App::build_retriever(mode) -> Result<Arc<dyn Retriever>>` 추출.
  `ask` 와 `ask_with_session` 모두 사용. 35+ 줄 retriever stack 중복
  제거 — 미래 retriever 변경이 한 곳만.
- V005 migration `chat_sessions.sql` 의 `citations_json` doc 수정:
  `Vec<Citation>` → `Vec<AnswerCitation>` (실제 stored type 과 일치).
  AnswerCitation 가 marker + Citation 등 포함하므로 deserialize 시
  type mismatch 회피.

15 app lib + 9 store chat_sessions + clippy 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 06:25:13 +00:00
parent 4f96b1b01d
commit 6ca089286c
2 changed files with 22 additions and 50 deletions

View File

@@ -254,7 +254,20 @@ impl App {
/// Run a RAG `ask` against the configured retriever + LLM. Reuses /// Run a RAG `ask` against the configured retriever + LLM. Reuses
/// the memoized embedder / vector / LLM where applicable. /// the memoized embedder / vector / LLM where applicable.
pub fn ask(&self, query: &str, opts: AskOpts) -> Result<Answer> { pub fn ask(&self, query: &str, opts: AskOpts) -> Result<Answer> {
let retriever: Arc<dyn Retriever> = match opts.mode { let retriever = self.build_retriever(opts.mode)?;
let llm = self.llm()?;
let pipeline =
RagPipeline::new(self.config.clone(), retriever, llm, self.sqlite.clone());
pipeline.ask(query, opts)
}
/// p9-fb-18: shared retriever-stack builder used by [`Self::ask`]
/// and [`Self::ask_with_session`]. Lexical mode uses the FTS5
/// retriever directly; vector / hybrid require embeddings (and
/// surface the same "switch to --mode lexical" error from
/// [`Self::require_embeddings`] when disabled).
fn build_retriever(&self, mode: SearchMode) -> Result<Arc<dyn Retriever>> {
Ok(match mode {
SearchMode::Lexical => Arc::new(LexicalRetriever::with_settings( SearchMode::Lexical => Arc::new(LexicalRetriever::with_settings(
self.sqlite.clone(), self.sqlite.clone(),
lexical_index_version(&self.config), lexical_index_version(&self.config),
@@ -292,12 +305,7 @@ impl App {
)) as Arc<dyn Retriever>; )) as Arc<dyn Retriever>;
Arc::new(HybridRetriever::new(&self.config, lex, vec_retr)) Arc::new(HybridRetriever::new(&self.config, lex, vec_retr))
} }
}; })
let llm = self.llm()?;
let pipeline =
RagPipeline::new(self.config.clone(), retriever, llm, self.sqlite.clone());
pipeline.ask(query, opts)
} }
/// p9-fb-18: ask under a persistent chat session. Loads the /// p9-fb-18: ask under a persistent chat session. Loads the
@@ -353,46 +361,9 @@ impl App {
}) })
.collect(); .collect();
// Build the retriever stack the same way `ask` does. // p9-fb-18 R1: shared retriever builder removes the prior
let retriever: Arc<dyn Retriever> = match opts.mode { // copy of `ask`'s 35-line stack — see [`Self::build_retriever`].
SearchMode::Lexical => Arc::new(LexicalRetriever::with_settings( let retriever = self.build_retriever(opts.mode)?;
self.sqlite.clone(),
lexical_index_version(&self.config),
self.config.search.snippet_chars,
)),
SearchMode::Vector => {
let (emb, vec_store) = self.require_embeddings()?;
let vec_iv = vector_index_version(emb.as_ref());
let vec_dyn: Arc<dyn VectorStore + Send + Sync> = vec_store;
let emb_dyn: Arc<dyn Embedder> = emb;
Arc::new(VectorRetriever::with_settings(
vec_dyn,
emb_dyn,
self.sqlite.clone(),
vec_iv,
self.config.search.snippet_chars,
))
}
SearchMode::Hybrid => {
let lex = Arc::new(LexicalRetriever::with_settings(
self.sqlite.clone(),
lexical_index_version(&self.config),
self.config.search.snippet_chars,
)) as Arc<dyn Retriever>;
let (emb, vec_store) = self.require_embeddings()?;
let vec_iv = vector_index_version(emb.as_ref());
let vec_dyn: Arc<dyn VectorStore + Send + Sync> = vec_store;
let emb_dyn: Arc<dyn Embedder> = emb;
let vec_retr = Arc::new(VectorRetriever::with_settings(
vec_dyn,
emb_dyn,
self.sqlite.clone(),
vec_iv,
self.config.search.snippet_chars,
)) as Arc<dyn Retriever>;
Arc::new(HybridRetriever::new(&self.config, lex, vec_retr))
}
};
let llm = self.llm()?; let llm = self.llm()?;
let pipeline = let pipeline =
RagPipeline::new(self.config.clone(), retriever, llm, self.sqlite.clone()); RagPipeline::new(self.config.clone(), retriever, llm, self.sqlite.clone());

View File

@@ -25,9 +25,10 @@
-- max_context_tokens that produced the session so a retroactive -- max_context_tokens that produced the session so a retroactive
-- answer-quality regression can be re-traced. -- answer-quality regression can be re-traced.
-- --
-- * `citations_json` carries `Vec<Citation>` so the answer can be -- * `citations_json` carries `Vec<AnswerCitation>` (per p9-fb-18) —
-- redisplayed with the same citation markers a future session -- each AnswerCitation holds a `Citation` plus `marker`, so the
-- sees on resume. -- answer can be redisplayed with the same citation markers a
-- future session sees on resume.
-- --
-- * `INTEGER` timestamps (unix epoch seconds) — same convention the -- * `INTEGER` timestamps (unix epoch seconds) — same convention the
-- rest of the schema uses (P1-7 baselines this). -- rest of the schema uses (P1-7 baselines this).