diff --git a/Cargo.lock b/Cargo.lock index fd87080..0d434f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5414,9 +5414,9 @@ checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" [[package]] name = "rig" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce03971e6115d30ef53fb3244d06718a4e62bbde82c103065600c09459b989a" +checksum = "b989f4060e5f3c1ea4b8b6999f6759949d820704286cc6976b7a688cc0133029" dependencies = [ "rig-agent", "rig-core", @@ -5429,9 +5429,9 @@ dependencies = [ [[package]] name = "rig-agent" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0796bbf47d7b76670401aac975bc619cf7fba3482b22dfe14992edaa9c2e04" +checksum = "b66477c0d0a786358d078e7e3541a73089dd9009100d6e2b3fe3b0d730e8550f" dependencies = [ "async-stream", "fastrand", @@ -5451,9 +5451,9 @@ dependencies = [ [[package]] name = "rig-core" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f5520515ae8f6851adcbc6fde9eea8e96f657418c062e16c82cd81cce44e8e" +checksum = "432d83e0facf16749f91fe729cbffca84437e8062d2f4e92f4f12e903693922d" dependencies = [ "as-any", "async-stream", @@ -5475,6 +5475,7 @@ dependencies = [ "schemars 1.2.2", "serde", "serde_json", + "sha2", "thiserror", "tokio", "tokio-tungstenite", @@ -5485,9 +5486,9 @@ dependencies = [ [[package]] name = "rig-derive" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb868fcebdf3ba425e3afad2e4926bb6d9e1188a856843b00bcee2e15c07424f" +checksum = "de0a33f1bac45f16e50146c248bcbbfaa44518c7252d274e972c7f4ad71aaba7" dependencies = [ "convert_case", "proc-macro-crate", @@ -5498,9 +5499,9 @@ dependencies = [ [[package]] name = "rig-fastembed" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ba93356242940918fac76a4b1cfe78b964f757e5cb65bb3feebd597635cfb0" +checksum = "f1dff99f9bea13108bb14df5431b6fc0738beb87f1fe5ebfcf26ae9fe54a188d" dependencies = [ "fastembed", "rig-core", @@ -5512,9 +5513,9 @@ dependencies = [ [[package]] name = "rig-helixdb" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d40ae8fa89af33793626b898ec527e246183ef8b4b5095afee5f6a65cbbb1a" +checksum = "b5fa2b188a1e9e65d29c34738c97465f0822c57540d6d45fd4f04dad906c966a" dependencies = [ "reqwest 0.13.4", "rig-core", @@ -5525,12 +5526,11 @@ dependencies = [ [[package]] name = "rig-lancedb" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f703b9e081ee4d77e60953598a5071a5cff4b8b702a2f06d251310950b0dae7c" +checksum = "c5fe3555202c5bd4e30948414c2e234fddd9260d4520046733fcf0d58718f3a1" dependencies = [ "arrow-array", - "deranged", "futures", "lancedb", "rig-core", @@ -5540,9 +5540,9 @@ dependencies = [ [[package]] name = "rig-milvus" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76662eb7f75de4e54b5f71e616430dc7f75d96d32f109d9435ad426b511eb565" +checksum = "af9c7b7ff7ed9c16da983a21f1e81f425c9805355427a92f4d2d97ba1fe2aa12" dependencies = [ "reqwest 0.13.4", "rig-core", @@ -6610,9 +6610,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", @@ -6799,9 +6799,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ "bytes", "data-encoding", @@ -6813,7 +6813,6 @@ dependencies = [ "rustls-pki-types", "sha1", "thiserror", - "utf-8", ] [[package]] @@ -6934,12 +6933,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8-ranges" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index 6fb0358..47af285 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ clap = { version = "4", features = ["derive"] } futures = "0.3" indicatif = "0.18.6" reqwest = { version = "0.13.4", features = ["query", "json"] } -rig = "0.41.0" +rig = "0.42.0" schemars = "1" scraper = "0.27" serde = { version = "1.0.229", features = ["derive"] } diff --git a/docs/case-study.md b/docs/case-study.md index 4b57357..99d3c6d 100644 --- a/docs/case-study.md +++ b/docs/case-study.md @@ -1,10 +1,9 @@ # Case study: hardening an agentic pipeline against real failure -This is a walkthrough of how `doubleo7`'s multi-agent pipeline went -from "works in the happy path" to actually resilient — driven by two real -failures it hit in normal use, not by imagining edge cases in the abstract. -The throughline: root-cause failures instead of papering over them, and -verify fixes against a live run, not just a green test suite. +This is a walkthrough of how `deep_research`'s multi-agent pipeline went +from "works in the happy path" to an improved resiliency — driven by two real failures +it hit in normal use. The throughline: root-cause failure debugging via manual testing and +post-fix verification. ## The starting point diff --git a/src/history.rs b/src/history.rs index 5eb75f4..d506fd6 100644 --- a/src/history.rs +++ b/src/history.rs @@ -149,11 +149,11 @@ mod tests { fn assistant_text_ignores_tool_calls() { let message = Message::Assistant { id: None, - content: rig::OneOrMany::one(AssistantContent::tool_call( + content: vec![AssistantContent::tool_call( "call-1", "search_web", - serde_json::json!({ "query": "test" }), - )), + serde_json::json!({ "query": "test"}), + )], }; assert!(extract_assistant_text(&message).is_empty()); } @@ -166,13 +166,13 @@ mod tests { #[test] fn tool_result_text_extracts_and_truncates() { - let short = Message::tool_result("call-1", "short result"); + let short = Message::tool_result("call-1", "call-1-name", "short result"); assert_eq!( tool_result_text(&short, 100), vec!["short result".to_string()] ); - let long = Message::tool_result("call-2", "0123456789"); + let long = Message::tool_result("call-2", "call-2-result", "0123456789"); assert_eq!( tool_result_text(&long, 5), vec!["01234 ...[truncated]".to_string()] @@ -191,11 +191,11 @@ mod tests { Message::system("preamble"), Message::Assistant { id: None, - content: rig::OneOrMany::one(AssistantContent::tool_call( + content: vec![AssistantContent::tool_call( "call-1", "search_web", serde_json::json!({ "query": "test" }), - )), + )], }, ]; @@ -209,9 +209,9 @@ mod tests { fn partial_findings_from_history_collects_assistant_text_and_tool_results_in_order() { let history = vec![ Message::assistant("Checking sources..."), - Message::tool_result("call-1", "Result A [1]"), + Message::tool_result("call-1", "call-1-name", "Result A [1]"), Message::assistant("Cross-checking..."), - Message::tool_result("call-2", "Result B [2]"), + Message::tool_result("call-2", "call-2-name", "Result B [2]"), ]; assert_eq!( @@ -233,11 +233,11 @@ mod tests { fn transcript_lines_renders_tool_calls_with_their_arguments() { let message = Message::Assistant { id: None, - content: rig::OneOrMany::one(AssistantContent::tool_call( + content: vec![AssistantContent::tool_call( "call-1", "search_web", serde_json::json!({ "query": "uruguay senior engineers" }), - )), + )], }; assert_eq!( transcript_lines(&message), @@ -247,7 +247,7 @@ mod tests { #[test] fn transcript_lines_prefixes_and_truncates_tool_results() { - let short = Message::tool_result("call-1", "found via search [1]"); + let short = Message::tool_result("call-1", "call-1-name", "found via search [1]"); assert_eq!( transcript_lines(&short), vec!["Result: found via search [1]".to_string()] @@ -264,13 +264,13 @@ mod tests { let history = vec![ Message::Assistant { id: None, - content: rig::OneOrMany::one(AssistantContent::tool_call( + content: vec![AssistantContent::tool_call( "call-1", "search_web", serde_json::json!({ "query": "test" }), - )), + )], }, - Message::tool_result("call-1", "1. Example\n https://example.com\n snippet"), + Message::tool_result("call-1", "call-1-name", "1. Example\n https://example.com\n snippet"), Message::assistant("that source looks solid"), ]; diff --git a/src/stream.rs b/src/stream.rs index 9e24127..6618ce3 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -18,14 +18,11 @@ use std::io::Write; /// covering the gap between the prompt being sent and generation starting /// (otherwise the terminal would go blank for however long that takes) /// rather than being dropped by the caller before this is even called. -pub(crate) async fn write_text_stream( - mut stream: impl Stream, StreamingError>> + Unpin, +pub(crate) async fn write_text_stream( + mut stream: impl Stream> + Unpin, writer: &mut impl Write, mut spinner: Spinner, -) -> anyhow::Result -where - R: Clone, -{ +) -> anyhow::Result { let mut text = String::new(); while let Some(chunk) = stream.next().await { @@ -50,10 +47,7 @@ mod tests { use rig::completion::CompletionError; use rig::message::Text; - #[derive(Clone)] - struct DummyResponse; - - fn text_item(text: &str) -> Result, StreamingError> { + fn text_item(text: &str) -> Result { Ok(MultiTurnStreamItem::StreamAssistantItem( StreamedAssistantContent::Text(Text::new(text)), )) @@ -76,7 +70,7 @@ mod tests { #[tokio::test] async fn ignores_non_text_items() { let final_item = Ok(MultiTurnStreamItem::final_response( - rig::OneOrMany::one(rig::message::AssistantContent::text("ignored")), + vec![rig::message::AssistantContent::text("ignored")], rig::completion::Usage::new(), )); let items = vec![text_item("kept"), final_item];