ableton-mcp-server-rag
Version: 
Ableton Live MCP depend on Ableton JS
196 lines • 8.35 kB
JavaScript
// Enhanced RAG tools with backward compatibility
// Add this new method to the existing RagTools class
smartQueryV2({ query, maxTokenBudget = 180000, clientType = 'swift' }, {
    query: string,
    maxTokenBudget: number,
    clientType: string
});
{
    // console.log("🧠 smart_query_v2 tool invoked with query:", query)
    try {
        // Process the hybrid query
        const result = await HybridQueryProcessor.processHybridQuery(query);
        // Get all available tools from the registry
        const { toolRegistry } = await import('./tool-vector.js');
        const allTools = Array.from(toolRegistry.values());
        // Smart tool selection with token budgeting
        const selectedTools = this.selectToolsWithTokenBudget(result.relevantTools, allTools, result.queryAnalysis.hasKnowledge, result.queryAnalysis.hasActions, maxTokenBudget, query);
        // Compress knowledge context
        const compressedKnowledge = this.compressKnowledgeContext(result.knowledgeAnswer, Math.min(1500, Math.floor(maxTokenBudget * 0.08)) // Max 8% for knowledge
        );
        // Calculate token usage
        const tokenAnalysis = this.calculateTokenUsage(compressedKnowledge, selectedTools);
        // Format response based on client type
        if (clientType === 'swift') {
            // Swift-compatible format
            const response = {
                knowledge: compressedKnowledge,
                tools: selectedTools.map(tool => tool.name),
                queryType: result.queryAnalysis.hasKnowledge ? 'knowledge' : 'action',
                confidence: result.queryAnalysis.detectedParts[0]?.confidence || 0.5,
                tokenAnalysis,
                debugInfo: {
                    originalToolCount: result.relevantTools.length,
                    selectedToolCount: selectedTools.length,
                    tokenBudgetUsed: tokenAnalysis.total,
                    tokenBudgetLimit: maxTokenBudget,
                    withinBudget: tokenAnalysis.total <= maxTokenBudget
                }
            };
            return Result.data(response);
        }
        else {
            // Legacy string format for backward compatibility
            let response = '';
            if (compressedKnowledge) {
                response += `KNOWLEDGE:\n${compressedKnowledge}\n\n`;
            }
            if (selectedTools.length > 0) {
                response += `TOOLS:\n`;
                selectedTools.forEach(tool => {
                    response += `${tool.name}\n`;
                });
            }
            return Result.data(response.trim() || "No relevant knowledge or tools found for this query.");
        }
    }
    catch (error) {
        // console.error("❌ Error in smart_query_v2:", error)
        return Result.error(`Smart query failed: ${error instanceof Error ? error.message : String(error)}`);
    }
}
selectToolsWithTokenBudget(suggestedTools, any[], allTools, any[], hasKnowledge, boolean, hasActions, boolean, maxTokenBudget, number, query, string);
any[];
{
    const toolTokenBudget = Math.floor(maxTokenBudget * 0.7); // 70% for tools
    const selected = [];
    let currentTokens = 0;
    // Priority 1: Essential tools based on query type
    const essentialToolNames = this.getEssentialToolNames(hasKnowledge, hasActions, query);
    for (const toolName of essentialToolNames) {
        const tool = allTools.find(t => t.name === toolName);
        if (tool && !selected.some(s => s.name === toolName)) {
            const tokenCost = this.estimateToolTokens(tool);
            if (currentTokens + tokenCost <= toolTokenBudget) {
                selected.push(tool);
                currentTokens += tokenCost;
            }
        }
    }
    // Priority 2: Add suggested tools from RAG
    for (const tool of suggestedTools) {
        if (!selected.some(s => s.name === tool.name)) {
            const tokenCost = this.estimateToolTokens(tool);
            if (currentTokens + tokenCost <= toolTokenBudget && selected.length < 20) {
                selected.push(tool);
                currentTokens += tokenCost;
            }
        }
    }
    // Priority 3: Add complementary tools from different categories
    const seenCategories = new Set(selected.map(t => t.category));
    for (const tool of allTools) {
        if (selected.length >= 18)
            break; // Reasonable limit
        if (!selected.some(s => s.name === tool.name) && !seenCategories.has(tool.category)) {
            const tokenCost = this.estimateToolTokens(tool);
            if (currentTokens + tokenCost <= toolTokenBudget) {
                selected.push(tool);
                currentTokens += tokenCost;
                seenCategories.add(tool.category);
            }
        }
    }
    return selected;
}
getEssentialToolNames(hasKnowledge, boolean, hasActions, boolean, query, string);
string[];
{
    const essential = [];
    if (hasKnowledge) {
        essential.push('get_song_properties', 'get_track_properties', 'get_application_info', 'get_song_view_properties');
    }
    if (hasActions) {
        const lowerQuery = query.toLowerCase();
        // Context-aware tool selection
        if (lowerQuery.includes('track')) {
            essential.push('create_track', 'set_tracks_property');
        }
        if (lowerQuery.includes('clip')) {
            essential.push('create_midi_clip', 'add_notes_to_clip');
        }
        if (lowerQuery.includes('tempo') || lowerQuery.includes('bpm')) {
            essential.push('set_song_property');
        }
        if (lowerQuery.includes('device') || lowerQuery.includes('effect') ||
            lowerQuery.includes('reverb') || lowerQuery.includes('delay')) {
            essential.push('load_device', 'modify_device_parameter_value', 'list_resources');
        }
        if (lowerQuery.includes('record')) {
            essential.push('record_by_time_range');
        }
        // Always include these for action queries
        essential.push('get_device_properties', 'get_clip_properties');
    }
    return [...new Set(essential)]; // Remove duplicates
}
estimateToolTokens(tool, any);
number;
{
    const baseTokens = 400; // More accurate base estimate
    const descriptionTokens = (tool.description || '').length / 3.2;
    const paramsTokens = JSON.stringify(tool.params || {}).length / 3.2;
    const keywordTokens = (tool.keywords || []).join(' ').length / 3.2;
    return Math.ceil(baseTokens + descriptionTokens + paramsTokens + keywordTokens);
}
compressKnowledgeContext(knowledge, string, maxTokens, number);
string;
{
    if (!knowledge)
        return '';
    const maxLength = Math.floor(maxTokens * 3.2); // Convert tokens to chars
    if (knowledge.length <= maxLength)
        return knowledge;
    // Smart compression: prioritize relevant content
    const sentences = knowledge.split(/[.!?]+/).filter(s => s.trim().length > 15);
    const keyTerms = ['ableton', 'live', 'track', 'tempo', 'device', 'clip', 'effect', 'bpm'];
    // Score sentences by relevance
    const scored = sentences.map(sentence => ({
        text: sentence.trim(),
        score: keyTerms.reduce((score, term) => score + (sentence.toLowerCase().includes(term) ? 2 : 0), 0) +
            (sentence.length < 100 ? 1 : 0) // Prefer shorter sentences
    }));
    // Sort by relevance
    scored.sort((a, b) => b.score - a.score);
    let compressed = '';
    for (const item of scored) {
        const nextLength = compressed.length + item.text.length + 2;
        if (nextLength <= maxLength) {
            compressed += item.text + '. ';
        }
        else {
            break;
        }
    }
    return compressed.trim();
}
calculateTokenUsage(knowledge, string, tools, any[]);
any;
{
    const knowledgeTokens = Math.ceil(knowledge.length / 3.2);
    const toolsTokens = tools.reduce((sum, tool) => sum + this.estimateToolTokens(tool), 0);
    const total = knowledgeTokens + toolsTokens;
    return {
        knowledge: knowledgeTokens,
        tools: toolsTokens,
        total,
        breakdown: {
            knowledgePercent: Math.round((knowledgeTokens / total) * 100),
            toolsPercent: Math.round((toolsTokens / total) * 100)
        },
        withinBudget: total <= 180000,
        budgetUsage: Math.round((total / 180000) * 100)
    };
}
export {};
//# sourceMappingURL=rag-tools-enhanced.js.map