Skip to content

Commit 33c3bf1

Browse files
committed
fix: capture text from intermediate tool_use iterations
RightNow-AI#989
1 parent 994788a commit 33c3bf1

1 file changed

Lines changed: 69 additions & 23 deletions

File tree

crates/openfang-runtime/src/agent_loop.rs

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,11 @@ pub async fn run_agent_loop(
333333
let mut total_usage = TokenUsage::default();
334334
let final_response;
335335

336+
// Accumulate text from intermediate iterations (tool_use turns may include text
337+
// alongside tool calls — this text would otherwise be lost when the final
338+
// EndTurn iteration has empty text).
339+
let mut accumulated_text = String::new();
340+
336341
// Safety valve: trim excessively long message histories to prevent context overflow.
337342
// The full compaction system handles sophisticated summarization, but this prevents
338343
// the catastrophic case where 200+ messages cause instant context overflow.
@@ -518,19 +523,29 @@ pub async fn run_agent_loop(
518523
}
519524

520525
// Guard against empty response — covers both iteration 0 and post-tool cycles
526+
// Use accumulated_text from intermediate tool_use iterations as fallback.
521527
let text = if text.trim().is_empty() {
522-
warn!(
523-
agent = %manifest.name,
524-
iteration,
525-
input_tokens = total_usage.input_tokens,
526-
output_tokens = total_usage.output_tokens,
527-
messages_count = messages.len(),
528-
"Empty response from LLM — guard activated"
529-
);
530-
if any_tools_executed {
531-
"[Task completed — the agent executed tools but did not produce a text summary.]".to_string()
528+
if !accumulated_text.is_empty() {
529+
debug!(
530+
agent = %manifest.name,
531+
accumulated_len = accumulated_text.len(),
532+
"Using accumulated text from intermediate tool_use iterations"
533+
);
534+
accumulated_text.clone()
532535
} else {
533-
"[The model returned an empty response. This usually means the model is overloaded, the context is too large, or the API key lacks credits. Try again or check /status.]".to_string()
536+
warn!(
537+
agent = %manifest.name,
538+
iteration,
539+
input_tokens = total_usage.input_tokens,
540+
output_tokens = total_usage.output_tokens,
541+
messages_count = messages.len(),
542+
"Empty response from LLM — guard activated"
543+
);
544+
if any_tools_executed {
545+
"[Task completed — the agent executed tools but did not produce a text summary.]".to_string()
546+
} else {
547+
"[The model returned an empty response. This usually means the model is overloaded, the context is too large, or the API key lacks credits. Try again or check /status.]".to_string()
548+
}
534549
}
535550
} else {
536551
text
@@ -651,6 +666,18 @@ pub async fn run_agent_loop(
651666
consecutive_max_tokens = 0;
652667
any_tools_executed = true;
653668

669+
// Capture any text content from this tool_use turn — the LLM may
670+
// produce text alongside tool calls (e.g., a message to the user
671+
// before calling memory_store). Without this, the text is lost if
672+
// the next iteration returns EndTurn with empty text.
673+
let intermediate_text = response.text();
674+
if !intermediate_text.trim().is_empty() {
675+
if !accumulated_text.is_empty() {
676+
accumulated_text.push_str("\n\n");
677+
}
678+
accumulated_text.push_str(intermediate_text.trim());
679+
}
680+
654681
// Execute tool calls
655682
let assistant_blocks = response.content.clone();
656683

@@ -1352,6 +1379,7 @@ pub async fn run_agent_loop_streaming(
13521379

13531380
let mut total_usage = TokenUsage::default();
13541381
let final_response;
1382+
let mut accumulated_text = String::new();
13551383

13561384
// Safety valve: trim excessively long message histories to prevent context overflow.
13571385
if messages.len() > MAX_HISTORY_MESSAGES {
@@ -1549,20 +1577,29 @@ pub async fn run_agent_loop_streaming(
15491577
}
15501578
}
15511579

1552-
// Guard against empty response — covers both iteration 0 and post-tool cycles
1580+
// Guard against empty response — use accumulated text as fallback (streaming).
15531581
let text = if text.trim().is_empty() {
1554-
warn!(
1555-
agent = %manifest.name,
1556-
iteration,
1557-
input_tokens = total_usage.input_tokens,
1558-
output_tokens = total_usage.output_tokens,
1559-
messages_count = messages.len(),
1560-
"Empty response from LLM (streaming) — guard activated"
1561-
);
1562-
if any_tools_executed {
1563-
"[Task completed — the agent executed tools but did not produce a text summary.]".to_string()
1582+
if !accumulated_text.is_empty() {
1583+
debug!(
1584+
agent = %manifest.name,
1585+
accumulated_len = accumulated_text.len(),
1586+
"Using accumulated text from intermediate tool_use iterations (streaming)"
1587+
);
1588+
accumulated_text.clone()
15641589
} else {
1565-
"[The model returned an empty response. This usually means the model is overloaded, the context is too large, or the API key lacks credits. Try again or check /status.]".to_string()
1590+
warn!(
1591+
agent = %manifest.name,
1592+
iteration,
1593+
input_tokens = total_usage.input_tokens,
1594+
output_tokens = total_usage.output_tokens,
1595+
messages_count = messages.len(),
1596+
"Empty response from LLM (streaming) — guard activated"
1597+
);
1598+
if any_tools_executed {
1599+
"[Task completed — the agent executed tools but did not produce a text summary.]".to_string()
1600+
} else {
1601+
"[The model returned an empty response. This usually means the model is overloaded, the context is too large, or the API key lacks credits. Try again or check /status.]".to_string()
1602+
}
15661603
}
15671604
} else {
15681605
text
@@ -1662,6 +1699,15 @@ pub async fn run_agent_loop_streaming(
16621699
consecutive_max_tokens = 0;
16631700
any_tools_executed = true;
16641701

1702+
// Capture text from intermediate tool_use turns (streaming path).
1703+
let intermediate_text = response.text();
1704+
if !intermediate_text.trim().is_empty() {
1705+
if !accumulated_text.is_empty() {
1706+
accumulated_text.push_str("\n\n");
1707+
}
1708+
accumulated_text.push_str(intermediate_text.trim());
1709+
}
1710+
16651711
let assistant_blocks = response.content.clone();
16661712

16671713
session.messages.push(Message {

0 commit comments

Comments
 (0)