From e968e00c800f1fd177144b2c68a5d77137bd44cf Mon Sep 17 00:00:00 2001 From: perf3ct Date: Thu, 17 Apr 2025 03:08:11 +0000 Subject: [PATCH] try to fix tools again... trying to fix tools, again... --- .../llm/pipeline/stages/tool_calling_stage.ts | 225 +++++++++++++++++- src/services/llm/tools/search_notes_tool.ts | 67 +++++- src/services/llm/tools/tool_registry.ts | 102 +++++++- 3 files changed, 378 insertions(+), 16 deletions(-) diff --git a/src/services/llm/pipeline/stages/tool_calling_stage.ts b/src/services/llm/pipeline/stages/tool_calling_stage.ts index dc1fcdad0..6d53fdee6 100644 --- a/src/services/llm/pipeline/stages/tool_calling_stage.ts +++ b/src/services/llm/pipeline/stages/tool_calling_stage.ts @@ -4,6 +4,7 @@ import type { StreamCallback, ToolExecutionInput } from '../interfaces.js'; import { BasePipelineStage } from '../pipeline_stage.js'; import toolRegistry from '../../tools/tool_registry.js'; import chatStorageService from '../../chat_storage_service.js'; +import aiServiceManager from '../../ai_service_manager.js'; /** * Pipeline stage for handling LLM tool calling @@ -16,6 +17,11 @@ import chatStorageService from '../../chat_storage_service.js'; export class ToolCallingStage extends BasePipelineStage { constructor() { super('ToolCalling'); + + // Preload the vectorSearchTool to ensure it's available when needed + this.preloadVectorSearchTool().catch(error => { + log.error(`Error preloading vector search tool: ${error.message}`); + }); } /** @@ -81,7 +87,50 @@ export class ToolCallingStage extends BasePipelineStage { + + // First validate all tools before executing them + log.info(`Validating ${response.tool_calls?.length || 0} tools before execution`); + const validationResults = await Promise.all((response.tool_calls || []).map(async (toolCall) => { + try { + // Get the tool from registry + const tool = toolRegistry.getTool(toolCall.function.name); + + if (!tool) { + log.error(`Tool not found in registry: ${toolCall.function.name}`); + return { + toolCall, + valid: false, + tool: null, + error: `Tool not found: ${toolCall.function.name}` + }; + } + + // Validate the tool before execution + const isToolValid = await this.validateToolBeforeExecution(tool, toolCall.function.name); + if (!isToolValid) { + throw new Error(`Tool '${toolCall.function.name}' failed validation before execution`); + } + + return { + toolCall, + valid: true, + tool, + error: null + }; + } catch (error: any) { + return { + toolCall, + valid: false, + tool: null, + error: error.message || String(error) + }; + } + })); + + // Execute the validated tools + const toolResults = await Promise.all(validationResults.map(async (validation, index) => { + const { toolCall, valid, tool, error } = validation; + try { log.info(`========== TOOL CALL ${index + 1} OF ${response.tool_calls?.length || 0} ==========`); log.info(`Tool call ${index + 1} received - Name: ${toolCall.function.name}, ID: ${toolCall.id || 'unknown'}`); @@ -92,16 +141,12 @@ export class ToolCallingStage extends BasePipelineStage t.definition.function.name).join(', ')}`); - throw new Error(`Tool not found: ${toolCall.function.name}`); + // If validation failed, throw the error + if (!valid || !tool) { + throw new Error(error || `Unknown validation error for tool '${toolCall.function.name}'`); } - log.info(`Tool found in registry: ${toolCall.function.name}`); + log.info(`Tool validated successfully: ${toolCall.function.name}`); // Parse arguments (handle both string and object formats) let args; @@ -366,4 +411,166 @@ export class ToolCallingStage extends BasePipelineStage { + const aiServiceManager = require('../../../ai_service_manager.js').default; + + try { + log.info(`Getting dependency '${dependencyType}' for tool '${toolName}'`); + + // Check for specific dependency types + if (dependencyType === 'vectorSearchTool') { + // Try to get the existing vector search tool + let vectorSearchTool = aiServiceManager.getVectorSearchTool(); + + if (vectorSearchTool) { + log.info(`Found existing vectorSearchTool dependency`); + return vectorSearchTool; + } + + // No existing tool, try to initialize it + log.info(`Dependency '${dependencyType}' not found, attempting initialization`); + + // Get agent tools manager and initialize it + const agentTools = aiServiceManager.getAgentTools(); + if (agentTools && typeof agentTools.initialize === 'function') { + log.info('Initializing agent tools to create vectorSearchTool'); + try { + // Force initialization to ensure it runs even if previously marked as initialized + await agentTools.initialize(true); + log.info('Agent tools initialized successfully'); + } catch (initError: any) { + log.error(`Failed to initialize agent tools: ${initError.message}`); + return null; + } + } else { + log.error('Agent tools manager not available'); + return null; + } + + // Try getting the vector search tool again after initialization + vectorSearchTool = aiServiceManager.getVectorSearchTool(); + + if (vectorSearchTool) { + log.info('Successfully created vectorSearchTool dependency'); + return vectorSearchTool; + } else { + log.error('Failed to create vectorSearchTool dependency after initialization'); + return null; + } + } + + // Add more dependency types as needed + + // Unknown dependency type + log.error(`Unknown dependency type: ${dependencyType}`); + return null; + } catch (error: any) { + log.error(`Error getting or creating dependency '${dependencyType}': ${error.message}`); + return null; + } + } + + /** + * Validate a tool before execution + * @param tool The tool to validate + * @param toolName The name of the tool + */ + private async validateToolBeforeExecution(tool: any, toolName: string): Promise { + try { + if (!tool) { + log.error(`Tool '${toolName}' not found or failed validation`); + return false; + } + + // Validate execute method + if (!tool.execute || typeof tool.execute !== 'function') { + log.error(`Tool '${toolName}' is missing execute method`); + return false; + } + + // For the search_notes tool specifically, check if vectorSearchTool is available + if (toolName === 'search_notes') { + try { + // Use the imported aiServiceManager instead of dynamic import + let vectorSearchTool = aiServiceManager.getVectorSearchTool(); + + if (!vectorSearchTool) { + log.error(`Tool '${toolName}' is missing dependency: vectorSearchTool - attempting to initialize`); + + // Try to initialize the agent tools + try { + // Get agent tools manager and initialize it if needed + const agentTools = aiServiceManager.getAgentTools(); + if (agentTools && typeof agentTools.initialize === 'function') { + log.info('Attempting to initialize agent tools'); + // Force initialization to ensure it runs even if previously initialized + await agentTools.initialize(true); + } + + // Try getting the vector search tool again + vectorSearchTool = aiServiceManager.getVectorSearchTool(); + + if (!vectorSearchTool) { + log.error('Unable to initialize vectorSearchTool after initialization attempt'); + return false; + } + log.info('Successfully initialized vectorSearchTool'); + } catch (initError: any) { + log.error(`Failed to initialize agent tools: ${initError.message}`); + return false; + } + } + + if (!vectorSearchTool.searchNotes || typeof vectorSearchTool.searchNotes !== 'function') { + log.error(`Tool '${toolName}' dependency vectorSearchTool is missing searchNotes method`); + return false; + } + } catch (error: any) { + log.error(`Error validating dependencies for tool '${toolName}': ${error.message}`); + return false; + } + } + + // Add additional tool-specific validations here + + return true; + } catch (error: any) { + log.error(`Error validating tool before execution: ${error.message}`); + return false; + } + } + + /** + * Preload the vector search tool to ensure it's available before tool execution + */ + private async preloadVectorSearchTool(): Promise { + try { + log.info(`Preloading vector search tool...`); + + // Get the agent tools and initialize them if needed + const agentTools = aiServiceManager.getAgentTools(); + if (agentTools && typeof agentTools.initialize === 'function') { + await agentTools.initialize(true); + log.info(`Agent tools initialized during preloading`); + } + + // Check if the vector search tool is available + const vectorSearchTool = aiServiceManager.getVectorSearchTool(); + if (vectorSearchTool && typeof vectorSearchTool.searchNotes === 'function') { + log.info(`Vector search tool successfully preloaded`); + } else { + log.error(`Vector search tool not available after initialization`); + } + } catch (error: any) { + log.error(`Failed to preload vector search tool: ${error.message}`); + } + } } diff --git a/src/services/llm/tools/search_notes_tool.ts b/src/services/llm/tools/search_notes_tool.ts index 32f72d0db..8c048d76e 100644 --- a/src/services/llm/tools/search_notes_tool.ts +++ b/src/services/llm/tools/search_notes_tool.ts @@ -37,6 +37,56 @@ export const searchNotesToolDefinition: Tool = { } }; +/** + * Get or create the vector search tool dependency + * @returns The vector search tool or null if it couldn't be created + */ +async function getOrCreateVectorSearchTool(): Promise { + try { + // Try to get the existing vector search tool + let vectorSearchTool = aiServiceManager.getVectorSearchTool(); + + if (vectorSearchTool) { + log.info(`Found existing vectorSearchTool`); + return vectorSearchTool; + } + + // No existing tool, try to initialize it + log.info(`VectorSearchTool not found, attempting initialization`); + + // Get agent tools manager and initialize it + const agentTools = aiServiceManager.getAgentTools(); + if (agentTools && typeof agentTools.initialize === 'function') { + log.info('Initializing agent tools to create vectorSearchTool'); + try { + // Force initialization to ensure it runs even if previously marked as initialized + await agentTools.initialize(true); + log.info('Agent tools initialized successfully'); + } catch (initError: any) { + log.error(`Failed to initialize agent tools: ${initError.message}`); + return null; + } + } else { + log.error('Agent tools manager not available'); + return null; + } + + // Try getting the vector search tool again after initialization + vectorSearchTool = aiServiceManager.getVectorSearchTool(); + + if (vectorSearchTool) { + log.info('Successfully created vectorSearchTool'); + return vectorSearchTool; + } else { + log.error('Failed to create vectorSearchTool after initialization'); + return null; + } + } catch (error: any) { + log.error(`Error getting or creating vectorSearchTool: ${error.message}`); + return null; + } +} + /** * Search notes tool implementation */ @@ -53,9 +103,20 @@ export class SearchNotesTool implements ToolHandler { log.info(`Executing search_notes tool - Query: "${query}", ParentNoteId: ${parentNoteId || 'not specified'}, MaxResults: ${maxResults}`); // Get the vector search tool from the AI service manager - const vectorSearchTool = aiServiceManager.getVectorSearchTool(); + const vectorSearchTool = await getOrCreateVectorSearchTool(); + + if (!vectorSearchTool) { + return `Error: Vector search tool is not available. The system may still be initializing or there could be a configuration issue.`; + } + log.info(`Retrieved vector search tool from AI service manager`); + // Check if searchNotes method exists + if (!vectorSearchTool.searchNotes || typeof vectorSearchTool.searchNotes !== 'function') { + log.error(`Vector search tool is missing searchNotes method`); + return `Error: Vector search tool is improperly configured (missing searchNotes method).`; + } + // Execute the search log.info(`Performing semantic search for: "${query}"`); const searchStartTime = Date.now(); @@ -69,7 +130,7 @@ export class SearchNotesTool implements ToolHandler { if (results.length > 0) { // Log top results - results.slice(0, 3).forEach((result, index) => { + results.slice(0, 3).forEach((result: any, index: number) => { log.info(`Result ${index + 1}: "${result.title}" (similarity: ${Math.round(result.similarity * 100)}%)`); }); } else { @@ -79,7 +140,7 @@ export class SearchNotesTool implements ToolHandler { // Format the results return { count: results.length, - results: results.map(result => ({ + results: results.map((result: any) => ({ noteId: result.noteId, title: result.title, preview: result.contentPreview, diff --git a/src/services/llm/tools/tool_registry.ts b/src/services/llm/tools/tool_registry.ts index d32898233..9ad41fdce 100644 --- a/src/services/llm/tools/tool_registry.ts +++ b/src/services/llm/tools/tool_registry.ts @@ -13,6 +13,7 @@ import log from '../../log.js'; export class ToolRegistry { private static instance: ToolRegistry; private tools: Map = new Map(); + private initializationAttempted: boolean = false; private constructor() {} @@ -23,14 +24,81 @@ export class ToolRegistry { if (!ToolRegistry.instance) { ToolRegistry.instance = new ToolRegistry(); } - + return ToolRegistry.instance; } + /** + * Try to initialize tools if registry is empty + */ + private tryInitializeTools(): boolean { + if (this.initializationAttempted || this.tools.size > 0) { + return this.tools.size > 0; + } + + this.initializationAttempted = true; + log.info("Tool registry is empty, attempting synchronous initialization"); + + try { + // Use existing tooling to initialize + // This is a light touch, not creating anything new + log.info("Tools should be initialized by AIServiceManager constructor"); + return this.tools.size > 0; + } catch (error: any) { + log.error(`Error during tool initialization attempt: ${error.message}`); + return false; + } + } + + /** + * Validate a tool to ensure it's properly initialized + * @param handler Tool handler to validate + */ + private validateToolHandler(handler: ToolHandler): boolean { + try { + if (!handler) { + log.error(`Invalid tool handler: null or undefined`); + return false; + } + + if (!handler.definition) { + log.error(`Tool handler is missing definition`); + return false; + } + + if (!handler.definition.function || !handler.definition.function.name) { + log.error(`Tool definition is missing function name`); + return false; + } + + if (!handler.execute || typeof handler.execute !== 'function') { + log.error(`Tool '${handler.definition.function.name}' is missing execute method`); + return false; + } + + // Try to invoke the execute method with a test parameter to verify it's bound properly + // We don't actually execute, just check that it's callable + if (handler.execute.toString().includes('[native code]')) { + log.error(`Tool '${handler.definition.function.name}' has an unbound execute method`); + return false; + } + + return true; + } catch (error: any) { + log.error(`Error validating tool handler: ${error.message}`); + return false; + } + } + /** * Register a tool with the registry */ public registerTool(handler: ToolHandler): void { + if (!this.validateToolHandler(handler)) { + log.error(`Failed to register tool: validation failed`); + return; + } + const name = handler.definition.function.name; if (this.tools.has(name)) { @@ -45,21 +113,47 @@ export class ToolRegistry { * Get a tool by name */ public getTool(name: string): ToolHandler | undefined { - return this.tools.get(name); + // Try initialization if registry is empty + if (this.tools.size === 0) { + this.tryInitializeTools(); + } + + const tool = this.tools.get(name); + + if (!tool) { + log.error(`Tool '${name}' not found in registry`); + return undefined; + } + + // Validate the tool before returning it + if (!this.validateToolHandler(tool)) { + log.error(`Tool '${name}' failed validation when retrieved`); + return undefined; + } + + return tool; } /** * Get all registered tools */ public getAllTools(): ToolHandler[] { - return Array.from(this.tools.values()); + // Try initialization if registry is empty + if (this.tools.size === 0) { + this.tryInitializeTools(); + } + + // Filter out any tools that fail validation + return Array.from(this.tools.values()).filter(tool => this.validateToolHandler(tool)); } /** * Get all tool definitions for sending to LLM */ public getAllToolDefinitions(): Tool[] { - const toolDefs = Array.from(this.tools.values()).map(handler => handler.definition); + // Only get definitions from valid tools + const validTools = this.getAllTools(); + const toolDefs = validTools.map(handler => handler.definition); return toolDefs; } }