@ -6,6 +6,26 @@ import toolRegistry from '../../tools/tool_registry.js';
import chatStorageService from '../../chat_storage_service.js' ;
import aiServiceManager from '../../ai_service_manager.js' ;
// Type definitions for tools and validation results
interface ToolInterface {
execute : ( args : Record < string , unknown > ) = > Promise < unknown > ;
[ key : string ] : unknown ;
}
interface ToolValidationResult {
toolCall : {
id? : string ;
function : {
name : string ;
arguments : string | Record < string , unknown > ;
} ;
} ;
valid : boolean ;
tool : ToolInterface | null ;
error : string | null ;
guidance? : string ; // Guidance to help the LLM select better tools/parameters
}
/ * *
* Pipeline stage for handling LLM tool calling
* This stage is responsible for :
@ -50,12 +70,35 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
}
// Check if the registry has any tools
const availableTools = toolRegistry . getAllTools ( ) ;
const registryTools = toolRegistry . getAllTools ( ) ;
// Convert ToolHandler[] to ToolInterface[] with proper type safety
const availableTools : ToolInterface [ ] = registryTools . map ( tool = > {
// Create a proper ToolInterface from the ToolHandler
const toolInterface : ToolInterface = {
// Pass through the execute method
execute : ( args : Record < string , unknown > ) = > tool . execute ( args ) ,
// Include other properties from the tool definition
. . . tool . definition
} ;
return toolInterface ;
} ) ;
log . info ( ` Available tools in registry: ${ availableTools . length } ` ) ;
// Log available tools for debugging
if ( availableTools . length > 0 ) {
const availableToolNames = availableTools . map ( t = > t . definition . function . name ) . join ( ', ' ) ;
const availableToolNames = availableTools . map ( t = > {
// Safely access the name property using type narrowing
if ( t && typeof t === 'object' && 'definition' in t &&
t . definition && typeof t . definition === 'object' &&
'function' in t . definition && t . definition . function &&
typeof t . definition . function === 'object' &&
'name' in t . definition . function &&
typeof t . definition . function . name === 'string' ) {
return t . definition . function . name ;
}
return 'unknown' ;
} ) . join ( ', ' ) ;
log . info ( ` Available tools: ${ availableToolNames } ` ) ;
}
@ -66,9 +109,11 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
log . info ( 'Attempting to initialize tools as recovery step' ) ;
// Tools are already initialized in the AIServiceManager constructor
// No need to initialize them again
log . info ( ` After recovery initialization: ${ toolRegistry . getAllTools ( ) . length } tools available ` ) ;
} catch ( error : any ) {
log . error ( ` Failed to initialize tools in recovery step: ${ error . message } ` ) ;
const toolCount = toolRegistry . getAllTools ( ) . length ;
log . info ( ` After recovery initialization: ${ toolCount } tools available ` ) ;
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
log . error ( ` Failed to initialize tools in recovery step: ${ errorMessage } ` ) ;
}
}
@ -88,25 +133,29 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
const executionStartTime = Date . now ( ) ;
// First validate all tools before executi ng them
// First validate all tools before executi o n
log . info ( ` Validating ${ response . tool_calls ? . length || 0 } tools before execution ` ) ;
const validationResults = await Promise . all ( ( response . tool_calls || [ ] ) . map ( async ( toolCall ) = > {
const validationResults : ToolValidationResult [ ] = 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 } ` ) ;
// Generate guidance for the LLM when a tool is not found
const guidance = this . generateToolGuidance ( toolCall . function . name , ` Tool not found: ${ toolCall . function . name } ` ) ;
return {
toolCall ,
valid : false ,
tool : null ,
error : ` Tool not found: ${ toolCall . function . name } `
error : ` Tool not found: ${ toolCall . function . name } ` ,
guidance // Add guidance for the LLM
} ;
}
// Validate the tool before execution
const isToolValid = await this . validateToolBeforeExecution ( tool , toolCall . function . name ) ;
// Use unknown as an intermediate step for type conversion
const isToolValid = await this . validateToolBeforeExecution ( tool as unknown as ToolInterface , toolCall . function . name ) ;
if ( ! isToolValid ) {
throw new Error ( ` Tool ' ${ toolCall . function . name } ' failed validation before execution ` ) ;
}
@ -114,15 +163,16 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
return {
toolCall ,
valid : true ,
tool ,
tool : tool as unknown as ToolInterface ,
error : null
} ;
} catch ( error : any ) {
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
return {
toolCall ,
valid : false ,
tool : null ,
error : error .message || String ( error )
error : error Message
} ;
}
} ) ) ;
@ -141,15 +191,21 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
: JSON . stringify ( toolCall . function . arguments ) ;
log . info ( ` Tool parameters: ${ argsStr } ` ) ;
// If validation failed, throw the error
// If validation failed, generate guidance and throw the error
if ( ! valid || ! tool ) {
throw new Error ( error || ` Unknown validation error for tool ' ${ toolCall . function . name } ' ` ) ;
// If we already have guidance from validation, use it, otherwise generate it
const toolGuidance = validation . guidance ||
this . generateToolGuidance ( toolCall . function . name ,
error || ` Unknown validation error for tool ' ${ toolCall . function . name } ' ` ) ;
// Include the guidance in the error message
throw new Error ( ` ${ error || ` Unknown validation error for tool ' ${ toolCall . function . name } ' ` } \ n ${ toolGuidance } ` ) ;
}
log . info ( ` Tool validated successfully: ${ toolCall . function . name } ` ) ;
// Parse arguments (handle both string and object formats)
let args ;
let args : Record < string , unknown > ;
// At this stage, arguments should already be processed by the provider-specific service
// But we still need to handle different formats just in case
if ( typeof toolCall . function . arguments === 'string' ) {
@ -157,7 +213,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
try {
// Try to parse as JSON first
args = JSON . parse ( toolCall . function . arguments ) ;
args = JSON . parse ( toolCall . function . arguments ) as Record < string , unknown > ;
log . info ( ` Parsed JSON arguments: ${ Object . keys ( args ) . join ( ', ' ) } ` ) ;
} catch ( e : unknown ) {
// If it's not valid JSON, try to check if it's a stringified object with quotes
@ -168,25 +224,26 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
// Try to clean it up
try {
const cleaned = toolCall . function . arguments
. replace ( /^['"]|['"]$/g , '' ) // Remove surrounding quotes
. replace ( /^['"]/g , '' ) // Remove surrounding quotes
. replace ( /['"]$/g , '' ) // Remove surrounding quotes
. replace ( /\\"/g , '"' ) // Replace escaped quotes
. replace ( /([{,])\s*'([^']+)'\s*:/g , '$1"$2":' ) // Replace single quotes around property names
. replace ( /([{,])\s*(\w+)\s*:/g , '$1"$2":' ) ; // Add quotes around unquoted property names
log . info ( ` Cleaned argument string: ${ cleaned } ` ) ;
args = JSON . parse ( cleaned ) ;
args = JSON . parse ( cleaned ) as Record < string , unknown > ;
log . info ( ` Successfully parsed cleaned arguments: ${ Object . keys ( args ) . join ( ', ' ) } ` ) ;
} catch ( cleanError : unknown ) {
// If all parsing fails, treat it as a text argument
const cleanErrorMessage = cleanError instanceof Error ? cleanError.message : String ( cleanError ) ;
log . info ( ` Failed to parse cleaned arguments: ${ cleanErrorMessage } ` ) ;
args = { text : toolCall.function.arguments } ;
log . info ( ` Using text argument: ${ args . text . substring ( 0 , 50 ) } ... ` ) ;
log . info ( ` Using text argument: ${ ( args . text as string ) . substring ( 0 , 50 ) } ... ` ) ;
}
}
} else {
// Arguments are already an object
args = toolCall . function . arguments ;
args = toolCall . function . arguments as Record < string , unknown > ;
log . info ( ` Using object arguments with keys: ${ Object . keys ( args ) . join ( ', ' ) } ` ) ;
}
@ -263,9 +320,16 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
callbackResult . catch ( ( e : Error ) = > log . error ( ` Error sending tool execution complete event: ${ e . message } ` ) ) ;
}
}
} catch ( execError : any ) {
} catch ( execError : unknown ) {
const executionTime = Date . now ( ) - executionStart ;
log . error ( ` ================ TOOL EXECUTION FAILED in ${ executionTime } ms: ${ execError . message } ================ ` ) ;
const errorMessage = execError instanceof Error ? execError.message : String ( execError ) ;
log . error ( ` ================ TOOL EXECUTION FAILED in ${ executionTime } ms: ${ errorMessage } ================ ` ) ;
// Generate guidance for the failed tool execution
const toolGuidance = this . generateToolGuidance ( toolCall . function . name , errorMessage ) ;
// Add the guidance to the error message for the LLM
const enhancedErrorMessage = ` ${ errorMessage } \ n ${ toolGuidance } ` ;
// Record this failed tool execution if there's a sessionId available
if ( input . options ? . sessionId ) {
@ -276,7 +340,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
toolCall . id || ` tool- ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 9 ) } ` ,
args ,
"" , // No result for failed execution
e xecError. message || String ( execError )
e nhancedErrorMessage // Use enhanced error message with guidance
) ;
} catch ( storageError ) {
log . error ( ` Failed to record tool execution error in chat storage: ${ storageError } ` ) ;
@ -291,7 +355,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
name : toolCall.function.name ,
arguments : { } as Record < string , unknown >
} ,
error : e xecError.message || String ( execError ) ,
error : e nhancedErrorMessage, // Include guidance in the error message
type : 'error' as const
} ;
@ -306,6 +370,10 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
}
}
// Modify the error to include our guidance
if ( execError instanceof Error ) {
execError . message = enhancedErrorMessage ;
}
throw execError ;
}
@ -322,19 +390,24 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
name : toolCall.function.name ,
result
} ;
} catch ( error : any ) {
log . error ( ` Error executing tool ${ toolCall . function . name } : ${ error . message || String ( error ) } ` ) ;
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
log . error ( ` Error executing tool ${ toolCall . function . name } : ${ errorMessage } ` ) ;
// Emit tool error event if not already handled in the try/catch above
// and if streaming is enabled
if ( streamCallback && error . name !== "ExecutionError" ) {
// Need to check if error is an object with a name property of type string
const isExecutionError = typeof error === 'object' && error !== null &&
'name' in error && ( error as { name : unknown } ) . name === "ExecutionError" ;
if ( streamCallback && ! isExecutionError ) {
const toolExecutionData = {
action : 'error' ,
tool : {
name : toolCall.function.name ,
arguments : { } as Record < string , unknown >
} ,
error : error .message || String ( error ) ,
error : error Message ,
type : 'error' as const
} ;
@ -353,7 +426,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
return {
toolCallId : toolCall.id ,
name : toolCall.function.name ,
result : ` Error: ${ error . message || String ( error ) } `
result : ` Error: ${ error Message } `
} ;
}
} ) ) ;
@ -364,6 +437,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
// Add each tool result to the messages array
const toolResultMessages : Message [ ] = [ ] ;
let hasEmptyResults = false ;
for ( const result of toolResults ) {
const { toolCallId , name , result : toolResult } = result ;
@ -373,10 +447,23 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
? toolResult
: JSON . stringify ( toolResult , null , 2 ) ;
// Check if result is empty or unhelpful
const isEmptyResult = this . isEmptyToolResult ( toolResult , name ) ;
if ( isEmptyResult && ! resultContent . startsWith ( 'Error:' ) ) {
hasEmptyResults = true ;
log . info ( ` Empty result detected for tool ${ name } . Will add suggestion to try different parameters. ` ) ;
}
// Add enhancement for empty results
let enhancedContent = resultContent ;
if ( isEmptyResult && ! resultContent . startsWith ( 'Error:' ) ) {
enhancedContent = ` ${ resultContent } \ n \ nNOTE: This tool returned no useful results with the provided parameters. Consider trying again with different parameters such as broader search terms, different filters, or alternative approaches. ` ;
}
// Add a new message for the tool result
const toolMessage : Message = {
role : 'tool' ,
content : resultContent ,
content : enhanced Content,
name : name ,
tool_call_id : toolCallId
} ;
@ -385,7 +472,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
log . info ( ` -------- Tool Result for ${ name } (ID: ${ toolCallId } ) -------- ` ) ;
log . info ( ` Result type: ${ typeof toolResult } ` ) ;
log . info ( ` Result preview: ${ resultContent . substring ( 0 , 150 ) } ${ resultContent . length > 150 ? '...' : '' } ` ) ;
log . info ( ` Tool result status: ${ resultContent . startsWith ( 'Error:' ) ? 'ERROR' : 'SUCCESS' } ` ) ;
log . info ( ` Tool result status: ${ resultContent . startsWith ( 'Error:' ) ? 'ERROR' : isEmptyResult ? 'EMPTY' : 'SUCCESS' } ` ) ;
updatedMessages . push ( toolMessage ) ;
toolResultMessages . push ( toolMessage ) ;
@ -398,7 +485,36 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
const needsFollowUp = hasToolResults ;
log . info ( ` Follow-up needed: ${ needsFollowUp } ` ) ;
log . info ( ` Reasoning: ${ hasToolResults ? 'Has tool results to process' : 'No tool results' } ${ hasErrors ? ', contains errors' : '' } ` ) ;
log . info ( ` Reasoning: ${ hasToolResults ? 'Has tool results to process' : 'No tool results' } ${ hasErrors ? ', contains errors' : '' } ${ hasEmptyResults ? ', contains empty results' : '' } ` ) ;
// Add a system message with hints for empty results
if ( hasEmptyResults && needsFollowUp ) {
log . info ( 'Adding system message requiring the LLM to run additional tools with different parameters' ) ;
// Build a more directive message based on which tools were empty
const emptyToolNames = toolResultMessages
. filter ( msg = > this . isEmptyToolResult ( msg . content , msg . name || '' ) )
. map ( msg = > msg . name ) ;
let directiveMessage = ` YOU MUST NOT GIVE UP AFTER A SINGLE EMPTY SEARCH RESULT. ` ;
if ( emptyToolNames . includes ( 'search_notes' ) || emptyToolNames . includes ( 'vector_search' ) ) {
directiveMessage += ` IMMEDIATELY RUN ANOTHER SEARCH TOOL with broader search terms, alternative keywords, or related concepts. ` ;
directiveMessage += ` Try synonyms, more general terms, or related topics. ` ;
}
if ( emptyToolNames . includes ( 'keyword_search' ) ) {
directiveMessage += ` IMMEDIATELY TRY VECTOR_SEARCH INSTEAD as it might find semantic matches where keyword search failed. ` ;
}
directiveMessage += ` DO NOT ask the user what to do next or if they want general information. CONTINUE SEARCHING with different parameters. ` ;
updatedMessages . push ( {
role : 'system' ,
content : directiveMessage
} ) ;
}
log . info ( ` Total messages to return to pipeline: ${ updatedMessages . length } ` ) ;
log . info ( ` Last 3 messages in conversation: ` ) ;
const lastMessages = updatedMessages . slice ( - 3 ) ;
@ -421,7 +537,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
* @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 > {
private async getOrCreateDependency ( dependencyType : string , toolName : string ) : Promise < unknown | null > {
const aiServiceManager = ( await import ( '../../ai_service_manager.js' ) ) . default ;
try {
@ -448,8 +564,9 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
// 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 } ` ) ;
} catch ( initError : unknown ) {
const errorMessage = initError instanceof Error ? initError.message : String ( initError ) ;
log . error ( ` Failed to initialize agent tools: ${ errorMessage } ` ) ;
return null ;
}
} else {
@ -474,8 +591,9 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
// Unknown dependency type
log . error ( ` Unknown dependency type: ${ dependencyType } ` ) ;
return null ;
} catch ( error : any ) {
log . error ( ` Error getting or creating dependency ' ${ dependencyType } ': ${ error . message } ` ) ;
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
log . error ( ` Error getting or creating dependency ' ${ dependencyType } ': ${ errorMessage } ` ) ;
return null ;
}
}
@ -485,7 +603,7 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
* @param tool The tool to validate
* @param toolName The name of the tool
* /
private async validateToolBeforeExecution ( tool : any , toolName : string ) : Promise < boolean > {
private async validateToolBeforeExecution ( tool : ToolInterface , toolName : string ) : Promise < boolean > {
try {
if ( ! tool ) {
log . error ( ` Tool ' ${ toolName } ' not found or failed validation ` ) ;
@ -525,31 +643,164 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
return false ;
}
log . info ( 'Successfully initialized vectorSearchTool' ) ;
} catch ( initError : any ) {
log . error ( ` Failed to initialize agent tools: ${ initError . message } ` ) ;
} catch ( initError : unknown ) {
const errorMessage = initError instanceof Error ? initError.message : String ( initError ) ;
log . error ( ` Failed to initialize agent tools: ${ errorMessage } ` ) ;
return false ;
}
}
// Verify the vectorSearchTool has the required methods
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 } ` ) ;
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
log . error ( ` Error validating dependencies for tool ' ${ toolName } ': ${ errorMessage } ` ) ;
return false ;
}
}
// Add additional tool-specific validations here
return true ;
} catch ( error : any ) {
log . error ( ` Error validating tool before execution: ${ error . message } ` ) ;
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
log . error ( ` Error validating tool before execution: ${ errorMessage } ` ) ;
return false ;
}
}
/ * *
* Generate guidance for the LLM when a tool fails or is not found
* @param toolName The name of the tool that failed
* @param errorMessage The error message from the failed tool
* @returns A guidance message for the LLM with suggestions of what to try next
* /
private generateToolGuidance ( toolName : string , errorMessage : string ) : string {
// Get all available tool names for recommendations
const availableTools = toolRegistry . getAllTools ( ) ;
const availableToolNames = availableTools
. map ( t = > {
if ( t && typeof t === 'object' && 'definition' in t &&
t . definition && typeof t . definition === 'object' &&
'function' in t . definition && t . definition . function &&
typeof t . definition . function === 'object' &&
'name' in t . definition . function &&
typeof t . definition . function . name === 'string' ) {
return t . definition . function . name ;
}
return '' ;
} )
. filter ( name = > name !== '' ) ;
// Create specific guidance based on the error and tool
let guidance = ` TOOL GUIDANCE: The tool ' ${ toolName } ' failed with error: ${ errorMessage } . \ n ` ;
// Add suggestions based on the specific tool and error
if ( toolName === 'attribute_search' && errorMessage . includes ( 'Invalid attribute type' ) ) {
guidance += "CRITICAL REQUIREMENT: The 'attribute_search' tool requires 'attributeType' parameter that must be EXACTLY 'label' or 'relation' (lowercase, no other values).\n" ;
guidance += "CORRECT EXAMPLE: { \"attributeType\": \"label\", \"attributeName\": \"important\", \"attributeValue\": \"yes\" }\n" ;
guidance += "INCORRECT EXAMPLE: { \"attributeType\": \"Label\", ... } - Case matters! Must be lowercase.\n" ;
}
else if ( errorMessage . includes ( 'Tool not found' ) ) {
// Provide guidance on available search tools if a tool wasn't found
const searchTools = availableToolNames . filter ( name = > name . includes ( 'search' ) ) ;
guidance += ` AVAILABLE SEARCH TOOLS: ${ searchTools . join ( ', ' ) } \ n ` ;
guidance += "TRY VECTOR SEARCH: For conceptual matches, use 'vector_search' with a query parameter.\n" ;
guidance += "EXAMPLE: { \"query\": \"your search terms here\" }\n" ;
}
else if ( errorMessage . includes ( 'missing required parameter' ) ) {
// Provide parameter guidance based on the tool name
if ( toolName === 'vector_search' ) {
guidance += "REQUIRED PARAMETERS: The 'vector_search' tool requires a 'query' parameter.\n" ;
guidance += "EXAMPLE: { \"query\": \"your search terms here\" }\n" ;
} else if ( toolName === 'keyword_search' ) {
guidance += "REQUIRED PARAMETERS: The 'keyword_search' tool requires a 'query' parameter.\n" ;
guidance += "EXAMPLE: { \"query\": \"your search terms here\" }\n" ;
}
}
// Add a general suggestion to try vector_search as a fallback
if ( ! toolName . includes ( 'vector_search' ) ) {
guidance += "RECOMMENDATION: If specific searches fail, try the 'vector_search' tool which performs semantic searches.\n" ;
}
return guidance ;
}
/ * *
* Determines if a tool result is effectively empty or unhelpful
* @param result The result from the tool execution
* @param toolName The name of the tool that was executed
* @returns true if the result is considered empty or unhelpful
* /
private isEmptyToolResult ( result : unknown , toolName : string ) : boolean {
// Handle string results
if ( typeof result === 'string' ) {
const trimmed = result . trim ( ) ;
if ( trimmed === '' || trimmed === '[]' || trimmed === '{}' ) {
return true ;
}
// Tool-specific empty results (for string responses)
if ( toolName === 'search_notes' &&
( trimmed === 'No matching notes found.' ||
trimmed . includes ( 'No results found' ) ||
trimmed . includes ( 'No matches found' ) ||
trimmed . includes ( 'No notes found' ) ) ) {
// This is a valid result (empty, but valid), don't mark as empty so LLM can see feedback
return false ;
}
if ( toolName === 'vector_search' &&
( trimmed . includes ( 'No results found' ) ||
trimmed . includes ( 'No matching documents' ) ) ) {
return true ;
}
if ( toolName === 'keyword_search' &&
( trimmed . includes ( 'No matches found' ) ||
trimmed . includes ( 'No results for' ) ) ) {
return true ;
}
}
// Handle object/array results
else if ( result !== null && typeof result === 'object' ) {
// Check if it's an empty array
if ( Array . isArray ( result ) && result . length === 0 ) {
return true ;
}
// Check if it's an object with no meaningful properties
// or with properties indicating empty results
if ( ! Array . isArray ( result ) ) {
if ( Object . keys ( result ) . length === 0 ) {
return true ;
}
// Tool-specific object empty checks
const resultObj = result as Record < string , unknown > ;
if ( toolName === 'search_notes' &&
'results' in resultObj &&
Array . isArray ( resultObj . results ) &&
resultObj . results . length === 0 ) {
return true ;
}
if ( toolName === 'vector_search' &&
'matches' in resultObj &&
Array . isArray ( resultObj . matches ) &&
resultObj . matches . length === 0 ) {
return true ;
}
}
}
return false ;
}
/ * *
* Preload the vector search tool to ensure it ' s available before tool execution
* /
@ -571,8 +822,9 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
} else {
log . error ( ` Vector search tool not available after initialization ` ) ;
}
} catch ( error : any ) {
log . error ( ` Failed to preload vector search tool: ${ error . message } ` ) ;
} catch ( error : unknown ) {
const errorMessage = error instanceof Error ? error.message : String ( error ) ;
log . error ( ` Failed to preload vector search tool: ${ errorMessage } ` ) ;
}
}
}