doubleo7/src/summarizer.rs
Austin Schaefer f2c10783db
Some checks failed
CI / test (push) Failing after 6s
CI / test (pull_request) Failing after 7s
Extract swear_cleanup to its own repo, flatten deep_research to root
deep_research is the only project this repo is meant to showcase, so the
Cargo workspace wrapping it and an unrelated side project no longer earns
its keep:

- swear_cleanup moved to a new standalone local repo (~/dev/swear_cleanup,
  not pushed anywhere) via `git subtree split`, with its pre-workspace-
  split history (when it lived at src/swear_cleanup/ in a single shared
  crate) spliced onto its post-split history rather than starting from a
  single flattened snapshot. FINDINGS.md, which was sitting at this repo's
  root but was actually swear_cleanup's own build log, went with it.
- deep_research/{src,Cargo.toml,README.md,docs} moved to the repo root;
  the [workspace] table collapsed into a plain [package] manifest with
  dependency versions inlined from the old [workspace.dependencies].
- Cargo.toml keeps an explicit empty [workspace] table (not just omitted)
  so that checking this repo out as a nested git worktree — this
  project's own normal workflow — can't accidentally inherit a stale
  ancestor directory's workspace manifest, which is exactly what broke
  the build while testing this change from a worktree.
- .forgejo/workflows/deep_research-ci.yml -> ci.yml, dropping the now-
  meaningless -p deep_research scoping and path filters (redundant when
  it's the only thing in the repo).
- README.md and docs/case-study.md updated for the flattened commands
  (cargo run/test with no -p flag); their relative links to each other
  and to src/ were already correct since both moved together.

Verified: cargo build/test/clippy/fmt all clean from the new repo root.
2026-08-18 13:43:26 +02:00

69 lines
3 KiB
Rust

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)
}
}
}