225 lines
9.0 KiB
TypeScript
225 lines
9.0 KiB
TypeScript
import { flatten } from 'lodash'
|
|
import { Tool, StructuredTool } from '@langchain/core/tools'
|
|
import { BaseChatModel } from '@langchain/core/language_models/chat_models'
|
|
import { AIMessage, HumanMessage, SystemMessage } from '@langchain/core/messages'
|
|
import { VectorStoreRetriever } from '@langchain/core/vectorstores'
|
|
import { PromptTemplate } from '@langchain/core/prompts'
|
|
import { AutoGPT } from 'langchain/experimental/autogpt'
|
|
import { LLMChain } from 'langchain/chains'
|
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
|
import { checkInputs, Moderation } from '../../moderation/Moderation'
|
|
import { formatResponse } from '../../outputparsers/OutputParserHelpers'
|
|
|
|
type ObjectTool = StructuredTool
|
|
const FINISH_NAME = 'finish'
|
|
|
|
class AutoGPT_Agents implements INode {
|
|
label: string
|
|
name: string
|
|
version: number
|
|
description: string
|
|
type: string
|
|
icon: string
|
|
category: string
|
|
baseClasses: string[]
|
|
inputs: INodeParams[]
|
|
|
|
constructor() {
|
|
this.label = 'AutoGPT'
|
|
this.name = 'autoGPT'
|
|
this.version = 2.0
|
|
this.type = 'AutoGPT'
|
|
this.category = 'Agents'
|
|
this.icon = 'autogpt.svg'
|
|
this.description = 'Autonomous agent with chain of thoughts for self-guided task completion'
|
|
this.baseClasses = ['AutoGPT']
|
|
this.inputs = [
|
|
{
|
|
label: 'Allowed Tools',
|
|
name: 'tools',
|
|
type: 'Tool',
|
|
list: true
|
|
},
|
|
{
|
|
label: 'Chat Model',
|
|
name: 'model',
|
|
type: 'BaseChatModel'
|
|
},
|
|
{
|
|
label: 'Vector Store Retriever',
|
|
name: 'vectorStoreRetriever',
|
|
type: 'BaseRetriever'
|
|
},
|
|
{
|
|
label: 'AutoGPT Name',
|
|
name: 'aiName',
|
|
type: 'string',
|
|
placeholder: 'Tom',
|
|
optional: true
|
|
},
|
|
{
|
|
label: 'AutoGPT Role',
|
|
name: 'aiRole',
|
|
type: 'string',
|
|
placeholder: 'Assistant',
|
|
optional: true
|
|
},
|
|
{
|
|
label: 'Maximum Loop',
|
|
name: 'maxLoop',
|
|
type: 'number',
|
|
default: 5,
|
|
optional: true
|
|
},
|
|
{
|
|
label: 'Input Moderation',
|
|
description: 'Detect text that could generate harmful output and prevent it from being sent to the language model',
|
|
name: 'inputModeration',
|
|
type: 'Moderation',
|
|
optional: true,
|
|
list: true
|
|
}
|
|
]
|
|
}
|
|
|
|
async init(nodeData: INodeData): Promise<any> {
|
|
const model = nodeData.inputs?.model as BaseChatModel
|
|
const vectorStoreRetriever = nodeData.inputs?.vectorStoreRetriever as VectorStoreRetriever
|
|
let tools = nodeData.inputs?.tools as Tool[]
|
|
tools = flatten(tools)
|
|
const aiName = (nodeData.inputs?.aiName as string) || 'AutoGPT'
|
|
const aiRole = (nodeData.inputs?.aiRole as string) || 'Assistant'
|
|
const maxLoop = nodeData.inputs?.maxLoop as string
|
|
|
|
const autogpt = AutoGPT.fromLLMAndTools(model, tools, {
|
|
memory: vectorStoreRetriever,
|
|
aiName,
|
|
aiRole
|
|
})
|
|
|
|
autogpt.maxIterations = parseInt(maxLoop, 10)
|
|
|
|
return autogpt
|
|
}
|
|
|
|
async run(nodeData: INodeData, input: string): Promise<string | object> {
|
|
const executor = nodeData.instance as AutoGPT
|
|
const model = nodeData.inputs?.model as BaseChatModel
|
|
const moderations = nodeData.inputs?.inputModeration as Moderation[]
|
|
|
|
if (moderations && moderations.length > 0) {
|
|
try {
|
|
// Use the output of the moderation chain as input for the AutoGPT agent
|
|
input = await checkInputs(moderations, input)
|
|
} catch (e) {
|
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
// if (options.shouldStreamResponse) {
|
|
// streamResponse(options.sseStreamer, options.chatId, e.message)
|
|
// }
|
|
return formatResponse(e.message)
|
|
}
|
|
}
|
|
|
|
try {
|
|
let totalAssistantReply = ''
|
|
executor.run = async (goals: string[]): Promise<string | undefined> => {
|
|
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])
|
|
|
|
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 }
|