15 KiB
Tool Management System
Overview
The Bot Server now supports multiple tool associations per user session. This allows users to dynamically load, manage, and use multiple BASIC tools during a single conversation without needing to restart or change sessions.
Features
- Multiple Tools per Session: Associate multiple compiled BASIC tools with a single conversation
- Dynamic Management: Add or remove tools on-the-fly during a conversation
- Session Isolation: Each session has its own independent set of active tools
- Persistent Associations: Tool associations are stored in the database and survive across requests
- Real Database Implementation: No SQL placeholders - fully implemented with Diesel ORM
Database Schema
session_tool_associations Table
CREATE TABLE IF NOT EXISTS session_tool_associations (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
added_at TEXT NOT NULL,
UNIQUE(session_id, tool_name)
);
Indexes:
idx_session_tool_sessiononsession_ididx_session_tool_nameontool_name
The UNIQUE constraint ensures a tool cannot be added twice to the same session.
BASIC Keywords
ADD_TOOL
Adds a compiled tool to the current session, making it available for the LLM to call.
Syntax:
ADD_TOOL "<path_to_tool>"
Example:
ADD_TOOL ".gbdialog/enrollment.bas"
ADD_TOOL ".gbdialog/payment.bas"
ADD_TOOL ".gbdialog/support.bas"
Behavior:
- Validates that the tool exists in the
basic_toolstable - Verifies the tool is active (
is_active = 1) - Checks the tool belongs to the current bot
- Inserts into
session_tool_associationstable - Returns success message or error if tool doesn't exist
- If tool is already associated, reports it's already active
Returns:
- Success:
"Tool 'enrollment' is now available in this conversation" - Already added:
"Tool 'enrollment' is already available in this conversation" - Error:
"Tool 'enrollment' is not available. Make sure the tool file is compiled and active."
REMOVE_TOOL
Removes a tool association from the current session.
Syntax:
REMOVE_TOOL "<path_to_tool>"
Example:
REMOVE_TOOL ".gbdialog/support.bas"
Behavior:
- Removes the tool from
session_tool_associationsfor this session - Does not delete the compiled tool itself
- Only affects the current session
Returns:
- Success:
"Tool 'support' has been removed from this conversation" - Not found:
"Tool 'support' was not active in this conversation"
CLEAR_TOOLS
Removes all tool associations from the current session.
Syntax:
CLEAR_TOOLS
Example:
CLEAR_TOOLS
Behavior:
- Removes all entries in
session_tool_associationsfor this session - Does not affect other sessions
- Does not delete compiled tools
Returns:
- Success:
"All 3 tool(s) have been removed from this conversation" - No tools:
"No tools were active in this conversation"
LIST_TOOLS
Lists all tools currently associated with the session.
Syntax:
LIST_TOOLS
Example:
LIST_TOOLS
Output:
Active tools in this conversation (3):
1. enrollment
2. payment
3. analytics
Returns:
- With tools: Lists all active tools with numbering
- No tools:
"No tools are currently active in this conversation"
How It Works
Tool Loading Flow
-
User calls
ADD_TOOLin BASIC scriptADD_TOOL ".gbdialog/enrollment.bas" -
System validates tool exists
- Queries
basic_toolstable - Checks
bot_idmatches current bot - Verifies
is_active = 1
- Queries
-
Association is created
- Inserts into
session_tool_associations - Uses UNIQUE constraint to prevent duplicates
- Stores session_id, tool_name, and timestamp
- Inserts into
-
LLM requests include tools
- When processing prompts, system loads all tools from
session_tool_associations - Tools are added to the LLM's available function list
- LLM can now call any associated tool
- When processing prompts, system loads all tools from
Integration with Prompt Processor
The PromptProcessor::get_available_tools() method now:
- Loads tool stack from bot configuration (existing behavior)
- NEW: Queries
session_tool_associationsfor the current session - Adds all associated tools to the available tools list
- Maintains backward compatibility with legacy
current_toolfield
Code Example:
// From src/context/prompt_processor.rs
if let Ok(mut conn) = self.state.conn.lock() {
match get_session_tools(&mut *conn, &session.id) {
Ok(session_tools) => {
for tool_name in session_tools {
if !tools.iter().any(|t| t.tool_name == tool_name) {
tools.push(ToolContext {
tool_name: tool_name.clone(),
description: format!("Tool: {}", tool_name),
endpoint: format!("/default/{}", tool_name),
});
}
}
}
Err(e) => error!("Failed to load session tools: {}", e),
}
}
Rust API
Public Functions
All functions are in botserver/src/basic/keywords/add_tool.rs:
/// Get all tools associated with a session
pub fn get_session_tools(
conn: &mut PgConnection,
session_id: &Uuid,
) -> Result<Vec<String>, diesel::result::Error>
/// Remove a tool association from a session
pub fn remove_session_tool(
conn: &mut PgConnection,
session_id: &Uuid,
tool_name: &str,
) -> Result<usize, diesel::result::Error>
/// Clear all tool associations for a session
pub fn clear_session_tools(
conn: &mut PgConnection,
session_id: &Uuid,
) -> Result<usize, diesel::result::Error>
Usage Example:
use crate::basic::keywords::add_tool::get_session_tools;
let tools = get_session_tools(&mut conn, &session_id)?;
for tool_name in tools {
println!("Active tool: {}", tool_name);
}
Use Cases
1. Progressive Tool Loading
Start with basic tools and add more as needed:
REM Start with customer service tool
ADD_TOOL ".gbdialog/customer_service.bas"
REM If user needs technical support, add that tool
IF user_needs_technical_support THEN
ADD_TOOL ".gbdialog/technical_support.bas"
END IF
REM If billing question, add payment tool
IF user_asks_about_billing THEN
ADD_TOOL ".gbdialog/billing.bas"
END IF
2. Context-Aware Tool Management
Different tools for different conversation stages:
REM Initial greeting phase
ADD_TOOL ".gbdialog/greeting.bas"
HEAR "start"
REM Main interaction phase
REMOVE_TOOL ".gbdialog/greeting.bas"
ADD_TOOL ".gbdialog/enrollment.bas"
ADD_TOOL ".gbdialog/faq.bas"
HEAR "continue"
REM Closing phase
CLEAR_TOOLS
ADD_TOOL ".gbdialog/feedback.bas"
HEAR "finish"
3. Department-Specific Tools
Route to different tool sets based on department:
GET "/api/user/department" AS department
IF department = "sales" THEN
ADD_TOOL ".gbdialog/lead_capture.bas"
ADD_TOOL ".gbdialog/quote_generator.bas"
ADD_TOOL ".gbdialog/crm_integration.bas"
ELSE IF department = "support" THEN
ADD_TOOL ".gbdialog/ticket_system.bas"
ADD_TOOL ".gbdialog/knowledge_base.bas"
ADD_TOOL ".gbdialog/escalation.bas"
END IF
4. A/B Testing Tools
Test different tool combinations:
GET "/api/user/experiment_group" AS group
IF group = "A" THEN
ADD_TOOL ".gbdialog/tool_variant_a.bas"
ELSE
ADD_TOOL ".gbdialog/tool_variant_b.bas"
END IF
REM Both groups get common tools
ADD_TOOL ".gbdialog/common_tools.bas"
Answer Modes
The system respects the session's answer_mode:
- Mode 0 (Direct): No tools used
- Mode 1 (WithTools): Uses associated tools + legacy
current_tool - Mode 2 (DocumentsOnly): Only KB documents, no tools
- Mode 3 (WebSearch): Web search enabled
- Mode 4 (Mixed): Tools from
session_tool_associations+ KB documents
Set answer mode via session configuration or dynamically.
Best Practices
1. Validate Before Use
Always check if a tool is successfully added:
ADD_TOOL ".gbdialog/payment.bas"
LIST_TOOLS REM Verify it was added
2. Clean Up When Done
Remove tools that are no longer needed to improve LLM performance:
REMOVE_TOOL ".gbdialog/onboarding.bas"
3. Use LIST_TOOLS for Debugging
When developing, list tools to verify state:
LIST_TOOLS
PRINT "Current tools listed above"
4. Tool Names are Simple
Tool names are extracted from paths automatically:
.gbdialog/enrollment.bas→enrollmentpayment.bas→payment
5. Session Isolation
Each session maintains its own tool list. Tools added in one session don't affect others.
6. Compile Before Adding
Ensure tools are compiled and present in the basic_tools table before attempting to add them. The DriveMonitor service handles compilation automatically when .bas files are saved.
Migration Guide
Upgrading from Single Tool (current_tool)
Before (Legacy):
// Single tool stored in session.current_tool
session.current_tool = Some("enrollment".to_string());
After (Multi-Tool):
ADD_TOOL ".gbdialog/enrollment.bas"
ADD_TOOL ".gbdialog/payment.bas"
ADD_TOOL ".gbdialog/support.bas"
Backward Compatibility:
The system still supports the legacy current_tool field. If set, it will be included in the available tools list alongside tools from session_tool_associations.
Technical Implementation Details
Database Operations
All operations use Diesel ORM with proper error handling:
// Insert with conflict resolution
diesel::insert_into(session_tool_associations::table)
.values((/* ... */))
.on_conflict((session_id, tool_name))
.do_nothing()
.execute(&mut *conn)
// Delete specific tool
diesel::delete(
session_tool_associations::table
.filter(session_id.eq(&session_id_str))
.filter(tool_name.eq(tool_name))
).execute(&mut *conn)
// Load all tools
session_tool_associations::table
.filter(session_id.eq(&session_id_str))
.select(tool_name)
.load::<String>(&mut *conn)
Thread Safety
All operations use Arc<Mutex> for thread-safe database access:
let mut conn = state.conn.lock().map_err(|e| {
error!("Failed to acquire database lock: {}", e);
format!("Database connection error: {}", e)
})?;
Async Execution
Keywords spawn async tasks using Tokio runtime to avoid blocking the Rhai engine:
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build();
// ... execute async operation
});
Error Handling
Common Errors
-
Tool Not Found
- Message:
"Tool 'xyz' is not available. Make sure the tool file is compiled and active." - Cause: Tool doesn't exist in
basic_toolsor is inactive - Solution: Compile the tool or check bot_id matches
- Message:
-
Database Lock Error
- Message:
"Database connection error: ..." - Cause: Failed to acquire database mutex
- Solution: Check database connection health
- Message:
-
Timeout
- Message:
"ADD_TOOL timed out" - Cause: Operation took longer than 10 seconds
- Solution: Check database performance
- Message:
Error Recovery
All operations are atomic - if they fail, no partial state is committed:
ADD_TOOL ".gbdialog/nonexistent.bas"
REM Error returned, no changes made
LIST_TOOLS
REM Still shows previous tools only
Performance Considerations
Database Indexes
The following indexes ensure fast lookups:
idx_session_tool_session: Fast retrieval of all tools for a sessionidx_session_tool_name: Fast tool name lookups- UNIQUE constraint on (session_id, tool_name): Prevents duplicates
Query Optimization
Tools are loaded once per prompt processing:
// Efficient batch load
let tools = get_session_tools(&mut conn, &session.id)?;
Memory Usage
- Tool associations are lightweight (only stores IDs and names)
- No tool code is duplicated in the database
- Compiled tools are referenced, not copied
Security
Access Control
- Tools are validated against bot_id
- Users can only add tools belonging to their current bot
- Session isolation prevents cross-session access
Input Validation
- Tool names are extracted and sanitized
- SQL injection prevented by Diesel parameterization
- Empty tool names are rejected
Testing
Example Test Script
See botserver/examples/tool_management_example.bas for a complete working example.
Unit Testing
Test the Rust API directly:
#[test]
fn test_multiple_tool_association() {
let mut conn = establish_connection();
let session_id = Uuid::new_v4();
// Add tools
add_tool(&mut conn, &session_id, "tool1").unwrap();
add_tool(&mut conn, &session_id, "tool2").unwrap();
// Verify
let tools = get_session_tools(&mut conn, &session_id).unwrap();
assert_eq!(tools.len(), 2);
// Remove one
remove_session_tool(&mut conn, &session_id, "tool1").unwrap();
let tools = get_session_tools(&mut conn, &session_id).unwrap();
assert_eq!(tools.len(), 1);
// Clear all
clear_session_tools(&mut conn, &session_id).unwrap();
let tools = get_session_tools(&mut conn, &session_id).unwrap();
assert_eq!(tools.len(), 0);
}
Future Enhancements
Potential improvements:
- Tool Priority/Ordering: Specify which tools to try first
- Tool Groups: Add/remove sets of related tools together
- Auto-Cleanup: Remove tool associations when session ends
- Tool Statistics: Track which tools are used most frequently
- Conditional Tool Loading: Load tools based on LLM decisions
- Tool Permissions: Fine-grained control over which users can use which tools
Troubleshooting
Tools Not Appearing
-
Check compilation:
SELECT * FROM basic_tools WHERE tool_name = 'enrollment'; -
Verify bot_id matches:
SELECT bot_id FROM basic_tools WHERE tool_name = 'enrollment'; -
Check is_active flag:
SELECT is_active FROM basic_tools WHERE tool_name = 'enrollment';
Tools Not Being Called
- Verify answer_mode is 1 or 4
- Check tool is in session associations:
SELECT * FROM session_tool_associations WHERE session_id = '<your-session-id>'; - Review LLM logs to see if tool was included in prompt
Database Issues
Check connection:
psql -h localhost -U your_user -d your_database
\dt session_tool_associations
References
- Schema:
botserver/migrations/6.0.3.sql - Implementation:
botserver/src/basic/keywords/add_tool.rs - Prompt Integration:
botserver/src/context/prompt_processor.rs - Models:
botserver/src/shared/models.rs - Example:
botserver/examples/tool_management_example.bas
License
This feature is part of the Bot Server project. See the main LICENSE file for details.