diff options
-rw-r--r-- | src/client/views/nodes/chatbot/agentsystem/prompts.ts | 1 | ||||
-rw-r--r-- | src/client/views/nodes/chatbot/tools/TagDocsTool.ts | 137 |
2 files changed, 63 insertions, 75 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/prompts.ts b/src/client/views/nodes/chatbot/agentsystem/prompts.ts index b7678bd08..ed97fd5c1 100644 --- a/src/client/views/nodes/chatbot/agentsystem/prompts.ts +++ b/src/client/views/nodes/chatbot/agentsystem/prompts.ts @@ -110,6 +110,7 @@ export function getReactPrompt(tools: BaseTool<ReadonlyArray<Parameter>>[], summ <tools> ${toolDescriptions} + <note>The tagging tool takes priority over the metadata tool for queries relating to tagging.</note> <note>If no external tool is required, use 'no_tool', but if there might be relevant external information, use the appropriate tool.</note> </tools> diff --git a/src/client/views/nodes/chatbot/tools/TagDocsTool.ts b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts index 75f476348..c88c32e50 100644 --- a/src/client/views/nodes/chatbot/tools/TagDocsTool.ts +++ b/src/client/views/nodes/chatbot/tools/TagDocsTool.ts @@ -5,11 +5,12 @@ import { AgentDocumentManager } from '../utils/AgentDocumentManager'; import { gptAPICall, GPTCallType, DescriptionSeperator, DataSeperator } from '../../../../apis/gpt/GPT'; import { v4 as uuidv4 } from 'uuid'; import { TagItem } from '../../../TagsView'; + const parameterRules = [ { name: 'taggingCriteria', type: 'string', - description: 'Natural-language criteria for tagging documents.', + description: 'Natural‐language criteria for tagging documents.', required: true, }, ] as const; @@ -27,92 +28,77 @@ export class TagDocsTool extends BaseTool<typeof parameterRules> { constructor(docManager: AgentDocumentManager) { super(toolInfo); this._docManager = docManager; - // make sure manager has scanned all docs + // ensure our manager has scanned all freeform docs this._docManager.initializeFindDocsFreeform(); } -async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { + + async execute(args: ParametersType<typeof parameterRules>): Promise<Observation[]> { const chunkId = uuidv4(); + try { - // Build a single string of all docs in the EXACT same format as GPTPopup does: - - // 1) gather metadata & build map from text→id + // 1) Build description→ID map and the prompt string const textToId = new Map<string, string>(); - //make this a general UTIL - const descriptions = (await Promise.all( - this._docManager.docIds.map(async id => { - const text = await this._docManager.getDocDescription(id); - textToId.set(text,id); - return DescriptionSeperator + text + DescriptionSeperator; - }) - )) - .join(''); - // Call GPT + const descriptionsArray: string[] = []; + + // We assume getDocDescription returns a Promise<string> with the text + for (const id of this._docManager.docIds) { + const desc = (await this._docManager.getDocDescription(id)).replace(/\n/g, ' ').trim(); + if (!desc) continue; + textToId.set(desc, id); + // wrap in separators exactly as GPTPopup does + descriptionsArray.push(`${DescriptionSeperator}${desc}${DescriptionSeperator}`); + } + + const promptDescriptions = descriptionsArray.join(''); + + // 2) Call GPT const raw = await gptAPICall( args.taggingCriteria, GPTCallType.TAGDOCS, - descriptions + promptDescriptions ); - console.log('TAG RESP:', raw); - // Prepare to collect what we actually applied + console.log('[TagDocsTool] GPT raw:', raw); + + // 3) Parse GPT’s response, look up each description, and apply tags const appliedTags: Record<string, string[]> = {}; - // Parse and apply tags exactly like GPTPopup - /*raw - .split(DescriptionSeperator) // 1) break into blocks at "======" - .filter(block => block.trim() !== '') // 2) drop empty - .map(block => block.replace(/\n/g, ' ').trim()) // 3) flatten & trim - .map(block => { - const [idPart = '', tagsPart = ''] = block.split(DataSeperator); - return { id: idPart.trim(), tags: tagsPart.trim() }; - }) - .filter(({ id, tags }) => id && tags) // 4) valid pairs only - .forEach(({ id, tags }) => { - const doc = this._docManager.getDocument(id); - if (!doc) return; - - // Split tags, normalize, then apply - const normalized = tags + raw + .split(DescriptionSeperator) // split into blocks + .filter(block => block.trim() !== '') // remove empties + .map(block => block.replace(/\n/g, ' ').trim()) // flatten whitespace + .forEach(block => { + // each block should look like: "<desc_text>>>>><tag1,tag2>" + const [descText, tagsText = ''] = block.split(DataSeperator).map(s => s.trim()); + if (!descText || !tagsText) { + console.warn('[TagDocsTool] skipping invalid block:', block); + return; + } + const docId = textToId.get(descText); + if (!docId) { + console.warn('[TagDocsTool] no docId for description:', descText); + return; + } + const doc = this._docManager.getDocument(docId); + if (!doc) { + console.warn('[TagDocsTool] no Doc instance for ID:', docId); + return; + } + + // split/normalize tags + const normalized = tagsText .split(',') - .map(t => t.trim()) - .filter(t => t.length > 0) - .map(t => (t.startsWith('#') ? t : `#${t}`)); + .map(tag => tag.trim()) + .filter(tag => tag.length > 0) + .map(tag => (tag.startsWith('#') ? tag : `#${tag}`)); + // apply tags normalized.forEach(tag => TagItem.addTagToDoc(doc, tag)); - // Record for our summary - appliedTags[id] = normalized; - });*/ - - raw - .split(DescriptionSeperator) // 1) Split into “blocks” - .filter(item => item.trim() !== '') // 2) Drop empty blocks - .map(block => block.replace(/\n/g, ' ').trim()) // 3) Flatten & trim - .map(block => { - // 4) block looks like: "docId>>>>>>tag1, tag2" - const [idPart, tagsPart] = block.split(DataSeperator); - return { id: idPart.trim(), data: (tagsPart || '').trim() }; - }) - .filter(({ id, data }) => !!id && !!data) // 5) Keep only valid pairs - .forEach(({ id, data }) => { - // 6) Lookup doc by ID - const doc = this._docManager.getDocument(id); - if (!doc) return; - - // 7) Only in the AssignTags branch do we add tags - // And GPTPopup normalizes by lowercasing first letter if no '#' - const tagToApply = data.startsWith('#') - ? data - : '#' + data[0].toLowerCase() + data.slice(1); - - TagItem.addTagToDoc(doc, tagToApply); - - // 8) Record for summary - if (!appliedTags[id]) appliedTags[id] = []; - appliedTags[id].push(tagToApply); - }); - - - // Build single observation with summary + // record for our summary + appliedTags[docId] = normalized; + }); + + // 4) Build a summary observation const summary = Object.entries(appliedTags) .map(([id, tags]) => `${id}: ${tags.join(', ')}`) .join('; '); @@ -125,15 +111,16 @@ Successfully tagged documents based on "${args.taggingCriteria}". Tags applied: </chunk>`, }, ]; - } catch (e) { + } catch (err) { + console.error('[TagDocsTool] error:', err); return [ { type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="error"> -Tagging failed: ${e instanceof Error ? e.message : String(e)} +Tagging failed: ${err instanceof Error ? err.message : String(err)} </chunk>`, }, ]; } } -}
\ No newline at end of file +} |