reset filter suppression state between tool calls (still broken)

This commit is contained in:
Dhanji Prasanna
2025-10-15 21:15:24 +11:00
parent c9037ede22
commit beccc8fa15
3 changed files with 125 additions and 101 deletions

View File

@@ -4,8 +4,8 @@
// 3. Only elide JSON content between first '{' and last '}' (inclusive) // 3. Only elide JSON content between first '{' and last '}' (inclusive)
// 4. Return everything else as the final filtered string // 4. Return everything else as the final filtered string
use std::cell::RefCell;
use regex::Regex; use regex::Regex;
use std::cell::RefCell;
use tracing::debug; use tracing::debug;
// Thread-local state for tracking JSON tool call suppression // Thread-local state for tracking JSON tool call suppression
@@ -23,7 +23,6 @@ struct FixedJsonToolState {
} }
impl FixedJsonToolState { impl FixedJsonToolState {
fn new() -> Self { fn new() -> Self {
Self { Self {
suppression_mode: false, suppression_mode: false,
@@ -34,7 +33,6 @@ fn new() -> Self {
} }
} }
fn reset(&mut self) { fn reset(&mut self) {
self.suppression_mode = false; self.suppression_mode = false;
self.brace_depth = 0; self.brace_depth = 0;
@@ -70,7 +68,10 @@ pub fn fixed_filter_json_tool_calls(content: &str) -> String {
debug!("JSON tool call completed - exiting suppression mode"); debug!("JSON tool call completed - exiting suppression mode");
// Extract the complete result with JSON filtered out // Extract the complete result with JSON filtered out
let result = extract_fixed_content(&state.buffer, state.json_start_in_buffer.unwrap_or(0)); let result = extract_fixed_content(
&state.buffer,
state.json_start_in_buffer.unwrap_or(0),
);
// Return only the part we haven't returned yet // Return only the part we haven't returned yet
let new_content = if result.len() > state.content_returned_up_to { let new_content = if result.len() > state.content_returned_up_to {
@@ -92,7 +93,7 @@ pub fn fixed_filter_json_tool_calls(content: &str) -> String {
// Check for tool call pattern using corrected regex // Check for tool call pattern using corrected regex
// More flexible than the strict specification to handle real-world JSON // More flexible than the strict specification to handle real-world JSON
let tool_call_regex = Regex::new(r#"(?m)^.*\{\s*"tool"\s*:\s*""#).unwrap(); let tool_call_regex = Regex::new(r#"(?m)^\s*\{\s*"tool"\s*:\s*""#).unwrap();
if let Some(captures) = tool_call_regex.find(&state.buffer) { if let Some(captures) = tool_call_regex.find(&state.buffer) {
let match_text = captures.as_str(); let match_text = captures.as_str();
@@ -101,7 +102,10 @@ pub fn fixed_filter_json_tool_calls(content: &str) -> String {
if let Some(brace_offset) = match_text.find('{') { if let Some(brace_offset) = match_text.find('{') {
let json_start = captures.start() + brace_offset; let json_start = captures.start() + brace_offset;
debug!("Detected JSON tool call at position {} - entering suppression mode", json_start); debug!(
"Detected JSON tool call at position {} - entering suppression mode",
json_start
);
// Return content before JSON that we haven't returned yet // Return content before JSON that we haven't returned yet
let content_before_json = if json_start >= state.content_returned_up_to { let content_before_json = if json_start >= state.content_returned_up_to {
@@ -136,7 +140,8 @@ pub fn fixed_filter_json_tool_calls(content: &str) -> String {
"" ""
}; };
let final_result = format!("{}{}", content_before_json, content_after_json); let final_result =
format!("{}{}", content_before_json, content_after_json);
state.reset(); state.reset();
return final_result; return final_result;
} }

View File

@@ -33,7 +33,7 @@ Some text after"#;
"{\"tool\": \"", "{\"tool\": \"",
"shell\", \"args\": {", "shell\", \"args\": {",
"\"command\": \"ls\"", "\"command\": \"ls\"",
"}}\nText after" "}}\nText after",
]; ];
let mut results = Vec::new(); let mut results = Vec::new();
@@ -64,26 +64,48 @@ Text after"#;
#[test] #[test]
fn test_regex_pattern_specification() { fn test_regex_pattern_specification() {
// Test the corrected regex pattern that's more flexible with whitespace // Test the corrected regex pattern that's more flexible with whitespace
let pattern = Regex::new(r#"(?m)^.*\{\s*"tool"\s*:"#).unwrap(); let pattern = Regex::new(r#"(?m)^\s*\{\s*"tool"\s*:"#).unwrap();
let test_cases = vec![ let test_cases = vec![
(r#"line (
{"tool":"#, true), r#"line
(r#"line {"tool":"#,
{"tool" :"#, true), true,
(r#"line ),
{ "tool":"#, true), // Space after { DOES match with \s* (
(r#"line r#"line
abc{"tool":"#, true), {"tool" :"#,
(r#"line true,
{"tool123":"#, false), // "tool123" is not exactly "tool" ),
(r#"line (
{"tool" : "#, true), r#"line
{ "tool":"#,
true,
), // Space after { DOES match with \s*
(
r#"line
abc{"tool":"#,
true,
),
(
r#"line
{"tool123":"#,
false,
), // "tool123" is not exactly "tool"
(
r#"line
{"tool" : "#,
true,
),
]; ];
for (input, should_match) in test_cases { for (input, should_match) in test_cases {
let matches = pattern.is_match(input); let matches = pattern.is_match(input);
assert_eq!(matches, should_match, "Pattern matching failed for: {}", input); assert_eq!(
matches, should_match,
"Pattern matching failed for: {}",
input
);
} }
} }
@@ -264,18 +286,7 @@ More text"#;
// Test streaming with very small chunks // Test streaming with very small chunks
let chunks = vec![ let chunks = vec![
"Text\n", "Text\n", "{", "\"", "tool", "\"", ":", " ", "\"", "test", "\"", "}", "\nAfter",
"{",
"\"",
"tool",
"\"",
":",
" ",
"\"",
"test",
"\"",
"}",
"\nAfter"
]; ];
let mut results = Vec::new(); let mut results = Vec::new();
@@ -301,7 +312,7 @@ More text"#;
"{\"tool\": \"", "{\"tool\": \"",
"shell\", \"args\": {", "shell\", \"args\": {",
"\"command\": \"ls\"", "\"command\": \"ls\"",
"}}\nText after" "}}\nText after",
]; ];
let mut results = Vec::new(); let mut results = Vec::new();

View File

@@ -706,6 +706,10 @@ impl<W: UiWriter> Agent<W> {
show_timing: bool, show_timing: bool,
cancellation_token: CancellationToken, cancellation_token: CancellationToken,
) -> Result<TaskResult> { ) -> Result<TaskResult> {
// Reset the JSON tool call filter state at the start of each new task
// This prevents the filter from staying in suppression mode between user interactions
fixed_filter_json::reset_fixed_json_tool_state();
// Generate session ID based on the initial prompt if this is a new session // Generate session ID based on the initial prompt if this is a new session
if self.session_id.is_none() { if self.session_id.is_none() {
self.session_id = Some(self.generate_session_id(description)); self.session_id = Some(self.generate_session_id(description));
@@ -1635,6 +1639,10 @@ The tool will execute immediately and you'll receive the result (success or erro
} }
tool_executed = true; tool_executed = true;
// Reset the JSON tool call filter state after each tool execution
// This ensures the filter doesn't stay in suppression mode for subsequent streaming content
fixed_filter_json::reset_fixed_json_tool_state();
// Reset parser for next iteration // Reset parser for next iteration
parser.reset(); parser.reset();
// Clear current_response for next iteration to prevent buffered text // Clear current_response for next iteration to prevent buffered text