Perform pre-analysis in a single LLM call: intent classification, goal extraction, execution plan, and input optimization. Falls back to heuristics on failure.
(llm: &Arc<dyn LlmClient>, prompt: &str)
| 213 | /// Perform pre-analysis in a single LLM call: intent classification, goal extraction, |
| 214 | /// execution plan, and input optimization. Falls back to heuristics on failure. |
| 215 | pub async fn pre_analyze(llm: &Arc<dyn LlmClient>, prompt: &str) -> Result<PreAnalysis> { |
| 216 | let system = crate::prompts::PRE_ANALYSIS_SYSTEM; |
| 217 | |
| 218 | // One initial attempt plus one repair round: if the model returns |
| 219 | // unparseable JSON, re-prompt it once to emit strictly valid JSON before |
| 220 | // giving up (callers fall back to heuristics on the returned error). |
| 221 | const MAX_ATTEMPTS: usize = 2; |
| 222 | let mut messages = vec![Message::user(prompt)]; |
| 223 | let mut last_err: Option<anyhow::Error> = None; |
| 224 | |
| 225 | for attempt in 0..MAX_ATTEMPTS { |
| 226 | let response = llm |
| 227 | .complete(&messages, Some(system), &[]) |
| 228 | .await |
| 229 | .context("LLM pre-analysis call failed")?; |
| 230 | |
| 231 | let text = response.text(); |
| 232 | match Self::parse_pre_analysis_response(&text, prompt) { |
| 233 | Ok(analysis) => return Ok(analysis), |
| 234 | Err(e) => { |
| 235 | last_err = Some(e); |
| 236 | if attempt + 1 < MAX_ATTEMPTS { |
| 237 | messages.push(response.message.clone()); |
| 238 | messages.push(Message::user( |
| 239 | "Your previous response was not valid JSON matching the required \ |
| 240 | schema. Respond again with ONLY the JSON object — no markdown \ |
| 241 | fences, no prose, no explanation.", |
| 242 | )); |
| 243 | } |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | Err(last_err.unwrap_or_else(|| anyhow::anyhow!("pre-analysis produced no result"))) |
| 249 | } |
| 250 | |
| 251 | fn parse_pre_analysis_response(text: &str, original_prompt: &str) -> Result<PreAnalysis> { |
| 252 | let parsed: PreAnalysisResponse = Self::parse_json_lenient(text) |