Skip to content

Feature: Intelligent Tool Management System (Multi-Provider) #72

@Sahil5963

Description

@Sahil5963

Summary

Implement a comprehensive Intelligent Tool Management System that handles 100+ tools efficiently across multiple AI providers (Anthropic, OpenAI, Gemini). This includes on-demand tool discovery, provider-specific optimizations, policy-based filtering, and result truncation.

Consolidates: #69 (Tool Profiles), #70 (Budget Management), #67 (Result Truncation)


Problem

When many tools are registered:

  • Context bloat: 80 tools × ~500 tokens = 40,000+ tokens overhead
  • Selection accuracy degrades after 30-50 tools
  • Provider differences: Each AI provider has different tool calling quirks
  • No unified management: Tools scattered without organization
  • Result overhead: Large tool results consume context

Real-World Impact

Scenario Token Overhead
5 MCP servers ~55K tokens
80 custom tools ~40K tokens
Large results +10-50K tokens

Solution Overview

┌─────────────────────────────────────────────────────────────────────┐
│                    Tool Management System                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────────────────┐│
│  │ Active Tools │   │   Deferred   │   │    Tool Profiles         ││
│  │  (Always On) │   │   (Search)   │   │  coding, minimal, full   ││
│  └──────────────┘   └──────────────┘   └──────────────────────────┘│
│         │                  │                      │                 │
│         └──────────────────┴──────────────────────┘                 │
│                            │                                        │
│                    ┌───────▼───────┐                                │
│                    │ Policy Filter │                                │
│                    │ (allow/deny)  │                                │
│                    └───────┬───────┘                                │
│                            │                                        │
│         ┌──────────────────┼──────────────────┐                     │
│         ▼                  ▼                  ▼                     │
│  ┌────────────┐    ┌────────────┐    ┌────────────┐                │
│  │  Anthropic │    │   OpenAI   │    │   Gemini   │                │
│  │ formatter  │    │ formatter  │    │ formatter  │                │
│  └────────────┘    └────────────┘    └────────────┘                │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐  │
│  │              Result Truncation & Budget                       │  │
│  └──────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

1. Tool Search & Deferred Loading

1.1 ToolDefinition Extensions

interface ToolDefinition<TParams = Record<string, unknown>> {
  name: string;
  description: string;
  // ... existing fields ...
  
  // === NEW: Deferred Loading ===
  /** Defer loading - discovered via tool_search */
  deferLoading?: boolean;
  
  // === NEW: Organization ===
  /** Category for search/filtering */
  category?: string;
  /** Profile memberships */
  profiles?: string[];
  /** Search keywords */
  searchKeywords?: string[];
  
  // === NEW: Result Control ===
  /** Max result tokens (truncate if exceeded) */
  maxResultTokens?: number;
  /** Custom truncation strategy */
  truncationStrategy?: "head" | "tail" | "smart";
}

1.2 Tool Search Implementation

// BM25 keyword search (works with all providers)
function searchTools(
  tools: ToolDefinition[],
  query: string,
  config?: { maxResults?: number; minScore?: number }
): ToolSearchResult[] {
  const { maxResults = 5, minScore = 0.1 } = config ?? {};
  const queryTerms = tokenize(query);
  const idf = calculateIDF(tools, queryTerms);
  
  return tools
    .map(tool => ({
      ...tool,
      score: calculateBM25Score(tool, queryTerms, idf)
    }))
    .filter(t => t.score >= minScore)
    .sort((a, b) => b.score - a.score)
    .slice(0, maxResults);
}

// Regex search (Anthropic/Vercel pattern)
function searchToolsByRegex(
  tools: ToolDefinition[],
  pattern: string
): ToolDefinition[] {
  const regex = new RegExp(pattern, "i");
  return tools.filter(t => 
    regex.test(t.name) || 
    regex.test(t.description) ||
    regex.test(t.category ?? "")
  );
}

1.3 tool_search Meta-Tool

const toolSearchTool: ToolDefinition = {
  name: "tool_search",
  description: `Search for tools by keyword. Use when you need a capability not currently available.
Returns up to 5 relevant tools that become available immediately.

Examples:
- "upload training files" → file tools
- "FAQ knowledge" → training tools
- "conversation analytics" → analytics tools`,
  inputSchema: {
    type: "object",
    properties: {
      query: { type: "string", description: "Search keywords (2-5 words)" },
      method: { type: "string", enum: ["keyword", "regex"], default: "keyword" }
    },
    required: ["query"]
  },
  handler: async ({ query, method }) => {
    const results = method === "regex" 
      ? searchToolsByRegex(deferredTools, query)
      : searchTools(deferredTools, query);
    
    // Load discovered tools
    loadDeferredTools(results);
    
    return {
      success: true,
      message: `Found ${results.length} tools. Now available for use.`,
      data: { tools: results.map(t => ({ name: t.name, description: t.description })) }
    };
  }
};

2. Multi-Provider Support

2.1 Provider Comparison

Feature Anthropic OpenAI Gemini
Native defer_loading ✅ Yes ✅ GPT-5.4+ ❌ No
Tool filtering Via tool_search allowed_tools Schema filtering
Strict mode ✅ (recommended)
Max tools No limit ~100 optimal No limit
Schema quirks Nested objects OK No root unions Strip constraints

2.2 Provider Formatters

class ToolManager {
  formatForProvider(provider: AIProvider): FormattedTools {
    const active = this.getActiveTools();
    
    switch (provider) {
      case "anthropic":
        return this.formatForAnthropic(active);
      case "openai":
        return this.formatForOpenAI(active);
      case "gemini":
        return this.formatForGemini(active);
      default:
        return { tools: active };
    }
  }
  
  private formatForAnthropic(tools: ToolDefinition[]) {
    // Anthropic: Add tool_search, use defer_loading natively
    const hasDeferred = this.deferredTools.size > 0;
    
    return {
      tools: tools.map(t => ({
        name: t.name,
        description: t.description,
        input_schema: t.inputSchema,
        // Native defer_loading support
        ...(t.deferLoading && { cache_control: { type: "ephemeral" } })
      })),
      ...(hasDeferred && { 
        tool_search: this.createToolSearchTool() 
      })
    };
  }
  
  private formatForOpenAI(tools: ToolDefinition[]) {
    // OpenAI: Use allowed_tools for filtering, strict mode
    return {
      tools: tools.map(t => ({
        type: "function",
        function: {
          name: t.name,
          description: t.description,
          parameters: t.inputSchema,
          strict: true // Recommended
        }
      })),
      tool_choice: { type: "auto" }
    };
  }
  
  private formatForGemini(tools: ToolDefinition[]) {
    // Gemini: Strip constraint keywords, clean schemas
    return {
      tools: [{
        functionDeclarations: tools.map(t => ({
          name: t.name,
          description: t.description,
          parameters: this.cleanSchemaForGemini(t.inputSchema)
        }))
      }]
    };
  }
  
  private cleanSchemaForGemini(schema: ToolInputSchema): object {
    // Remove unsupported keywords
    const cleaned = { ...schema };
    delete cleaned.minLength;
    delete cleaned.maxLength;
    delete cleaned.pattern;
    delete cleaned.minimum;
    delete cleaned.maximum;
    return cleaned;
  }
}

3. Tool Profiles & Policies

3.1 Profile System (from #69)

interface ToolProfile {
  name: string;
  description?: string;
  include?: string[];  // Tool names or "category:*" patterns
  exclude?: string[];
}

const BUILT_IN_PROFILES: Record<string, ToolProfile> = {
  minimal: {
    name: "minimal",
    include: ["navigate", "search", "read"],
  },
  coding: {
    name: "coding",
    include: ["category:filesystem", "category:code", "search"],
    exclude: ["admin_*"],
  },
  training: {
    name: "training",
    include: ["category:training", "category:faq", "navigate"],
  },
  full: {
    name: "full",
    // Include everything
  }
};

// Usage
chat.setToolProfile("coding");

3.2 Policy Pipeline (OpenClaw Pattern)

interface PolicyStep {
  policy: ToolPolicy;
  label: string;
}

function applyToolPolicyPipeline(params: {
  tools: ToolDefinition[];
  steps: PolicyStep[];
}): ToolDefinition[] {
  let filtered = params.tools;
  
  for (const step of params.steps) {
    if (!step.policy) continue;
    
    filtered = filtered.filter(tool => {
      // Check allow list
      if (step.policy.allow && !step.policy.allow.includes(tool.name)) {
        return false;
      }
      // Check deny list
      if (step.policy.deny?.includes(tool.name)) {
        return false;
      }
      // Check category patterns
      if (step.policy.allowCategories) {
        return step.policy.allowCategories.includes(tool.category);
      }
      return true;
    });
  }
  
  return filtered;
}

// Usage (layered policies)
const tools = applyToolPolicyPipeline({
  tools: allTools,
  steps: [
    { policy: profilePolicy, label: "profile" },
    { policy: globalPolicy, label: "global" },
    { policy: agentPolicy, label: "agent" },
    { policy: userPolicy, label: "user" },
  ]
});

4. Result Truncation & Budget (from #67, #70)

4.1 Token Budget Management

interface TokenBudget {
  /** Max tokens for tool definitions */
  toolDefinitions?: number;  // Default: 10000
  /** Max tokens per tool result */
  perToolResult?: number;    // Default: 2000
  /** Max total tokens for all results in loop */
  totalResults?: number;     // Default: 8000
}

class BudgetManager {
  private budget: TokenBudget;
  private usedTokens = { definitions: 0, results: 0 };
  
  shouldDeferTool(tool: ToolDefinition): boolean {
    const toolTokens = estimateToolTokens(tool);
    return this.usedTokens.definitions + toolTokens > this.budget.toolDefinitions!;
  }
  
  truncateResult(result: ToolResponse, tool: ToolDefinition): ToolResponse {
    const maxTokens = tool.maxResultTokens ?? this.budget.perToolResult!;
    const resultTokens = estimateTokens(JSON.stringify(result));
    
    if (resultTokens <= maxTokens) return result;
    
    return this.applyTruncation(result, maxTokens, tool.truncationStrategy);
  }
  
  private applyTruncation(
    result: ToolResponse, 
    maxTokens: number,
    strategy: "head" | "tail" | "smart" = "smart"
  ): ToolResponse {
    if (strategy === "smart") {
      // Keep structure, truncate large arrays/strings
      return this.smartTruncate(result, maxTokens);
    }
    // Simple head/tail truncation
    const json = JSON.stringify(result.data);
    const truncated = strategy === "head" 
      ? json.slice(0, maxTokens * 4)
      : json.slice(-maxTokens * 4);
    
    return {
      ...result,
      data: JSON.parse(truncated),
      _truncated: true
    };
  }
  
  private smartTruncate(result: ToolResponse, maxTokens: number): ToolResponse {
    // Intelligent truncation that preserves structure
    // - Keep first/last N items of arrays
    // - Summarize long strings
    // - Add "[...N more items]" indicators
    // Implementation details...
  }
}

4.2 Auto-Summarization for Large Results

interface SummarizationConfig {
  /** Threshold to trigger summarization */
  tokenThreshold: number;
  /** Summary prompt */
  summaryPrompt?: string;
}

async function summarizeToolResult(
  result: ToolResponse,
  config: SummarizationConfig
): Promise<ToolResponse> {
  const tokens = estimateTokens(JSON.stringify(result));
  
  if (tokens < config.tokenThreshold) return result;
  
  // Use smaller model for summarization
  const summary = await generateSummary(result, config.summaryPrompt);
  
  return {
    success: true,
    message: result.message,
    data: {
      _summarized: true,
      _originalTokens: tokens,
      summary
    }
  };
}

5. Implementation

Files to Create

File Purpose
src/core/tools/toolManager.ts Main ToolManager class
src/core/tools/toolSearch/index.ts Search functions
src/core/tools/toolSearch/bm25.ts BM25 algorithm
src/core/tools/toolProfiles.ts Profile management
src/core/tools/toolPolicy.ts Policy pipeline
src/core/tools/providers/anthropic.ts Anthropic formatter
src/core/tools/providers/openai.ts OpenAI formatter
src/core/tools/providers/gemini.ts Gemini formatter
src/core/tools/budget.ts Token budget management
src/core/tools/truncation.ts Result truncation
src/react/hooks/useToolSearch.ts React hook
src/react/hooks/useToolProfile.ts Profile hook

Files to Modify

File Changes
src/core/types/tools.ts Add new fields
src/chat/ChatWithTools.ts Integrate ToolManager
src/react/provider/CopilotProvider.tsx Add context methods

6. API Examples

Basic Usage

// Register tools with deferred loading
useTool({
  name: "add_faq",
  description: "Add FAQ to training",
  deferLoading: true,        // Won't be sent initially
  category: "training",      // For search/profiles
  profiles: ["training"],    // Profile membership
  maxResultTokens: 500,      // Truncate large results
  // ...
});

// Enable tool search
useToolSearch({ enabled: true, maxResults: 5 });

// Or set a profile
useToolProfile("coding");

Advanced Usage

// Custom policy
const chat = createChatWithTools({
  runtimeUrl: "/api/chat",
  toolManager: {
    profiles: {
      custom: {
        include: ["search", "category:training"],
        exclude: ["admin_*"]
      }
    },
    policies: [
      { allow: ["read", "write"], label: "base" },
      { deny: ["delete_*"], label: "safety" }
    ],
    budget: {
      toolDefinitions: 8000,
      perToolResult: 1500,
      totalResults: 6000
    },
    providers: {
      anthropic: { deferLoading: true },
      openai: { strictMode: true }
    }
  }
});

7. Token Savings

Scenario Before After Savings
80 tools ~40K ~3K 92%
Large results (10 calls) ~50K ~15K 70%
Combined ~90K ~18K 80%

8. Checklist

Phase 1: Core Infrastructure

  • Extend ToolDefinition with new fields
  • Implement ToolManager class
  • BM25 search algorithm
  • Regex search
  • tool_search meta-tool
  • Token estimation utilities

Phase 2: Multi-Provider

  • Anthropic formatter (defer_loading)
  • OpenAI formatter (allowed_tools, strict)
  • Gemini formatter (schema cleaning)
  • Provider detection & auto-formatting

Phase 3: Profiles & Policies

  • Profile system
  • Built-in profiles (minimal, coding, full)
  • Policy pipeline
  • Category-based filtering

Phase 4: Budget & Truncation

  • Token budget tracking
  • Per-tool result limits
  • Smart truncation
  • Auto-summarization (optional)

Phase 5: React Integration

  • useToolSearch hook
  • useToolProfile hook
  • useToolBudget hook
  • DevTools integration

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions