From be3ff167006865b79cb4d3a35ce38f74697b859d Mon Sep 17 00:00:00 2001 From: d-oit Date: Mon, 10 Aug 2026 10:29:50 +0200 Subject: [PATCH] fix(resolver): combine and re-score query results on semantic cache hit On a semantic cache hit, join all result chunks and assign first.score = score_result(&first.url, combined) so hit-path scores match the cache-miss path instead of returning results[0] uncombined and under-gated. --- cli/src/resolver/query/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/cli/src/resolver/query/mod.rs b/cli/src/resolver/query/mod.rs index eee3d699..be299936 100644 --- a/cli/src/resolver/query/mod.rs +++ b/cli/src/resolver/query/mod.rs @@ -111,7 +111,7 @@ impl QueryCascade { if let Ok(Some(results)) = cache.query(query).await { if !results.is_empty() { let cache_latency = start_time.elapsed().as_millis() as u64; - let mut first = results[0].clone(); + let mut first = Self::combine_cache_results(&results); metrics.record_semantic_cache_hit(cache_latency, first.score); first.metrics = Some(metrics); return Ok(first); @@ -478,6 +478,22 @@ impl QueryCascade { HashMap::new(), ); } + + /// Combine all cache-hit result chunks into one and re-score it, so the + /// hit path assigns a score exactly like the cache-miss path does. + fn combine_cache_results(results: &[ResolvedResult]) -> ResolvedResult { + let mut first = results[0].clone(); + if results.len() > 1 { + let combined_content = results + .iter() + .filter_map(|r| r.content.as_deref()) + .collect::>() + .join("\n\n"); + first.content = Some(combined_content); + first.score = score_result(&first.url, first.content.as_deref().unwrap_or("")); + } + first + } } impl Default for QueryCascade {