Add Delegation System with 3rd Party AI Tool Integration
NEW: Delegation System (v1.2.0) - Request Classifier for fast request analysis (<50ms) - Agent Pool Manager with auto-scaling (8 agent types) - Delegation Engine with 4 strategies (full, parallel, hierarchical, hybrid) - Progress Streamer for real-time updates - Context Handoff Protocol for inter-agent communication - Quality Gate with confidence thresholds and auto-escalation NEW: 3rd Party Integration Adapters - OpenClaw adapter with parallel execution support - Claude Code CLI adapter with tool registration - Generic adapter for custom integrations - Standardized IntegrationAdapter interface Agent Types Added: - fast-responder (quick answers < 2s) - explorer (code navigation) - researcher (deep analysis) - coder (implementation) - reviewer (quality checks) - planner (architecture) - executor (commands) - analyzer (debugging) Tests: All 6 tests passing This project was 100% autonomously built by Z.AI GLM-5
This commit is contained in:
379
delegation-system/core/request-classifier.ts
Normal file
379
delegation-system/core/request-classifier.ts
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Request Classifier
|
||||
*
|
||||
* Fast request analysis for delegation decisions.
|
||||
* Classifies requests by complexity and determines optimal routing.
|
||||
*/
|
||||
|
||||
import {
|
||||
RequestClassification,
|
||||
RequestComplexity,
|
||||
ClassifiableRequest,
|
||||
AgentType
|
||||
} from './types';
|
||||
|
||||
// ============================================================================
|
||||
// Classification Rules
|
||||
// ============================================================================
|
||||
|
||||
interface ClassificationRule {
|
||||
pattern: RegExp | string;
|
||||
complexity: RequestComplexity;
|
||||
agentType: AgentType;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
const CLASSIFICATION_RULES: ClassificationRule[] = [
|
||||
// Quick patterns - instant response
|
||||
{ pattern: /^(what is|explain|show me|list|get|find)\s/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.9 },
|
||||
{ pattern: /status|help|version|info$/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.95 },
|
||||
{ pattern: /^(yes|no|ok|thanks|done)$/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.99 },
|
||||
|
||||
// Code patterns - moderate complexity
|
||||
{ pattern: /(review|check|analyze|inspect)\s+(this|the|my)?\s*(code|file|function)/i, complexity: 'moderate', agentType: 'reviewer', weight: 0.85 },
|
||||
{ pattern: /(fix|solve|debug|resolve)\s+/i, complexity: 'moderate', agentType: 'analyzer', weight: 0.8 },
|
||||
{ pattern: /(add|update|modify|change)\s+(a|the|this)?\s*(function|method|class)/i, complexity: 'moderate', agentType: 'coder', weight: 0.85 },
|
||||
|
||||
// Research patterns - streaming
|
||||
{ pattern: /(research|investigate|explore|search)\s+/i, complexity: 'streaming', agentType: 'researcher', weight: 0.8 },
|
||||
{ pattern: /(analyze|audit|examine)\s+(the|entire|whole)?\s*(codebase|project|repo)/i, complexity: 'streaming', agentType: 'explorer', weight: 0.85 },
|
||||
|
||||
// Complex patterns - need main agent or parallel delegation
|
||||
{ pattern: /(refactor|rewrite|restructure|redesign)\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.9 },
|
||||
{ pattern: /(implement|build|create|develop)\s+(a|an|the)?\s*(new|feature|system)/i, complexity: 'complex', agentType: 'planner', weight: 0.85 },
|
||||
{ pattern: /(architect|design|plan)\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.9 },
|
||||
{ pattern: /migrate|upgrade|port\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.85 },
|
||||
];
|
||||
|
||||
// Keywords that indicate complexity
|
||||
const COMPLEXITY_INDICATORS = {
|
||||
high: ['architecture', 'system', 'multiple', 'integrate', 'refactor', 'migrate', 'redesign'],
|
||||
medium: ['implement', 'feature', 'function', 'module', 'component', 'service', 'api'],
|
||||
low: ['fix', 'update', 'change', 'add', 'remove', 'rename', 'comment']
|
||||
};
|
||||
|
||||
// Time estimates by complexity (ms)
|
||||
const TIME_ESTIMATES: Record<RequestComplexity, { min: number; max: number }> = {
|
||||
quick: { min: 100, max: 2000 },
|
||||
moderate: { min: 2000, max: 15000 },
|
||||
complex: { min: 15000, max: 120000 },
|
||||
streaming: { min: 5000, max: 60000 }
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Request Classifier Class
|
||||
// ============================================================================
|
||||
|
||||
export class RequestClassifier {
|
||||
private cachedClassifications: Map<string, RequestClassification> = new Map();
|
||||
private readonly cacheMaxSize: number = 1000;
|
||||
|
||||
// Capability mapping by request type
|
||||
private readonly typeCapabilities: Record<ClassifiableRequest['type'], string[]> = {
|
||||
code: ['syntax', 'semantics', 'patterns', 'best-practices'],
|
||||
question: ['knowledge', 'explanation', 'examples'],
|
||||
task: ['execution', 'planning', 'coordination'],
|
||||
analysis: ['inspection', 'metrics', 'patterns', 'security'],
|
||||
review: ['quality', 'standards', 'best-practices', 'security'],
|
||||
refactor: ['patterns', 'optimization', 'clean-code'],
|
||||
debug: ['tracing', 'analysis', 'diagnosis']
|
||||
};
|
||||
|
||||
/**
|
||||
* Classify a request for delegation decisions
|
||||
*/
|
||||
async classify(request: ClassifiableRequest): Promise<RequestClassification> {
|
||||
// Check cache first
|
||||
const cacheKey = this.getCacheKey(request);
|
||||
const cached = this.cachedClassifications.get(cacheKey);
|
||||
if (cached) {
|
||||
return { ...cached, confidence: Math.max(0, cached.confidence - 0.1) }; // Slightly lower confidence for cached
|
||||
}
|
||||
|
||||
// Analyze request
|
||||
const analysis = await this.analyzeRequest(request);
|
||||
|
||||
// Determine complexity
|
||||
const complexity = this.determineComplexity(request, analysis);
|
||||
|
||||
// Get recommended agent
|
||||
const recommendedAgent = this.getRecommendedAgent(request, complexity, analysis);
|
||||
|
||||
// Calculate estimated time
|
||||
const estimatedTime = this.estimateTime(complexity, analysis);
|
||||
|
||||
// Determine capabilities needed
|
||||
const requiredCapabilities = this.getRequiredCapabilities(request, analysis);
|
||||
|
||||
// Build classification
|
||||
const classification: RequestClassification = {
|
||||
complexity,
|
||||
score: analysis.complexityScore,
|
||||
confidence: analysis.confidence,
|
||||
recommendedAgent,
|
||||
estimatedTime,
|
||||
canDelegate: this.canDelegate(complexity, analysis),
|
||||
delegationPriority: this.getPriority(request, analysis),
|
||||
requiredCapabilities,
|
||||
contextRequirements: {
|
||||
files: analysis.fileCount,
|
||||
depth: analysis.contextDepth,
|
||||
history: analysis.needsHistory
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
this.cacheClassification(cacheKey, classification);
|
||||
|
||||
return classification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick classification for fast-path decisions (< 50ms target)
|
||||
*/
|
||||
quickClassify(content: string): { complexity: RequestComplexity; agent: AgentType } {
|
||||
// Fast pattern matching
|
||||
for (const rule of CLASSIFICATION_RULES) {
|
||||
if (typeof rule.pattern === 'string') {
|
||||
if (content.toLowerCase().includes(rule.pattern.toLowerCase())) {
|
||||
return { complexity: rule.complexity, agent: rule.agentType };
|
||||
}
|
||||
} else {
|
||||
if (rule.pattern.test(content)) {
|
||||
return { complexity: rule.complexity, agent: rule.agentType };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default based on length
|
||||
const length = content.length;
|
||||
if (length < 100) return { complexity: 'quick', agent: 'fast-responder' };
|
||||
if (length < 500) return { complexity: 'moderate', agent: 'coder' };
|
||||
return { complexity: 'complex', agent: 'planner' };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Private Methods
|
||||
// ============================================================================
|
||||
|
||||
private async analyzeRequest(request: ClassifiableRequest): Promise<{
|
||||
complexityScore: number;
|
||||
confidence: number;
|
||||
fileCount: number;
|
||||
contextDepth: 'shallow' | 'medium' | 'deep';
|
||||
needsHistory: boolean;
|
||||
matchedRules: ClassificationRule[];
|
||||
keywordDensity: Record<string, number>;
|
||||
}> {
|
||||
const content = request.content.toLowerCase();
|
||||
|
||||
// Match rules
|
||||
const matchedRules: ClassificationRule[] = [];
|
||||
for (const rule of CLASSIFICATION_RULES) {
|
||||
const pattern = typeof rule.pattern === 'string'
|
||||
? new RegExp(rule.pattern, 'i')
|
||||
: rule.pattern;
|
||||
if (pattern.test(content)) {
|
||||
matchedRules.push(rule);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate keyword density
|
||||
const keywordDensity: Record<string, number> = {};
|
||||
for (const [level, keywords] of Object.entries(COMPLEXITY_INDICATORS)) {
|
||||
keywordDensity[level] = keywords.reduce((count, kw) => {
|
||||
return count + (content.includes(kw) ? 1 : 0);
|
||||
}, 0) / keywords.length;
|
||||
}
|
||||
|
||||
// Calculate complexity score (0-1)
|
||||
let complexityScore = 0;
|
||||
|
||||
// Length factor
|
||||
complexityScore += Math.min(request.content.length / 2000, 0.3);
|
||||
|
||||
// Rule matching factor
|
||||
if (matchedRules.length > 0) {
|
||||
const avgWeight = matchedRules.reduce((sum, r) => sum + r.weight, 0) / matchedRules.length;
|
||||
complexityScore += avgWeight * 0.4;
|
||||
}
|
||||
|
||||
// Keyword density factor
|
||||
complexityScore += keywordDensity.high * 0.2;
|
||||
complexityScore += keywordDensity.medium * 0.1;
|
||||
|
||||
// File count factor
|
||||
const fileCount = request.files?.length || 0;
|
||||
complexityScore += Math.min(fileCount / 10, 0.1);
|
||||
|
||||
// Normalize
|
||||
complexityScore = Math.min(complexityScore, 1);
|
||||
|
||||
// Determine context depth
|
||||
let contextDepth: 'shallow' | 'medium' | 'deep' = 'shallow';
|
||||
if (complexityScore > 0.6 || fileCount > 3) contextDepth = 'deep';
|
||||
else if (complexityScore > 0.3 || fileCount > 1) contextDepth = 'medium';
|
||||
|
||||
// Check if history is needed
|
||||
const needsHistory = /context|previous|earlier|before|last|history/i.test(content);
|
||||
|
||||
// Calculate confidence
|
||||
let confidence = 0.5;
|
||||
if (matchedRules.length > 0) confidence += 0.3;
|
||||
if (keywordDensity.high > 0 || keywordDensity.medium > 0) confidence += 0.1;
|
||||
confidence = Math.min(confidence, 0.95);
|
||||
|
||||
return {
|
||||
complexityScore,
|
||||
confidence,
|
||||
fileCount,
|
||||
contextDepth,
|
||||
needsHistory,
|
||||
matchedRules,
|
||||
keywordDensity
|
||||
};
|
||||
}
|
||||
|
||||
private determineComplexity(
|
||||
request: ClassifiableRequest,
|
||||
analysis: { complexityScore: number; matchedRules: ClassificationRule[] }
|
||||
): RequestComplexity {
|
||||
// Check matched rules first
|
||||
if (analysis.matchedRules.length > 0) {
|
||||
// Get the highest weight rule's complexity
|
||||
const topRule = analysis.matchedRules.reduce((best, rule) =>
|
||||
rule.weight > best.weight ? rule : best
|
||||
);
|
||||
return topRule.complexity;
|
||||
}
|
||||
|
||||
// Fall back to score-based classification
|
||||
if (analysis.complexityScore < 0.25) return 'quick';
|
||||
if (analysis.complexityScore < 0.5) return 'moderate';
|
||||
if (analysis.complexityScore < 0.75) return 'streaming';
|
||||
return 'complex';
|
||||
}
|
||||
|
||||
private getRecommendedAgent(
|
||||
request: ClassifiableRequest,
|
||||
complexity: RequestComplexity,
|
||||
analysis: { matchedRules: ClassificationRule[] }
|
||||
): AgentType {
|
||||
// Check matched rules
|
||||
if (analysis.matchedRules.length > 0) {
|
||||
const topRule = analysis.matchedRules.reduce((best, rule) =>
|
||||
rule.weight > best.weight ? rule : best
|
||||
);
|
||||
return topRule.agentType;
|
||||
}
|
||||
|
||||
// Map request type to agent
|
||||
const typeToAgent: Record<ClassifiableRequest['type'], AgentType> = {
|
||||
code: 'coder',
|
||||
question: 'fast-responder',
|
||||
task: 'executor',
|
||||
analysis: 'analyzer',
|
||||
review: 'reviewer',
|
||||
refactor: 'coder',
|
||||
debug: 'analyzer'
|
||||
};
|
||||
|
||||
return typeToAgent[request.type] || 'fast-responder';
|
||||
}
|
||||
|
||||
private estimateTime(
|
||||
complexity: RequestComplexity,
|
||||
analysis: { complexityScore: number; fileCount: number }
|
||||
): number {
|
||||
const base = TIME_ESTIMATES[complexity];
|
||||
let estimate = base.min + (base.max - base.min) * analysis.complexityScore;
|
||||
|
||||
// Add time for files
|
||||
estimate += analysis.fileCount * 500;
|
||||
|
||||
return Math.round(estimate);
|
||||
}
|
||||
|
||||
private canDelegate(complexity: RequestComplexity, analysis: any): boolean {
|
||||
// Quick and moderate can always be delegated
|
||||
if (complexity === 'quick' || complexity === 'moderate') return true;
|
||||
|
||||
// Streaming can be delegated with progress
|
||||
if (complexity === 'streaming') return true;
|
||||
|
||||
// Complex depends on confidence
|
||||
return analysis.confidence > 0.7;
|
||||
}
|
||||
|
||||
private getPriority(
|
||||
request: ClassifiableRequest,
|
||||
analysis: any
|
||||
): 'low' | 'medium' | 'high' | 'critical' {
|
||||
// Check metadata for explicit priority
|
||||
if (request.metadata?.priority) {
|
||||
return request.metadata.priority;
|
||||
}
|
||||
|
||||
// Determine from analysis
|
||||
if (analysis.keywordDensity?.high > 0.5) return 'high';
|
||||
if (analysis.complexityScore > 0.7) return 'high';
|
||||
if (analysis.complexityScore > 0.4) return 'medium';
|
||||
return 'low';
|
||||
}
|
||||
|
||||
private getRequiredCapabilities(
|
||||
request: ClassifiableRequest,
|
||||
analysis: any
|
||||
): string[] {
|
||||
const capabilities = new Set<string>();
|
||||
|
||||
// Add type-based capabilities
|
||||
const typeCaps = this.typeCapabilities[request.type] || [];
|
||||
typeCaps.forEach(c => capabilities.add(c));
|
||||
|
||||
// Add based on content analysis
|
||||
if (/security|auth|encrypt/i.test(request.content)) capabilities.add('security');
|
||||
if (/test|spec|coverage/i.test(request.content)) capabilities.add('testing');
|
||||
if (/performance|optim|speed/i.test(request.content)) capabilities.add('performance');
|
||||
if (/doc|comment|readme/i.test(request.content)) capabilities.add('documentation');
|
||||
|
||||
return Array.from(capabilities);
|
||||
}
|
||||
|
||||
private getCacheKey(request: ClassifiableRequest): string {
|
||||
return `${request.type}:${request.content.slice(0, 100)}:${request.files?.length || 0}`;
|
||||
}
|
||||
|
||||
private cacheClassification(key: string, classification: RequestClassification): void {
|
||||
// Enforce max size
|
||||
if (this.cachedClassifications.size >= this.cacheMaxSize) {
|
||||
// Remove oldest entry
|
||||
const firstKey = this.cachedClassifications.keys().next().value;
|
||||
if (firstKey) {
|
||||
this.cachedClassifications.delete(firstKey);
|
||||
}
|
||||
}
|
||||
this.cachedClassifications.set(key, classification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear classification cache
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.cachedClassifications.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Factory Function
|
||||
// ============================================================================
|
||||
|
||||
export function createRequestClassifier(): RequestClassifier {
|
||||
return new RequestClassifier();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export
|
||||
// ============================================================================
|
||||
|
||||
export default RequestClassifier;
|
||||
Reference in New Issue
Block a user