Aggregate suppressed tool-call log lines
The forced-text guard in consume_stream logged inside the per-delta loop, so one suppressed structured call could produce a warning per streamed argument fragment. Move the check to after tool calls are assembled, so it fires once per turn with the count and the names of what was dropped. consume_json's non-streaming path gets the same tool names added to its existing aggregate message. Co-Authored-By: siGit Code <noreply@sigit.si>
Seto Elkahfi committed
Sep 15, 2026 at 22:09 UTC
76e88483239ba1b135687dd83da1bbe3405d63ac
1 file changed
+27
-15
src/backend.rs
+27
-15
@@ -576,7 +576,15 @@ impl OpenAiBackend {
576
text = cleaned;
577
let suppressed = tool_calls.len() + recovered.len();
578
if suppressed > 0 {
579
- log::warn!("suppressed {suppressed} tool call(s) from a forced-text response");
579
+ let names = tool_calls
580
+ .iter()
581
+ .map(|call| call.name.as_str())
582
+ .chain(recovered.iter().map(|call| call.name.as_str()))
583
+ .collect::<Vec<_>>()
584
+ .join(", ");
585
+ log::warn!(
586
+ "suppressed {suppressed} tool call(s) from a forced-text response: {names}"
587
+ );
588
}
589
self.history
590
.lock()
@@ -717,17 +725,12 @@ impl OpenAiBackend {
725
"recovered tool call '{}' the model emitted as text instead of a structured call",
726
call.name
727
);
720
- recovered.push(ToolCall {
721
- id: format!("call_recovered_{}", recovered.len()),
722
- name: call.name,
723
- arguments: call.arguments,
724
- });
725
- } else {
726
- log::warn!(
727
- "suppressed inline tool call '{}' from a forced-text response",
728
- call.name
729
- );
728
}
729
+ recovered.push(ToolCall {
730
+ id: format!("call_recovered_{}", recovered.len()),
731
+ name: call.name,
732
+ arguments: call.arguments,
733
+ });
734
}
735
}
736
}
@@ -737,10 +740,6 @@ impl OpenAiBackend {
740
}
741
}
742
for delta in choice.delta.tool_calls.into_iter().flatten() {
740
- if !allow_tool_calls {
741
- log::warn!("suppressed a structured tool call from a forced-text response");
742
- continue;
743
- }
743
let index = delta.index.unwrap_or(0) as usize;
744
if tool_accum.len() <= index {
745
tool_accum.resize_with(index + 1, StreamingToolCall::default);
@@ -787,6 +786,19 @@ impl OpenAiBackend {
786
.collect();
787
tool_calls.extend(recovered);
788
789
+ if !allow_tool_calls && !tool_calls.is_empty() {
790
+ let names = tool_calls
791
+ .iter()
792
+ .map(|call| call.name.as_str())
793
+ .collect::<Vec<_>>()
794
+ .join(", ");
795
+ log::warn!(
796
+ "suppressed {} tool call(s) from a forced-text response: {names}",
797
+ tool_calls.len()
798
+ );
799
+ tool_calls.clear();
800
+ }
801
+
802
// Record the assistant turn so later tool results have context.
803
self.history
804
.lock()