620 lines
No EOL
15 KiB
Markdown
620 lines
No EOL
15 KiB
Markdown
# 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
|
|
|
|
```sql
|
|
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_session` on `session_id`
|
|
- `idx_session_tool_name` on `tool_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:**
|
|
```basic
|
|
ADD_TOOL "<path_to_tool>"
|
|
```
|
|
|
|
**Example:**
|
|
```basic
|
|
ADD_TOOL ".gbdialog/enrollment.bas"
|
|
ADD_TOOL ".gbdialog/payment.bas"
|
|
ADD_TOOL ".gbdialog/support.bas"
|
|
```
|
|
|
|
**Behavior:**
|
|
- Validates that the tool exists in the `basic_tools` table
|
|
- Verifies the tool is active (`is_active = 1`)
|
|
- Checks the tool belongs to the current bot
|
|
- Inserts into `session_tool_associations` table
|
|
- 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:**
|
|
```basic
|
|
REMOVE_TOOL "<path_to_tool>"
|
|
```
|
|
|
|
**Example:**
|
|
```basic
|
|
REMOVE_TOOL ".gbdialog/support.bas"
|
|
```
|
|
|
|
**Behavior:**
|
|
- Removes the tool from `session_tool_associations` for 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:**
|
|
```basic
|
|
CLEAR_TOOLS
|
|
```
|
|
|
|
**Example:**
|
|
```basic
|
|
CLEAR_TOOLS
|
|
```
|
|
|
|
**Behavior:**
|
|
- Removes all entries in `session_tool_associations` for 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:**
|
|
```basic
|
|
LIST_TOOLS
|
|
```
|
|
|
|
**Example:**
|
|
```basic
|
|
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
|
|
|
|
1. **User calls `ADD_TOOL` in BASIC script**
|
|
```basic
|
|
ADD_TOOL ".gbdialog/enrollment.bas"
|
|
```
|
|
|
|
2. **System validates tool exists**
|
|
- Queries `basic_tools` table
|
|
- Checks `bot_id` matches current bot
|
|
- Verifies `is_active = 1`
|
|
|
|
3. **Association is created**
|
|
- Inserts into `session_tool_associations`
|
|
- Uses UNIQUE constraint to prevent duplicates
|
|
- Stores session_id, tool_name, and timestamp
|
|
|
|
4. **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
|
|
|
|
### Integration with Prompt Processor
|
|
|
|
The `PromptProcessor::get_available_tools()` method now:
|
|
|
|
1. Loads tool stack from bot configuration (existing behavior)
|
|
2. **NEW**: Queries `session_tool_associations` for the current session
|
|
3. Adds all associated tools to the available tools list
|
|
4. Maintains backward compatibility with legacy `current_tool` field
|
|
|
|
**Code Example:**
|
|
```rust
|
|
// 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`:
|
|
|
|
```rust
|
|
/// 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:**
|
|
```rust
|
|
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:
|
|
|
|
```basic
|
|
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:
|
|
|
|
```basic
|
|
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:
|
|
|
|
```basic
|
|
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:
|
|
|
|
```basic
|
|
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:
|
|
```basic
|
|
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:
|
|
```basic
|
|
REMOVE_TOOL ".gbdialog/onboarding.bas"
|
|
```
|
|
|
|
### 3. **Use LIST_TOOLS for Debugging**
|
|
When developing, list tools to verify state:
|
|
```basic
|
|
LIST_TOOLS
|
|
PRINT "Current tools listed above"
|
|
```
|
|
|
|
### 4. **Tool Names are Simple**
|
|
Tool names are extracted from paths automatically:
|
|
- `.gbdialog/enrollment.bas` → `enrollment`
|
|
- `payment.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):**
|
|
```rust
|
|
// Single tool stored in session.current_tool
|
|
session.current_tool = Some("enrollment".to_string());
|
|
```
|
|
|
|
**After (Multi-Tool):**
|
|
```basic
|
|
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:
|
|
|
|
```rust
|
|
// 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<PgConnection>> for thread-safe database access:
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
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
|
|
|
|
1. **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_tools` or is inactive
|
|
- Solution: Compile the tool or check bot_id matches
|
|
|
|
2. **Database Lock Error**
|
|
- Message: `"Database connection error: ..."`
|
|
- Cause: Failed to acquire database mutex
|
|
- Solution: Check database connection health
|
|
|
|
3. **Timeout**
|
|
- Message: `"ADD_TOOL timed out"`
|
|
- Cause: Operation took longer than 10 seconds
|
|
- Solution: Check database performance
|
|
|
|
### Error Recovery
|
|
|
|
All operations are atomic - if they fail, no partial state is committed:
|
|
|
|
```basic
|
|
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 session
|
|
- `idx_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:
|
|
```rust
|
|
// 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:
|
|
|
|
```rust
|
|
#[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:
|
|
|
|
1. **Tool Priority/Ordering**: Specify which tools to try first
|
|
2. **Tool Groups**: Add/remove sets of related tools together
|
|
3. **Auto-Cleanup**: Remove tool associations when session ends
|
|
4. **Tool Statistics**: Track which tools are used most frequently
|
|
5. **Conditional Tool Loading**: Load tools based on LLM decisions
|
|
6. **Tool Permissions**: Fine-grained control over which users can use which tools
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Tools Not Appearing
|
|
|
|
1. Check compilation:
|
|
```sql
|
|
SELECT * FROM basic_tools WHERE tool_name = 'enrollment';
|
|
```
|
|
|
|
2. Verify bot_id matches:
|
|
```sql
|
|
SELECT bot_id FROM basic_tools WHERE tool_name = 'enrollment';
|
|
```
|
|
|
|
3. Check is_active flag:
|
|
```sql
|
|
SELECT is_active FROM basic_tools WHERE tool_name = 'enrollment';
|
|
```
|
|
|
|
### Tools Not Being Called
|
|
|
|
1. Verify answer_mode is 1 or 4
|
|
2. Check tool is in session associations:
|
|
```sql
|
|
SELECT * FROM session_tool_associations WHERE session_id = '<your-session-id>';
|
|
```
|
|
3. Review LLM logs to see if tool was included in prompt
|
|
|
|
### Database Issues
|
|
|
|
Check connection:
|
|
```bash
|
|
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. |