mirror of
https://github.com/zadam/trilium.git
synced 2025-12-05 15:04:24 +01:00
feat(llm): try to improve tool and tool calling, part 1
This commit is contained in:
parent
8ed62398fa
commit
dccd6477d2
@ -483,27 +483,51 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
|
|||||||
log.info(`Follow-up needed: ${needsFollowUp}`);
|
log.info(`Follow-up needed: ${needsFollowUp}`);
|
||||||
log.info(`Reasoning: ${hasToolResults ? 'Has tool results to process' : 'No tool results'} ${hasErrors ? ', contains errors' : ''} ${hasEmptyResults ? ', contains empty results' : ''}`);
|
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
|
// Add aggressive system message for continued tool usage
|
||||||
if (hasEmptyResults && needsFollowUp) {
|
if (needsFollowUp) {
|
||||||
log.info('Adding system message requiring the LLM to run additional tools with different parameters');
|
log.info('Adding enhanced system message to encourage continued tool usage');
|
||||||
|
|
||||||
// Build a more directive message based on which tools were empty
|
let directiveMessage = '';
|
||||||
|
|
||||||
|
if (hasEmptyResults) {
|
||||||
|
// Empty results - be very directive about trying alternatives
|
||||||
const emptyToolNames = toolResultMessages
|
const emptyToolNames = toolResultMessages
|
||||||
.filter(msg => this.isEmptyToolResult(msg.content, msg.name || ''))
|
.filter(msg => this.isEmptyToolResult(msg.content, msg.name || ''))
|
||||||
.map(msg => msg.name);
|
.map(msg => msg.name);
|
||||||
|
|
||||||
let directiveMessage = `YOU MUST NOT GIVE UP AFTER A SINGLE EMPTY SEARCH RESULT. `;
|
directiveMessage = `CRITICAL INSTRUCTION: YOU MUST NOT STOP AFTER EMPTY RESULTS!\n\n`;
|
||||||
|
directiveMessage += `REQUIRED ACTIONS:\n`;
|
||||||
|
|
||||||
if (emptyToolNames.includes('search_notes') || emptyToolNames.includes('keyword_search')) {
|
if (emptyToolNames.includes('search_notes')) {
|
||||||
directiveMessage += `IMMEDIATELY RUN ANOTHER SEARCH TOOL with broader search terms, alternative keywords, or related concepts. `;
|
directiveMessage += `1. IMMEDIATELY use keyword_search_notes with specific terms\n`;
|
||||||
directiveMessage += `Try synonyms, more general terms, or related topics. `;
|
directiveMessage += `2. Try attribute_search if content might be tagged/categorized\n`;
|
||||||
|
directiveMessage += `3. Use discover_tools to find alternative approaches\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emptyToolNames.includes('keyword_search')) {
|
if (emptyToolNames.includes('keyword_search_notes')) {
|
||||||
directiveMessage += `IMMEDIATELY TRY SEARCH_NOTES INSTEAD as it might find matches where keyword search failed. `;
|
directiveMessage += `1. IMMEDIATELY use search_notes for semantic matching\n`;
|
||||||
|
directiveMessage += `2. Try broader or alternative keyword terms\n`;
|
||||||
|
directiveMessage += `3. Use workflow_helper for guidance on next steps\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
directiveMessage += `DO NOT ask the user what to do next or if they want general information. CONTINUE SEARCHING with different parameters.`;
|
if (emptyToolNames.includes('attribute_search')) {
|
||||||
|
directiveMessage += `1. Use search_notes to find content about the attribute topic\n`;
|
||||||
|
directiveMessage += `2. Try different attribute names or types\n`;
|
||||||
|
directiveMessage += `3. Use search_suggestion to see available attributes\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
directiveMessage += `\nFORBIDDEN: Do NOT ask user for clarification or offer general information!\n`;
|
||||||
|
directiveMessage += `REQUIRED: CONTINUE with alternative tools and approaches immediately!`;
|
||||||
|
} else {
|
||||||
|
// Has results - encourage follow-up actions
|
||||||
|
directiveMessage = `EXCELLENT! You found results. Now CONTINUE the workflow:\n\n`;
|
||||||
|
directiveMessage += `NEXT REQUIRED ACTIONS:\n`;
|
||||||
|
directiveMessage += `1. Use read_note to examine the most relevant results\n`;
|
||||||
|
directiveMessage += `2. Use workflow_helper to plan next steps based on your findings\n`;
|
||||||
|
directiveMessage += `3. Consider using related tools for deeper analysis\n\n`;
|
||||||
|
directiveMessage += `GOAL: Provide comprehensive information by using multiple tools in sequence.\n`;
|
||||||
|
directiveMessage += `CONTINUE with tool usage - don't stop at just search results!`;
|
||||||
|
}
|
||||||
|
|
||||||
updatedMessages.push({
|
updatedMessages.push({
|
||||||
role: 'system',
|
role: 'system',
|
||||||
@ -609,11 +633,20 @@ export class ToolCallingStage extends BasePipelineStage<ToolExecutionInput, { re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a general suggestion to try search_notes as a fallback
|
// Add general recommendations including new helper tools
|
||||||
if (!toolName.includes('search_notes')) {
|
if (!toolName.includes('search_notes')) {
|
||||||
guidance += "RECOMMENDATION: If specific searches fail, try the 'search_notes' tool which performs semantic searches.\n";
|
guidance += "RECOMMENDATION: If specific searches fail, try the 'search_notes' tool which performs semantic searches.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always suggest helper tools for guidance
|
||||||
|
guidance += "HELPER TOOLS AVAILABLE:\n";
|
||||||
|
guidance += "• Use 'discover_tools' to find the right tool for your task\n";
|
||||||
|
guidance += "• Use 'workflow_helper' to get guidance on next steps\n";
|
||||||
|
guidance += "• Use 'search_suggestion' for search syntax help\n";
|
||||||
|
|
||||||
|
// Encourage continued tool usage
|
||||||
|
guidance += "\nIMPORTANT: Don't stop after one failed tool - try alternatives immediately!";
|
||||||
|
|
||||||
return guidance;
|
return guidance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,26 +19,61 @@ 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. IMPORTANT: attributeType must be exactly "label" or "relation" (lowercase).',
|
description: `ATTRIBUTE-BASED search for notes. Find notes by their labels or relations (metadata/tags).
|
||||||
|
|
||||||
|
BEST FOR: Finding notes by categories, tags, status, relationships, or other metadata
|
||||||
|
USE WHEN: You need notes with specific labels, relations, or organizational attributes
|
||||||
|
DIFFERENT FROM: search_notes (content) and keyword_search_notes (text)
|
||||||
|
|
||||||
|
CRITICAL: attributeType MUST be exactly "label" or "relation" (lowercase only!)
|
||||||
|
|
||||||
|
COMMON ATTRIBUTES:
|
||||||
|
• Labels: #important, #todo, #project, #status, #priority
|
||||||
|
• Relations: ~relatedTo, ~childOf, ~contains, ~references
|
||||||
|
|
||||||
|
NEXT STEPS: Use read_note with returned noteId values for full content`,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
attributeType: {
|
attributeType: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'MUST be exactly "label" or "relation" (lowercase, no other values are valid)',
|
description: `MUST be exactly "label" or "relation" (lowercase only!)
|
||||||
|
|
||||||
|
CORRECT: "label", "relation"
|
||||||
|
WRONG: "Label", "LABEL", "labels", "relations"
|
||||||
|
|
||||||
|
• "label" = tags/categories like #important, #todo
|
||||||
|
• "relation" = connections like ~relatedTo, ~childOf`,
|
||||||
enum: ['label', 'relation']
|
enum: ['label', 'relation']
|
||||||
},
|
},
|
||||||
attributeName: {
|
attributeName: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Name of the attribute to search for (e.g., "important", "todo", "related-to")'
|
description: `Name of the attribute to search for.
|
||||||
|
|
||||||
|
LABEL EXAMPLES:
|
||||||
|
- "important" (finds notes with #important)
|
||||||
|
- "status" (finds notes with #status label)
|
||||||
|
- "project" (finds notes tagged #project)
|
||||||
|
|
||||||
|
RELATION EXAMPLES:
|
||||||
|
- "relatedTo" (finds notes with ~relatedTo relation)
|
||||||
|
- "childOf" (finds notes with ~childOf relation)
|
||||||
|
- "contains" (finds notes with ~contains relation)`
|
||||||
},
|
},
|
||||||
attributeValue: {
|
attributeValue: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Optional value of the attribute. If not provided, will find all notes with the given attribute name.'
|
description: `OPTIONAL: Specific value of the attribute.
|
||||||
|
|
||||||
|
• Leave empty to find ALL notes with this attribute
|
||||||
|
• Specify value to find notes where attribute = specific value
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
- attributeName: "status", attributeValue: "completed"
|
||||||
|
- attributeName: "priority", attributeValue: "high"`
|
||||||
},
|
},
|
||||||
maxResults: {
|
maxResults: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Maximum number of results to return (default: 20)'
|
description: 'Number of results (1-50, default: 20). Use higher values for comprehensive searches.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['attributeType', 'attributeName']
|
required: ['attributeType', 'attributeName']
|
||||||
@ -61,9 +96,33 @@ export class AttributeSearchTool implements ToolHandler {
|
|||||||
|
|
||||||
log.info(`Executing attribute_search tool - Type: "${attributeType}", Name: "${attributeName}", Value: "${attributeValue || 'any'}", MaxResults: ${maxResults}`);
|
log.info(`Executing attribute_search tool - Type: "${attributeType}", Name: "${attributeName}", Value: "${attributeValue || 'any'}", MaxResults: ${maxResults}`);
|
||||||
|
|
||||||
// Validate attribute type
|
// Enhanced validation with helpful guidance
|
||||||
if (attributeType !== 'label' && attributeType !== 'relation') {
|
if (attributeType !== 'label' && attributeType !== 'relation') {
|
||||||
return `Error: Invalid attribute type. Must be exactly "label" or "relation" (lowercase). You provided: "${attributeType}".`;
|
const suggestions: string[] = [];
|
||||||
|
|
||||||
|
if (attributeType.toLowerCase() === 'label' || attributeType.toLowerCase() === 'relation') {
|
||||||
|
suggestions.push(`CASE SENSITIVE: Use "${attributeType.toLowerCase()}" (lowercase)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributeType.includes('label') || attributeType.includes('Label')) {
|
||||||
|
suggestions.push('CORRECT: Use "label" for tags and categories');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributeType.includes('relation') || attributeType.includes('Relation')) {
|
||||||
|
suggestions.push('CORRECT: Use "relation" for connections and relationships');
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessage = `Invalid attributeType: "${attributeType}"
|
||||||
|
|
||||||
|
REQUIRED: Must be exactly "label" or "relation" (lowercase only!)
|
||||||
|
|
||||||
|
${suggestions.length > 0 ? suggestions.join('\n') : ''}
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
• Find notes with #important tag: { "attributeType": "label", "attributeName": "important" }
|
||||||
|
• Find notes with ~relatedTo relation: { "attributeType": "relation", "attributeName": "relatedTo" }`;
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the search
|
// Execute the search
|
||||||
|
|||||||
@ -17,21 +17,49 @@ export const keywordSearchToolDefinition: Tool = {
|
|||||||
type: 'function',
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
name: 'keyword_search_notes',
|
name: 'keyword_search_notes',
|
||||||
description: 'Search for notes using exact keyword matching and attribute filters. Use this for precise searches when you need exact matches or want to filter by attributes.',
|
description: `EXACT KEYWORD search for notes. Finds notes containing specific words, phrases, or attribute filters.
|
||||||
|
|
||||||
|
BEST FOR: Finding notes with specific words/phrases you know exist
|
||||||
|
USE WHEN: You need exact text matches, specific terms, or attribute-based filtering
|
||||||
|
DIFFERENT FROM: search_notes (which finds conceptual/semantic matches)
|
||||||
|
|
||||||
|
SEARCH TYPES:
|
||||||
|
• Simple: "machine learning" (finds notes containing both words)
|
||||||
|
• Phrase: "\"exact phrase\"" (finds this exact phrase)
|
||||||
|
• Attributes: "#label" or "~relation" (notes with specific labels/relations)
|
||||||
|
• Complex: "AI #project ~relatedTo" (combines keywords with attributes)
|
||||||
|
|
||||||
|
NEXT STEPS: Use read_note with returned noteId values for full content`,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
query: {
|
query: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The search query using Trilium\'s search syntax. Examples: "rings tolkien" (find notes with both words), "#book #year >= 2000" (notes with label "book" and "year" attribute >= 2000), "note.content *=* important" (notes with "important" in content)'
|
description: `Keyword search query using Trilium search syntax.
|
||||||
|
|
||||||
|
SIMPLE EXAMPLES:
|
||||||
|
- "machine learning" (both words anywhere)
|
||||||
|
- "\"project management\"" (exact phrase)
|
||||||
|
- "python OR javascript" (either word)
|
||||||
|
|
||||||
|
ATTRIBUTE EXAMPLES:
|
||||||
|
- "#important" (notes with 'important' label)
|
||||||
|
- "~project" (notes with 'project' relation)
|
||||||
|
- "#status = completed" (specific label value)
|
||||||
|
|
||||||
|
COMBINED EXAMPLES:
|
||||||
|
- "AI #project #status = active" (AI content with project label and active status)
|
||||||
|
- "note.title *= \"weekly\"" (titles containing 'weekly')
|
||||||
|
|
||||||
|
AVOID: Conceptual queries better suited for search_notes`
|
||||||
},
|
},
|
||||||
maxResults: {
|
maxResults: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Maximum number of results to return (default: 10)'
|
description: 'Number of results (1-50, default: 10). Use higher values for comprehensive searches.'
|
||||||
},
|
},
|
||||||
includeArchived: {
|
includeArchived: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether to include archived notes in search results (default: false)'
|
description: 'INCLUDE ARCHIVED: Search archived notes too (default: false). Use true for complete historical search.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['query']
|
required: ['query']
|
||||||
@ -45,6 +73,22 @@ export const keywordSearchToolDefinition: Tool = {
|
|||||||
export class KeywordSearchTool implements ToolHandler {
|
export class KeywordSearchTool implements ToolHandler {
|
||||||
public definition: Tool = keywordSearchToolDefinition;
|
public definition: Tool = keywordSearchToolDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a keyword query to a semantic query suggestion
|
||||||
|
*/
|
||||||
|
private convertToSemanticQuery(keywordQuery: string): string {
|
||||||
|
// Remove search operators and attributes to create a semantic query
|
||||||
|
return keywordQuery
|
||||||
|
.replace(/#\w+/g, '') // Remove label filters
|
||||||
|
.replace(/~\w+/g, '') // Remove relation filters
|
||||||
|
.replace(/\"[^\"]*\"/g, (match) => match.slice(1, -1)) // Remove quotes but keep content
|
||||||
|
.replace(/\s+OR\s+/gi, ' ') // Replace OR with space
|
||||||
|
.replace(/\s+AND\s+/gi, ' ') // Replace AND with space
|
||||||
|
.replace(/note\.(title|content)\s*\*=\*\s*/gi, '') // Remove note.content operators
|
||||||
|
.replace(/\s+/g, ' ') // Normalize spaces
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the keyword search notes tool
|
* Execute the keyword search notes tool
|
||||||
*/
|
*/
|
||||||
@ -80,21 +124,52 @@ export class KeywordSearchTool implements ToolHandler {
|
|||||||
log.info(`No matching notes found for query: "${query}"`);
|
log.info(`No matching notes found for query: "${query}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format the results
|
// Format the results with enhanced guidance
|
||||||
|
if (limitedResults.length === 0) {
|
||||||
|
return {
|
||||||
|
count: 0,
|
||||||
|
results: [],
|
||||||
|
query: query,
|
||||||
|
searchType: 'keyword',
|
||||||
|
message: 'No exact keyword matches found.',
|
||||||
|
nextSteps: {
|
||||||
|
immediate: [
|
||||||
|
`Try search_notes for semantic/conceptual search: "${this.convertToSemanticQuery(query)}"`,
|
||||||
|
`Use attribute_search if looking for specific labels or relations`,
|
||||||
|
`Try simpler keywords or check spelling`
|
||||||
|
],
|
||||||
|
queryHelp: [
|
||||||
|
'Remove quotes for broader matching',
|
||||||
|
'Try individual words instead of phrases',
|
||||||
|
'Use OR operator: "word1 OR word2"',
|
||||||
|
'Check if content might be in archived notes (set includeArchived: true)'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
count: limitedResults.length,
|
count: limitedResults.length,
|
||||||
totalFound: searchResults.length,
|
totalFound: searchResults.length,
|
||||||
|
query: query,
|
||||||
|
searchType: 'keyword',
|
||||||
|
message: 'Found exact keyword matches. Use noteId values with other tools.',
|
||||||
|
nextSteps: {
|
||||||
|
examine: `Use read_note with any noteId (e.g., "${limitedResults[0].noteId}") to get full content`,
|
||||||
|
refine: limitedResults.length < searchResults.length ? `Found ${searchResults.length} total matches (showing ${limitedResults.length}). Increase maxResults for more.` : null,
|
||||||
|
related: 'Use search_notes for conceptually related content beyond exact keywords'
|
||||||
|
},
|
||||||
results: limitedResults.map(note => {
|
results: limitedResults.map(note => {
|
||||||
// Get a preview of the note content
|
// Get a preview of the note content with highlighted search terms
|
||||||
let contentPreview = '';
|
let contentPreview = '';
|
||||||
try {
|
try {
|
||||||
const content = note.getContent();
|
const content = note.getContent();
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
contentPreview = content.length > 150 ? content.substring(0, 150) + '...' : content;
|
contentPreview = content.length > 200 ? content.substring(0, 200) + '...' : content;
|
||||||
} else if (Buffer.isBuffer(content)) {
|
} else if (Buffer.isBuffer(content)) {
|
||||||
contentPreview = '[Binary content]';
|
contentPreview = '[Binary content]';
|
||||||
} else {
|
} else {
|
||||||
contentPreview = String(content).substring(0, 150) + (String(content).length > 150 ? '...' : '');
|
contentPreview = String(content).substring(0, 200) + (String(content).length > 200 ? '...' : '');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
contentPreview = '[Content not available]';
|
contentPreview = '[Content not available]';
|
||||||
@ -114,7 +189,8 @@ export class KeywordSearchTool implements ToolHandler {
|
|||||||
attributes: attributes.length > 0 ? attributes : undefined,
|
attributes: attributes.length > 0 ? attributes : undefined,
|
||||||
type: note.type,
|
type: note.type,
|
||||||
mime: note.mime,
|
mime: note.mime,
|
||||||
isArchived: note.isArchived
|
isArchived: note.isArchived,
|
||||||
|
dateModified: note.dateModified
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@ -34,17 +34,37 @@ export const readNoteToolDefinition: Tool = {
|
|||||||
type: 'function',
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
name: 'read_note',
|
name: 'read_note',
|
||||||
description: 'Read the content of a specific note by its ID',
|
description: `READ FULL CONTENT of a specific note by its ID. Get complete note content and metadata.
|
||||||
|
|
||||||
|
BEST FOR: Getting complete content after finding notes through search tools
|
||||||
|
USE WHEN: You have a noteId from search results and need the full content
|
||||||
|
IMPORTANT: Must use noteId (like "abc123def456") from search results - NOT note titles
|
||||||
|
|
||||||
|
TIP: This is typically used after search_notes, keyword_search_notes, or attribute_search
|
||||||
|
|
||||||
|
NEXT STEPS: Use note_update or attribute_manager tools to modify the note if needed`,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
noteId: {
|
noteId: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The system ID of the note to read (not the title). This is a unique identifier like "abc123def456" that must be used to access a specific note.'
|
description: `SYSTEM ID of the note to read.
|
||||||
|
|
||||||
|
CRITICAL: Must be a noteId (like "abc123def456") - NOT a note title!
|
||||||
|
|
||||||
|
CORRECT: "abc123def456" (from search results)
|
||||||
|
WRONG: "My Note Title" (this will fail)
|
||||||
|
|
||||||
|
WHERE TO GET: From noteId field in search tool results`
|
||||||
},
|
},
|
||||||
includeAttributes: {
|
includeAttributes: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether to include note attributes in the response (default: false)'
|
description: `INCLUDE METADATA: Get note attributes (labels, relations) in response.
|
||||||
|
|
||||||
|
• true = Get full note with all attributes/metadata
|
||||||
|
• false = Get just note content (default)
|
||||||
|
|
||||||
|
Use true when you need to see tags, labels, relations, or other metadata`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['noteId']
|
required: ['noteId']
|
||||||
@ -71,8 +91,23 @@ export class ReadNoteTool implements ToolHandler {
|
|||||||
const note = becca.notes[noteId];
|
const note = becca.notes[noteId];
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
log.info(`Note with ID ${noteId} not found - returning error`);
|
log.info(`Note with ID ${noteId} not found - returning helpful error`);
|
||||||
return `Error: Note with ID ${noteId} not found`;
|
return {
|
||||||
|
error: `Note not found: "${noteId}"`,
|
||||||
|
troubleshooting: {
|
||||||
|
possibleCauses: [
|
||||||
|
'Invalid noteId format (should be like "abc123def456")',
|
||||||
|
'Note may have been deleted or moved',
|
||||||
|
'Using note title instead of noteId'
|
||||||
|
],
|
||||||
|
solutions: [
|
||||||
|
'Use search_notes to find the note by content or title',
|
||||||
|
'Use keyword_search_notes to find notes with specific text',
|
||||||
|
'Use attribute_search if you know the note has specific attributes',
|
||||||
|
'Ensure you\'re using noteId from search results, not the note title'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`Found note: "${note.title}" (Type: ${note.type})`);
|
log.info(`Found note: "${note.title}" (Type: ${note.type})`);
|
||||||
@ -84,14 +119,33 @@ export class ReadNoteTool implements ToolHandler {
|
|||||||
|
|
||||||
log.info(`Retrieved note content in ${duration}ms, content length: ${content?.length || 0} chars`);
|
log.info(`Retrieved note content in ${duration}ms, content length: ${content?.length || 0} chars`);
|
||||||
|
|
||||||
// Prepare the response
|
// Prepare enhanced response with next steps
|
||||||
const response: NoteResponse = {
|
const response: NoteResponse & {
|
||||||
|
nextSteps?: {
|
||||||
|
modify?: string;
|
||||||
|
related?: string;
|
||||||
|
organize?: string;
|
||||||
|
};
|
||||||
|
metadata?: {
|
||||||
|
wordCount?: number;
|
||||||
|
hasAttributes?: boolean;
|
||||||
|
lastModified?: string;
|
||||||
|
};
|
||||||
|
} = {
|
||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
title: note.title,
|
title: note.title,
|
||||||
type: note.type,
|
type: note.type,
|
||||||
content: content || ''
|
content: content || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add helpful metadata
|
||||||
|
const contentStr = typeof content === 'string' ? content : String(content || '');
|
||||||
|
response.metadata = {
|
||||||
|
wordCount: contentStr.split(/\s+/).filter(word => word.length > 0).length,
|
||||||
|
hasAttributes: note.getOwnedAttributes().length > 0,
|
||||||
|
lastModified: note.dateModified
|
||||||
|
};
|
||||||
|
|
||||||
// Include attributes if requested
|
// Include attributes if requested
|
||||||
if (includeAttributes) {
|
if (includeAttributes) {
|
||||||
const attributes = note.getOwnedAttributes();
|
const attributes = note.getOwnedAttributes();
|
||||||
@ -111,6 +165,15 @@ export class ReadNoteTool implements ToolHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add next steps guidance
|
||||||
|
response.nextSteps = {
|
||||||
|
modify: `Use note_update with noteId: "${noteId}" to edit this note's content`,
|
||||||
|
related: `Use search_notes with related concepts to find similar notes`,
|
||||||
|
organize: response.metadata.hasAttributes
|
||||||
|
? `Use attribute_manager with noteId: "${noteId}" to modify attributes`
|
||||||
|
: `Use attribute_manager with noteId: "${noteId}" to add labels or relations`
|
||||||
|
};
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = isError(error) ? error.message : String(error);
|
const errorMessage = isError(error) ? error.message : String(error);
|
||||||
|
|||||||
@ -17,25 +17,50 @@ 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. Use specific, descriptive queries for best results.',
|
description: `SEMANTIC/CONCEPTUAL search for notes. Finds notes related to concepts, topics, or themes even without exact keyword matches.
|
||||||
|
|
||||||
|
BEST FOR: Finding notes about ideas, concepts, or topics described in various ways
|
||||||
|
USE WHEN: Looking for conceptual relationships, thematic content, or related ideas
|
||||||
|
DIFFERENT FROM: keyword_search (which finds exact text matches)
|
||||||
|
|
||||||
|
TIPS:
|
||||||
|
- Use descriptive phrases like "project management methodologies" rather than single words
|
||||||
|
- Think conceptually: "machine learning classification" vs just "ML"
|
||||||
|
- Results include noteId values - ALWAYS use these IDs (not titles) with other tools
|
||||||
|
|
||||||
|
NEXT STEPS: Use read_note with returned noteId values to get full content`,
|
||||||
parameters: {
|
parameters: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
query: {
|
query: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The search query to find semantically related notes. Be specific and descriptive for best results.'
|
description: `Descriptive search query for semantic matching.
|
||||||
|
|
||||||
|
GOOD EXAMPLES:
|
||||||
|
- "machine learning algorithms for classification"
|
||||||
|
- "personal productivity and time management techniques"
|
||||||
|
- "software development best practices"
|
||||||
|
|
||||||
|
AVOID:
|
||||||
|
- Single words: "ML", "productivity"
|
||||||
|
- Overly broad: "work", "notes"
|
||||||
|
- Overly specific: exact phrases that might not exist`
|
||||||
},
|
},
|
||||||
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". Do not use note titles here.'
|
description: `SCOPE LIMITER: Search only within children of this note.
|
||||||
|
|
||||||
|
IMPORTANT: Must be a noteId (like "abc123def456") from previous search results - NOT a note title.
|
||||||
|
|
||||||
|
USE FOR: Searching within specific projects, categories, or sections.`
|
||||||
},
|
},
|
||||||
maxResults: {
|
maxResults: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Maximum number of results to return (default: 5)'
|
description: 'Number of results (1-20, default: 5). Use 10-15 for comprehensive exploration, 3-5 for quick lookup.'
|
||||||
},
|
},
|
||||||
summarize: {
|
summarize: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether to provide summarized content previews instead of truncated ones (default: false)'
|
description: 'AI SUMMARIES: Get intelligent summaries instead of truncated text (default: false). Use true for cleaner result overview.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['query']
|
required: ['query']
|
||||||
@ -189,6 +214,39 @@ export class SearchNotesTool implements ToolHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract keywords from a semantic query for alternative search suggestions
|
||||||
|
*/
|
||||||
|
private extractKeywords(query: string): string {
|
||||||
|
return query.split(' ')
|
||||||
|
.filter(word => word.length > 3 && !['using', 'with', 'for', 'and', 'the', 'that', 'this'].includes(word.toLowerCase()))
|
||||||
|
.slice(0, 3)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest broader search terms when specific searches fail
|
||||||
|
*/
|
||||||
|
private suggestBroaderTerms(query: string): string {
|
||||||
|
const broaderTermsMap: Record<string, string> = {
|
||||||
|
'machine learning': 'AI technology',
|
||||||
|
'productivity': 'work methods',
|
||||||
|
'development': 'programming',
|
||||||
|
'management': 'organization',
|
||||||
|
'planning': 'strategy'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [specific, broader] of Object.entries(broaderTermsMap)) {
|
||||||
|
if (query.toLowerCase().includes(specific)) {
|
||||||
|
return broader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: take first significant word and make it broader
|
||||||
|
const firstWord = query.split(' ').find(word => word.length > 3);
|
||||||
|
return firstWord ? `${firstWord} concepts` : 'general topics';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the search notes tool
|
* Execute the search notes tool
|
||||||
*/
|
*/
|
||||||
@ -260,19 +318,39 @@ export class SearchNotesTool implements ToolHandler {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Format the results
|
// Format the results with enhanced guidance
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
return {
|
return {
|
||||||
count: 0,
|
count: 0,
|
||||||
results: [],
|
results: [],
|
||||||
query: query,
|
query: query,
|
||||||
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.'
|
searchType: 'semantic',
|
||||||
|
message: 'No semantic matches found for your query.',
|
||||||
|
nextSteps: {
|
||||||
|
immediate: [
|
||||||
|
`Try keyword_search with specific terms: "${this.extractKeywords(query)}"`,
|
||||||
|
`Use attribute_search if looking for labeled/categorized notes`,
|
||||||
|
`Try broader search terms like "${this.suggestBroaderTerms(query)}"`
|
||||||
|
],
|
||||||
|
tips: [
|
||||||
|
'Semantic search finds conceptual matches - try describing the topic differently',
|
||||||
|
'If you know specific words that appear in the notes, use keyword_search instead',
|
||||||
|
'Check if the content might be tagged with labels using attribute_search'
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
count: enhancedResults.length,
|
count: enhancedResults.length,
|
||||||
results: enhancedResults,
|
results: enhancedResults,
|
||||||
message: "Note: Use the noteId (not the title) when performing operations on specific notes with other tools."
|
query: query,
|
||||||
|
searchType: 'semantic',
|
||||||
|
message: 'Found semantic matches. Use noteId values with other tools.',
|
||||||
|
nextSteps: {
|
||||||
|
examine: `Use read_note with any noteId (e.g., "${enhancedResults[0].noteId}") to get full content`,
|
||||||
|
refine: parentNoteId ? 'Remove parentNoteId to search all notes' : `Add parentNoteId: "${enhancedResults[0].noteId}" to search within the first result's children`,
|
||||||
|
related: 'Search for related concepts or use different descriptive terms'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|||||||
368
apps/server/src/services/llm/tools/tool_discovery_helper.ts
Normal file
368
apps/server/src/services/llm/tools/tool_discovery_helper.ts
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
/**
|
||||||
|
* Tool Discovery Helper
|
||||||
|
*
|
||||||
|
* This tool helps LLMs understand what tools are available and when to use them.
|
||||||
|
* It provides smart recommendations based on user queries and current context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Tool, ToolHandler } from './tool_interfaces.js';
|
||||||
|
import log from '../../log.js';
|
||||||
|
import toolRegistry from './tool_registry.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of the tool discovery helper
|
||||||
|
*/
|
||||||
|
export const toolDiscoveryHelperDefinition: Tool = {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'discover_tools',
|
||||||
|
description: `DISCOVER AVAILABLE TOOLS and get guidance on which tools to use for your task.
|
||||||
|
|
||||||
|
BEST FOR: Understanding what tools are available and getting usage recommendations
|
||||||
|
USE WHEN: You're unsure which tool to use, want to see all options, or need workflow guidance
|
||||||
|
HELPS WITH: Tool selection, parameter guidance, workflow planning
|
||||||
|
|
||||||
|
TIP: Use this when you have a task but aren't sure which tools can help accomplish it
|
||||||
|
|
||||||
|
NEXT STEPS: Use the recommended tools based on the guidance provided`,
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
taskDescription: {
|
||||||
|
type: 'string',
|
||||||
|
description: `📝 DESCRIBE YOUR TASK: What are you trying to accomplish?
|
||||||
|
|
||||||
|
✅ GOOD EXAMPLES:
|
||||||
|
- "Find notes about machine learning"
|
||||||
|
- "Create a new project planning note"
|
||||||
|
- "Find all notes tagged as important"
|
||||||
|
- "Read the content of a specific note"
|
||||||
|
|
||||||
|
💡 Be specific about your goal for better tool recommendations`
|
||||||
|
},
|
||||||
|
includeExamples: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'INCLUDE EXAMPLES: Get specific usage examples for recommended tools (default: true)'
|
||||||
|
},
|
||||||
|
showAllTools: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'SHOW ALL TOOLS: List all available tools, not just recommended ones (default: false)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['taskDescription']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool discovery helper implementation
|
||||||
|
*/
|
||||||
|
export class ToolDiscoveryHelper implements ToolHandler {
|
||||||
|
public definition: Tool = toolDiscoveryHelperDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map task types to relevant tools
|
||||||
|
*/
|
||||||
|
private getRelevantTools(taskDescription: string): string[] {
|
||||||
|
const task = taskDescription.toLowerCase();
|
||||||
|
const relevantTools: string[] = [];
|
||||||
|
|
||||||
|
// Search-related tasks
|
||||||
|
if (task.includes('find') || task.includes('search') || task.includes('look for')) {
|
||||||
|
if (task.includes('tag') || task.includes('label') || task.includes('attribute') || task.includes('category')) {
|
||||||
|
relevantTools.push('attribute_search');
|
||||||
|
}
|
||||||
|
if (task.includes('concept') || task.includes('about') || task.includes('related to')) {
|
||||||
|
relevantTools.push('search_notes');
|
||||||
|
}
|
||||||
|
if (task.includes('exact') || task.includes('specific') || task.includes('contains')) {
|
||||||
|
relevantTools.push('keyword_search_notes');
|
||||||
|
}
|
||||||
|
// Default to both semantic and keyword search if no specific indicators
|
||||||
|
if (!relevantTools.some(tool => tool.includes('search'))) {
|
||||||
|
relevantTools.push('search_notes', 'keyword_search_notes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading tasks
|
||||||
|
if (task.includes('read') || task.includes('view') || task.includes('show') || task.includes('content')) {
|
||||||
|
relevantTools.push('read_note');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creation tasks
|
||||||
|
if (task.includes('create') || task.includes('new') || task.includes('add') || task.includes('make')) {
|
||||||
|
relevantTools.push('note_creation');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modification tasks
|
||||||
|
if (task.includes('edit') || task.includes('update') || task.includes('change') || task.includes('modify')) {
|
||||||
|
relevantTools.push('note_update');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute/metadata tasks
|
||||||
|
if (task.includes('attribute') || task.includes('tag') || task.includes('label') || task.includes('metadata')) {
|
||||||
|
relevantTools.push('attribute_manager');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relationship tasks
|
||||||
|
if (task.includes('relation') || task.includes('connect') || task.includes('link') || task.includes('relationship')) {
|
||||||
|
relevantTools.push('relationship');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary tasks
|
||||||
|
if (task.includes('summary') || task.includes('summarize') || task.includes('overview')) {
|
||||||
|
relevantTools.push('note_summarization');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calendar tasks
|
||||||
|
if (task.includes('calendar') || task.includes('date') || task.includes('schedule') || task.includes('time')) {
|
||||||
|
relevantTools.push('calendar_integration');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content extraction tasks
|
||||||
|
if (task.includes('extract') || task.includes('parse') || task.includes('analyze content')) {
|
||||||
|
relevantTools.push('content_extraction');
|
||||||
|
}
|
||||||
|
|
||||||
|
return relevantTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tool information with descriptions
|
||||||
|
*/
|
||||||
|
private getToolInfo(): Record<string, { description: string; bestFor: string; parameters: string[] }> {
|
||||||
|
return {
|
||||||
|
'search_notes': {
|
||||||
|
description: '🧠 Semantic/conceptual search for notes',
|
||||||
|
bestFor: 'Finding notes about ideas, concepts, or topics described in various ways',
|
||||||
|
parameters: ['query (required)', 'parentNoteId', 'maxResults', 'summarize']
|
||||||
|
},
|
||||||
|
'keyword_search_notes': {
|
||||||
|
description: '🔍 Exact keyword/phrase search for notes',
|
||||||
|
bestFor: 'Finding notes with specific words, phrases, or using search operators',
|
||||||
|
parameters: ['query (required)', 'maxResults', 'includeArchived']
|
||||||
|
},
|
||||||
|
'attribute_search': {
|
||||||
|
description: '🏷️ Search notes by attributes (labels/relations)',
|
||||||
|
bestFor: 'Finding notes by categories, tags, status, or metadata',
|
||||||
|
parameters: ['attributeType (required)', 'attributeName (required)', 'attributeValue', 'maxResults']
|
||||||
|
},
|
||||||
|
'read_note': {
|
||||||
|
description: '📖 Read full content of a specific note',
|
||||||
|
bestFor: 'Getting complete note content after finding it through search',
|
||||||
|
parameters: ['noteId (required)', 'includeAttributes']
|
||||||
|
},
|
||||||
|
'note_creation': {
|
||||||
|
description: '📝 Create new notes',
|
||||||
|
bestFor: 'Adding new content, projects, or ideas to your notes',
|
||||||
|
parameters: ['title (required)', 'content', 'parentNoteId', 'noteType', 'attributes']
|
||||||
|
},
|
||||||
|
'note_update': {
|
||||||
|
description: '✏️ Update existing note content',
|
||||||
|
bestFor: 'Modifying or adding to existing note content',
|
||||||
|
parameters: ['noteId (required)', 'title', 'content', 'updateMode']
|
||||||
|
},
|
||||||
|
'attribute_manager': {
|
||||||
|
description: '🎯 Manage note attributes (labels, relations)',
|
||||||
|
bestFor: 'Adding, removing, or modifying note metadata and tags',
|
||||||
|
parameters: ['noteId (required)', 'action (required)', 'attributeType', 'attributeName', 'attributeValue']
|
||||||
|
},
|
||||||
|
'relationship': {
|
||||||
|
description: '🔗 Manage note relationships',
|
||||||
|
bestFor: 'Creating connections between notes',
|
||||||
|
parameters: ['sourceNoteId (required)', 'action (required)', 'targetNoteId', 'relationType']
|
||||||
|
},
|
||||||
|
'note_summarization': {
|
||||||
|
description: '📄 Summarize note content',
|
||||||
|
bestFor: 'Getting concise overviews of long notes',
|
||||||
|
parameters: ['noteId (required)', 'summaryType', 'maxLength']
|
||||||
|
},
|
||||||
|
'content_extraction': {
|
||||||
|
description: '🎯 Extract specific information from notes',
|
||||||
|
bestFor: 'Pulling out specific data, facts, or structured information',
|
||||||
|
parameters: ['noteId (required)', 'extractionType (required)', 'criteria']
|
||||||
|
},
|
||||||
|
'calendar_integration': {
|
||||||
|
description: '📅 Calendar and date-related operations',
|
||||||
|
bestFor: 'Working with dates, schedules, and time-based organization',
|
||||||
|
parameters: ['action (required)', 'date', 'noteId', 'eventDetails']
|
||||||
|
},
|
||||||
|
'search_suggestion': {
|
||||||
|
description: '💡 Get search syntax help and suggestions',
|
||||||
|
bestFor: 'Learning how to use advanced search features',
|
||||||
|
parameters: ['searchType', 'query']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate workflow recommendations
|
||||||
|
*/
|
||||||
|
private generateWorkflow(taskDescription: string, relevantTools: string[]): string[] {
|
||||||
|
const task = taskDescription.toLowerCase();
|
||||||
|
const workflows: string[] = [];
|
||||||
|
|
||||||
|
if (task.includes('find') && relevantTools.includes('search_notes')) {
|
||||||
|
workflows.push('1. Use search_notes for conceptual search → 2. Use read_note with returned noteId for full content');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.includes('find') && relevantTools.includes('attribute_search')) {
|
||||||
|
workflows.push('1. Use attribute_search to find tagged notes → 2. Use read_note for detailed content');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.includes('create') || task.includes('new')) {
|
||||||
|
workflows.push('1. Use note_creation to make the note → 2. Use attribute_manager to add tags/metadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.includes('update') || task.includes('edit')) {
|
||||||
|
workflows.push('1. Use search tools to find the note → 2. Use read_note to see current content → 3. Use note_update to modify');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.includes('organize') || task.includes('categorize')) {
|
||||||
|
workflows.push('1. Use search tools to find notes → 2. Use attribute_manager to add labels/categories');
|
||||||
|
}
|
||||||
|
|
||||||
|
return workflows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the tool discovery helper
|
||||||
|
*/
|
||||||
|
public async execute(args: {
|
||||||
|
taskDescription: string,
|
||||||
|
includeExamples?: boolean,
|
||||||
|
showAllTools?: boolean
|
||||||
|
}): Promise<string | object> {
|
||||||
|
try {
|
||||||
|
const { taskDescription, includeExamples = true, showAllTools = false } = args;
|
||||||
|
|
||||||
|
log.info(`Executing discover_tools - Task: "${taskDescription}", ShowAll: ${showAllTools}`);
|
||||||
|
|
||||||
|
const allTools = toolRegistry.getAllTools();
|
||||||
|
const toolInfo = this.getToolInfo();
|
||||||
|
|
||||||
|
if (showAllTools) {
|
||||||
|
// Show all available tools
|
||||||
|
const allToolsInfo = allTools.map(tool => {
|
||||||
|
const name = tool.definition.function.name;
|
||||||
|
const info = toolInfo[name];
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
description: info?.description || tool.definition.function.description,
|
||||||
|
bestFor: info?.bestFor || 'General purpose tool',
|
||||||
|
parameters: info?.parameters || ['See tool definition for parameters']
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
taskDescription,
|
||||||
|
mode: 'all_tools',
|
||||||
|
message: '🗂️ All available tools in the system',
|
||||||
|
totalTools: allToolsInfo.length,
|
||||||
|
tools: allToolsInfo,
|
||||||
|
tip: 'Use discover_tools with a specific task description for targeted recommendations'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get relevant tools for the specific task
|
||||||
|
const relevantToolNames = this.getRelevantTools(taskDescription);
|
||||||
|
const workflows = this.generateWorkflow(taskDescription, relevantToolNames);
|
||||||
|
|
||||||
|
const recommendations = relevantToolNames.map(toolName => {
|
||||||
|
const info = toolInfo[toolName];
|
||||||
|
const result: any = {
|
||||||
|
tool: toolName,
|
||||||
|
description: info?.description || 'Tool description not available',
|
||||||
|
bestFor: info?.bestFor || 'Not specified',
|
||||||
|
priority: this.getToolPriority(toolName, taskDescription)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (includeExamples) {
|
||||||
|
result.exampleUsage = this.getToolExample(toolName, taskDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by priority
|
||||||
|
recommendations.sort((a, b) => a.priority - b.priority);
|
||||||
|
|
||||||
|
return {
|
||||||
|
taskDescription,
|
||||||
|
mode: 'targeted_recommendations',
|
||||||
|
message: `🎯 Found ${recommendations.length} relevant tools for your task`,
|
||||||
|
recommendations,
|
||||||
|
workflows: workflows.length > 0 ? {
|
||||||
|
message: '🔄 Suggested workflows for your task:',
|
||||||
|
steps: workflows
|
||||||
|
} : undefined,
|
||||||
|
nextSteps: {
|
||||||
|
immediate: recommendations.length > 0
|
||||||
|
? `Start with: ${recommendations[0].tool} (highest priority for your task)`
|
||||||
|
: 'Try rephrasing your task or use showAllTools: true to see all options',
|
||||||
|
alternative: 'Use showAllTools: true to see all available tools if these don\'t fit your needs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
log.error(`Error executing discover_tools: ${errorMessage}`);
|
||||||
|
return `Error: ${errorMessage}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get priority for a tool based on task description (lower = higher priority)
|
||||||
|
*/
|
||||||
|
private getToolPriority(toolName: string, taskDescription: string): number {
|
||||||
|
const task = taskDescription.toLowerCase();
|
||||||
|
|
||||||
|
// Exact matches get highest priority
|
||||||
|
if (task.includes(toolName.replace('_', ' '))) return 1;
|
||||||
|
|
||||||
|
// Task-specific priorities
|
||||||
|
if (task.includes('find') || task.includes('search')) {
|
||||||
|
if (toolName === 'search_notes') return 2;
|
||||||
|
if (toolName === 'keyword_search_notes') return 3;
|
||||||
|
if (toolName === 'attribute_search') return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.includes('create') && toolName === 'note_creation') return 1;
|
||||||
|
if (task.includes('read') && toolName === 'read_note') return 1;
|
||||||
|
if (task.includes('update') && toolName === 'note_update') return 1;
|
||||||
|
|
||||||
|
return 5; // Default priority
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get example usage for a tool based on task description
|
||||||
|
*/
|
||||||
|
private getToolExample(toolName: string, taskDescription: string): string {
|
||||||
|
const task = taskDescription.toLowerCase();
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'search_notes':
|
||||||
|
if (task.includes('machine learning')) {
|
||||||
|
return '{ "query": "machine learning algorithms classification" }';
|
||||||
|
}
|
||||||
|
return '{ "query": "project management methodologies" }';
|
||||||
|
|
||||||
|
case 'keyword_search_notes':
|
||||||
|
return '{ "query": "important TODO" }';
|
||||||
|
|
||||||
|
case 'attribute_search':
|
||||||
|
return '{ "attributeType": "label", "attributeName": "important" }';
|
||||||
|
|
||||||
|
case 'read_note':
|
||||||
|
return '{ "noteId": "abc123def456", "includeAttributes": true }';
|
||||||
|
|
||||||
|
case 'note_creation':
|
||||||
|
return '{ "title": "New Project Plan", "content": "Project details here..." }';
|
||||||
|
|
||||||
|
case 'note_update':
|
||||||
|
return '{ "noteId": "abc123def456", "content": "Updated content" }';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return `Use ${toolName} with appropriate parameters`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@ import { RelationshipTool } from './relationship_tool.js';
|
|||||||
import { AttributeManagerTool } from './attribute_manager_tool.js';
|
import { AttributeManagerTool } from './attribute_manager_tool.js';
|
||||||
import { CalendarIntegrationTool } from './calendar_integration_tool.js';
|
import { CalendarIntegrationTool } from './calendar_integration_tool.js';
|
||||||
import { NoteSummarizationTool } from './note_summarization_tool.js';
|
import { NoteSummarizationTool } from './note_summarization_tool.js';
|
||||||
|
import { ToolDiscoveryHelper } from './tool_discovery_helper.js';
|
||||||
|
import { WorkflowHelper } from './workflow_helper.js';
|
||||||
import log from '../../log.js';
|
import log from '../../log.js';
|
||||||
|
|
||||||
// Error type guard
|
// Error type guard
|
||||||
@ -52,6 +54,10 @@ export async function initializeTools(): Promise<void> {
|
|||||||
toolRegistry.registerTool(new ContentExtractionTool()); // Extract info from note content
|
toolRegistry.registerTool(new ContentExtractionTool()); // Extract info from note content
|
||||||
toolRegistry.registerTool(new CalendarIntegrationTool()); // Calendar-related operations
|
toolRegistry.registerTool(new CalendarIntegrationTool()); // Calendar-related operations
|
||||||
|
|
||||||
|
// Register helper and guidance tools
|
||||||
|
toolRegistry.registerTool(new ToolDiscoveryHelper()); // Tool discovery and usage guidance
|
||||||
|
toolRegistry.registerTool(new WorkflowHelper()); // Multi-step workflow guidance
|
||||||
|
|
||||||
// Log registered tools
|
// Log registered tools
|
||||||
const toolCount = toolRegistry.getAllTools().length;
|
const toolCount = toolRegistry.getAllTools().length;
|
||||||
const toolNames = toolRegistry.getAllTools().map(tool => tool.definition.function.name).join(', ');
|
const toolNames = toolRegistry.getAllTools().map(tool => tool.definition.function.name).join(', ');
|
||||||
|
|||||||
408
apps/server/src/services/llm/tools/workflow_helper.ts
Normal file
408
apps/server/src/services/llm/tools/workflow_helper.ts
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
/**
|
||||||
|
* Workflow Helper Tool
|
||||||
|
*
|
||||||
|
* This tool helps LLMs understand and execute multi-step workflows by providing
|
||||||
|
* smart guidance on tool chaining and next steps.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Tool, ToolHandler } from './tool_interfaces.js';
|
||||||
|
import log from '../../log.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of the workflow helper tool
|
||||||
|
*/
|
||||||
|
export const workflowHelperDefinition: Tool = {
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
name: 'workflow_helper',
|
||||||
|
description: `WORKFLOW GUIDANCE for multi-step tasks. Get smart suggestions for tool chaining and next steps.
|
||||||
|
|
||||||
|
BEST FOR: Planning complex workflows, understanding tool sequences, getting unstuck
|
||||||
|
USE WHEN: You need to do multiple operations, aren't sure what to do next, or want workflow optimization
|
||||||
|
HELPS WITH: Tool sequencing, parameter passing, workflow planning
|
||||||
|
|
||||||
|
TIP: Use this when you have partial results and need guidance on next steps
|
||||||
|
|
||||||
|
NEXT STEPS: Follow the recommended workflow steps provided`,
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
currentStep: {
|
||||||
|
type: 'string',
|
||||||
|
description: `📍 DESCRIBE YOUR CURRENT STEP: What have you just done or what results do you have?
|
||||||
|
|
||||||
|
✅ GOOD EXAMPLES:
|
||||||
|
- "I just found 5 notes about machine learning using search_notes"
|
||||||
|
- "I have a noteId abc123def456 and want to modify it"
|
||||||
|
- "I searched but got no results"
|
||||||
|
- "I created a new note and want to organize it"
|
||||||
|
|
||||||
|
💡 Be specific about your current state and what you've accomplished`
|
||||||
|
},
|
||||||
|
goal: {
|
||||||
|
type: 'string',
|
||||||
|
description: `🎯 FINAL GOAL: What are you ultimately trying to accomplish?
|
||||||
|
|
||||||
|
✅ EXAMPLES:
|
||||||
|
- "Find and read all notes about a specific project"
|
||||||
|
- "Create a comprehensive summary of all my research notes"
|
||||||
|
- "Organize all my TODO notes by priority"
|
||||||
|
- "Find related notes and create connections between them"`
|
||||||
|
},
|
||||||
|
availableData: {
|
||||||
|
type: 'string',
|
||||||
|
description: `📊 AVAILABLE DATA: What noteIds, search results, or other data do you currently have?
|
||||||
|
|
||||||
|
✅ EXAMPLES:
|
||||||
|
- "noteIds: abc123, def456, ghi789"
|
||||||
|
- "Search results with 3 notes about project management"
|
||||||
|
- "Empty search results for machine learning"
|
||||||
|
- "Just created noteId xyz999"`
|
||||||
|
},
|
||||||
|
includeExamples: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: '📚 INCLUDE EXAMPLES: Get specific command examples for next steps (default: true)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['currentStep', 'goal']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workflow helper implementation
|
||||||
|
*/
|
||||||
|
export class WorkflowHelper implements ToolHandler {
|
||||||
|
public definition: Tool = workflowHelperDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common workflow patterns
|
||||||
|
*/
|
||||||
|
private getWorkflowPatterns(): Record<string, {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
steps: string[];
|
||||||
|
examples: string[];
|
||||||
|
}> {
|
||||||
|
return {
|
||||||
|
'search_read_analyze': {
|
||||||
|
name: '🔍➡️📖➡️🧠 Search → Read → Analyze',
|
||||||
|
description: 'Find notes, read their content, then analyze or summarize',
|
||||||
|
steps: [
|
||||||
|
'Use search tools to find relevant notes',
|
||||||
|
'Use read_note to get full content of interesting results',
|
||||||
|
'Use note_summarization or content_extraction for analysis'
|
||||||
|
],
|
||||||
|
examples: [
|
||||||
|
'Research project: Find all research notes → Read them → Summarize findings',
|
||||||
|
'Learning topic: Search for learning materials → Read content → Extract key concepts'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'search_create_organize': {
|
||||||
|
name: '🔍➡️📝➡️🏷️ Search → Create → Organize',
|
||||||
|
description: 'Find related content, create new notes, then organize with attributes',
|
||||||
|
steps: [
|
||||||
|
'Search for related existing content',
|
||||||
|
'Create new note with note_creation',
|
||||||
|
'Add attributes/relations with attribute_manager'
|
||||||
|
],
|
||||||
|
examples: [
|
||||||
|
'New project: Find similar projects → Create project note → Tag with #project',
|
||||||
|
'Meeting notes: Search for project context → Create meeting note → Link to project'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'find_read_update': {
|
||||||
|
name: '🔍➡️📖➡️✏️ Find → Read → Update',
|
||||||
|
description: 'Find existing notes, review content, then make updates',
|
||||||
|
steps: [
|
||||||
|
'Use search tools to locate the note',
|
||||||
|
'Use read_note to see current content',
|
||||||
|
'Use note_update to make changes'
|
||||||
|
],
|
||||||
|
examples: [
|
||||||
|
'Update project status: Find project note → Read current status → Update with progress',
|
||||||
|
'Improve documentation: Find doc note → Read content → Add new information'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'organize_existing': {
|
||||||
|
name: '🔍➡️🏷️➡️🔗 Find → Tag → Connect',
|
||||||
|
description: 'Find notes that need organization, add attributes, create relationships',
|
||||||
|
steps: [
|
||||||
|
'Search for notes to organize',
|
||||||
|
'Use attribute_manager to add labels/categories',
|
||||||
|
'Use relationship tool to create connections'
|
||||||
|
],
|
||||||
|
examples: [
|
||||||
|
'Organize research: Find research notes → Tag by topic → Link related studies',
|
||||||
|
'Clean up TODOs: Find TODO notes → Tag by priority → Link to projects'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze current step and recommend next actions
|
||||||
|
*/
|
||||||
|
private analyzeCurrentStep(currentStep: string, goal: string, availableData?: string): {
|
||||||
|
analysis: string;
|
||||||
|
recommendations: Array<{
|
||||||
|
action: string;
|
||||||
|
tool: string;
|
||||||
|
parameters: Record<string, any>;
|
||||||
|
reasoning: string;
|
||||||
|
priority: number;
|
||||||
|
}>;
|
||||||
|
warnings?: string[];
|
||||||
|
} {
|
||||||
|
const step = currentStep.toLowerCase();
|
||||||
|
const goalLower = goal.toLowerCase();
|
||||||
|
const recommendations: any[] = [];
|
||||||
|
const warnings: string[] = [];
|
||||||
|
|
||||||
|
// Analyze search results
|
||||||
|
if (step.includes('found') && step.includes('notes')) {
|
||||||
|
if (step.includes('no results') || step.includes('empty') || step.includes('0 notes')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Try alternative search approaches',
|
||||||
|
tool: 'search_notes',
|
||||||
|
parameters: { query: 'broader or alternative search terms' },
|
||||||
|
reasoning: 'Empty results suggest need for different search strategy',
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Try keyword search instead',
|
||||||
|
tool: 'keyword_search_notes',
|
||||||
|
parameters: { query: 'specific keywords from your search' },
|
||||||
|
reasoning: 'Keyword search might find what semantic search missed',
|
||||||
|
priority: 2
|
||||||
|
});
|
||||||
|
warnings.push('Consider if the content might not exist yet - you may need to create it');
|
||||||
|
} else {
|
||||||
|
// Has search results
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Read the most relevant notes',
|
||||||
|
tool: 'read_note',
|
||||||
|
parameters: { noteId: 'from search results', includeAttributes: true },
|
||||||
|
reasoning: 'Get full content to understand what you found',
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
if (goalLower.includes('summary') || goalLower.includes('analyze')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Summarize the content',
|
||||||
|
tool: 'note_summarization',
|
||||||
|
parameters: { noteId: 'from search results' },
|
||||||
|
reasoning: 'Goal involves analysis or summarization',
|
||||||
|
priority: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze note reading
|
||||||
|
if (step.includes('read') || step.includes('noteId')) {
|
||||||
|
if (goalLower.includes('update') || goalLower.includes('edit') || goalLower.includes('modify')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Update the note content',
|
||||||
|
tool: 'note_update',
|
||||||
|
parameters: { noteId: 'the one you just read', content: 'new content' },
|
||||||
|
reasoning: 'Goal involves modifying existing content',
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goalLower.includes('organize') || goalLower.includes('tag') || goalLower.includes('categorize')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Add organizing attributes',
|
||||||
|
tool: 'attribute_manager',
|
||||||
|
parameters: { noteId: 'the one you read', action: 'add', attributeType: 'label' },
|
||||||
|
reasoning: 'Goal involves organization and categorization',
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goalLower.includes('related') || goalLower.includes('connect') || goalLower.includes('link')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Search for related content',
|
||||||
|
tool: 'search_notes',
|
||||||
|
parameters: { query: 'concepts from the note you read' },
|
||||||
|
reasoning: 'Goal involves finding and connecting related content',
|
||||||
|
priority: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze creation
|
||||||
|
if (step.includes('created') || step.includes('new note')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Add organizing attributes',
|
||||||
|
tool: 'attribute_manager',
|
||||||
|
parameters: { noteId: 'the newly created note', action: 'add' },
|
||||||
|
reasoning: 'New notes should be organized with appropriate tags',
|
||||||
|
priority: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
if (goalLower.includes('project') || goalLower.includes('research')) {
|
||||||
|
recommendations.push({
|
||||||
|
action: 'Find and link related notes',
|
||||||
|
tool: 'search_notes',
|
||||||
|
parameters: { query: 'related to your new note topic' },
|
||||||
|
reasoning: 'Connect new content to existing related materials',
|
||||||
|
priority: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
analysis: this.generateAnalysis(currentStep, goal, recommendations.length),
|
||||||
|
recommendations: recommendations.sort((a, b) => a.priority - b.priority),
|
||||||
|
warnings: warnings.length > 0 ? warnings : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate workflow analysis
|
||||||
|
*/
|
||||||
|
private generateAnalysis(currentStep: string, goal: string, recommendationCount: number): string {
|
||||||
|
const patterns = this.getWorkflowPatterns();
|
||||||
|
|
||||||
|
let analysis = `📊 CURRENT STATE: ${currentStep}\n`;
|
||||||
|
analysis += `🎯 TARGET GOAL: ${goal}\n\n`;
|
||||||
|
|
||||||
|
if (recommendationCount > 0) {
|
||||||
|
analysis += `✅ I've identified ${recommendationCount} recommended next steps based on your current progress and goal.\n\n`;
|
||||||
|
} else {
|
||||||
|
analysis += `🤔 Your situation is unique. I'll provide general guidance based on common patterns.\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suggest relevant workflow patterns
|
||||||
|
const goalLower = goal.toLowerCase();
|
||||||
|
if (goalLower.includes('read') && goalLower.includes('find')) {
|
||||||
|
analysis += `📖 PATTERN MATCH: This looks like a "${patterns.search_read_analyze.name}" workflow\n`;
|
||||||
|
} else if (goalLower.includes('create') && goalLower.includes('organize')) {
|
||||||
|
analysis += `📝 PATTERN MATCH: This looks like a "${patterns.search_create_organize.name}" workflow\n`;
|
||||||
|
} else if (goalLower.includes('update') && goalLower.includes('find')) {
|
||||||
|
analysis += `✏️ PATTERN MATCH: This looks like a "${patterns.find_read_update.name}" workflow\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the workflow helper tool
|
||||||
|
*/
|
||||||
|
public async execute(args: {
|
||||||
|
currentStep: string,
|
||||||
|
goal: string,
|
||||||
|
availableData?: string,
|
||||||
|
includeExamples?: boolean
|
||||||
|
}): Promise<string | object> {
|
||||||
|
try {
|
||||||
|
const { currentStep, goal, availableData, includeExamples = true } = args;
|
||||||
|
|
||||||
|
log.info(`Executing workflow_helper - Current: "${currentStep}", Goal: "${goal}"`);
|
||||||
|
|
||||||
|
const analysis = this.analyzeCurrentStep(currentStep, goal, availableData);
|
||||||
|
const patterns = this.getWorkflowPatterns();
|
||||||
|
|
||||||
|
// Extract noteIds from available data if provided
|
||||||
|
const noteIds = availableData ? this.extractNoteIds(availableData) : [];
|
||||||
|
|
||||||
|
const response: any = {
|
||||||
|
currentStep,
|
||||||
|
goal,
|
||||||
|
analysis: analysis.analysis,
|
||||||
|
immediateNext: analysis.recommendations.length > 0 ? {
|
||||||
|
primaryAction: analysis.recommendations[0],
|
||||||
|
alternatives: analysis.recommendations.slice(1, 3)
|
||||||
|
} : undefined,
|
||||||
|
extractedData: {
|
||||||
|
noteIds: noteIds.length > 0 ? noteIds : undefined,
|
||||||
|
hasData: !!availableData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (analysis.warnings) {
|
||||||
|
response.warnings = {
|
||||||
|
message: '⚠️ Important considerations:',
|
||||||
|
items: analysis.warnings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeExamples && analysis.recommendations.length > 0) {
|
||||||
|
response.examples = {
|
||||||
|
message: '📚 Specific tool usage examples:',
|
||||||
|
commands: analysis.recommendations.slice(0, 2).map(rec => ({
|
||||||
|
tool: rec.tool,
|
||||||
|
example: this.generateExample(rec.tool, rec.parameters, noteIds),
|
||||||
|
description: rec.reasoning
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add relevant workflow patterns
|
||||||
|
response.workflowPatterns = {
|
||||||
|
message: '🔄 Common workflow patterns you might find useful:',
|
||||||
|
patterns: Object.values(patterns).slice(0, 2).map(pattern => ({
|
||||||
|
name: pattern.name,
|
||||||
|
description: pattern.description,
|
||||||
|
steps: pattern.steps
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
response.tips = [
|
||||||
|
'💡 Use the noteId values from search results, not note titles',
|
||||||
|
'🔄 Check tool results carefully before proceeding to next step',
|
||||||
|
'📊 Use workflow_helper again if you get stuck or need guidance'
|
||||||
|
];
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
log.error(`Error executing workflow_helper: ${errorMessage}`);
|
||||||
|
return `Error: ${errorMessage}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract noteIds from data string
|
||||||
|
*/
|
||||||
|
private extractNoteIds(data: string): string[] {
|
||||||
|
// Look for patterns like noteId: "abc123" or "abc123def456"
|
||||||
|
const idPattern = /(?:noteId[:\s]*["']?|["'])([a-zA-Z0-9]{8,})['"]/g;
|
||||||
|
const matches: string[] = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = idPattern.exec(data)) !== null) {
|
||||||
|
if (match[1] && !matches.includes(match[1])) {
|
||||||
|
matches.push(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate specific examples for tool usage
|
||||||
|
*/
|
||||||
|
private generateExample(tool: string, parameters: Record<string, any>, noteIds: string[]): string {
|
||||||
|
const sampleNoteId = noteIds[0] || 'abc123def456';
|
||||||
|
|
||||||
|
switch (tool) {
|
||||||
|
case 'read_note':
|
||||||
|
return `{ "noteId": "${sampleNoteId}", "includeAttributes": true }`;
|
||||||
|
case 'note_update':
|
||||||
|
return `{ "noteId": "${sampleNoteId}", "content": "Updated content here" }`;
|
||||||
|
case 'attribute_manager':
|
||||||
|
return `{ "noteId": "${sampleNoteId}", "action": "add", "attributeType": "label", "attributeName": "important" }`;
|
||||||
|
case 'search_notes':
|
||||||
|
return `{ "query": "broader search terms related to your topic" }`;
|
||||||
|
case 'keyword_search_notes':
|
||||||
|
return `{ "query": "specific keywords OR alternative terms" }`;
|
||||||
|
case 'note_creation':
|
||||||
|
return `{ "title": "New Note Title", "content": "Note content here" }`;
|
||||||
|
default:
|
||||||
|
return `Use ${tool} with appropriate parameters`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user