diff --git a/Cargo.lock b/Cargo.lock index d00ca1f..8bd308e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1979,6 +1979,7 @@ name = "doubleo7" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "reqwest 0.13.4", "rig", "schemars 1.2.2", diff --git a/Cargo.toml b/Cargo.toml index 0429525..f12cc8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ tokio = { version = "1.53.1", features = ["full"] } toml = "1.1.4+spec-1.1.0" tracing = "0.1" tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +chrono = "0.4.45" diff --git a/src/deep_research/starter.rs b/src/deep_research/starter.rs index 3d27ddc..96aa9b6 100644 --- a/src/deep_research/starter.rs +++ b/src/deep_research/starter.rs @@ -1,8 +1,8 @@ +use crate::deep_research::review::{self, Review}; +use crate::deep_research::tools::{FetchPage, SearchWeb}; use rig::client::{AgentClientExt, Nothing}; use rig::completion::Prompt; use rig::providers::ollama; -use crate::deep_research::review::{self, Review}; -use crate::deep_research::tools::{FetchPage, SearchWeb}; /// The tool-calling research loop needs to reliably decide what to search /// for, when a page is worth fetching, and when it has enough evidence — @@ -37,7 +37,9 @@ fn initialize_observability() { pub(crate) async fn start() -> anyhow::Result<()> { initialize_observability(); - let topic = std::env::args().nth(1).unwrap_or_else(|| DEFAULT_TOPIC.to_string()); + let topic = std::env::args() + .nth(1) + .unwrap_or_else(|| DEFAULT_TOPIC.to_string()); let report = research(&topic).await?; @@ -84,17 +86,25 @@ async fn gather_findings( feedback: Option<&Review>, round: usize, ) -> anyhow::Result { + let current_date = chrono::offset::Local::now().to_string(); + let researcher = client .agent(RESEARCHER_MODEL) .name("researcher") - .preamble( + .preamble(format!( "You are a meticulous research assistant. Use the search_web and fetch_page tools to \ investigate the user's topic: run several searches with varied phrasing, fetch the \ most promising pages, and cross-check claims across at least two sources before \ - trusting them. Once you are confident you have enough evidence, stop calling tools \ - and reply with a plain-text dump of every fact you gathered, the source URL it came \ - from, and any open questions or contradictions between sources. This is raw research \ - material for a writer, not a final report, so favor completeness over polish.", + trusting them. Ensure sources are up to date: the current date is {current_date}. \ + Once you are confident you have enough evidence, stop calling tools and reply with a \ + plain-text dump of every fact you gathered, using footnote-style citations: write each \ + fact followed by a bracketed number like [1], then at the end of your reply list a \ + 'Sources' section mapping each number to the exact URL it came from, one per line, e.g. \ + '[1] https://example.com/page'. Reuse the same number when multiple facts come from the \ + same URL — do not give one URL two different numbers. Also call out any open questions \ + or contradictions between sources, citing the footnotes involved. This is raw research \ + material for a writer, not a final report, so favor completeness over polish.") + .as_str(), ) .tool(SearchWeb) .tool(FetchPage) @@ -107,7 +117,8 @@ async fn gather_findings( You already ran a research pass on this topic. A reviewer checked it against its \ cited sources and found it insufficient. Do more research to address the reviewer's \ feedback, then produce an updated findings dump: carry forward what's solid, and \ - add, correct, or better-source whatever the gaps call for.\n\n\ + add, correct, or better-source whatever the gaps call for. Gaps include out-of-date,\ + irrelevant, or clearly wrong information. The date is {current_date}.\n\n\ Solid findings from the last pass — keep and build on these:\n{}\n\n\ Gaps the reviewer found — conclusions not actually backed by their source, sources \ that don't line up with the conclusion drawn from them, or parts of the topic still \ @@ -129,15 +140,23 @@ async fn gather_findings( } #[tracing::instrument(skip(client, findings), fields(gen_ai.agent.name = "writer"))] -async fn write_report(client: &ollama::Client, topic: &str, findings: &str) -> anyhow::Result { +async fn write_report( + client: &ollama::Client, + topic: &str, + findings: &str, +) -> anyhow::Result { let writer = client .agent(WRITER_MODEL) .name("writer") .preamble( - "You turn raw research notes into a clear, well-organized report for the reader. \ - Structure the report with headings, cite source URLs inline next to the claims they \ - support, and call out any open questions or contradictions the research turned up. Do \ - not invent facts beyond what the notes provide.", + "You turn raw research notes into a clear, well-organized report for the reader. The \ + notes use footnote-style citations — a bracketed number like [1] after a fact, with a \ + Sources section mapping numbers to URLs. Preserve this scheme in your report: keep the \ + same [n] markers next to the claims they support (renumbering only if you drop unused \ + sources), and end the report with a 'Sources' section listing every footnote number \ + still in use next to its exact URL. Structure the body with headings, and call out any \ + open questions or contradictions the research turned up. Do not invent facts beyond \ + what the notes provide.", ) .build();