mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	refactor(llm): improve search tools error handling and parameter validation with clearer guidance
This commit is contained in:
		
							parent
							
								
									910c5039f4
								
							
						
					
					
						commit
						a7906d6b99
					
				| @ -699,8 +699,9 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re | |||||||
| 
 | 
 | ||||||
|         // Add suggestions based on the specific tool and error
 |         // Add suggestions based on the specific tool and error
 | ||||||
|         if (toolName === 'attribute_search' && errorMessage.includes('Invalid attribute type')) { |         if (toolName === 'attribute_search' && errorMessage.includes('Invalid attribute type')) { | ||||||
|             guidance += "CORRECT USAGE: The 'attribute_search' tool requires a valid 'attributeType' parameter that must be either 'label' or 'relation'.\n"; |             guidance += "CRITICAL REQUIREMENT: The 'attribute_search' tool requires 'attributeType' parameter that must be EXACTLY 'label' or 'relation' (lowercase, no other values).\n"; | ||||||
|             guidance += "EXAMPLE: { \"attributeType\": \"label\", \"attributeValue\": \"important\" }\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')) { |         else if (errorMessage.includes('Tool not found')) { | ||||||
|             // Provide guidance on available search tools if a tool wasn't found
 |             // Provide guidance on available search tools if a tool wasn't found
 | ||||||
| @ -748,7 +749,8 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re | |||||||
|                  trimmed.includes('No results found') || |                  trimmed.includes('No results found') || | ||||||
|                  trimmed.includes('No matches found') || |                  trimmed.includes('No matches found') || | ||||||
|                  trimmed.includes('No notes found'))) { |                  trimmed.includes('No notes found'))) { | ||||||
|                 return true; |                 // This is a valid result (empty, but valid), don't mark as empty so LLM can see feedback
 | ||||||
|  |                 return false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (toolName === 'vector_search' && |             if (toolName === 'vector_search' && | ||||||
|  | |||||||
| @ -19,18 +19,18 @@ export const attributeSearchToolDefinition: Tool = { | |||||||
|     type: 'function', |     type: 'function', | ||||||
|     function: { |     function: { | ||||||
|         name: 'attribute_search', |         name: 'attribute_search', | ||||||
|         description: 'Search for notes with specific attributes (labels or relations). Use this when you need to find notes based on their metadata rather than content.', |         description: 'Search for notes with specific attributes (labels or relations). Use this when you need to find notes based on their metadata rather than content. IMPORTANT: attributeType must be exactly "label" or "relation" (lowercase).', | ||||||
|         parameters: { |         parameters: { | ||||||
|             type: 'object', |             type: 'object', | ||||||
|             properties: { |             properties: { | ||||||
|                 attributeType: { |                 attributeType: { | ||||||
|                     type: 'string', |                     type: 'string', | ||||||
|                     description: 'Type of attribute to search for: "label" or "relation"', |                     description: 'MUST be exactly "label" or "relation" (lowercase, no other values are valid)', | ||||||
|                     enum: ['label', 'relation'] |                     enum: ['label', 'relation'] | ||||||
|                 }, |                 }, | ||||||
|                 attributeName: { |                 attributeName: { | ||||||
|                     type: 'string', |                     type: 'string', | ||||||
|                     description: 'Name of the attribute to search for' |                     description: 'Name of the attribute to search for (e.g., "important", "todo", "related-to")' | ||||||
|                 }, |                 }, | ||||||
|                 attributeValue: { |                 attributeValue: { | ||||||
|                     type: 'string', |                     type: 'string', | ||||||
| @ -63,7 +63,7 @@ export class AttributeSearchTool implements ToolHandler { | |||||||
| 
 | 
 | ||||||
|             // Validate attribute type
 |             // Validate attribute type
 | ||||||
|             if (attributeType !== 'label' && attributeType !== 'relation') { |             if (attributeType !== 'label' && attributeType !== 'relation') { | ||||||
|                 return `Error: Invalid attribute type. Must be either "label" or "relation".`; |                 return `Error: Invalid attribute type. Must be exactly "label" or "relation" (lowercase). You provided: "${attributeType}".`; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Execute the search
 |             // Execute the search
 | ||||||
| @ -133,7 +133,7 @@ export class AttributeSearchTool implements ToolHandler { | |||||||
|                         } else { |                         } else { | ||||||
|                             contentPreview = String(content).substring(0, 150) + (String(content).length > 150 ? '...' : ''); |                             contentPreview = String(content).substring(0, 150) + (String(content).length > 150 ? '...' : ''); | ||||||
|                         } |                         } | ||||||
|                     } catch (e) { |                     } catch (_) { | ||||||
|                         contentPreview = '[Content not available]'; |                         contentPreview = '[Content not available]'; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
| @ -148,9 +148,10 @@ export class AttributeSearchTool implements ToolHandler { | |||||||
|                     }; |                     }; | ||||||
|                 }) |                 }) | ||||||
|             }; |             }; | ||||||
|         } catch (error: any) { |         } catch (error: unknown) { | ||||||
|             log.error(`Error executing attribute_search tool: ${error.message || String(error)}`); |             const errorMessage = error instanceof Error ? error.message : String(error); | ||||||
|             return `Error: ${error.message || String(error)}`; |             log.error(`Error executing attribute_search tool: ${errorMessage}`); | ||||||
|  |             return `Error: ${errorMessage}`; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,17 +17,17 @@ export const searchNotesToolDefinition: Tool = { | |||||||
|     type: 'function', |     type: 'function', | ||||||
|     function: { |     function: { | ||||||
|         name: 'search_notes', |         name: 'search_notes', | ||||||
|         description: 'Search for notes in the database using semantic search. Returns notes most semantically related to the query.', |         description: 'Search for notes in the database using semantic search. Returns notes most semantically related to the query. Use specific, descriptive queries for best results.', | ||||||
|         parameters: { |         parameters: { | ||||||
|             type: 'object', |             type: 'object', | ||||||
|             properties: { |             properties: { | ||||||
|                 query: { |                 query: { | ||||||
|                     type: 'string', |                     type: 'string', | ||||||
|                     description: 'The search query to find semantically related notes' |                     description: 'The search query to find semantically related notes. Be specific and descriptive for best results.' | ||||||
|                 }, |                 }, | ||||||
|                 parentNoteId: { |                 parentNoteId: { | ||||||
|                     type: 'string', |                     type: 'string', | ||||||
|                     description: 'Optional system ID of the parent note to restrict search to a specific branch (not the title). This is a unique identifier like "abc123def456".' |                     description: 'Optional system ID of the parent note to restrict search to a specific branch (not the title). This is a unique identifier like "abc123def456". Do not use note titles here.' | ||||||
|                 }, |                 }, | ||||||
|                 maxResults: { |                 maxResults: { | ||||||
|                     type: 'number', |                     type: 'number', | ||||||
| @ -142,11 +142,11 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|                         const result = await llmService.generateChatCompletion(messages, { |                         const result = await llmService.generateChatCompletion(messages, { | ||||||
|                             temperature: 0.3, |                             temperature: 0.3, | ||||||
|                             maxTokens: 200, |                             maxTokens: 200, | ||||||
|                             // Use any to bypass the type checking for special parameters
 |                             // Type assertion to bypass type checking for special internal parameters
 | ||||||
|                             ...(({  |                             ...(({  | ||||||
|                                 bypassFormatter: true, |                                 bypassFormatter: true, | ||||||
|                                 bypassContextProcessing: true |                                 bypassContextProcessing: true | ||||||
|                             } as any)) |                             } as Record<string, boolean>)) | ||||||
|                         }); |                         }); | ||||||
| 
 | 
 | ||||||
|                         if (result && result.text) { |                         if (result && result.text) { | ||||||
| @ -159,30 +159,33 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Fall back to smart truncation if summarization fails or isn't requested
 |             try { | ||||||
|             const previewLength = Math.min(formattedContent.length, 600); |                 // Fall back to smart truncation if summarization fails or isn't requested
 | ||||||
|             let preview = formattedContent.substring(0, previewLength); |                 const previewLength = Math.min(formattedContent.length, 600); | ||||||
|  |                 let preview = formattedContent.substring(0, previewLength); | ||||||
| 
 | 
 | ||||||
|             // Only add ellipsis if we've truncated the content
 |                 // Only add ellipsis if we've truncated the content
 | ||||||
|             if (previewLength < formattedContent.length) { |                 if (previewLength < formattedContent.length) { | ||||||
|                 // Try to find a natural break point
 |                     // Try to find a natural break point
 | ||||||
|                 const breakPoints = ['. ', '.\n', '\n\n', '\n', '. ']; |                     const breakPoints = ['. ', '.\n', '\n\n', '\n', '. ']; | ||||||
|                 let breakFound = false; |  | ||||||
| 
 | 
 | ||||||
|                 for (const breakPoint of breakPoints) { |                     for (const breakPoint of breakPoints) { | ||||||
|                     const lastBreak = preview.lastIndexOf(breakPoint); |                         const lastBreak = preview.lastIndexOf(breakPoint); | ||||||
|                     if (lastBreak > previewLength * 0.6) { // At least 60% of the way through
 |                         if (lastBreak > previewLength * 0.6) { // At least 60% of the way through
 | ||||||
|                         preview = preview.substring(0, lastBreak + breakPoint.length); |                             preview = preview.substring(0, lastBreak + breakPoint.length); | ||||||
|                         breakFound = true; |                             break; | ||||||
|                         break; |                         } | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|  |                     // Add ellipsis if truncated
 | ||||||
|  |                     preview += '...'; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // Add ellipsis if truncated
 |                 return preview; | ||||||
|                 preview += '...'; |             } catch (error) { | ||||||
|  |                 log.error(`Error getting rich content preview: ${error}`); | ||||||
|  |                 return 'Error retrieving content preview'; | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             return preview; |  | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             log.error(`Error getting rich content preview: ${error}`); |             log.error(`Error getting rich content preview: ${error}`); | ||||||
|             return 'Error retrieving content preview'; |             return 'Error retrieving content preview'; | ||||||
| @ -226,11 +229,8 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|             // Execute the search
 |             // Execute the search
 | ||||||
|             log.info(`Performing semantic search for: "${query}"`); |             log.info(`Performing semantic search for: "${query}"`); | ||||||
|             const searchStartTime = Date.now(); |             const searchStartTime = Date.now(); | ||||||
|             const results = await vectorSearchTool.searchNotes(query, { |             const response = await vectorSearchTool.searchNotes(query, parentNoteId, maxResults); | ||||||
|                 parentNoteId, |             const results: Array<Record<string, unknown>> = response?.matches ?? []; | ||||||
|                 maxResults |  | ||||||
|                 // Don't pass summarize - we'll handle it ourselves
 |  | ||||||
|             }); |  | ||||||
|             const searchDuration = Date.now() - searchStartTime; |             const searchDuration = Date.now() - searchStartTime; | ||||||
| 
 | 
 | ||||||
|             log.info(`Search completed in ${searchDuration}ms, found ${results.length} matching notes`); |             log.info(`Search completed in ${searchDuration}ms, found ${results.length} matching notes`); | ||||||
| @ -247,12 +247,16 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|             // Get enhanced previews for each result
 |             // Get enhanced previews for each result
 | ||||||
|             const enhancedResults = await Promise.all( |             const enhancedResults = await Promise.all( | ||||||
|                 results.map(async (result: any) => { |                 results.map(async (result: any) => { | ||||||
|                     const preview = await this.getRichContentPreview(result.noteId, summarize); |                     const noteId = result.noteId; | ||||||
|  |                     const preview = await this.getRichContentPreview(noteId, summarize); | ||||||
| 
 | 
 | ||||||
|                     return { |                     return { | ||||||
|                         noteId: result.noteId, |                         noteId: noteId, | ||||||
|                         title: result.title, |                         title: result?.title as string || '[Unknown title]', | ||||||
|                         preview: preview, |                         preview: preview, | ||||||
|  |                         score: result?.score as number, | ||||||
|  |                         dateCreated: result?.dateCreated as string, | ||||||
|  |                         dateModified: result?.dateModified as string, | ||||||
|                         similarity: Math.round(result.similarity * 100) / 100, |                         similarity: Math.round(result.similarity * 100) / 100, | ||||||
|                         parentId: result.parentId |                         parentId: result.parentId | ||||||
|                     }; |                     }; | ||||||
| @ -260,14 +264,24 @@ export class SearchNotesTool implements ToolHandler { | |||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             // Format the results
 |             // Format the results
 | ||||||
|             return { |             if (results.length === 0) { | ||||||
|                 count: enhancedResults.length, |                 return { | ||||||
|                 results: enhancedResults, |                     count: 0, | ||||||
|                 message: "Note: Use the noteId (not the title) when performing operations on specific notes with other tools." |                     results: [], | ||||||
|             }; |                     query: query, | ||||||
|         } catch (error: any) { |                     message: 'No notes found matching your query. Try using more general terms or try the keyword_search_notes tool with a different query. Note: Use the noteId (not the title) when performing operations on specific notes with other tools.' | ||||||
|             log.error(`Error executing search_notes tool: ${error.message || String(error)}`); |                 }; | ||||||
|             return `Error: ${error.message || String(error)}`; |             } else { | ||||||
|  |                 return { | ||||||
|  |                     count: enhancedResults.length, | ||||||
|  |                     results: enhancedResults, | ||||||
|  |                     message: "Note: Use the noteId (not the title) when performing operations on specific notes with other tools." | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         } catch (error: unknown) { | ||||||
|  |             const errorMessage = error instanceof Error ? error.message : String(error); | ||||||
|  |             log.error(`Error executing search_notes tool: ${errorMessage}`); | ||||||
|  |             return `Error: ${errorMessage}`; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 perf3ct
						perf3ct