lc_agents/deep_research/
types.rs1use super::planner;
5
6#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
8pub struct Citation {
9 pub index: usize,
11 pub source: String,
13 pub url: Option<String>,
15 pub snippet: String,
17}
18
19#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
21pub struct ResearchReport {
22 pub markdown: String,
24 pub citations: Vec<Citation>,
26 pub subtopics: Vec<String>,
28 pub rounds_completed: usize,
30}
31
32pub(crate) fn parse_gap_queries(
40 content: &str,
41 original_gaps: &[String],
42) -> Result<Vec<String>, super::ResearchError> {
43 #[derive(serde::Deserialize)]
44 struct GapMapping {
45 gap: String,
46 queries: Vec<String>,
47 }
48
49 let json_str = planner::extract_json(content);
50 let mappings: Vec<GapMapping> = serde_json::from_str(&json_str).map_err(|e| {
51 let preview: String = content.chars().take(200).collect();
52 super::ResearchError::Llm(format!(
53 "failed to parse gap→query mapping: {} | raw: {}",
54 e, preview
55 ))
56 })?;
57
58 let mut all_queries: Vec<String> = Vec::new();
60 let mut covered_gaps: std::collections::HashSet<usize> = std::collections::HashSet::new();
61
62 for mapping in &mappings {
63 for (i, gap) in original_gaps.iter().enumerate() {
65 if !covered_gaps.contains(&i)
66 && (mapping.gap.contains(gap.as_str()) || gap.contains(mapping.gap.as_str()))
67 {
68 covered_gaps.insert(i);
69 }
70 }
71 all_queries.extend(mapping.queries.iter().filter(|q| !q.is_empty()).cloned());
72 }
73
74 for (i, gap) in original_gaps.iter().enumerate() {
76 if !covered_gaps.contains(&i) {
77 log::warn!(
78 "Deep Research: gap '{}' has no follow-up queries, using gap as query",
79 gap
80 );
81 all_queries.push(gap.clone());
82 }
83 }
84
85 Ok(all_queries)
86}