diff --git a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts index 69e9b9ed..6c26b944 100644 --- a/packages/components/nodes/agents/AutoGPT/AutoGPT.ts +++ b/packages/components/nodes/agents/AutoGPT/AutoGPT.ts @@ -2,8 +2,15 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { BaseChatModel } from 'langchain/chat_models/base' import { AutoGPT } from 'langchain/experimental/autogpt' import { Tool } from 'langchain/tools' +import { AIMessage, HumanMessage, SystemMessage } from 'langchain/schema' import { VectorStoreRetriever } from 'langchain/vectorstores/base' import { flatten } from 'lodash' +import { StructuredTool } from 'langchain/tools' +import { LLMChain } from 'langchain/chains' +import { PromptTemplate } from 'langchain/prompts' + +type ObjectTool = StructuredTool +const FINISH_NAME = 'finish' class AutoGPT_Agents implements INode { label: string @@ -88,13 +95,107 @@ class AutoGPT_Agents implements INode { async run(nodeData: INodeData, input: string): Promise { const executor = nodeData.instance as AutoGPT + const model = nodeData.inputs?.model as BaseChatModel + try { + let totalAssistantReply = '' + executor.run = async (goals: string[]): Promise => { + const user_input = 'Determine which next command to use, and respond using the format specified above:' + let loopCount = 0 + while (loopCount < executor.maxIterations) { + loopCount += 1 + + const { text: assistantReply } = await executor.chain.call({ + goals, + user_input, + memory: executor.memory, + messages: executor.fullMessageHistory + }) + + // eslint-disable-next-line no-console + console.log('\x1b[92m\x1b[1m\n*****AutoGPT*****\n\x1b[0m\x1b[0m') + // eslint-disable-next-line no-console + console.log(assistantReply) + totalAssistantReply += assistantReply + '\n' + executor.fullMessageHistory.push(new HumanMessage(user_input)) + executor.fullMessageHistory.push(new AIMessage(assistantReply)) + + const action = await executor.outputParser.parse(assistantReply) + const tools = executor.tools.reduce((acc, tool) => ({ ...acc, [tool.name]: tool }), {} as { [key: string]: ObjectTool }) + if (action.name === FINISH_NAME) { + return action.args.response + } + let result: string + if (action.name in tools) { + const tool = tools[action.name] + let observation + try { + observation = await tool.call(action.args) + } catch (e) { + observation = `Error in args: ${e}` + } + result = `Command ${tool.name} returned: ${observation}` + } else if (action.name === 'ERROR') { + result = `Error: ${action.args}. ` + } else { + result = `Unknown command '${action.name}'. Please refer to the 'COMMANDS' list for available commands and only respond in the specified JSON format.` + } + + let memoryToAdd = `Assistant Reply: ${assistantReply}\nResult: ${result} ` + if (executor.feedbackTool) { + const feedback = `\n${await executor.feedbackTool.call('Input: ')}` + if (feedback === 'q' || feedback === 'stop') { + return 'EXITING' + } + memoryToAdd += feedback + } + + const documents = await executor.textSplitter.createDocuments([memoryToAdd]) + await executor.memory.addDocuments(documents) + executor.fullMessageHistory.push(new SystemMessage(result)) + } + + return undefined + } + const res = await executor.run([input]) - return res || 'I have completed all my tasks.' + + if (!res) { + const sentence = `Unfortunately I was not able to complete all the task. Here is the chain of thoughts:` + return `${await rephraseString(sentence, model)}\n\`\`\`javascript\n${totalAssistantReply}\n\`\`\`\n` + } + + const sentence = `I have completed all my tasks. Here is the chain of thoughts:` + let writeFilePath = '' + const writeTool = executor.tools.find((tool) => tool.name === 'write_file') + if (executor.tools.length && writeTool) { + writeFilePath = (writeTool as any).store.basePath + } + return `${await rephraseString( + sentence, + model + )}\n\`\`\`javascript\n${totalAssistantReply}\n\`\`\`\nAnd the final result:\n\`\`\`javascript\n${res}\n\`\`\`\n${ + writeFilePath + ? await rephraseString( + `You can download the final result displayed above, or see if a new file has been successfully written to \`${writeFilePath}\``, + model + ) + : '' + }` } catch (e) { throw new Error(e) } } } +const rephraseString = async (sentence: string, model: BaseChatModel) => { + const promptTemplate = new PromptTemplate({ + template: 'You are a helpful Assistant that rephrase a sentence: {sentence}', + inputVariables: ['sentence'] + }) + const chain = new LLMChain({ llm: model, prompt: promptTemplate }) + const res = await chain.call({ sentence }) + return res?.text +} + module.exports = { nodeClass: AutoGPT_Agents } diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 9be26987..b95dd301 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -481,7 +481,7 @@ export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[], nodes: if (inputVariables.length > 0) return true } } - const whitelistNodeNames = ['vectorStoreToDocument'] + const whitelistNodeNames = ['vectorStoreToDocument', 'autoGPT'] for (const node of nodes) { if (whitelistNodeNames.includes(node.data.name)) return true }