UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

355 lines (309 loc) 14.5 kB
/** * Intent Router Plugin * * Smart query → agent/tool mapping using @ruvector/wasm semantic matching. * Routes user intents to optimal handlers with confidence scoring. * * Features: * - Semantic intent classification (<1ms) * - Multi-label routing (can route to multiple handlers) * - Confidence-based fallback * - Intent history for optimization * - Dynamic route learning * * @example * ```typescript * import { intentRouterPlugin } from '@claude-flow/plugins/examples/ruvector-plugins'; * await getDefaultRegistry().register(intentRouterPlugin); * ``` */ import { PluginBuilder, MCPToolBuilder, HookBuilder, HookEvent, HookPriority, Security, } from '../../src/index.js'; // Import shared vector utilities (consolidated from all plugins) import { IVectorDB, createVectorDB, generateHashEmbedding, } from './shared/vector-utils.js'; // ============================================================================ // Types // ============================================================================ export interface Intent { id: string; pattern: string; category: string; handlers: IntentHandler[]; priority: number; examples: string[]; embedding?: Float32Array; metadata: { createdAt: Date; updatedAt: Date; usageCount: number; successRate: number; }; } export interface IntentHandler { type: 'agent' | 'tool' | 'workflow' | 'skill'; name: string; config?: Record<string, unknown>; weight: number; } export interface RouteResult { intent: Intent; confidence: number; handlers: Array<IntentHandler & { score: number }>; alternatives: Array<{ intent: Intent; confidence: number }>; } export interface RouterConfig { minConfidence: number; maxAlternatives: number; enableLearning: boolean; defaultHandler?: IntentHandler; } // ============================================================================ // Intent Router Core // ============================================================================ export class IntentRouter { private vectorDb: IVectorDB | null = null; private intents = new Map<string, Intent>(); private routeHistory: Array<{ query: string; intentId: string; success: boolean; timestamp: Date }> = []; private config: RouterConfig; private dimensions = 768; private nextId = 1; private initPromise: Promise<void> | null = null; constructor(config?: Partial<RouterConfig>) { this.config = { minConfidence: 0.4, maxAlternatives: 3, enableLearning: true, ...config, }; } async initialize(): Promise<void> { if (this.vectorDb) return; if (this.initPromise) return this.initPromise; this.initPromise = (async () => { this.vectorDb = await createVectorDB(this.dimensions); await this.initializeDefaultIntents(); })(); return this.initPromise; } private async ensureInitialized(): Promise<IVectorDB> { await this.initialize(); return this.vectorDb!; } /** * Register a new intent pattern. */ async registerIntent( pattern: string, category: string, handlers: IntentHandler[], options?: { priority?: number; examples?: string[] } ): Promise<Intent> { const db = await this.ensureInitialized(); const safePattern = Security.validateString(pattern, { maxLength: 500 }); const safeCategory = Security.validateString(category, { maxLength: 100 }); const id = `intent-${this.nextId++}`; const examples = options?.examples ?? []; const allText = [safePattern, ...examples].join(' '); const embedding = this.generateEmbedding(allText); const intent: Intent = { id, pattern: safePattern, category: safeCategory, handlers, priority: options?.priority ?? 0, examples, embedding, metadata: { createdAt: new Date(), updatedAt: new Date(), usageCount: 0, successRate: 1.0 }, }; db.insert(embedding, id, { pattern: safePattern, category: safeCategory, priority: intent.priority }); this.intents.set(id, intent); return intent; } /** * Route a query to the best matching intent handlers (<1ms). */ async route(query: string): Promise<RouteResult> { const db = await this.ensureInitialized(); const safeQuery = Security.validateString(query, { maxLength: 1000 }); const queryEmbedding = this.generateEmbedding(safeQuery); const searchResults = db.search(queryEmbedding, this.config.maxAlternatives + 1); if (searchResults.length === 0 || searchResults[0].score < this.config.minConfidence) { if (this.config.defaultHandler) { const fallbackIntent: Intent = { id: 'fallback', pattern: 'default', category: 'general', handlers: [this.config.defaultHandler], priority: -1, examples: [], metadata: { createdAt: new Date(), updatedAt: new Date(), usageCount: 0, successRate: 0 }, }; return { intent: fallbackIntent, confidence: 0, handlers: [{ ...this.config.defaultHandler, score: 0 }], alternatives: [] }; } throw new Error('No matching intent found'); } const bestMatch = searchResults[0]; const intent = this.intents.get(bestMatch.id)!; intent.metadata.usageCount++; intent.metadata.updatedAt = new Date(); if (this.config.enableLearning) { this.routeHistory.push({ query: safeQuery, intentId: intent.id, success: true, timestamp: new Date() }); if (this.routeHistory.length > 1000) this.routeHistory = this.routeHistory.slice(-500); } const scoredHandlers = intent.handlers.map(h => ({ ...h, score: h.weight * bestMatch.score })).sort((a, b) => b.score - a.score); const alternatives = searchResults.slice(1, this.config.maxAlternatives + 1) .filter(r => r.score >= this.config.minConfidence * 0.7) .map(r => ({ intent: this.intents.get(r.id)!, confidence: r.score })) .filter(a => a.intent); return { intent, confidence: bestMatch.score, handlers: scoredHandlers, alternatives }; } /** * Provide feedback on a routing decision. */ async feedback(intentId: string, success: boolean): Promise<void> { const intent = this.intents.get(intentId); if (!intent) return; const alpha = 0.1; intent.metadata.successRate = alpha * (success ? 1 : 0) + (1 - alpha) * intent.metadata.successRate; const lastRoute = this.routeHistory.find(r => r.intentId === intentId); if (lastRoute) lastRoute.success = success; } /** * Get router statistics. */ getStats(): { totalIntents: number; byCategory: Record<string, number>; topIntents: Array<{ id: string; pattern: string; usageCount: number; successRate: number }>; recentRoutes: number; } { const byCategory: Record<string, number> = {}; for (const intent of this.intents.values()) { byCategory[intent.category] = (byCategory[intent.category] ?? 0) + 1; } const topIntents = Array.from(this.intents.values()) .sort((a, b) => b.metadata.usageCount - a.metadata.usageCount) .slice(0, 5) .map(i => ({ id: i.id, pattern: i.pattern, usageCount: i.metadata.usageCount, successRate: i.metadata.successRate })); return { totalIntents: this.intents.size, byCategory, topIntents, recentRoutes: this.routeHistory.length }; } listIntents(): Intent[] { return Array.from(this.intents.values()).sort((a, b) => b.priority - a.priority); } // ========================================================================= // Private Helpers // ========================================================================= private async initializeDefaultIntents(): Promise<void> { const defaults: Array<{ pattern: string; category: string; handlers: IntentHandler[]; priority: number; examples: string[] }> = [ { pattern: 'write code implement function', category: 'coding', handlers: [{ type: 'agent', name: 'coder', weight: 1.0 }], priority: 10, examples: ['create a function', 'implement feature'] }, { pattern: 'review code analyze quality', category: 'coding', handlers: [{ type: 'agent', name: 'reviewer', weight: 1.0 }], priority: 10, examples: ['review my code', 'check for bugs'] }, { pattern: 'test write unit test', category: 'testing', handlers: [{ type: 'agent', name: 'tester', weight: 1.0 }], priority: 10, examples: ['write tests', 'add unit tests'] }, { pattern: 'search find research', category: 'research', handlers: [{ type: 'agent', name: 'researcher', weight: 1.0 }, { type: 'tool', name: 'WebSearch', weight: 0.8 }], priority: 8, examples: ['look up', 'research topic'] }, { pattern: 'plan design architecture', category: 'planning', handlers: [{ type: 'agent', name: 'planner', weight: 1.0 }], priority: 8, examples: ['plan implementation', 'design system'] }, { pattern: 'read file open', category: 'files', handlers: [{ type: 'tool', name: 'Read', weight: 1.0 }], priority: 5, examples: ['show me file', 'read content'] }, { pattern: 'edit modify change', category: 'files', handlers: [{ type: 'tool', name: 'Edit', weight: 1.0 }], priority: 5, examples: ['change this', 'update file'] }, { pattern: 'search grep find', category: 'files', handlers: [{ type: 'tool', name: 'Grep', weight: 1.0 }, { type: 'tool', name: 'Glob', weight: 0.8 }], priority: 5, examples: ['find in files', 'search codebase'] }, { pattern: 'commit git save', category: 'git', handlers: [{ type: 'skill', name: 'commit', weight: 1.0 }], priority: 7, examples: ['commit changes', 'save to git'] }, { pattern: 'swarm coordinate agents', category: 'swarm', handlers: [{ type: 'agent', name: 'hierarchical-coordinator', weight: 1.0 }], priority: 9, examples: ['run swarm', 'coordinate agents'] }, ]; for (const def of defaults) { await this.registerIntent(def.pattern, def.category, def.handlers, { priority: def.priority, examples: def.examples }); } } private generateEmbedding(text: string): Float32Array { const embedding = new Float32Array(this.dimensions); const normalized = text.toLowerCase(); let hash = 0; for (let i = 0; i < normalized.length; i++) { hash = ((hash << 5) - hash) + normalized.charCodeAt(i); hash = hash & hash; } for (let i = 0; i < this.dimensions; i++) { embedding[i] = Math.sin(hash * (i + 1) * 0.001) * 0.5 + 0.5; } let norm = 0; for (let i = 0; i < this.dimensions; i++) norm += embedding[i] * embedding[i]; norm = Math.sqrt(norm); for (let i = 0; i < this.dimensions; i++) embedding[i] /= norm; return embedding; } } // ============================================================================ // Plugin Definition // ============================================================================ let routerInstance: IntentRouter | null = null; async function getRouter(): Promise<IntentRouter> { if (!routerInstance) { routerInstance = new IntentRouter({ defaultHandler: { type: 'agent', name: 'general-purpose', weight: 0.5 } }); await routerInstance.initialize(); } return routerInstance; } export const intentRouterPlugin = new PluginBuilder('intent-router', '1.0.0') .withDescription('Smart intent → handler routing using @ruvector/wasm HNSW (<1ms)') .withAuthor('Claude Flow Team') .withTags(['routing', 'intent', 'semantic', 'ruvector', 'hnsw']) .withMCPTools([ new MCPToolBuilder('intent-route') .withDescription('Route a query to best handlers (<1ms)') .addStringParam('query', 'User query or intent', { required: true }) .withHandler(async (params) => { try { const router = await getRouter(); const result = await router.route(params.query as string); const handlersOutput = result.handlers.map((h, i) => ` ${i + 1}. [${h.type}] ${h.name} (${(h.score * 100).toFixed(0)}%)`).join('\n'); return { content: [{ type: 'text', text: `🎯 **${result.intent.pattern}** (${(result.confidence * 100).toFixed(0)}%)\n\n**Handlers:**\n${handlersOutput}` }] }; } catch (error) { return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }) .build(), new MCPToolBuilder('intent-register') .withDescription('Register a new intent') .addStringParam('pattern', 'Intent pattern', { required: true }) .addStringParam('category', 'Category', { required: true }) .addStringParam('handlers', 'JSON handlers [{type, name, weight}]', { required: true }) .withHandler(async (params) => { try { const router = await getRouter(); const handlers = JSON.parse(params.handlers as string) as IntentHandler[]; const intent = await router.registerIntent(params.pattern as string, params.category as string, handlers); return { content: [{ type: 'text', text: `✅ Registered: ${intent.id}` }] }; } catch (error) { return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }) .build(), new MCPToolBuilder('intent-stats') .withDescription('Get router statistics') .withHandler(async () => { const router = await getRouter(); const stats = router.getStats(); return { content: [{ type: 'text', text: `📊 **Intent Router:**\n\n**Intents:** ${stats.totalIntents}\n**Routes:** ${stats.recentRoutes}\n**Backend:** @ruvector/wasm HNSW` }] }; }) .build(), ]) .withHooks([ new HookBuilder(HookEvent.PreTaskExecute) .withName('intent-auto-route') .withDescription('Auto-route tasks') .withPriority(HookPriority.High) .when((ctx) => (ctx.data as { autoRoute?: boolean; query?: string } | undefined)?.autoRoute === true) .handle(async (ctx) => { const data = ctx.data as { query: string }; try { const router = await getRouter(); const result = await router.route(data.query); return { success: true, data: { ...data, routedIntent: result.intent.id, routedHandlers: result.handlers }, modified: true }; } catch { return { success: true }; } }) .build(), ]) .onInitialize(async (ctx) => { ctx.logger.info('Intent Router initializing with @ruvector/wasm...'); const router = await getRouter(); ctx.logger.info(`Intent Router ready - ${router.listIntents().length} intents, HNSW enabled`); }) .build(); export default intentRouterPlugin;