use crate::history::{annotated_transcript_from_history, partial_findings_from_history}; use crate::models::WRITER_MODEL; use crate::progress::{SUMMARIZE_EMOJI, Spinner}; use rig::client::AgentClientExt; use rig::completion::{Message, Prompt}; use rig::providers::ollama; /// Agentic alternative to the plain programmatic extraction in `history`: /// hands the annotated transcript to a fresh model call and asks it to /// reconstruct the same footnote-style findings dump the researcher would /// have written itself, had it not run out of turns. This can dedupe /// repeated URLs and restore correct `[n]` citation numbering in a way /// string concatenation can't — but it's a model call like any other, so on /// an empty transcript or a failure it falls back to /// `partial_findings_from_history` rather than letting a second turn-budget /// problem take down the one recovery path that's supposed to be /// bulletproof. #[tracing::instrument(skip(client, chat_history), fields(gen_ai.agent.name = "history-summarizer"))] pub(crate) async fn summarize_partial_history( client: &ollama::Client, topic: &str, chat_history: &[Message], show_progress: bool, ) -> String { let transcript = annotated_transcript_from_history(chat_history); if transcript.is_empty() { tracing::warn!("no usable transcript to summarize; skipping summarizer pass"); return partial_findings_from_history(chat_history); } let summarizer = client .agent(WRITER_MODEL) .name("history-summarizer") .preamble( "You are reconstructing research notes from a research session that was cut off \ before the researcher could write its own summary. You'll be given a raw transcript \ of tool calls (searches run, pages fetched), their results, and any interim comments \ the researcher made. Turn this into a footnote-style findings dump: write each fact \ the transcript actually supports, followed by a bracketed number like [1], then end \ with a 'Sources' section mapping each number to its exact URL, one per line. Reuse \ the same number for a URL that appears more than once. Do not invent facts beyond \ what the transcript shows, and note explicitly where it looks thin or cuts off \ mid-investigation.", ) .build(); let spinner = Spinner::start( show_progress, format!("{SUMMARIZE_EMOJI} Reconstructing partial findings..."), ); let result = summarizer .prompt(format!( "Topic: {topic}\n\nPartial research transcript:\n{transcript}" )) .await; drop(spinner); match result { Ok(findings) => { tracing::info!(findings = %findings, "summarizer reconstructed partial findings"); findings } Err(error) => { tracing::warn!(%error, "summarizer agent failed; falling back to programmatic extraction"); partial_findings_from_history(chat_history) } } }