botserver/docs/TOOL_MANAGEMENT.md

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_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:

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_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:

REMOVE_TOOL "<path_to_tool>"

Example:

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:

CLEAR_TOOLS

Example:

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:

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

  1. User calls ADD_TOOL in BASIC script

    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:

// 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.basenrollment
  • payment.baspayment

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

  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:

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:

// 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:

  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:

    SELECT * FROM basic_tools WHERE tool_name = 'enrollment';
    
  2. Verify bot_id matches:

    SELECT bot_id FROM basic_tools WHERE tool_name = 'enrollment';
    
  3. Check is_active flag:

    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:
    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:

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.