fix: prevent duplicate message in chat when tool is executed
- Track tool_was_executed flag in stream_response - Send empty content in final is_complete message when tool already sent results - Prevents the LLM's pre-tool text from appearing twice in the chat UI - DB message saving is unaffected (uses full_response_clone before the check)
This commit is contained in:
parent
b1118f977d
commit
3b21ab5ef9
2 changed files with 36 additions and 7 deletions
|
|
@ -1410,6 +1410,9 @@ impl ScriptService {
|
||||||
/// Transforms: SELECT var ... CASE "value" ... END SELECT
|
/// Transforms: SELECT var ... CASE "value" ... END SELECT
|
||||||
/// Into: if var == "value" { ... } else if var == "value2" { ... }
|
/// Into: if var == "value" { ... } else if var == "value2" { ... }
|
||||||
/// Note: We use if-else instead of match because 'match' is a reserved keyword in Rhai
|
/// Note: We use if-else instead of match because 'match' is a reserved keyword in Rhai
|
||||||
|
///
|
||||||
|
/// IMPORTANT: This function strips 'let ' keywords from assignment statements inside CASE blocks
|
||||||
|
/// to avoid creating local variables that shadow outer scope variables.
|
||||||
pub fn convert_select_case_syntax(script: &str) -> String {
|
pub fn convert_select_case_syntax(script: &str) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut lines: Vec<&str> = script.lines().collect();
|
let mut lines: Vec<&str> = script.lines().collect();
|
||||||
|
|
@ -1417,6 +1420,20 @@ impl ScriptService {
|
||||||
|
|
||||||
log::info!("[TOOL] Converting SELECT/CASE syntax to if-else chains");
|
log::info!("[TOOL] Converting SELECT/CASE syntax to if-else chains");
|
||||||
|
|
||||||
|
// Helper function to strip 'let ' from the beginning of a line
|
||||||
|
// This is needed because convert_if_then_syntax adds 'let' to all assignments,
|
||||||
|
// but inside CASE blocks we want to modify outer variables, not create new ones
|
||||||
|
fn strip_let_from_assignment(line: &str) -> String {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
let trimmed_lower = trimmed.to_lowercase();
|
||||||
|
if trimmed_lower.starts_with("let ") && trimmed.contains('=') {
|
||||||
|
// This is a 'let' assignment - strip the 'let ' keyword
|
||||||
|
trimmed[4..].trim().to_string()
|
||||||
|
} else {
|
||||||
|
trimmed.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while i < lines.len() {
|
while i < lines.len() {
|
||||||
let trimmed = lines[i].trim();
|
let trimmed = lines[i].trim();
|
||||||
let upper = trimmed.to_uppercase();
|
let upper = trimmed.to_uppercase();
|
||||||
|
|
@ -1450,9 +1467,11 @@ impl ScriptService {
|
||||||
if in_case {
|
if in_case {
|
||||||
for body_line in ¤t_case_body {
|
for body_line in ¤t_case_body {
|
||||||
result.push_str(" ");
|
result.push_str(" ");
|
||||||
result.push_str(body_line);
|
// Strip 'let ' from assignments to avoid creating local variables
|
||||||
|
let processed_line = strip_let_from_assignment(body_line);
|
||||||
|
result.push_str(&processed_line);
|
||||||
// Add semicolon if line doesn't have one
|
// Add semicolon if line doesn't have one
|
||||||
if !body_line.ends_with(';') && !body_line.ends_with('{') && !body_line.ends_with('}') {
|
if !processed_line.ends_with(';') && !processed_line.ends_with('{') && !processed_line.ends_with('}') {
|
||||||
result.push(';');
|
result.push(';');
|
||||||
}
|
}
|
||||||
result.push('\n');
|
result.push('\n');
|
||||||
|
|
@ -1471,9 +1490,11 @@ impl ScriptService {
|
||||||
if in_case {
|
if in_case {
|
||||||
for body_line in ¤t_case_body {
|
for body_line in ¤t_case_body {
|
||||||
result.push_str(" ");
|
result.push_str(" ");
|
||||||
result.push_str(body_line);
|
// Strip 'let ' from assignments to avoid creating local variables
|
||||||
|
let processed_line = strip_let_from_assignment(body_line);
|
||||||
|
result.push_str(&processed_line);
|
||||||
// Add semicolon if line doesn't have one
|
// Add semicolon if line doesn't have one
|
||||||
if !body_line.ends_with(';') && !body_line.ends_with('{') && !body_line.ends_with('}') {
|
if !processed_line.ends_with(';') && !processed_line.ends_with('{') && !processed_line.ends_with('}') {
|
||||||
result.push(';');
|
result.push(';');
|
||||||
}
|
}
|
||||||
result.push('\n');
|
result.push('\n');
|
||||||
|
|
@ -1490,9 +1511,11 @@ impl ScriptService {
|
||||||
if in_case {
|
if in_case {
|
||||||
for body_line in ¤t_case_body {
|
for body_line in ¤t_case_body {
|
||||||
result.push_str(" ");
|
result.push_str(" ");
|
||||||
result.push_str(body_line);
|
// Strip 'let ' from assignments to avoid creating local variables
|
||||||
|
let processed_line = strip_let_from_assignment(body_line);
|
||||||
|
result.push_str(&processed_line);
|
||||||
// Add semicolon if line doesn't have one
|
// Add semicolon if line doesn't have one
|
||||||
if !body_line.ends_with(';') && !body_line.ends_with('{') && !body_line.ends_with('}') {
|
if !processed_line.ends_with(';') && !processed_line.ends_with('{') && !processed_line.ends_with('}') {
|
||||||
result.push(';');
|
result.push(';');
|
||||||
}
|
}
|
||||||
result.push('\n');
|
result.push('\n');
|
||||||
|
|
|
||||||
|
|
@ -654,6 +654,7 @@ impl BotOrchestrator {
|
||||||
let mut in_analysis = false;
|
let mut in_analysis = false;
|
||||||
let mut tool_call_buffer = String::new(); // Accumulate potential tool call JSON chunks
|
let mut tool_call_buffer = String::new(); // Accumulate potential tool call JSON chunks
|
||||||
let mut accumulating_tool_call = false; // Track if we're currently accumulating a tool call
|
let mut accumulating_tool_call = false; // Track if we're currently accumulating a tool call
|
||||||
|
let mut tool_was_executed = false; // Track if a tool was executed to avoid duplicate final message
|
||||||
let handler = llm_models::get_handler(&model);
|
let handler = llm_models::get_handler(&model);
|
||||||
|
|
||||||
info!("[STREAM_START] Entering stream processing loop for model: {}", model);
|
info!("[STREAM_START] Entering stream processing loop for model: {}", model);
|
||||||
|
|
@ -835,6 +836,7 @@ impl BotOrchestrator {
|
||||||
// Clear the tool_call_buffer since we found and executed a tool call
|
// Clear the tool_call_buffer since we found and executed a tool call
|
||||||
tool_call_buffer.clear();
|
tool_call_buffer.clear();
|
||||||
accumulating_tool_call = false; // Reset accumulation flag
|
accumulating_tool_call = false; // Reset accumulation flag
|
||||||
|
tool_was_executed = true; // Mark that a tool was executed
|
||||||
// Continue to next chunk
|
// Continue to next chunk
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1004,12 +1006,16 @@ impl BotOrchestrator {
|
||||||
#[cfg(not(feature = "chat"))]
|
#[cfg(not(feature = "chat"))]
|
||||||
let suggestions: Vec<crate::core::shared::models::Suggestion> = Vec::new();
|
let suggestions: Vec<crate::core::shared::models::Suggestion> = Vec::new();
|
||||||
|
|
||||||
|
// When a tool was executed, the content was already sent as streaming chunks
|
||||||
|
// (pre-tool text + tool result). Sending full_response again would duplicate it.
|
||||||
|
let final_content = if tool_was_executed { String::new() } else { full_response };
|
||||||
|
|
||||||
let final_response = BotResponse {
|
let final_response = BotResponse {
|
||||||
bot_id: message.bot_id,
|
bot_id: message.bot_id,
|
||||||
user_id: message.user_id,
|
user_id: message.user_id,
|
||||||
session_id: message.session_id,
|
session_id: message.session_id,
|
||||||
channel: message.channel,
|
channel: message.channel,
|
||||||
content: full_response,
|
content: final_content,
|
||||||
message_type: MessageType::BOT_RESPONSE,
|
message_type: MessageType::BOT_RESPONSE,
|
||||||
stream_token: None,
|
stream_token: None,
|
||||||
is_complete: true,
|
is_complete: true,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue