Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
use super::model_exchange_trace::prepare_model_exchange_trace;
use super::stream_processor::{StreamProcessOptions, StreamProcessor, StreamResult};
use super::types::{FinishReason, RoundContext, RoundResult};
use super::write_content_sanitizer::{
contains_tool_invocation_artifacts, strip_tool_invocation_artifacts,
};
use crate::agentic::core::{Message, ToolCall};
use crate::agentic::events::{
AgenticEvent, EventPriority, EventQueue, ModelRoundAttemptDiagnostic,
Expand Down Expand Up @@ -1180,6 +1183,12 @@ impl RoundExecutor {
let parsed_memory_citation =
Self::parsed_memory_citation_from_stream_result(&stream_result);
let (clean_text, _) = strip_bitfun_memory_citations(&stream_result.full_text);
let clean_text = if contains_tool_invocation_artifacts(&clean_text) {
warn!("Detected tool invocation artifacts in assistant text, stripping to prevent context explosion");
strip_tool_invocation_artifacts(&clean_text)
} else {
clean_text
};
let assistant_message =
Message::assistant_with_reasoning(reasoning, clean_text, tool_calls.clone())
.with_turn_id(context.dialog_turn_id.clone())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,27 @@ mod tests {
"export const value = 1;"
);
}

/// Regression test for issue #1492: when a model leaks `<tool_calls>` XML as
/// plain text content instead of structured tool-call deltas, the assistant
/// text must be detected and stripped so the artifacts do not pollute the
/// next round's context and cause infinite recursion.
#[test]
fn strips_leaked_tool_calls_xml_from_assistant_text() {
let leaked = concat!(
"I'll help you with that.\n",
"<tool_calls>\n",
"<invoke name=\"Read\">\n",
"<parameter name=\"file_path\">src/main.rs</parameter>\n",
"</invoke>\n",
"</tool_calls>\n",
"Let me read the file first."
);
assert!(contains_tool_invocation_artifacts(leaked));
let stripped = strip_tool_invocation_artifacts(leaked);
assert!(!stripped.contains("<tool_calls"));
assert!(!stripped.contains("<invoke"));
assert!(stripped.contains("I'll help you with that."));
assert!(stripped.contains("Let me read the file first."));
}
}