From 23039cee90fc9334feb7014f2d4e81c947638aea Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 25 Sep 2026 17:50:48 +0530 Subject: [PATCH 1/2] test: cover greeting to deferred email tool continuation --- .../src/agent_loop/dialect/test.rs | 5 +- .../tests/e2e_tool_dialects.rs | 104 +++++++++++++++++- vendor/tinyinference | 2 +- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/crates/tinyagents-harness/src/agent_loop/dialect/test.rs b/crates/tinyagents-harness/src/agent_loop/dialect/test.rs index 5506136e..0c04526f 100644 --- a/crates/tinyagents-harness/src/agent_loop/dialect/test.rs +++ b/crates/tinyagents-harness/src/agent_loop/dialect/test.rs @@ -41,7 +41,10 @@ fn text_dialect_keeps_latest_request_after_a_tool_search_result() { dialect.apply_to_request(&mut request, true, &[]); assert!( - request.messages[request.messages.len() - 2] + request + .messages + .last() + .unwrap() .text() .contains("GMAIL_FETCH_EMAILS") ); diff --git a/crates/tinyagents-integration-tests/tests/e2e_tool_dialects.rs b/crates/tinyagents-integration-tests/tests/e2e_tool_dialects.rs index e1bdb27a..9fd2c0c6 100644 --- a/crates/tinyagents-integration-tests/tests/e2e_tool_dialects.rs +++ b/crates/tinyagents-integration-tests/tests/e2e_tool_dialects.rs @@ -25,7 +25,7 @@ use tinyinference_llm::model::{ }; use tinyinference_llm::providers::MockModel; use tinyinference_llm::tool::ToolCall; -use tinytools::{Tool, ToolResult}; +use tinytools::{Tool, ToolExposure, ToolResult}; struct CaptureMiddleware { listener: Arc, @@ -108,6 +108,108 @@ fn harness_with( harness } +/// Mimics the failure in a real conversation: a greeting precedes a request +/// to fetch email, and a discovered tool must still be invoked after search. +struct EmailContinuationModel { + calls: Mutex, +} + +#[async_trait] +impl ChatModel<()> for EmailContinuationModel { + async fn invoke( + &self, + _state: &(), + request: ModelRequest, + ) -> tinyinference_llm::Result { + let mut calls = self.calls.lock().unwrap(); + let response = match *calls { + 0 => "tool_search(query=\"fetch emails from Gmail\")", + 1 => { + let latest_user = request + .messages + .iter() + .rev() + .find(|message| matches!(message, Message::User(_))) + .map(Message::text) + .unwrap_or_default(); + if latest_user.contains("fetch my latest email") + && latest_user.contains("GMAIL_FETCH_EMAILS") + { + "tool_call(name=\"GMAIL_FETCH_EMAILS\", arguments=\"{}\")" + } else { + "Hey! What's up?" + } + } + _ => "Email fetched.", + }; + *calls += 1; + Ok(ModelResponse::assistant(response)) + } +} + +struct DeferredEmail { + calls: Mutex, +} + +#[async_trait] +impl Tool for DeferredEmail { + fn name(&self) -> &str { + "GMAIL_FETCH_EMAILS" + } + + fn description(&self) -> &str { + "Fetch email messages from Gmail." + } + + fn parameters_schema(&self) -> serde_json::Value { + json!({"type": "object", "properties": {}}) + } + + fn exposure(&self) -> ToolExposure { + ToolExposure::Deferred + } + + async fn execute(&self, _arguments: serde_json::Value) -> anyhow::Result { + *self.calls.lock().unwrap() += 1; + Ok(ToolResult::success("one email found")) + } +} + +#[tokio::test] +async fn a_greeting_does_not_replace_the_email_request_after_tool_search() { + let model = Arc::new(EmailContinuationModel { + calls: Mutex::new(0), + }); + let email = Arc::new(DeferredEmail { + calls: Mutex::new(0), + }); + let mut harness: AgentHarness<()> = AgentHarness::new(); + harness + .register_model("mock", model.clone()) + .set_default_model("mock") + .register_tool(email.clone()) + .with_policy(RunPolicy { + tool_dialect: ToolDispatcher::Python, + ..RunPolicy::default() + }); + + let run = harness + .invoke_default( + &(), + vec![ + Message::user("Hi"), + Message::assistant("Hey! What's up?"), + Message::user("fetch my latest email"), + ], + ) + .await + .expect("email continuation completes"); + + assert_eq!(*email.calls.lock().unwrap(), 1, "deferred email tool ran"); + assert_eq!(*model.calls.lock().unwrap(), 3, "search, fetch, answer"); + assert_eq!(run.text().as_deref(), Some("Email fetched.")); +} + #[tokio::test] async fn a_native_model_narrating_a_call_in_any_grammar_dispatches_it() { for text in [ diff --git a/vendor/tinyinference b/vendor/tinyinference index e39f8ab4..71d3d2d3 160000 --- a/vendor/tinyinference +++ b/vendor/tinyinference @@ -1 +1 @@ -Subproject commit e39f8ab4b2d051b61bced9428d9b2a4c6c4b7f25 +Subproject commit 71d3d2d350fb997861ec59ceed9eabf9a91f0d82 From 081355fb526223d9de586a27efcd6297e374233b Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 25 Sep 2026 17:58:15 +0530 Subject: [PATCH 2/2] chore: pin preserved tool result continuation --- vendor/tinyinference | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/tinyinference b/vendor/tinyinference index 71d3d2d3..6aff95c5 160000 --- a/vendor/tinyinference +++ b/vendor/tinyinference @@ -1 +1 @@ -Subproject commit 71d3d2d350fb997861ec59ceed9eabf9a91f0d82 +Subproject commit 6aff95c549357d082f27bb32e492994df379c5d6