use std::collections::BTreeMap;
use crate::agent::config::{Evidence, Metrics, Output};
use crate::client::{Confidence, EvidenceItem, QueryMetrics, QueryResultItem};
pub fn to_results(output: &Output, doc_id: &str) -> Vec<QueryResultItem> {
if output.evidence.is_empty() {
return vec![empty_item(doc_id, &output.answer, output.confidence)];
}
let groups = group_by_doc(&output.evidence);
if groups.len() <= 1 {
return vec![build_item(
doc_id,
&output.answer,
output.confidence,
&output.evidence,
&output.metrics,
)];
}
groups
.into_iter()
.map(|(name, refs)| {
let did = name.as_deref().unwrap_or(doc_id);
let evidence: Vec<Evidence> = refs.iter().map(|e| (*e).clone()).collect();
build_item(
did,
&output.answer,
output.confidence,
&evidence,
&output.metrics,
)
})
.collect()
}
fn group_by_doc(evidence: &[Evidence]) -> BTreeMap<Option<String>, Vec<&Evidence>> {
let mut groups: BTreeMap<Option<String>, Vec<&Evidence>> = BTreeMap::new();
for ev in evidence {
groups.entry(ev.doc_name.clone()).or_default().push(ev);
}
groups
}
fn build_item(
doc_id: &str,
answer: &str,
confidence: Confidence,
evidence: &[Evidence],
metrics: &Metrics,
) -> QueryResultItem {
let node_ids: Vec<String> = evidence.iter().map(|e| e.source_path.clone()).collect();
let evidence_items: Vec<EvidenceItem> = evidence
.iter()
.map(|e| EvidenceItem {
title: e.node_title.clone(),
path: e.source_path.clone(),
content: e.content.clone(),
doc_name: e.doc_name.clone(),
})
.collect();
let content = if answer.is_empty() {
evidence
.iter()
.map(|e| format!("## {}\n{}", e.node_title, e.content))
.collect::<Vec<_>>()
.join("\n\n---\n\n")
} else {
answer.to_string()
};
let evidence_count = evidence.len();
QueryResultItem {
doc_id: doc_id.to_string(),
node_ids,
content,
evidence: evidence_items,
metrics: Some(QueryMetrics {
llm_calls: metrics.llm_calls,
rounds_used: metrics.rounds_used,
nodes_visited: metrics.nodes_visited,
evidence_count,
evidence_chars: metrics.evidence_chars,
}),
confidence,
}
}
fn empty_item(doc_id: &str, answer: &str, confidence: Confidence) -> QueryResultItem {
let content = if answer.is_empty() {
String::new()
} else {
answer.to_string()
};
QueryResultItem {
doc_id: doc_id.to_string(),
node_ids: Vec::new(),
content,
evidence: Vec::new(),
metrics: None,
confidence,
}
}