mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-10 10:22:29 +08:00
try to fix tools again...
trying to fix tools, again...
This commit is contained in:
parent
c1ea9e376a
commit
e968e00c80
@ -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<ToolExecutionInput, { response: ChatResponse, needsFollowUp: boolean, messages: Message[] }> {
|
||||
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<ToolExecutionInput, { re
|
||||
log.info(`Executing ${response.tool_calls?.length || 0} tool calls in parallel`);
|
||||
|
||||
const executionStartTime = Date.now();
|
||||
const toolResults = await Promise.all((response.tool_calls || []).map(async (toolCall, index) => {
|
||||
|
||||
// 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<ToolExecutionInput, { re
|
||||
: JSON.stringify(toolCall.function.arguments);
|
||||
log.info(`Tool parameters: ${argsStr}`);
|
||||
|
||||
// Get the tool from registry
|
||||
const tool = toolRegistry.getTool(toolCall.function.name);
|
||||
|
||||
if (!tool) {
|
||||
log.error(`Tool not found in registry: ${toolCall.function.name}`);
|
||||
log.info(`Available tools: ${availableTools.map(t => 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<ToolExecutionInput, { re
|
||||
messages: updatedMessages
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a dependency required by tools
|
||||
*
|
||||
* @param dependencyType The type of dependency to get or create
|
||||
* @param toolName The name of the tool requiring this dependency
|
||||
* @returns The requested dependency or null if it couldn't be created
|
||||
*/
|
||||
private async getOrCreateDependency(dependencyType: string, toolName: string): Promise<any> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<any> {
|
||||
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,
|
||||
|
@ -13,6 +13,7 @@ import log from '../../log.js';
|
||||
export class ToolRegistry {
|
||||
private static instance: ToolRegistry;
|
||||
private tools: Map<string, ToolHandler> = 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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user