const { ZeroShotAgentOutputParser } = require('langchain/agents'); class CustomOutputParser extends ZeroShotAgentOutputParser { constructor(fields) { super(fields); this.tools = fields.tools; this.longestToolName = ''; for (const tool of this.tools) { if (tool.name.length > this.longestToolName.length) { this.longestToolName = tool.name; } } this.finishToolNameRegex = /(?:the\s+)?final\s+answer:\s*/i; this.actionValues = /(?:Action(?: [1-9])?:) ([\s\S]*?)(?:\n(?:Action Input(?: [1-9])?:) ([\s\S]*?))?$/i; this.actionInputRegex = /(?:Action Input(?: *\d*):) ?([\s\S]*?)$/i; this.thoughtRegex = /(?:Thought(?: *\d*):) ?([\s\S]*?)$/i; } getValidTool(text) { let result = false; for (const tool of this.tools) { const { name } = tool; const toolIndex = text.indexOf(name); if (toolIndex !== -1) { result = name; break; } } return result; } checkIfValidTool(text) { let isValidTool = false; for (const tool of this.tools) { const { name } = tool; if (text === name) { isValidTool = true; break; } } return isValidTool; } async parse(text) { const finalMatch = text.match(this.finishToolNameRegex); // if (text.includes(this.finishToolName)) { // const parts = text.split(this.finishToolName); // const output = parts[parts.length - 1].trim(); // return { // returnValues: { output }, // log: text // }; // } if (finalMatch) { const output = text.substring(finalMatch.index + finalMatch[0].length).trim(); return { returnValues: { output }, log: text, }; } const match = this.actionValues.exec(text); // old v2 if (!match) { console.log( '\n\n<----------------------HIT NO MATCH PARSING ERROR---------------------->\n\n', match, ); const thoughts = text.replace(/[tT]hought:/, '').split('\n'); // return { // tool: 'self-reflection', // toolInput: thoughts[0], // log: thoughts.slice(1).join('\n') // }; return { returnValues: { output: thoughts[0] }, log: thoughts.slice(1).join('\n'), }; } let selectedTool = match?.[1].trim().toLowerCase(); if (match && selectedTool === 'n/a') { console.log( '\n\n<----------------------HIT N/A PARSING ERROR---------------------->\n\n', match, ); return { tool: 'self-reflection', toolInput: match[2]?.trim().replace(/^"+|"+$/g, '') ?? '', log: text, }; } let toolIsValid = this.checkIfValidTool(selectedTool); if (match && !toolIsValid) { console.log( '\n\n<----------------Tool invalid: Re-assigning Selected Tool---------------->\n\n', match, ); selectedTool = this.getValidTool(selectedTool); } if (match && !selectedTool) { console.log( '\n\n<----------------------HIT INVALID TOOL PARSING ERROR---------------------->\n\n', match, ); selectedTool = 'self-reflection'; } if (match && !match[2]) { console.log( '\n\n<----------------------HIT NO ACTION INPUT PARSING ERROR---------------------->\n\n', match, ); // In case there is no action input, let's double-check if there is an action input in 'text' variable const actionInputMatch = this.actionInputRegex.exec(text); const thoughtMatch = this.thoughtRegex.exec(text); if (actionInputMatch) { return { tool: selectedTool, toolInput: actionInputMatch[1].trim(), log: text, }; } if (thoughtMatch && !actionInputMatch) { return { tool: selectedTool, toolInput: thoughtMatch[1].trim(), log: text, }; } } if (match && selectedTool.length > this.longestToolName.length) { console.log('\n\n<----------------------HIT LONG PARSING ERROR---------------------->\n\n'); let action, input, thought; let firstIndex = Infinity; for (const tool of this.tools) { const { name } = tool; const toolIndex = text.indexOf(name); if (toolIndex !== -1 && toolIndex < firstIndex) { firstIndex = toolIndex; action = name; } } // In case there is no action input, let's double-check if there is an action input in 'text' variable const actionInputMatch = this.actionInputRegex.exec(text); if (action && actionInputMatch) { console.log( '\n\n<------Matched Action Input in Long Parsing Error------>\n\n', actionInputMatch, ); return { tool: action, toolInput: actionInputMatch[1].trim().replaceAll('"', ''), log: text, }; } if (action) { const actionEndIndex = text.indexOf('Action:', firstIndex + action.length); const inputText = text .slice(firstIndex + action.length, actionEndIndex !== -1 ? actionEndIndex : undefined) .trim(); const inputLines = inputText.split('\n'); input = inputLines[0]; if (inputLines.length > 1) { thought = inputLines.slice(1).join('\n'); } const returnValues = { tool: action, toolInput: input, log: thought || inputText, }; const inputMatch = this.actionValues.exec(returnValues.log); //new if (inputMatch) { console.log('inputMatch'); console.dir(inputMatch, { depth: null }); returnValues.toolInput = inputMatch[1].replaceAll('"', '').trim(); returnValues.log = returnValues.log.replace(this.actionValues, ''); } return returnValues; } else { console.log('No valid tool mentioned.', this.tools, text); return { tool: 'self-reflection', toolInput: 'Hypothetical actions: \n"' + text + '"\n', log: 'Thought: I need to look at my hypothetical actions and try one', }; } // if (action && input) { // console.log('Action:', action); // console.log('Input:', input); // } } return { tool: selectedTool, toolInput: match[2]?.trim()?.replace(/^"+|"+$/g, '') ?? '', log: text, }; } } module.exports = { CustomOutputParser };