fix(botserver): Handle TOOL_EXEC message type for direct tool execution without KB/LLM
Some checks failed
BotServer CI/CD / build (push) Failing after 5m40s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-05 19:11:04 -03:00
parent 3684c862c6
commit 86bb4cad8e
4 changed files with 128 additions and 4 deletions

View file

@ -323,18 +323,19 @@ fn add_tool_suggestion(
let redis_key = format!("suggestions:{}:{}", user_session.bot_id, user_session.id); let redis_key = format!("suggestions:{}:{}", user_session.bot_id, user_session.id);
info!("Adding suggestion to Redis key: {}", redis_key); info!("Adding suggestion to Redis key: {}", redis_key);
// Create action object and serialize it to JSON string let prompt_for_params = params.is_some() && !params.as_ref().unwrap().is_empty();
let action_obj = json!({ let action_obj = json!({
"type": "invoke_tool", "type": "invoke_tool",
"tool": tool_name, "tool": tool_name,
"params": params, "params": params,
"prompt_for_params": params.is_none() "prompt_for_params": prompt_for_params
}); });
let action_str = action_obj.to_string();
let suggestion = json!({ let suggestion = json!({
"type": "invoke_tool",
"text": button_text, "text": button_text,
"action": action_str "tool": tool_name,
"action": action_obj
}); });
let mut conn = match get_redis_connection(cache_client) { let mut conn = match get_redis_connection(cache_client) {

View file

@ -589,6 +589,8 @@ impl ScriptService {
trimmed.starts_with("PARAM\t") || trimmed.starts_with("PARAM\t") ||
trimmed.starts_with("DESCRIPTION ") || trimmed.starts_with("DESCRIPTION ") ||
trimmed.starts_with("DESCRIPTION\t") || trimmed.starts_with("DESCRIPTION\t") ||
trimmed.starts_with("REM ") ||
trimmed.starts_with("REM\t") ||
trimmed.starts_with('\'') || // BASIC comment lines trimmed.starts_with('\'') || // BASIC comment lines
trimmed.starts_with('#') || // Hash comment lines trimmed.starts_with('#') || // Hash comment lines
trimmed.is_empty()) trimmed.is_empty())

View file

@ -448,6 +448,102 @@ impl BotOrchestrator {
let session_id = Uuid::parse_str(&message.session_id)?; let session_id = Uuid::parse_str(&message.session_id)?;
let message_content = message.content.clone(); let message_content = message.content.clone();
// Handle direct tool execution via TOOL_EXEC message type (invisible to user)
if message.message_type == MessageType::TOOL_EXEC {
let tool_name = message_content.trim();
if !tool_name.is_empty() {
info!("[TOOL_EXEC] Direct tool execution: {}", tool_name);
// Get bot name from bot_id
let bot_name = if let Ok(bot_uuid) = Uuid::parse_str(&message.bot_id) {
let conn = self.state.conn.get().ok();
conn.and_then(|mut db_conn| {
use crate::core::shared::models::schema::bots::dsl::*;
bots.filter(id.eq(bot_uuid))
.select(name)
.first::<String>(&mut db_conn)
.ok()
}).unwrap_or_else(|| "default".to_string())
} else {
"default".to_string()
};
let tool_result = ToolExecutor::execute_tool_by_name(
&self.state,
&bot_name,
tool_name,
&session_id,
&user_id,
).await;
let response_content = if tool_result.success {
tool_result.result
} else {
format!("Erro ao executar '{}': {}", tool_name, tool_result.error.unwrap_or_default())
};
let final_response = BotResponse {
bot_id: message.bot_id.clone(),
user_id: message.user_id.clone(),
session_id: message.session_id.clone(),
channel: message.channel.clone(),
content: response_content,
message_type: MessageType::BOT_RESPONSE,
stream_token: None,
is_complete: true,
suggestions: vec![],
context_name: None,
context_length: 0,
context_max_length: 0,
};
let _ = response_tx.send(final_response).await;
return Ok(());
}
}
// Legacy: Handle direct tool invocation via __TOOL__: prefix
if message_content.starts_with("__TOOL__:") {
let tool_name = message_content.trim_start_matches("__TOOL__:").trim();
if !tool_name.is_empty() {
info!("Direct tool invocation via WS: {}", tool_name);
let tool_result = ToolExecutor::execute_tool_by_name(
&self.state,
&message.bot_id,
tool_name,
&session_id,
&user_id,
).await;
let response_content = if tool_result.success {
tool_result.result
} else {
format!("Erro ao executar tool '{}': {}", tool_name, tool_result.error.unwrap_or_default())
};
let final_response = BotResponse {
bot_id: message.bot_id.clone(),
user_id: message.user_id.clone(),
session_id: message.session_id.clone(),
channel: message.channel.clone(),
content: response_content,
message_type: MessageType::BOT_RESPONSE,
stream_token: None,
is_complete: true,
suggestions: vec![],
context_name: None,
context_length: 0,
context_max_length: 0,
};
if let Err(e) = response_tx.send(final_response).await {
error!("Failed to send tool response: {}", e);
}
return Ok(());
}
}
// If a HEAR is blocking the script thread for this session, deliver the input // If a HEAR is blocking the script thread for this session, deliver the input
// directly and return — the script continues from where it paused. // directly and return — the script continues from where it paused.
if crate::basic::keywords::hearing::deliver_hear_input( if crate::basic::keywords::hearing::deliver_hear_input(
@ -675,6 +771,7 @@ impl BotOrchestrator {
return Ok(()); return Ok(());
} }
// Inject KB context for normal messages
if let Some(kb_manager) = self.state.kb_manager.as_ref() { if let Some(kb_manager) = self.state.kb_manager.as_ref() {
let context = crate::core::bot::kb_context::KbInjectionContext { let context = crate::core::bot::kb_context::KbInjectionContext {
session_id, session_id,

View file

@ -364,6 +364,30 @@ impl ToolExecutor {
// Fallback to source path for error messages (even if it doesn't exist) // Fallback to source path for error messages (even if it doesn't exist)
source_path source_path
} }
/// Execute a tool directly by name (without going through LLM)
pub async fn execute_tool_by_name(
state: &Arc<AppState>,
bot_name: &str,
tool_name: &str,
session_id: &Uuid,
user_id: &Uuid,
) -> ToolExecutionResult {
let tool_call_id = format!("direct_{}", Uuid::new_v4());
info!(
"[TOOL_EXEC] Direct tool invocation: '{}' for bot '{}', session '{}'",
tool_name, bot_name, session_id
);
let tool_call = ParsedToolCall {
id: tool_call_id.clone(),
tool_name: tool_name.to_string(),
arguments: Value::Object(serde_json::Map::new()),
};
Self::execute_tool_call(state, bot_name, &tool_call, session_id, user_id).await
}
} }
#[cfg(test)] #[cfg(test)]