From dfd94951cea2469361d47bdf5b489f4612e58ebb Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Thu, 3 Aug 2023 15:59:23 +0900 Subject: [PATCH 01/42] Add dev page with sidebar and agent view --- src/components/Agent/AgentView.tsx | 515 +++++++++++++++++++++++++++++ src/pages/dev/index.tsx | 116 +++++++ 2 files changed, 631 insertions(+) create mode 100644 src/components/Agent/AgentView.tsx create mode 100644 src/pages/dev/index.tsx diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx new file mode 100644 index 00000000..7fdba349 --- /dev/null +++ b/src/components/Agent/AgentView.tsx @@ -0,0 +1,515 @@ +import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import va from '@vercel/analytics'; +import { + AgentStatus, + AgentType, + Execution, + Message, + MessageBlock, + SelectItem, + UserSettings, +} from '@/types'; +import { Input } from './Input'; +import AgentMessage from './AgentMessage'; +import { AgentParameter } from './AgentParameter'; +import { ProjectTile } from './ProjectTile'; +import { AgentMessageHeader } from './AgentMessageHeader'; +import { + getExportText, + getMessageBlocks, + loadingAgentMessage, +} from '../../utils/message'; +import { BabyAGI } from '@/agents/babyagi'; +import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; +import { AGENT, ITERATIONS, MODELS, SETTINGS_KEY } from '@/utils/constants'; +import { toast } from 'sonner'; +import { v4 as uuidv4 } from 'uuid'; +import { useExecution } from '@/hooks/useExecution'; +import { useExecutionStatus } from '@/hooks/useExecutionStatus'; +import { translate } from '../../utils/translate'; +import axios from 'axios'; +import { taskCompletedNotification } from '@/utils/notification'; +import { useTranslation } from 'next-i18next'; +import { AgentMessageBlock } from './AgentMessageBlock'; +import { AgentTask } from './AgentTask'; +import { IntroGuide } from './IntroGuide'; +import { BabyElfAGI } from '@/agents/babyelfagi/executer'; +import { SkillsList } from './SkillList'; + +export const AgentView: FC = () => { + const [model, setModel] = useState(MODELS[1]); + const [iterations, setIterations] = useState(ITERATIONS[0]); + const [objective, setObjective] = useState(''); + const [firstTask, setFirstTask] = useState( + translate('FIRST_TASK_PLACEHOLDER', 'constants'), + ); + const [messages, setMessages] = useState([]); + const [messageBlocks, setMessageBlocks] = useState([]); + const [agentStatus, setAgentStatus] = useState({ + type: 'ready', + }); + const [agent, setAgent] = useState( + null, + ); + const [selectedAgent, setSelectedAgent] = useState(AGENT[0]); + const { i18n } = useTranslation(); + const [language, setLanguage] = useState(i18n.language); + + const messagesEndRef = useRef(null); + const { + addExecution, + updateExec, + executions, + selectedExecutionId, + selectExecution, + } = useExecution(); + const { isExecuting, setExecuting } = useExecutionStatus(); + + const scrollToBottom = useCallback(() => { + const behavior = isExecuting ? 'smooth' : 'auto'; + messagesEndRef.current?.scrollIntoView({ behavior: behavior }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [messageBlocks]); + + useEffect(() => { + scrollToBottom(); + }, [scrollToBottom]); + + useEffect(() => { + if (selectedExecutionId) { + const selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + setMessages(selectedExecution.messages); + } + } else { + setMessages([]); + setObjective(''); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedExecutionId]); + + useEffect(() => { + const execution = executions.find((exe) => exe.id === selectedExecutionId); + if (execution) { + const updatedExecution: Execution = { + ...execution, + messages: messages, + }; + updateExec(updatedExecution); + } + + const blocks = getMessageBlocks(messages, isExecuting); + setMessageBlocks(blocks); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [messages]); + + useEffect(() => { + setLanguage(i18n.language); + }, [i18n]); + + // manage data + const saveNewData = async () => { + const execution: Execution = { + id: uuidv4(), + name: objective, + date: new Date().toISOString(), + params: { + objective: objective, + model: model, + iterations: iterations, + firstTask: firstTask, + agent: selectedAgent.id as AgentType, + }, + messages: messages, + }; + + selectExecution(execution.id); + await new Promise((resolve) => { + addExecution(execution); + resolve(null); + }); + + return execution; + }; + + // handler functions + const messageHandler = (message: Message) => { + setMessages((currentMessages) => { + if (selectedAgent.id !== 'babyagi') { + // if the message.type and id are the same, overwrite the message + const index = currentMessages.findIndex( + (msg) => msg.type === message.type && msg.id === message.id, + ); + if (index !== -1) { + const newMessages = [...currentMessages]; + newMessages[index] = message; + return newMessages; + } + } + + const updatedMessages = [...currentMessages, message]; + + // show toast notification + if (message.type === 'complete' || message.type === 'end-of-iterations') { + toast.success(translate('ALL_TASKS_COMPLETED_TOAST', 'agent')); + taskCompletedNotification(objective); + } else if (message.type === 'done') { + toast.success(translate('TASK_COMPLETED_TOAST', 'agent')); + } + + return updatedMessages; + }); + }; + + const inputHandler = (value: string) => { + setObjective(value); + }; + + const cancelHandle = () => { + setAgent(null); + setExecuting(false); + }; + + const startHandler = async () => { + if (needSettingsAlert()) { + alert(translate('ALERT_SET_UP_API_KEY', 'agent')); + return; + } + if (model.id === 'gpt-4') { + const enabled = await enabledGPT4(); + if (!enabled) { + alert(translate('ALERT_GPT_4_DISABLED', 'constants')); + return; + } + } + + setMessages([]); + setExecuting(true); + const execution = await saveNewData(); + const verbose = false; // You can set this to true to see the agent's internal state + + // switch agent + let agent = null; + switch (selectedAgent.id) { + case 'babyagi': + agent = new BabyAGI( + objective, + model.id, + Number(iterations.id), + firstTask, + execution.id, + messageHandler, + setAgentStatus, + cancelHandle, + language, + verbose, + ); + break; + case 'babydeeragi': + agent = new BabyDeerAGI( + objective, + model.id, + messageHandler, + setAgentStatus, + cancelHandle, + language, + verbose, + ); + break; + case 'babyelfagi': + agent = new BabyElfAGI( + objective, + model.id, + messageHandler, + setAgentStatus, + cancelHandle, + language, + verbose, + ); + break; + } + setAgent(agent); + agent?.start(); + + va.track('Start', { + model: model.id, + agent: selectedAgent.id, + iterations: iterations.id, + }); + }; + + const stopHandler = () => { + // refresh message blocks + const blocks = getMessageBlocks(messages, false); + setMessageBlocks(blocks); + + setExecuting(false); + agent?.stop(); + + va.track('Stop'); + }; + + const clearHandler = () => { + setMessages([]); + selectExecution(undefined); + setAgentStatus({ type: 'ready' }); + + va.track('New'); + }; + + const copyHandler = () => { + navigator.clipboard.writeText(getExportText(messages, selectedAgent.id)); + toast.success(translate('COPIED_TO_CLIPBOARD', 'agent')); + + va.track('CopyToClipboard'); + }; + + const downloadHandler = () => { + const element = document.createElement('a'); + const filename = + objective.length > 0 + ? `${objective.replace(/\s/g, '_')}.txt` + : 'download.txt'; + const file = new Blob( + ['\uFEFF' + getExportText(messages, selectedAgent.id)], + { + type: 'text/plain;charset=utf-8', + }, + ); + element.href = URL.createObjectURL(file); + element.download = filename; + document.body.appendChild(element); + element.click(); + + va.track('Download'); + }; + + const feedbackHandler = (isGood: boolean) => { + let selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + setMessages(selectedExecution.messages); + } + const feedbackObjective = selectedExecution?.params.objective; + const feedbackModel = selectedExecution?.params.model.id; + const feedbackAgent = selectedExecution?.params.agent; + const feedbackIterations = Number(selectedExecution?.params.iterations.id); + + let lastResult = messages + .filter( + (message) => + message.type === 'task-output' || message.type === 'task-result', + ) + .pop()?.text; + if (feedbackAgent === 'babybeeagi') { + lastResult = messages + .filter((message) => message.type === 'task-result-summary') + .pop()?.text; + } + const lastTaskList = messages + .filter((message) => message.type === 'task-list') + .pop()?.text; + const sessionSummary = messages + .filter((message) => message.type === 'session-summary') + .pop()?.text; + const iterationNumber = messages.filter( + (message) => message.type === 'done', + ).length; + const finished = + messages.filter( + (message) => + message.type === 'complete' || message.type === 'end-of-iterations', + ).length > 0; + const output = getExportText(messages); + + axios.post('/api/feedback', { + objective: feedbackObjective, + evaluation: isGood ? 'good' : 'bad', + model: feedbackModel, + agent: feedbackAgent, + iterations: feedbackIterations, + last_result: lastResult, + task_list: lastTaskList, + session_summary: sessionSummary, + iteration_number: iterationNumber, + finished: finished, + output: output, + }); + + toast.success(translate('FEEDBACK_SUBMITTED_TOAST', 'constants')); + + // update execution + if (selectedExecution) { + selectedExecution.evaluation = isGood ? 'good' : 'bad'; + updateExec(selectedExecution); + } + }; + + const userInputHandler = async (id: number, text: string) => { + if (agent instanceof BabyDeerAGI) { + agent.userInput(id, text); + } + }; + + const needSettingsAlert = () => { + const useUserApiKey = process.env.NEXT_PUBLIC_USE_USER_API_KEY; + if (useUserApiKey === 'false') { + return false; + } + + const userSettings = localStorage.getItem(SETTINGS_KEY); + if (userSettings) { + const { openAIApiKey } = JSON.parse(userSettings) as UserSettings; + if (openAIApiKey && openAIApiKey?.length > 0) { + return false; + } + } + return true; + }; + + const enabledGPT4 = async () => { + const userSettings = localStorage.getItem(SETTINGS_KEY); + if (!userSettings) { + return false; + } + + const { enabledGPT4 } = JSON.parse(userSettings) as UserSettings; + if (enabledGPT4 === undefined) { + return true; // If no value is given, its enabled by default + } + + return enabledGPT4; + }; + + const currentEvaluation = () => { + const selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + return selectedExecution.evaluation; + } + return undefined; + }; + + const currentAgentId = () => { + if (isExecuting) { + return selectedAgent.id; + } + + const selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + return selectedExecution.params.agent; + } + return undefined; + }; + + const skills = () => { + if (selectedAgent.id === 'babyelfagi') { + const elf = new BabyElfAGI( + objective, + model.id, + messageHandler, + setAgentStatus, + cancelHandle, + language, + false, + ); + const skills = elf.skillRegistry.getAllSkills(); + const skillInfos = skills.map((skill) => { + const skillInfo = { + name: skill.name, + description: skill.descriptionForHuman, + icon: skill.icon, + badge: skill.type, + }; + return skillInfo; + }); + return skillInfos; + } + return []; + }; + + return ( +
+

+ for development use only +

+ {messageBlocks.length === 0 ? ( + <> + + {selectedAgent.id === 'babyelfagi' && ( + + )} +
+
+ + {(selectedAgent.id === 'babydeeragi' || + selectedAgent.id === 'babyelfagi') && ( + setObjective(value)} + agent={selectedAgent.id} + /> + )} +
+
+ + ) : ( +
+ + {messageBlocks.map((block, index) => + currentAgentId() === 'babydeeragi' || + currentAgentId() === 'babyelfagi' ? ( + + ) : ( + + ), + )} + {isExecuting && ( + + )} +
+
+ )} + 0} + agent={selectedAgent.id as AgentType} + evaluation={currentEvaluation()} + /> +
+ ); +}; diff --git a/src/pages/dev/index.tsx b/src/pages/dev/index.tsx new file mode 100644 index 00000000..d6c7ffd9 --- /dev/null +++ b/src/pages/dev/index.tsx @@ -0,0 +1,116 @@ +import { AgentView } from '@/components/Agent/AgentView'; +import { Sidebar } from '@/components/Sidebar/Sidebar'; +import Head from 'next/head'; +import { useEffect, useState } from 'react'; +import type { GetStaticProps } from 'next'; +import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +import nextI18NextConfig from '../../../next-i18next.config.js'; +import { languages } from '@/utils/languages'; +import { STATE_KEY } from '@/utils/constants'; +import { UIState } from '@/types/index.js'; +import { CollapsedButton } from '@/components/Sidebar/CollapsedButton'; + +function Home() { + const [showSidebar, setShowSidebar] = useState(false); + + useEffect(() => { + const item = localStorage.getItem(STATE_KEY); + let show = false; + if (!item) { + if (window.innerWidth <= 768) { + show = false; + } else { + show = true; + } + } else { + const state = JSON.parse(item) as UIState; + if (state?.showSidebar === undefined) { + } else { + show = state.showSidebar; + } + } + setShowSidebar(show); + saveSidebarState(show); + }, []); + + const saveSidebarState = (show: boolean) => { + const state: UIState = { showSidebar: show }; + localStorage.setItem(STATE_KEY, JSON.stringify(state)); + }; + + const menuClickHandler = () => { + setShowSidebar(!showSidebar); + saveSidebarState(!showSidebar); + }; + + // This page is for development only. + if (process.env.NODE_ENV !== 'development') { + return

This page is for development.

; + } + + return ( + <> + + BabyAGI-UI | DEV + + + + + + + + + + + + +
+
+ {showSidebar && ( +
+ +
+ )} + +
+
+ +
+
+ + ); +} + +export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => { + const supportedLocales = languages.map((language) => language.code); + const chosenLocale = supportedLocales.includes(locale) ? locale : 'en'; + + return { + props: { + ...(await serverSideTranslations( + chosenLocale, + nextI18NextConfig.ns as string[], + )), + }, + }; +}; + +export default Home; From 595a3c9a6d93ec22a1aa9fbf8e867cea961684fd Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 5 Aug 2023 14:43:35 +0900 Subject: [PATCH 02/42] Add AI module and AgentStream implementation --- package-lock.json | 618 ++++++++++++++++++++++------- package.json | 1 + src/agents/base/AgentStream.ts | 96 +++++ src/components/Agent/AgentView.tsx | 24 +- src/hooks/useAgent.ts | 113 ++++++ src/pages/api/agent/index.ts | 26 ++ 6 files changed, 726 insertions(+), 152 deletions(-) create mode 100644 src/agents/base/AgentStream.ts create mode 100644 src/hooks/useAgent.ts create mode 100644 src/pages/api/agent/index.ts diff --git a/package-lock.json b/package-lock.json index 5ad5ad24..0159d1b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@types/react": "18.0.33", "@types/react-dom": "18.0.11", "@vercel/analytics": "^1.0.0", + "ai": "^2.1.31", "airtable": "^0.12.1", "axios": "^1.4.0", "cheerio": "^1.0.0-rc.12", @@ -64,6 +65,19 @@ "tailwindcss": "^3.3.1" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@anthropic-ai/sdk": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.4.3.tgz", @@ -189,6 +203,18 @@ "node": ">=4" } }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", @@ -336,6 +362,60 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "peer": true + }, "node_modules/@next/env": { "version": "13.2.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.2.4.tgz", @@ -1180,6 +1260,12 @@ "@types/ms": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "peer": true + }, "node_modules/@types/hast": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", @@ -1393,6 +1479,136 @@ "react": "^16.8||^17||^18" } }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "peer": true, + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/reactivity-transform/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "peer": true, + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "peer": true, + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "peer": true, + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "peer": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1410,9 +1626,9 @@ "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -1441,6 +1657,43 @@ "node": ">= 6.0.0" } }, + "node_modules/ai": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/ai/-/ai-2.1.31.tgz", + "integrity": "sha512-ihkimg9iP/C/OaxjYj9KgA4gPC9Rzw60Xv3+AWYrQ807L/NEF8sgZBmA1hv8/DC8KzMv/7N+a5HCQPDZXNUs+g==", + "dependencies": { + "eventsource-parser": "1.0.0", + "nanoid": "3.3.6", + "solid-swr-store": "0.10.7", + "sswr": "2.0.0", + "swr": "2.2.0", + "swr-store": "0.10.6", + "swrv": "1.0.4" + }, + "engines": { + "node": ">=14.6" + }, + "peerDependencies": { + "react": "^18.2.0", + "solid-js": "^1.7.7", + "svelte": "^3.0.0 || ^4.0.0", + "vue": "^3.3.4" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/airtable": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/airtable/-/airtable-0.12.1.tgz", @@ -1537,11 +1790,11 @@ } }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -1701,11 +1954,11 @@ } }, "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/bail": { @@ -2080,6 +2333,19 @@ "node": ">=6" } }, + "node_modules/code-red": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.3.tgz", + "integrity": "sha512-kVwJELqiILQyG5aeuyKFbdsI1fmQy1Cmf7dQ8eGmVuJoaRVdwey7WaMknr2ZFeVSYSKT0rExsa8EGw0aoI/1QQ==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "@types/estree": "^1.0.0", + "acorn": "^8.8.2", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2194,6 +2460,19 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -2254,33 +2533,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/deep-equal": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", - "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", - "dependencies": { - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.2", - "get-intrinsic": "^1.1.3", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.1", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2538,25 +2790,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -3025,6 +3258,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3046,6 +3288,14 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, + "node_modules/eventsource-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.0.0.tgz", + "integrity": "sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==", + "engines": { + "node": ">=14.18" + } + }, "node_modules/expr-eval": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz", @@ -3856,21 +4106,6 @@ "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.0.tgz", "integrity": "sha512-WdPV58rT3aOWXvvyuBydnCq4S2BM1Yz8shKxlEpk/6x+GX202XRvXOycEFtNgnHVLoc46hpexPFx8Pz1/sMS0w==" }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -4047,14 +4282,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -4107,6 +4334,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-reference": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz", + "integrity": "sha512-baJJdQLiYaJdvFbJqXrcGv3WU3QCzBlUcI5QhbesIm6/xPsvmO+2CDoi/GMOFBQEQm+PXkwOPrp9KK5ozZsp2w==", + "peer": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4122,14 +4358,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -4187,14 +4415,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -4206,18 +4426,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -4229,11 +4437,6 @@ "node": ">=8" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4508,6 +4711,12 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4586,6 +4795,18 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -4809,6 +5030,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8748,21 +8975,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -9099,6 +9311,17 @@ "optional": true, "peer": true }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9927,6 +10150,15 @@ "node": ">=10" } }, + "node_modules/seroval": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.5.1.tgz", + "integrity": "sha512-ZfhQVB59hmIauJG5Ydynupy8KHyr5imGNtdDhbZG68Ufh1Ynkv9KOYOAABf71oVbQxJ8VkWnMHAjEHE7fWkH5g==", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9967,6 +10199,28 @@ "node": ">=8" } }, + "node_modules/solid-js": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.9.tgz", + "integrity": "sha512-p1orXnauMQmwYULZtuPAXyKNRGEN2qh60kLX4YURa3jvulxAqjlh2kWEljXCtAVR6UZPC16NXdj9ASHcH383Fg==", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "^0.5.0" + } + }, + "node_modules/solid-swr-store": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/solid-swr-store/-/solid-swr-store-0.10.7.tgz", + "integrity": "sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "solid-js": "^1.2", + "swr-store": "^0.10" + } + }, "node_modules/sonner": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/sonner/-/sonner-0.3.5.tgz", @@ -9993,15 +10247,15 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "node_modules/sswr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.0.0.tgz", + "integrity": "sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==", "dependencies": { - "internal-slot": "^1.0.4" + "swrev": "^4.0.0" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "svelte": "^4.0.0" } }, "node_modules/string_decoder": { @@ -10217,6 +10471,65 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svelte": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.1.2.tgz", + "integrity": "sha512-/evA8U6CgOHe5ZD1C1W3va9iJG7mWflcCdghBORJaAhD2JzrVERJty/2gl0pIPrJYBGZwZycH6onYf+64XXF9g==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^3.2.1", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.0", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/swr-store": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/swr-store/-/swr-store-0.10.6.tgz", + "integrity": "sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==", + "dependencies": { + "dequal": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz", + "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==" + }, + "node_modules/swrv": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz", + "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==", + "peerDependencies": { + "vue": ">=3.2.26 < 4" + } + }, "node_modules/synckit": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", @@ -10727,6 +11040,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10794,6 +11115,19 @@ "node": ">=0.10.0" } }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -10837,20 +11171,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/which-typed-array": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", diff --git a/package.json b/package.json index f8268137..3ea4f516 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/react": "18.0.33", "@types/react-dom": "18.0.11", "@vercel/analytics": "^1.0.0", + "ai": "^2.1.31", "airtable": "^0.12.1", "axios": "^1.4.0", "cheerio": "^1.0.0-rc.12", diff --git a/src/agents/base/AgentStream.ts b/src/agents/base/AgentStream.ts new file mode 100644 index 00000000..9247274a --- /dev/null +++ b/src/agents/base/AgentStream.ts @@ -0,0 +1,96 @@ +import { AIStreamCallbacks, createCallbacksTransformer } from 'ai'; + +export function AgentStream(callbacks?: AIStreamCallbacks) { + const stream = new TransformStream(); + const writer = stream.writable.getWriter(); + + const runs = new Set(); + + const handleError = async (e: Error, runId: string) => { + runs.delete(runId); + await writer.ready; + await writer.abort(e); + }; + + const handleStart = async (runId: string) => { + runs.add(runId); + }; + + const handleEnd = async (runId: string) => { + runs.delete(runId); + + if (runs.size === 0) { + await writer.ready; + await writer.close(); + } + }; + + const test = async () => { + const obj = { + id: 'test', + content: 'test', + type: 'plane', + taskId: 'test', + }; + await writer.ready; + await writer.write(JSON.stringify(obj)); + }; + + return { + stream: stream.readable.pipeThrough(createCallbacksTransformer(callbacks)), + handlers: { + handleLLMNewToken: async (token: string) => { + await writer.ready; + await writer.write(token); + }, + handleLLMStart: async (_llm: any, _prompts: string[], runId: string) => { + handleStart(runId); + }, + handleLLMEnd: async (_output: any, runId: string) => { + await handleEnd(runId); + }, + handleLLMError: async (e: Error, runId: string) => { + await handleError(e, runId); + }, + handleChainStart: async (_chain: any, _inputs: any, runId: string) => { + handleStart(runId); + }, + handleChainEnd: async (_outputs: any, runId: string) => { + await handleEnd(runId); + }, + handleChainError: async (e: Error, runId: string) => { + await handleError(e, runId); + }, + handleToolStart: async (_tool: any, _input: string, runId: string) => { + handleStart(runId); + }, + handleToolEnd: async (_output: string, runId: string) => { + await handleEnd(runId); + }, + handleToolError: async (e: Error, runId: string) => { + await handleError(e, runId); + }, + handleMessage: async ( + id: string, + content: string, + type?: string, + taskId?: string, + style?: 'plane' | 'code' | 'log', + status?: 'complete' | 'incomplete' | 'running', + options?: any, + ) => { + await writer.ready; + await writer.write( + JSON.stringify({ + id, + content, + type, + taskId, + options, + }), + ); + }, + }, + func: test, + }; +} diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 7fdba349..44ad2e31 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -35,6 +35,7 @@ import { AgentTask } from './AgentTask'; import { IntroGuide } from './IntroGuide'; import { BabyElfAGI } from '@/agents/babyelfagi/executer'; import { SkillsList } from './SkillList'; +import { useAgent } from '@/hooks/useAgent'; export const AgentView: FC = () => { const [model, setModel] = useState(MODELS[1]); @@ -435,11 +436,28 @@ export const AgentView: FC = () => { return []; }; + const { input, handleInputChange, handleSubmit, agentMessages } = useAgent({ + api: '/api/agent', + }); + return (
-

- for development use only -

+
+

+ for development use only +

+ {agentMessages.map((m, index) => ( +
{m}
+ ))} +
+ +
+
{messageBlocks.length === 0 ? ( <> void; + onError?: (event: Event | ErrorEvent) => void; + onFinish?: () => void; +}; + +export type UseAgentHelpers = { + agentMessages: string[]; + setMessages: React.Dispatch>; + stop: () => void; + handleInputChange: (event: React.ChangeEvent) => void; + handleSubmit: (event: React.FormEvent) => void; + input: string; + setInput: React.Dispatch>; +}; + +export function useAgent({ + api = '/api/agent', + id, + onResponse, + onError, + onFinish, +}: UseAgentOptions = {}): UseAgentHelpers { + const abortControllerRef = useRef( + new AbortController(), + ); + const [agentMessages, setMessages] = useState([]); + const [input, setInput] = useState(''); + + // Handle input change + const handleInputChange = (event: React.ChangeEvent) => { + setInput(event.target.value); + }; + + // Handle form submission + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + // Create a new AbortController instance for each request + abortControllerRef.current = new AbortController(); + + try { + // Send POST request with input and id + const response = await fetch(api, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id, input }), + signal: abortControllerRef.current.signal, // Add the abort signal + }); + + const reader = response.body?.getReader(); + + if (reader) { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + break; + } + + if (value) { + const message = new TextDecoder().decode(value); + setMessages((prevMessages) => [...prevMessages, message]); + + // Call onResponse with the new message + if (onResponse) { + onResponse(new MessageEvent('message', { data: message })); + } + } + + if (abortControllerRef.current === null) { + reader.cancel(); + break; + } + } + + // Call onFinish when the stream is finished + if (onFinish) { + onFinish(); + } + } + } catch (error) { + // Call onError when an error occurs + if (onError) { + onError(new ErrorEvent('error', { error })); + } + } + }; + + // Stop function to abort the connection + const stop = () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + }; + + return { + agentMessages, + setMessages, + stop, + handleInputChange, + handleSubmit, + input, + setInput, + }; +} diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts new file mode 100644 index 00000000..fef06d97 --- /dev/null +++ b/src/pages/api/agent/index.ts @@ -0,0 +1,26 @@ +import type { NextRequest } from 'next/server'; +import { NextApiResponse } from 'next'; +import { LangChainStream, StreamingTextResponse } from 'ai'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { HumanChatMessage } from 'langchain/schema'; +import { AgentStream } from '@/agents/base/AgentStream'; + +export const config = { + runtime: 'edge', +}; + +export default async function handler(req: NextRequest, res: NextApiResponse) { + const { stream, handlers, func } = AgentStream(); + const { input, id } = await req.json(); + + const llm = new ChatOpenAI({ + streaming: true, + verbose: true, + }); + + // llm.call([new HumanChatMessage(input)], {}, [handlers]).catch(console.error); + + func(); + + return new StreamingTextResponse(stream); +} From d809c061eca6287d6ce7b0ca49efc07bbe4c636b Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sun, 6 Aug 2023 09:27:06 +0900 Subject: [PATCH 03/42] Refactor AgentStream.ts to handle a new message format --- src/agents/base/AgentStream.ts | 99 ++++++++++++------------------ src/agents/base/Executer.ts | 79 ++++++++++++++++++++++++ src/components/Agent/AgentView.tsx | 6 +- src/hooks/useAgent.ts | 22 +++++-- src/pages/api/agent/index.ts | 18 +++--- src/types/index.ts | 10 +++ src/utils/message.ts | 11 ++++ 7 files changed, 169 insertions(+), 76 deletions(-) create mode 100644 src/agents/base/Executer.ts diff --git a/src/agents/base/AgentStream.ts b/src/agents/base/AgentStream.ts index 9247274a..dcbe0145 100644 --- a/src/agents/base/AgentStream.ts +++ b/src/agents/base/AgentStream.ts @@ -1,3 +1,4 @@ +import { AgentMessage } from '@/types'; import { AIStreamCallbacks, createCallbacksTransformer } from 'ai'; export function AgentStream(callbacks?: AIStreamCallbacks) { @@ -25,72 +26,52 @@ export function AgentStream(callbacks?: AIStreamCallbacks) { } }; - const test = async () => { - const obj = { - id: 'test', - content: 'test', - type: 'plane', - taskId: 'test', - }; - await writer.ready; - await writer.write(JSON.stringify(obj)); - }; - return { stream: stream.readable.pipeThrough(createCallbacksTransformer(callbacks)), handlers: { - handleLLMNewToken: async (token: string) => { - await writer.ready; - await writer.write(token); - }, - handleLLMStart: async (_llm: any, _prompts: string[], runId: string) => { - handleStart(runId); - }, - handleLLMEnd: async (_output: any, runId: string) => { - await handleEnd(runId); - }, - handleLLMError: async (e: Error, runId: string) => { - await handleError(e, runId); - }, - handleChainStart: async (_chain: any, _inputs: any, runId: string) => { - handleStart(runId); - }, - handleChainEnd: async (_outputs: any, runId: string) => { - await handleEnd(runId); - }, - handleChainError: async (e: Error, runId: string) => { - await handleError(e, runId); - }, - handleToolStart: async (_tool: any, _input: string, runId: string) => { - handleStart(runId); - }, - handleToolEnd: async (_output: string, runId: string) => { - await handleEnd(runId); - }, - handleToolError: async (e: Error, runId: string) => { - await handleError(e, runId); - }, - handleMessage: async ( - id: string, - content: string, - type?: string, - taskId?: string, - style?: 'plane' | 'code' | 'log', - status?: 'complete' | 'incomplete' | 'running', - options?: any, - ) => { + // handleLLMNewToken: async (token: string) => { + // await writer.ready; + // await writer.write(token); + // }, + // handleLLMStart: async (_llm: any, _prompts: string[], runId: string) => { + // handleStart(runId); + // }, + // handleLLMEnd: async (_output: any, runId: string) => { + // await handleEnd(runId); + // }, + // handleLLMError: async (e: Error, runId: string) => { + // await handleError(e, runId); + // }, + // handleChainStart: async (_chain: any, _inputs: any, runId: string) => { + // handleStart(runId); + // }, + // handleChainEnd: async (_outputs: any, runId: string) => { + // await handleEnd(runId); + // }, + // handleChainError: async (e: Error, runId: string) => { + // await handleError(e, runId); + // }, + // handleToolStart: async (_tool: any, _input: string, runId: string) => { + // handleStart(runId); + // }, + // handleToolEnd: async (_output: string, runId: string) => { + // await handleEnd(runId); + // }, + // handleToolError: async (e: Error, runId: string) => { + // await handleError(e, runId); + // }, + handleMessage: async (message: AgentMessage) => { await writer.ready; await writer.write( - JSON.stringify({ - id, - content, - type, - taskId, - options, - }), + `${JSON.stringify({ + message, + })}\n`, ); }, + handleEnd: async () => { + await writer.ready; + await writer.close(); + }, }, - func: test, }; } diff --git a/src/agents/base/Executer.ts b/src/agents/base/Executer.ts new file mode 100644 index 00000000..ca65bd50 --- /dev/null +++ b/src/agents/base/Executer.ts @@ -0,0 +1,79 @@ +import { AgentMessage, AgentTask } from '@/types'; +import { v4 as uuidv4 } from 'uuid'; + +export class Executer { + objective: string; + modelName: string; + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }; + language: string; + + taskList: AgentTask[] = []; + + constructor( + objective: string, + modelName: string, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, + language: string = 'en', + ) { + this.objective = objective; + this.modelName = modelName; + this.handlers = handlers; + this.language = language; + } + + async run() { + this.taskList = []; + await this.prepare(); + await this.loop(); + await this.finishup(); + } + + // prepare() is called before loop() + async prepare() { + this.handleMessage({ + content: `objective: ${this.objective}`, + type: 'objective', + }); + } + + async loop() { + const tasks = []; + for (let i = 0; i < 3; i++) { + const task = new Promise((resolve) => { + setTimeout(async () => { + await this.handleMessage({ + content: `Test message ${i}`, + type: 'test', + }); + resolve(); + }, 1000 * i); + }); + tasks.push(task); + } + await Promise.all(tasks); + } + + async finishup() { + // Objective completed + this.handlers.handleMessage({ + content: 'Objective completed', + type: 'finish', + }); + this.handlers.handleEnd(); + } + + // handleMessage() is called by the agent to send a message to the frontend + async handleMessage(message: AgentMessage) { + const msg = { + ...message, + id: message.id || uuidv4(), + }; + await this.handlers.handleMessage(message); + } +} diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 44ad2e31..3ca0571a 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -446,8 +446,10 @@ export const AgentView: FC = () => {

for development use only

- {agentMessages.map((m, index) => ( -
{m}
+ {agentMessages.map((m) => ( +
+ {m.content} +
))}
>; + agentMessages: AgentMessage[]; + setMessages: React.Dispatch>; stop: () => void; handleInputChange: (event: React.ChangeEvent) => void; handleSubmit: (event: React.FormEvent) => void; @@ -28,8 +31,9 @@ export function useAgent({ const abortControllerRef = useRef( new AbortController(), ); - const [agentMessages, setMessages] = useState([]); + const [agentMessages, setMessages] = useState([]); const [input, setInput] = useState(''); + const [language] = useState(i18n?.language); // Handle input change const handleInputChange = (event: React.ChangeEvent) => { @@ -50,7 +54,7 @@ export function useAgent({ headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ id, input }), + body: JSON.stringify({ id, input, language }), signal: abortControllerRef.current.signal, // Add the abort signal }); @@ -66,7 +70,13 @@ export function useAgent({ if (value) { const message = new TextDecoder().decode(value); - setMessages((prevMessages) => [...prevMessages, message]); + const agentMessages: AgentMessage[] = message + .trim() + .split('\n') + .map((m) => parseMessage(m)); + console.log(agentMessages); + + setMessages((prevMessages) => [...prevMessages, ...agentMessages]); // Call onResponse with the new message if (onResponse) { diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts index fef06d97..89b21498 100644 --- a/src/pages/api/agent/index.ts +++ b/src/pages/api/agent/index.ts @@ -1,26 +1,26 @@ import type { NextRequest } from 'next/server'; import { NextApiResponse } from 'next'; import { LangChainStream, StreamingTextResponse } from 'ai'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { HumanChatMessage } from 'langchain/schema'; import { AgentStream } from '@/agents/base/AgentStream'; +import { Executer } from '@/agents/base/Executer'; export const config = { runtime: 'edge', }; export default async function handler(req: NextRequest, res: NextApiResponse) { - const { stream, handlers, func } = AgentStream(); - const { input, id } = await req.json(); + const { stream, handlers } = AgentStream(); + const { input, id, language } = await req.json(); - const llm = new ChatOpenAI({ - streaming: true, - verbose: true, - }); + // const llm = new ChatOpenAI({ + // streaming: true, + // verbose: true, + // }); // llm.call([new HumanChatMessage(input)], {}, [handlers]).catch(console.error); - func(); + const executer = new Executer(input, id, handlers, language || 'en'); + executer.run(); return new StreamingTextResponse(stream); } diff --git a/src/types/index.ts b/src/types/index.ts index 0489560f..377e97e2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -146,3 +146,13 @@ export type LLMParams = { streaming?: boolean; callbacks?: (message: Message) => void; }; + +export type AgentMessage = { + id?: string; + content: string; + type?: string; + taskId?: string; + style?: 'default' | 'log'; + status?: 'complete' | 'incomplete' | 'running'; + options?: { string: string }; +}; diff --git a/src/utils/message.ts b/src/utils/message.ts index 0b876c60..be23b5dc 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -1,4 +1,5 @@ import { + AgentMessage, AgentStatus, AgentTask, Message, @@ -336,3 +337,13 @@ export const getMessageBlocks = ( return messageBlocks; }; + +export const parseMessage = (json: string): AgentMessage => { + const message = JSON.parse(json).message as AgentMessage; + + return { + ...message, + style: message.style ?? 'default', + status: message.status ?? 'incomplete', + }; +}; From 8c2ef200adf753c412d1409602e791a28d59ebe6 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 7 Aug 2023 12:06:52 +0900 Subject: [PATCH 04/42] Add AgentInput component and useAgent hook --- src/components/Agent/AgentInput.tsx | 202 ++++++++++++++++++++++++++++ src/components/Agent/AgentView.tsx | 154 +++++---------------- src/hooks/useAgent.ts | 178 +++++++++++++++--------- 3 files changed, 356 insertions(+), 178 deletions(-) create mode 100644 src/components/Agent/AgentInput.tsx diff --git a/src/components/Agent/AgentInput.tsx b/src/components/Agent/AgentInput.tsx new file mode 100644 index 00000000..dbfbd9f4 --- /dev/null +++ b/src/components/Agent/AgentInput.tsx @@ -0,0 +1,202 @@ +import TextareaAutosize from 'react-textarea-autosize'; +import { translate } from '@/utils/translate'; +import { + ClipboardIcon, + DividerVerticalIcon, + DotFilledIcon, + DownloadIcon, + PlayIcon, + PlusIcon, + StopIcon, + UpdateIcon, +} from '@radix-ui/react-icons'; +import { ThumbsUp, ThumbsDown } from 'react-feather'; +import { FC, FormEvent, ChangeEvent, KeyboardEvent } from 'react'; + +type InputProps = { + value: string; + handleSubmit: ( + event: FormEvent | KeyboardEvent, + ) => void; + handleInputChange: (event: ChangeEvent) => void; + handleCancel: () => void; + handleClear: () => void; + handleCopy: () => void; + handleDownload: () => void; + handleFeedback: (value: boolean) => void; + isRunning: boolean; + hasMessages: boolean; + type?: string; + evaluation?: 'good' | 'bad'; +}; + +// Extracted the button components to make the main component cleaner +const StopButton = ({ onClick }: { onClick: () => void }) => ( + +); + +const NewButton = ({ onClick }: { onClick: () => void }) => ( + +); + +export const AgentInput: FC = ({ + value, + handleSubmit, + handleInputChange, + handleCancel, + handleClear, + handleCopy, + handleDownload, + handleFeedback, + isRunning, + hasMessages, + type, + evaluation, +}) => { + const handleKeyDown = (e: React.KeyboardEvent) => { + if (hasMessages) { + return; + } + + if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { + e.preventDefault(); + handleSubmit(e); + } + }; + + // Simplified the button rendering logic + const renderButton = () => { + if (isRunning) { + return ; + } + + if (hasMessages) { + return ; + } + + return null; + }; + + return ( +
+
+
+
+
{renderButton()}
+
+ {!isRunning && hasMessages && ( +
+
+ {!evaluation || evaluation === 'good' ? ( + + ) : null} + {!evaluation || evaluation === 'bad' ? ( + + ) : null} +
+
+ +
+
+ + +
+
+ )} +
+
+
+ + + + + +
+ {isRunning ? : null} +
+
+
+
+ + BabyAGI UI + + {' is designed to make it easier to run and develop with '} + + babyagi + + {' in a web app, like a ChatGPT.'} +
+
+ ); +}; diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 3ca0571a..dc14b978 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -9,7 +9,7 @@ import { SelectItem, UserSettings, } from '@/types'; -import { Input } from './Input'; +import { AgentInput } from './AgentInput'; import AgentMessage from './AgentMessage'; import { AgentParameter } from './AgentParameter'; import { ProjectTile } from './ProjectTile'; @@ -174,67 +174,12 @@ export const AgentView: FC = () => { setExecuting(false); }; - const startHandler = async () => { - if (needSettingsAlert()) { - alert(translate('ALERT_SET_UP_API_KEY', 'agent')); - return; - } - if (model.id === 'gpt-4') { - const enabled = await enabledGPT4(); - if (!enabled) { - alert(translate('ALERT_GPT_4_DISABLED', 'constants')); - return; - } - } - - setMessages([]); - setExecuting(true); - const execution = await saveNewData(); - const verbose = false; // You can set this to true to see the agent's internal state - - // switch agent - let agent = null; - switch (selectedAgent.id) { - case 'babyagi': - agent = new BabyAGI( - objective, - model.id, - Number(iterations.id), - firstTask, - execution.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - case 'babydeeragi': - agent = new BabyDeerAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - case 'babyelfagi': - agent = new BabyElfAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - } - setAgent(agent); - agent?.start(); + const stopHandler = () => { + va.track('Stop'); + }; + const startHandler = async () => { + saveNewData(); va.track('Start', { model: model.id, agent: selectedAgent.id, @@ -242,19 +187,8 @@ export const AgentView: FC = () => { }); }; - const stopHandler = () => { - // refresh message blocks - const blocks = getMessageBlocks(messages, false); - setMessageBlocks(blocks); - - setExecuting(false); - agent?.stop(); - - va.track('Stop'); - }; - const clearHandler = () => { - setMessages([]); + reset(); selectExecution(undefined); setAgentStatus({ type: 'ready' }); @@ -436,8 +370,18 @@ export const AgentView: FC = () => { return []; }; - const { input, handleInputChange, handleSubmit, agentMessages } = useAgent({ + const { + input, + agentMessages, + isRunning, + handleInputChange, + handleSubmit, + handleCancel, + reset, + } = useAgent({ api: '/api/agent', + onSubmit: startHandler, + onCancel: stopHandler, }); return ( @@ -446,21 +390,8 @@ export const AgentView: FC = () => {

for development use only

- {agentMessages.map((m) => ( -
- {m.content} -
- ))} -
- -
- {messageBlocks.length === 0 ? ( + {agentMessages.length === 0 ? ( <> { ) : (
- {messageBlocks.map((block, index) => - currentAgentId() === 'babydeeragi' || - currentAgentId() === 'babyelfagi' ? ( - - ) : ( - - ), - )} - {isExecuting && ( + {agentMessages.map((m) => ( +
+ {m.content} +
+ ))} + {isRunning && ( )}
{ />
)} - 0} - agent={selectedAgent.id as AgentType} + 0} + type={selectedAgent.id} evaluation={currentEvaluation()} />
diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index 149b5775..cccb158a 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -1,7 +1,7 @@ import { AgentMessage } from '@/types'; import { parseMessage } from '@/utils/message'; import { i18n } from 'next-i18next'; -import { useRef, useState } from 'react'; +import { useRef, useState, useEffect, useCallback } from 'react'; export type UseAgentOptions = { api?: string; @@ -9,16 +9,26 @@ export type UseAgentOptions = { onResponse?: (event: MessageEvent) => void; onError?: (event: Event | ErrorEvent) => void; onFinish?: () => void; + onSubmit?: () => void; + onCancel?: () => void; }; export type UseAgentHelpers = { agentMessages: AgentMessage[]; - setMessages: React.Dispatch>; - stop: () => void; - handleInputChange: (event: React.ChangeEvent) => void; - handleSubmit: (event: React.FormEvent) => void; + setAgentMessages: React.Dispatch>; + handleCancel: () => void; + handleInputChange: ( + event: React.ChangeEvent, + ) => void; + handleSubmit: ( + event: + | React.FormEvent + | React.KeyboardEvent, + ) => void; input: string; setInput: React.Dispatch>; + isRunning: boolean; + reset: () => void; }; export function useAgent({ @@ -27,97 +37,143 @@ export function useAgent({ onResponse, onError, onFinish, + onSubmit, + onCancel, }: UseAgentOptions = {}): UseAgentHelpers { - const abortControllerRef = useRef( - new AbortController(), - ); - const [agentMessages, setMessages] = useState([]); + const abortControllerRef = useRef(null); + const [agentMessages, setAgentMessages] = useState([]); const [input, setInput] = useState(''); const [language] = useState(i18n?.language); + const [isRunning, setIsRunning] = useState(false); // Handle input change - const handleInputChange = (event: React.ChangeEvent) => { + const handleInputChange = ( + event: React.ChangeEvent, + ) => { setInput(event.target.value); }; - // Handle form submission - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - - // Create a new AbortController instance for each request - abortControllerRef.current = new AbortController(); + // Function to send the request + const sendRequest = async (abortController: AbortController) => { + const response = await fetch(api, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id, input, language }), + signal: abortController.signal, // Add the abort signal + }); + + const reader = response.body?.getReader(); + + if (reader) { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + break; + } - try { - // Send POST request with input and id - const response = await fetch(api, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ id, input, language }), - signal: abortControllerRef.current.signal, // Add the abort signal - }); - - const reader = response.body?.getReader(); - - if (reader) { - while (true) { - const { done, value } = await reader.read(); - - if (done) { - break; + if (value) { + const message = new TextDecoder().decode(value); + const agentMessages: AgentMessage[] = message + .trim() + .split('\n') + .map((m) => parseMessage(m)); + + setAgentMessages((prevMessages) => [ + ...prevMessages, + ...agentMessages, + ]); + + // Call onResponse with the new message + if (onResponse) { + onResponse(new MessageEvent('message', { data: message })); } + } - if (value) { - const message = new TextDecoder().decode(value); - const agentMessages: AgentMessage[] = message - .trim() - .split('\n') - .map((m) => parseMessage(m)); - console.log(agentMessages); + if (abortController.signal.aborted) { + reader.cancel(); + break; + } + } - setMessages((prevMessages) => [...prevMessages, ...agentMessages]); + // Call onFinish when the stream is finished + if (onFinish) { + onFinish(); + } + } + }; - // Call onResponse with the new message - if (onResponse) { - onResponse(new MessageEvent('message', { data: message })); - } - } + // Handle form submission + const handleSubmit = async ( + event: + | React.FormEvent + | React.KeyboardEvent, + ) => { + event.preventDefault(); - if (abortControllerRef.current === null) { - reader.cancel(); - break; - } - } + // Create a new AbortController instance for each request + const abortController = new AbortController(); + abortControllerRef.current = abortController; + setIsRunning(true); + setInput(''); - // Call onFinish when the stream is finished - if (onFinish) { - onFinish(); - } - } + if (onSubmit) { + onSubmit(); + } + + try { + await sendRequest(abortController); } catch (error) { // Call onError when an error occurs if (onError) { onError(new ErrorEvent('error', { error })); } + } finally { + setIsRunning(false); } }; // Stop function to abort the connection - const stop = () => { + const handleCancel = useCallback(() => { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; } + + setIsRunning(false); + + if (onCancel) { + onCancel(); + } + }, [onCancel]); + + // Reset function to reset the state + const reset = () => { + setAgentMessages([]); + setInput(''); + setIsRunning(false); }; + useEffect(() => { + return () => { + // Abort any ongoing request when the component is unmounted + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + return { agentMessages, - setMessages, - stop, + setAgentMessages, + handleCancel, handleInputChange, handleSubmit, input, setInput, + isRunning, + reset, }; } From fe1db656c73077bcc910fa1c27558fddb40544da Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 7 Aug 2023 15:44:31 +0900 Subject: [PATCH 05/42] Refactor message grouping and rendering in AgentView --- src/agents/base/Executer.ts | 10 ++- src/components/Agent/AgentBlock.tsx | 20 ++++++ src/components/Agent/AgentResult.tsx | 6 +- src/components/Agent/AgentTastStatus.tsx | 4 +- src/components/Agent/AgentView.tsx | 18 ++++-- src/components/Agent/LabelBlock.tsx | 39 ++++++++++++ src/components/Agent/Markdown.tsx | 13 ++++ src/components/Agent/TaskBlock.tsx | 77 ++++++++++++++++++++++++ src/types/index.ts | 13 +++- src/utils/message.ts | 66 +++++++++++++++++++- 10 files changed, 251 insertions(+), 15 deletions(-) create mode 100644 src/components/Agent/AgentBlock.tsx create mode 100644 src/components/Agent/LabelBlock.tsx create mode 100644 src/components/Agent/Markdown.tsx create mode 100644 src/components/Agent/TaskBlock.tsx diff --git a/src/agents/base/Executer.ts b/src/agents/base/Executer.ts index ca65bd50..bd8b484a 100644 --- a/src/agents/base/Executer.ts +++ b/src/agents/base/Executer.ts @@ -47,9 +47,14 @@ export class Executer { for (let i = 0; i < 3; i++) { const task = new Promise((resolve) => { setTimeout(async () => { + const id = `task + ${i}`; await this.handleMessage({ content: `Test message ${i}`, - type: 'test', + title: `Task description ${i}`, + type: 'task', + style: 'task', + taskId: `${i}`, + id, }); resolve(); }, 1000 * i); @@ -73,7 +78,8 @@ export class Executer { const msg = { ...message, id: message.id || uuidv4(), + status: message.status || 'complete', }; - await this.handlers.handleMessage(message); + await this.handlers.handleMessage(msg); } } diff --git a/src/components/Agent/AgentBlock.tsx b/src/components/Agent/AgentBlock.tsx new file mode 100644 index 00000000..ec9a2df6 --- /dev/null +++ b/src/components/Agent/AgentBlock.tsx @@ -0,0 +1,20 @@ +import { Block } from '@/types'; +import { FC } from 'react'; +import { LabelBlock } from './LabelBlock'; +import { TaskBlock } from './TaskBlock'; + +export interface AgentBlockProps { + block: Block; +} + +export const AgentBlock: FC = ({ block }) => { + return ( +
+ {block.style === 'label' ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/src/components/Agent/AgentResult.tsx b/src/components/Agent/AgentResult.tsx index 3136e82d..281db65a 100644 --- a/src/components/Agent/AgentResult.tsx +++ b/src/components/Agent/AgentResult.tsx @@ -6,7 +6,7 @@ import React from 'react'; export interface AgentResultProps { children: React.ReactNode; title: string; - dependencies?: number[]; + dependencies?: number[] | string; isOpen?: boolean; } @@ -17,6 +17,8 @@ export const AgentResult: FC = ({ isOpen = true, }) => { const [open, setOpen] = React.useState(false); + const dependenciesString = + typeof dependencies === 'string' ? dependencies : dependencies?.join(', '); useEffect(() => { setOpen(isOpen); @@ -37,7 +39,7 @@ export const AgentResult: FC = ({ {dependencies && dependencies.length > 0 && (
-
{dependencies.join(', ')}
+
{dependenciesString}
)} diff --git a/src/components/Agent/AgentTastStatus.tsx b/src/components/Agent/AgentTastStatus.tsx index 95ed7889..c3cc82cb 100644 --- a/src/components/Agent/AgentTastStatus.tsx +++ b/src/components/Agent/AgentTastStatus.tsx @@ -1,8 +1,8 @@ -import { MessageBlock } from '@/types'; +import { Block, MessageBlock } from '@/types'; import { CheckCircledIcon, CircleIcon } from '@radix-ui/react-icons'; export interface AgentTaskStatusProps { - block: MessageBlock; + block: MessageBlock | Block; } export const AgentTaskStatus: React.FC = ({ block }) => { diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index dc14b978..6553b04c 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -8,6 +8,7 @@ import { MessageBlock, SelectItem, UserSettings, + Block, } from '@/types'; import { AgentInput } from './AgentInput'; import AgentMessage from './AgentMessage'; @@ -18,6 +19,7 @@ import { getExportText, getMessageBlocks, loadingAgentMessage, + groupMessages, } from '../../utils/message'; import { BabyAGI } from '@/agents/babyagi'; import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; @@ -30,12 +32,11 @@ import { translate } from '../../utils/translate'; import axios from 'axios'; import { taskCompletedNotification } from '@/utils/notification'; import { useTranslation } from 'next-i18next'; -import { AgentMessageBlock } from './AgentMessageBlock'; -import { AgentTask } from './AgentTask'; import { IntroGuide } from './IntroGuide'; import { BabyElfAGI } from '@/agents/babyelfagi/executer'; import { SkillsList } from './SkillList'; import { useAgent } from '@/hooks/useAgent'; +import { AgentBlock } from './AgentBlock'; export const AgentView: FC = () => { const [model, setModel] = useState(MODELS[1]); @@ -384,6 +385,13 @@ export const AgentView: FC = () => { onCancel: stopHandler, }); + const [agentBlocks, setAgentBlocks] = useState([]); + + useEffect(() => { + const newGroupedMessages = groupMessages(agentMessages); + setAgentBlocks(newGroupedMessages); + }, [agentMessages]); + return (
@@ -422,10 +430,8 @@ export const AgentView: FC = () => { ) : (
- {agentMessages.map((m) => ( -
- {m.content} -
+ {agentBlocks.map((block, index) => ( + ))} {isRunning && ( diff --git a/src/components/Agent/LabelBlock.tsx b/src/components/Agent/LabelBlock.tsx new file mode 100644 index 00000000..6320aa1b --- /dev/null +++ b/src/components/Agent/LabelBlock.tsx @@ -0,0 +1,39 @@ +import { Block } from '@/types'; +import { getEmoji, getTitle } from '@/utils/message'; +import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; +import remarkGfm from 'remark-gfm'; + +export interface LabelBlockProps { + block: Block; +} + +export const LabelBlock: React.FC = ({ block }) => { + const { icon, type, title, content } = block.messages[0]; + const emoji = icon || getEmoji(type); + const blockTitle = title || getTitle(type); + + const renderEmoji = () => ( +
+ {emoji} +
+ ); + + const renderContent = () => ( +
+ + {`### ${blockTitle}\n${content}`} + +
+ ); + + return ( +
+
+
+ {renderEmoji()} + {renderContent()} +
+
+
+ ); +}; diff --git a/src/components/Agent/Markdown.tsx b/src/components/Agent/Markdown.tsx new file mode 100644 index 00000000..f7fe24d6 --- /dev/null +++ b/src/components/Agent/Markdown.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import remarkGfm from 'remark-gfm'; +import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; + +interface MarkdownProps { + content: string; +} + +const Markdown: React.FC = ({ content }) => { + return {content}; +}; + +export default Markdown; diff --git a/src/components/Agent/TaskBlock.tsx b/src/components/Agent/TaskBlock.tsx new file mode 100644 index 00000000..d5495452 --- /dev/null +++ b/src/components/Agent/TaskBlock.tsx @@ -0,0 +1,77 @@ +import { Block, AgentMessage } from '@/types'; +import { FC } from 'react'; +import { AgentResult } from './AgentResult'; +import { AgentTaskStatus } from './AgentTastStatus'; +import { getEmoji } from '@/utils/message'; +import Markdown from './Markdown'; +import { AgentCollapsible } from './AgentCollapsible'; + +export interface AgentTaskProps { + block: Block; +} + +const renderIcon = (message: AgentMessage, block: Block) => { + return block.status === 'complete' + ? '✅' + : message.icon || getEmoji(message.type); +}; + +const renderContent = (message: AgentMessage, lastContent: string) => { + return message.type === 'log' ? ( + +
+ +
+
+ ) : ( +
+ +
+ ); +}; + +export const TaskBlock: FC = ({ block }) => { + const message = block.messages[0]; + const icon = message.icon || getEmoji(message.type); + const title = message.taskId + ? `${message.taskId}. ${message.title}` + : message.title; + const dependentTaskIds = message?.options?.dependentTaskIds ?? ''; + const lastContent = block.messages[block.messages.length - 1].content; + + return ( +
+
+
+
+ {icon} +
+
+ {title} +
+ +
+ {block.messages.length > 0 && ( + + {block.messages.map((message, index) => ( +
+
+
+ {renderIcon(message, block)} +
+
+ {renderContent(message, lastContent)} +
+
+
+ ))} +
+ )} +
+
+ ); +}; diff --git a/src/types/index.ts b/src/types/index.ts index 377e97e2..18a0e5d5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -151,8 +151,17 @@ export type AgentMessage = { id?: string; content: string; type?: string; + title?: string; + icon?: string; taskId?: string; - style?: 'default' | 'log'; + style?: 'label' | 'task' | 'log'; + status?: 'complete' | 'incomplete' | 'running'; + options?: { [key: string]: string }; +}; + +export type Block = { + id?: string; + messages: AgentMessage[]; status?: 'complete' | 'incomplete' | 'running'; - options?: { string: string }; + style?: 'label' | 'task'; }; diff --git a/src/utils/message.ts b/src/utils/message.ts index be23b5dc..74827bce 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -6,6 +6,7 @@ import { MessageBlock, MessageType, ToolType, + Block, } from '@/types'; import { translate } from './translate'; @@ -343,7 +344,70 @@ export const parseMessage = (json: string): AgentMessage => { return { ...message, - style: message.style ?? 'default', + style: message.style ?? 'label', status: message.status ?? 'incomplete', }; }; + +export const getEmoji = (type?: string) => { + switch (type) { + case 'objective': + return '🎯'; + case 'finish': + return '🏁'; + case 'task-list': + return '📝'; + case 'task': + return '📄'; + default: + return '🤖'; + } +}; + +export const getTitle = (type?: string) => { + switch (type) { + case 'objective': + return translate('OBJECTIVE', 'message'); + case 'finish': + return translate('FINISH', 'message'); + case 'task-list': + return translate('TASK_LIST', 'message'); + case 'task': + return translate('TASK', 'message'); + default: + return type?.toUpperCase() || 'Untitled'; + } +}; + +export const groupMessages = (messages: AgentMessage[]) => { + const messageGroups: Block[] = []; + + let block: Block | null = null; + let prevMessage: AgentMessage | null = null; + messages.forEach((message) => { + if (!block || block.id !== message.id) { + block = { + id: message.id, + status: message.status, + messages: [message], + style: message.style === 'task' ? 'task' : 'label', + }; + messageGroups.push(block); + } else if ( + prevMessage && + prevMessage.id === message.id && + prevMessage.type === message.type + ) { + block.messages[block.messages.length - 1].content += message.content; + block.status = message.status; + } else { + block.messages.push(message); + block.status = message.status; + } + prevMessage = block?.messages[block.messages.length - 1]; + }); + + console.log(messageGroups); + + return messageGroups; +}; From 874c3f3732297d754b51f28614682a407213c12c Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Fri, 11 Aug 2023 12:13:47 +0900 Subject: [PATCH 06/42] Refactor code and add new class BabyElfAGI for agent execution --- src/agents/base/Executer.ts | 49 +--- src/agents/base/TestExecuter.ts | 39 +++ src/agents/elf/executer.ts | 166 ++++++++++++ src/agents/elf/registory/index.ts | 2 + src/agents/elf/registory/skillRegistry.ts | 111 ++++++++ src/agents/elf/registory/taskRegistry.ts | 254 ++++++++++++++++++ src/agents/elf/skills/addons/airtableSaver.ts | 38 +++ src/agents/elf/skills/addons/youtubeSearch.ts | 65 +++++ src/agents/elf/skills/index.ts | 12 + src/agents/elf/skills/presets/codeReader.ts | 59 ++++ src/agents/elf/skills/presets/codeReviewer.ts | 42 +++ src/agents/elf/skills/presets/codeWriter.ts | 55 ++++ .../elf/skills/presets/directoryStructure.ts | 26 ++ .../elf/skills/presets/objectiveSaver.ts | 48 ++++ src/agents/elf/skills/presets/skillSaver.ts | 67 +++++ .../elf/skills/presets/textCompletion.ts | 34 +++ src/agents/elf/skills/presets/webLoader.ts | 120 +++++++++ src/agents/elf/skills/presets/webSearch.ts | 37 +++ src/agents/elf/skills/skill.ts | 134 +++++++++ src/components/Agent/LabelBlock.tsx | 5 +- src/hooks/useAgent.ts | 29 +- src/pages/api/agent/index.ts | 4 +- src/utils/elf/objective.ts | 81 ++++++ src/utils/elf/print.ts | 122 +++++++++ 24 files changed, 1549 insertions(+), 50 deletions(-) create mode 100644 src/agents/base/TestExecuter.ts create mode 100644 src/agents/elf/executer.ts create mode 100644 src/agents/elf/registory/index.ts create mode 100644 src/agents/elf/registory/skillRegistry.ts create mode 100644 src/agents/elf/registory/taskRegistry.ts create mode 100644 src/agents/elf/skills/addons/airtableSaver.ts create mode 100644 src/agents/elf/skills/addons/youtubeSearch.ts create mode 100644 src/agents/elf/skills/index.ts create mode 100644 src/agents/elf/skills/presets/codeReader.ts create mode 100644 src/agents/elf/skills/presets/codeReviewer.ts create mode 100644 src/agents/elf/skills/presets/codeWriter.ts create mode 100644 src/agents/elf/skills/presets/directoryStructure.ts create mode 100644 src/agents/elf/skills/presets/objectiveSaver.ts create mode 100644 src/agents/elf/skills/presets/skillSaver.ts create mode 100644 src/agents/elf/skills/presets/textCompletion.ts create mode 100644 src/agents/elf/skills/presets/webLoader.ts create mode 100644 src/agents/elf/skills/presets/webSearch.ts create mode 100644 src/agents/elf/skills/skill.ts create mode 100644 src/utils/elf/objective.ts create mode 100644 src/utils/elf/print.ts diff --git a/src/agents/base/Executer.ts b/src/agents/base/Executer.ts index bd8b484a..3186d27e 100644 --- a/src/agents/base/Executer.ts +++ b/src/agents/base/Executer.ts @@ -1,5 +1,5 @@ import { AgentMessage, AgentTask } from '@/types'; -import { v4 as uuidv4 } from 'uuid'; +import { Printer } from '@/utils/elf/print'; export class Executer { objective: string; @@ -9,7 +9,9 @@ export class Executer { handleEnd: () => Promise; }; language: string; + verbose: boolean; + printer: Printer; taskList: AgentTask[] = []; constructor( @@ -20,11 +22,14 @@ export class Executer { handleEnd: () => Promise; }, language: string = 'en', + varbose: boolean = false, ) { this.objective = objective; this.modelName = modelName; this.handlers = handlers; this.language = language; + this.verbose = varbose; + this.printer = new Printer(this.handlers.handleMessage, this.verbose); } async run() { @@ -36,50 +41,14 @@ export class Executer { // prepare() is called before loop() async prepare() { - this.handleMessage({ - content: `objective: ${this.objective}`, - type: 'objective', - }); + this.printer.printObjective(this.objective); } - async loop() { - const tasks = []; - for (let i = 0; i < 3; i++) { - const task = new Promise((resolve) => { - setTimeout(async () => { - const id = `task + ${i}`; - await this.handleMessage({ - content: `Test message ${i}`, - title: `Task description ${i}`, - type: 'task', - style: 'task', - taskId: `${i}`, - id, - }); - resolve(); - }, 1000 * i); - }); - tasks.push(task); - } - await Promise.all(tasks); - } + async loop() {} async finishup() { // Objective completed - this.handlers.handleMessage({ - content: 'Objective completed', - type: 'finish', - }); + this.printer.printAllTaskCompleted(); this.handlers.handleEnd(); } - - // handleMessage() is called by the agent to send a message to the frontend - async handleMessage(message: AgentMessage) { - const msg = { - ...message, - id: message.id || uuidv4(), - status: message.status || 'complete', - }; - await this.handlers.handleMessage(msg); - } } diff --git a/src/agents/base/TestExecuter.ts b/src/agents/base/TestExecuter.ts new file mode 100644 index 00000000..671ddbf4 --- /dev/null +++ b/src/agents/base/TestExecuter.ts @@ -0,0 +1,39 @@ +import { Executer } from './Executer'; +import { AgentMessage } from '@/types/index'; + +export class TestExecuter extends Executer { + constructor( + objective: string, + modelName: string, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, + language: string = 'en', + ) { + super(objective, modelName, handlers, language); + } + + // Override any methods if needed + async loop(): Promise { + const tasks = []; + for (let i = 0; i < 3; i++) { + const task = new Promise((resolve) => { + setTimeout(async () => { + const id = `task + ${i}`; + await this.handleMessage({ + content: `Test message ${i}`, + title: `Task description ${i}`, + type: 'task', + style: 'task', + taskId: `${i}`, + id, + }); + resolve(); + }, 1000 * i); + }); + tasks.push(task); + } + await Promise.all(tasks); + } +} diff --git a/src/agents/elf/executer.ts b/src/agents/elf/executer.ts new file mode 100644 index 00000000..2be36995 --- /dev/null +++ b/src/agents/elf/executer.ts @@ -0,0 +1,166 @@ +import { AgentStatus, AgentMessage, TaskOutputs } from '@/types'; // You need to define these types +import { Executer } from '../base/Executer'; +import { SkillRegistry, TaskRegistry } from './registory'; +import { translate } from '@/utils/translate'; +import { v4 as uuidv4 } from 'uuid'; + +const REFLECTION = false; // If you want to use reflection, set this to true. now support only client side reflection. + +export class BabyElfAGI extends Executer { + skillRegistry: SkillRegistry; + taskRegistry: TaskRegistry; + sessionSummary: string = ''; + + constructor( + objective: string, + modelName: string, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, + language: string = 'en', + verbose: boolean = true, + ) { + super(objective, modelName, handlers, language); + + this.skillRegistry = new SkillRegistry( + this.handlers.handleMessage, + verbose, + this.language, + ); + this.taskRegistry = new TaskRegistry(this.language, this.verbose); + } + + async prepare() { + await super.prepare(); + + const skillDescriptions = this.skillRegistry.getSkillDescriptions(); + const id = uuidv4(); + // Create task list + await this.taskRegistry.createTaskList( + id, + this.objective, + skillDescriptions, + this.modelName, // Culletly using GPT-4 + this.handlers.handleMessage, + ); + this.printer.printTaskList(this.taskRegistry.tasks, id); + } + + // async loop() { + // // Initialize task outputs + // let taskOutputs: TaskOutputs = {}; + // for (let task of this.taskRegistry.tasks) { + // taskOutputs[task.id] = { completed: false, output: undefined }; + // } + + // // Loop until all tasks are completed + // while (!Object.values(taskOutputs).every((task) => task.completed)) { + // if (!this.isRunningRef.current) { + // break; + // } + // this.handlers.handleMessage({ type: 'preparing' }); + + // // Get the tasks that are ready to be executed + // const tasks = this.taskRegistry.getTasks(); + + // // Update taskOutputs to include new tasks + // tasks.forEach((task) => { + // if (!(task.id in taskOutputs)) { + // taskOutputs[task.id] = { completed: false, output: undefined }; + // } + // }); + + // // Filter taskoutput not completed + // const incompleteTasks = tasks.filter((task) => { + // return !taskOutputs[task.id].completed; + // }); + + // // Filter tasks that have all their dependencies completed + // const MaxExecutableTasks = 5; + // const executableTasks = incompleteTasks + // .filter((task) => { + // if (!task.dependentTaskIds) return true; + // return task.dependentTaskIds.every((id) => { + // return taskOutputs[id]?.completed === true; + // }); + // }) + // .slice(0, MaxExecutableTasks); + + // // Execute all executable tasks in parallel + // const taskPromises = executableTasks.map(async (task, i) => { + // // Update task status to running + // this.taskRegistry.updateTasks({ + // id: task.id, + // updates: { status: 'running' }, + // }); + // this.printer.printTaskExecute(task); + // this.currentStatusCallback(); + // const output = await this.taskRegistry.executeTask( + // i, + // task, + // taskOutputs, + // this.objective, + // this.skillRegistry, + // ); + + // taskOutputs[task.id] = { completed: true, output: output }; + // this.taskRegistry.updateTasks({ + // id: task.id, + // updates: { status: 'complete', result: output }, + // }); + // this.printer.printTaskOutput(output, task); + // this.sessionSummary += `# ${task.id}: ${task.task}\n${output}\n\n`; + + // // Reflect on the output of the tasks and possibly add new tasks or update existing ones + // if (REFLECTION) { + // const skillDescriptions = this.skillRegistry.getSkillDescriptions(); + // const [newTasks, insertAfterIds, tasksToUpdate] = + // await this.taskRegistry.reflectOnOutput( + // this.objective, + // output, + // skillDescriptions, + // ); + + // // Insert new tasks + // for (let i = 0; i < newTasks.length; i++) { + // const newTask = newTasks[i]; + // const afterId = insertAfterIds[i]; + // this.taskRegistry.addTask(newTask, afterId); + // } + + // // Update existing tasks + // for (const taskToUpdate of tasksToUpdate) { + // this.taskRegistry.updateTasks({ + // id: taskToUpdate.id, + // updates: taskToUpdate, + // }); + // } + // } + // }); + + // // Wait for all tasks to complete + // await Promise.all(taskPromises); + // } + // } + + // async finishup() { + // const tasks = this.taskRegistry.getTasks(); + // const lastTask = tasks[tasks.length - 1]; + // this.handlers.handleMessage({ + // type: 'final-result', + // text: lastTask.result ?? '', + // title: translate('FINAL_TASK_RESULT', 'message'), + // icon: '✍️', + // id: 9999, + // }); + + // this.handlers.handleMessage({ + // type: 'session-summary', + // text: this.sessionSummary, + // id: 9999, + // }); + + // super.finishup(); + // } +} diff --git a/src/agents/elf/registory/index.ts b/src/agents/elf/registory/index.ts new file mode 100644 index 00000000..3d67ffbc --- /dev/null +++ b/src/agents/elf/registory/index.ts @@ -0,0 +1,2 @@ +export * from './skillRegistry'; +export * from './taskRegistry'; diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts new file mode 100644 index 00000000..82123c58 --- /dev/null +++ b/src/agents/elf/registory/skillRegistry.ts @@ -0,0 +1,111 @@ +import { AgentMessage } from '@/types'; +import { + AirtableSaver, + CodeReader, + CodeReviewer, + CodeWriter, + DirectoryStructure, + SkillSaver, + TextCompletion, + WebLoader, + WebSearch, + YoutubeSearch, +} from '../skills'; +import { Skill } from '../skills/skill'; +import { getUserApiKey } from '@/utils/settings'; + +export class SkillRegistry { + skillClasses: (typeof Skill)[]; + skills: Skill[] = []; + apiKeys: { [key: string]: string }; + // for UI + handleMessage: (message: AgentMessage) => Promise; + verbose: boolean; + language: string = 'en'; + + constructor( + handleMessage: (message: AgentMessage) => Promise, + verbose: boolean = false, + language: string = 'en', + ) { + this.skillClasses = SkillRegistry.getSkillClasses(); + this.apiKeys = SkillRegistry.apiKeys; + // + this.handleMessage = handleMessage; + this.verbose = verbose; + this.language = language; + + // Load all skills + for (let SkillClass of this.skillClasses) { + let skill = new SkillClass( + this.apiKeys, + this.handleMessage, + this.verbose, + this.language, + ); + if ( + skill.type === 'dev' ? process.env.NODE_ENV === 'development' : true + ) { + this.skills.push(skill); + } + } + + this.skills.filter((skill) => skill.valid); + + // Print the names and descriptions of all loaded skills + let loadedSkills = this.skills + .map((skill) => { + return `${skill.icon} ${skill.name}: ${skill.descriptionForHuman}`; + }) + .join('\n'); + if (this.verbose) { + console.log(`Loaded skills:\n${loadedSkills}`); + } + } + + static getSkillClasses(): (typeof Skill)[] { + const skills: (typeof Skill)[] = [ + Skill, + // TextCompletion, + // WebSearch, + // AirtableSaver, + // CodeReader, + // CodeWriter, + // SkillSaver, + // DirectoryStructure, + // YoutubeSearch, + // CodeReviewer, + // WebLoader, + ]; + return skills; + } + + static apiKeys = { + openai: getUserApiKey() || process.env.OPENAI_API_KEY || '', + airtable: 'keyXXXXXXXXXXXXXX', // Your Airtable API key here + }; + + getSkill(name: string): Skill { + const skill = this.skills.find((skill) => { + return skill.name === name; + }); + if (!skill) { + throw new Error( + `Skill '${name}' not found. Please make sure the skill is loaded and all required API keys are set.`, + ); + } + return skill; + } + + getAllSkills(): Skill[] { + return this.skills; + } + + getSkillDescriptions(): string { + return this.skills + .map((skill) => { + return `${skill.icon} ${skill.name}: ${skill.descriptionForModel}`; + }) + .join(','); + } +} diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts new file mode 100644 index 00000000..b4ab0bda --- /dev/null +++ b/src/agents/elf/registory/taskRegistry.ts @@ -0,0 +1,254 @@ +import _ from 'lodash'; +import { AgentTask, AgentMessage, TaskOutputs } from '@/types'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { parseTasks } from '@/utils/task'; +import { HumanChatMessage, SystemChatMessage } from 'langchain/schema'; +import { getUserApiKey } from '@/utils/settings'; +import { translate } from '@/utils/translate'; +import { SkillRegistry } from './skillRegistry'; +import { findMostRelevantObjective } from '@/utils/elf/objective'; +import axios from 'axios'; + +export class TaskRegistry { + tasks: AgentTask[]; + verbose: boolean = false; + language: string = 'en'; + + constructor(language = 'en', verbose = false) { + this.tasks = []; + this.verbose = verbose; + this.language = language; + } + + async createTaskList( + id: string, + objective: string, + skillDescriptions: string, + modelName: string = 'gpt-3.5-turbo', + handleMessage: (message: AgentMessage) => Promise, + abortController?: AbortController, + ): Promise { + const relevantObjective = await findMostRelevantObjective(objective); + const exapmleObjective = relevantObjective.objective; + const exampleTaskList = relevantObjective.examples; + const prompt = ` + You are an expert task list creation AI tasked with creating a list of tasks as a JSON array, considering the ultimate objective of your team: ${objective}. + Create a very short task list based on the objective, the final output of the last task will be provided back to the user. Limit tasks types to those that can be completed with the available skills listed below. Task description should be detailed.### + AVAILABLE SKILLS: ${skillDescriptions}.### + RULES: + Do not use skills that are not listed. + Always include one skill. + Do not create files unless specified in the objective. + dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. + Make sure all task IDs are in chronological order.### + Output must be answered in ${this.language}. + EXAMPLE OBJECTIVE=${exapmleObjective} + TASK LIST=${JSON.stringify(exampleTaskList)} + OBJECTIVE=${objective} + TASK LIST=`; + const systemPrompt = 'You are a task creation AI.'; + const systemMessage = new SystemChatMessage(systemPrompt); + const messages = new HumanChatMessage(prompt); + + let result = ''; + const model = new ChatOpenAI( + { + modelName, + temperature: 0, + maxTokens: 1500, + topP: 1, + verbose: this.verbose, + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + const message: AgentMessage = { + id, + title: translate('TASK_LIST', 'message'), + content: token, + icon: '📝', + style: 'log', + }; + handleMessage(message); + }, + }, + ], + }, + { baseOptions: { signal: abortController?.signal } }, + ); + + try { + const response = await model.call([systemMessage, messages]); + result = response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + console.log('Task creation aborted'); + } + console.log(error); + } + + if (result === undefined) { + return; + } + + this.tasks = parseTasks(result); + } + + async executeTask( + i: number, + task: AgentTask, + taskOutputs: TaskOutputs, + objective: string, + skillRegistry: SkillRegistry, + ): Promise { + const skill = skillRegistry.getSkill(task.skill ?? ''); + const dependentTaskOutputs = task.dependentTaskIds + ? task.dependentTaskIds.map((id) => taskOutputs[id].output).join('\n') + : ''; + + if (skill.executionLocation === 'server') { + // Call the API endpoint if the skill needs to be executed on the server side + const response = await axios.post('/api/execute-skill', { + task: JSON.stringify(task), + dependent_task_outputs: dependentTaskOutputs, + objective, + }); + return response.data.taskOutput; + } else { + // Execute the skill on the client side + let taskOutput = await skill.execute( + task, + dependentTaskOutputs, + objective, + ); + return taskOutput; + } + } + + getTasks(): AgentTask[] { + return this.tasks; + } + + getTask(taskId: number): AgentTask | undefined { + return this.tasks.find((task) => task.id === taskId); + } + + addTask(task: AgentTask, afterTaskId: number): void { + let index = this.tasks.findIndex((t) => t.id === afterTaskId); + if (index !== -1) { + this.tasks.splice(index + 1, 0, task); + } else { + this.tasks.push(task); + } + } + + updateTasks(taskUpdate: { id: number; updates: Partial }): void { + let task = this.getTask(taskUpdate.id); + if (task) { + Object.assign(task, taskUpdate.updates); + } + } + + reorderTasks(): void { + this.tasks = _.sortBy(this.tasks, ['priority', 'task_id']); + } + + async reflectOnOutput( + objective: string, + taskOutput: string, + skillDescriptions: string, + modelName: string = 'gpt-3.5-turbo-16k', + ): Promise<[AgentTask[], number[], AgentTask[]]> { + const example = [ + [ + { + id: 3, + task: 'New task 1 description', + skill: 'text_completion', + icon: '🤖', + dependent_task_ids: [], + status: 'complete', + }, + { + id: 4, + task: 'New task 2 description', + skill: 'text_completion', + icon: '🤖', + dependent_task_ids: [], + status: 'incomplete', + }, + ], + [2, 3], + [ + { + id: 5, + task: 'Complete the objective and provide a final report', + skill: 'text_completion', + icon: '🤖', + dependent_task_ids: [1, 2, 3, 4], + status: 'incomplete', + }, + ], + ]; + + const prompt = `You are an expert task manager, review the task output to decide at least one new task to add. + As you add a new task, see if there are any tasks that need to be updated (such as updating dependencies). + Use the current task list as reference. + considering the ultimate objective of your team: ${objective}. + Do not add duplicate tasks to those in the current task list. + Only provide JSON as your response without further comments. + Every new and updated task must include all variables, even they are empty array. + Dependent IDs must be smaller than the ID of the task. + New tasks IDs should be no larger than the last task ID. + Always select at least one skill. + Task IDs should be unique and in chronological order. + Do not change the status of complete tasks. + Only add skills from the AVAILABLE SKILLS, using the exact same spelling. + Provide your array as a JSON array with double quotes. The first object is new tasks to add as a JSON array, the second array lists the ID numbers where the new tasks should be added after (number of ID numbers matches array), The number of elements in the first and second arrays will always be the same. + And the third array provides the tasks that need to be updated. + Make sure to keep dependent_task_ids key, even if an empty array. + OBJECIVE: ${objective}. + AVAILABLE SKILLS: ${skillDescriptions}. + Here is the last task output: ${taskOutput} + Here is the current task list: ${JSON.stringify(this.tasks)} + EXAMPLE OUTPUT FORMAT = ${JSON.stringify(example)} + OUTPUT = `; + + console.log( + '\nReflecting on task output to generate new tasks if necessary...\n', + ); + + const model = new ChatOpenAI({ + openAIApiKey: getUserApiKey(), + modelName, + temperature: 0.7, + maxTokens: 1500, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + }); + + const response = await model.call([ + new SystemChatMessage('You are a task creation AI.'), + new HumanChatMessage(prompt), + ]); + + const result = response.text; + console.log('\n' + result); + + // Check if the returned result has the expected structure + if (typeof result === 'string') { + try { + const taskList = JSON.parse(result); + console.log(taskList); + return [taskList[0], taskList[1], taskList[2]]; + } catch (error) { + console.error(error); + } + } else { + throw new Error('Invalid task list structure in the output'); + } + + return [[], [], []]; + } +} diff --git a/src/agents/elf/skills/addons/airtableSaver.ts b/src/agents/elf/skills/addons/airtableSaver.ts new file mode 100644 index 00000000..5a352ed2 --- /dev/null +++ b/src/agents/elf/skills/addons/airtableSaver.ts @@ -0,0 +1,38 @@ +import Airtable from 'airtable'; +import { Skill, SkillType } from '../skill'; +import { AgentTask } from '@/types'; + +export class AirtableSaver extends Skill { + name = 'airtable_saver'; + descriptionForHuman = 'Saves data to Airtable'; + descriptionForModel = + 'Saves data to Airtable. If objective does not include airtable, this skill dont use anytime.'; + icon = '📦'; + type: SkillType = 'dev'; + + apiKeysRequired = ['airtable']; + + baseId = 'appXXXXXXX'; // Your base ID here + tableName = 'Table 1'; // Your table name here + + async execute( + task: AgentTask, + dependentTaskOutputs: any, + objective: string, + ): Promise { + if (!this.valid) { + return ''; + } + + const airtable = new Airtable({ apiKey: this.apiKeys['airtable'] }); + const base = airtable.base(this.baseId); + const fields = { Notes: dependentTaskOutputs }; // Your fields here + + try { + await base(this.tableName).create([{ fields }]); + return 'Record creation successful'; + } catch (error: any) { + return `Record creation failed: ${error.message}`; + } + } +} diff --git a/src/agents/elf/skills/addons/youtubeSearch.ts b/src/agents/elf/skills/addons/youtubeSearch.ts new file mode 100644 index 00000000..ceb0a196 --- /dev/null +++ b/src/agents/elf/skills/addons/youtubeSearch.ts @@ -0,0 +1,65 @@ +import { AgentTask } from '@/types'; +import { Skill } from '../skill'; +import axios from 'axios'; + +export class YoutubeSearch extends Skill { + name = 'youtube_search'; + descriptionForHuman = 'This skill searches YouTube for videos.'; + descriptionForModel = + 'This skill searches YouTube for videos. Returns a list of links.'; + icon = '📺'; + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + const prompt = `Generate query for YouTube search based on the dependent task outputs and the objective. + Dependent tasks output: ${dependentTaskOutputs} + Objective: ${objective} + `; + const query = await this.generateText(prompt, task); + const searchResults = await this.webSearchTool(`site:youtube.com ${query}`); + const youtubeLinks = this.extractYoutubeLinks(searchResults); + + return '```json\n' + JSON.stringify(youtubeLinks, null, 2) + '\n```'; + } + + webSearchTool = async (query: string) => { + const response = await axios + .post( + '/api/tools/search', + { + query, + }, + { + signal: this.abortController.signal, + }, + ) + .catch((error) => { + if (error.name === 'AbortError') { + console.log('Request aborted', error.message); + } else { + console.log(error.message); + } + }); + + return response?.data.response; + }; + + extractYoutubeLinks = (searchResults: any[]) => { + const youtubeLinks = searchResults + .filter((result) => { + return result?.link.includes('youtube.com/watch?v='); + }) + .map((result) => { + return { + position: result?.position || 0, + title: result?.title || '', + link: result?.link || '', + snippet: result?.snippet || '', + }; + }); + return youtubeLinks; + }; +} diff --git a/src/agents/elf/skills/index.ts b/src/agents/elf/skills/index.ts new file mode 100644 index 00000000..b6154137 --- /dev/null +++ b/src/agents/elf/skills/index.ts @@ -0,0 +1,12 @@ +export * from './skill'; +export * from './presets/textCompletion'; +export * from './presets/webSearch'; +export * from './presets/codeReader'; +export * from './presets/skillSaver'; +export * from './presets/directoryStructure'; +export * from './presets/objectiveSaver'; +export * from './presets/codeWriter'; +export * from './presets/codeReviewer'; +export * from './presets/webLoader'; +export * from './addons/airtableSaver'; +export * from './addons/youtubeSearch'; diff --git a/src/agents/elf/skills/presets/codeReader.ts b/src/agents/elf/skills/presets/codeReader.ts new file mode 100644 index 00000000..78de89a3 --- /dev/null +++ b/src/agents/elf/skills/presets/codeReader.ts @@ -0,0 +1,59 @@ +import { AgentTask } from '@/types'; +import { Skill, SkillType } from '../skill'; + +export class CodeReader extends Skill { + name = 'code_reader'; + descriptionForHuman = + "A skill that finds a file's location in its own program's directory and returns its contents."; + descriptionForModel = + "A skill that finds a file's location in its own program's directory and returns its contents."; + icon = '📖'; + type: SkillType = 'dev'; + apiKeysRequired = ['openai']; + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + if (!this.valid) return ''; + + const dirStructure = await this.getDirectoryStructure(); + + const prompt = `Find a specific file in a directory and return only the file path, based on the task description below. ### + First, find the file name in the task description below. Then, find the file path in the directory structure below. Finally, return the file path.### + Don't use only paths and don't put them in quotes. + The directory structure of src is as follows: \n${JSON.stringify( + dirStructure, + )} + Your task description: ${task.task}\n### + RESPONSE:`; + let filePath = await this.generateText(prompt, task, { + temperature: 0.2, + modelName: 'gpt-4', + }); + + console.log(`AI suggested file path: ${filePath}`); + + try { + const response = await fetch( + `/api/local/read-file?filename=${encodeURIComponent(filePath)}`, + { + method: 'GET', + }, + ); + if (!response.ok) { + throw new Error('Failed to read file'); + } + const fileContent = await response.json(); + console.log(`File content:\n${JSON.stringify(fileContent)}`); + return JSON.stringify(fileContent); + } catch (error) { + console.error( + "File not found. Please check the AI's suggested file path.", + error, + ); + return "File not found. Please check the AI's suggested file path."; + } + } +} diff --git a/src/agents/elf/skills/presets/codeReviewer.ts b/src/agents/elf/skills/presets/codeReviewer.ts new file mode 100644 index 00000000..aae59138 --- /dev/null +++ b/src/agents/elf/skills/presets/codeReviewer.ts @@ -0,0 +1,42 @@ +import { AgentTask } from '@/types'; +import { Skill, SkillType } from '../skill'; + +// Define constants +const DESCRIPTION = + 'A skill that reviews code and provides comments to improve its quality.'; +const ICON = '👨‍💻'; +const MODEL_NAME = 'gpt-4'; + +export class CodeReviewer extends Skill { + readonly name = 'code_reviewer'; + readonly descriptionForHuman = DESCRIPTION; + readonly descriptionForModel = DESCRIPTION; + readonly icon = ICON; + readonly type: SkillType = 'dev'; + readonly apiKeysRequired = ['openai']; + + generatePrompt(code: string): string { + return `Code review comments for the following code:\n\n${code}\n\nComments:`; + } + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + ): Promise { + if (!this.valid) + throw new Error( + 'Skill is not valid. Please check the required API keys.', + ); + + const prompt = this.generatePrompt(dependentTaskOutputs); + + try { + return this.generateText(prompt, task, { modelName: MODEL_NAME }); + } catch (error) { + console.error('Failed to generate text:', error); + throw new Error( + 'Failed to generate text. Please check your input and try again.', + ); + } + } +} diff --git a/src/agents/elf/skills/presets/codeWriter.ts b/src/agents/elf/skills/presets/codeWriter.ts new file mode 100644 index 00000000..f6ea5e62 --- /dev/null +++ b/src/agents/elf/skills/presets/codeWriter.ts @@ -0,0 +1,55 @@ +import { AgentTask } from '@/types'; +import { Skill, SkillType } from '../skill'; + +/** + * This skill uses the GPT-4 model to write code + * It takes in a task, dependent task outputs, and an objective, and returns a string + */ + +// Constants for the GPT-4 model +const MODEL_NAME = 'gpt-4'; +const TEMPERATURE = 0.2; +const MAX_TOKENS = 800; + +export class CodeWriter extends Skill { + readonly name = 'code_writer'; + readonly descriptionForHuman = + "A tool that uses OpenAI's text completion API to write code. This tool does not save the code."; + readonly descriptionForModel = + "A tool that uses OpenAI's text completion API to write code. This tool does not save the code. This skill must be a dependent task on the code_reader skill."; + readonly icon = '🖊️'; + readonly type: SkillType = 'dev'; + readonly apiKeysRequired = ['openai']; + + // The execute function takes in a task, dependent task outputs, and an objective, and returns a string + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + if (!this.valid) { + throw new Error('Invalid state'); + } + + const prompt = ` + You are a genius AI programmer. + Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. + Dependent tasks output include reference code. + Your objective: ${objective}. + Your task: ${task} + Dependent tasks output: ${dependentTaskOutputs} + RESPONSE: + `; + + try { + return await this.generateText(prompt, task, { + modelName: MODEL_NAME, + temperature: TEMPERATURE, + maxTokens: MAX_TOKENS, + }); + } catch (error) { + console.error('Error generating text:', error); + throw error; + } + } +} diff --git a/src/agents/elf/skills/presets/directoryStructure.ts b/src/agents/elf/skills/presets/directoryStructure.ts new file mode 100644 index 00000000..229a1fa0 --- /dev/null +++ b/src/agents/elf/skills/presets/directoryStructure.ts @@ -0,0 +1,26 @@ +import { AgentTask } from '@/types'; +import { Skill, SkillType } from '../skill'; + +export class DirectoryStructure extends Skill { + name = 'directory_structure'; + descriptionForHuman = + "A skill that outputs the directory structure of the 'src' folder."; + descriptionForModel = + "A skill that outputs the directory structure of the 'src' folder."; + icon = '📂'; + type: SkillType = 'dev'; + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + const response = await fetch('/api/local/directory-structure', { + method: 'GET', + }); + if (!response.ok) { + throw new Error('Failed to get directory structure'); + } + return await response.json(); + } +} diff --git a/src/agents/elf/skills/presets/objectiveSaver.ts b/src/agents/elf/skills/presets/objectiveSaver.ts new file mode 100644 index 00000000..b75ba66a --- /dev/null +++ b/src/agents/elf/skills/presets/objectiveSaver.ts @@ -0,0 +1,48 @@ +import { AgentTask } from '@/types'; +import { Skill, SkillType } from '../skill'; + +export class ObjectiveSaver extends Skill { + name = 'objective_saver'; + descriptionForHuman = + 'A skill that saves a new example_objective based on the concepts from skillSaver.ts'; + descriptionForModel = + 'A skill that saves a new example_objective based on the concepts from skillSaver.ts '; + icon = '💽'; + type: SkillType = 'dev'; + apiKeysRequired = ['openai']; + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + if (!this.valid) return ''; + + const code = dependentTaskOutputs; + const prompt = `Come up with a file name (eg. 'research_shoes.json') for the following objective:${code}\n###\nFILE_NAME:`; + const filename = await this.generateText(prompt, task, { + temperature: 0.2, + }); + const examplesPath = `data/example_objectives/`; + + try { + const response = await fetch('/api/local/write-file', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + filename: `${examplesPath}/${filename}`, + content: `${code}`, + }), + }); + if (!response.ok) { + throw new Error('Failed to save file'); + } + return `Code saved successfully: ${filename}`; + } catch (error) { + console.error('Error saving code.', error); + return 'Error saving code.'; + } + } +} diff --git a/src/agents/elf/skills/presets/skillSaver.ts b/src/agents/elf/skills/presets/skillSaver.ts new file mode 100644 index 00000000..21d39c4d --- /dev/null +++ b/src/agents/elf/skills/presets/skillSaver.ts @@ -0,0 +1,67 @@ +import { AgentTask } from '@/types'; +import { Skill, SkillType } from '../skill'; + +export class SkillSaver extends Skill { + name = 'skill_saver'; + descriptionForHuman = + 'A skill that saves code written in a previous step into a file within the skills folder. Not for writing code.'; + descriptionForModel = + 'A skill that saves code written in a previous step into a file within the skills folder. Not for writing code. If objective does not include save the skill, this skill dont use anytime.'; + icon = '💾'; + type: SkillType = 'dev'; + apiKeysRequired = ['openai']; + + async execute( + task: AgentTask, + dependentTaskOutputs: any, + objective: string, + ): Promise { + if (!this.valid) return ''; + + const params = { + temperature: 0.2, + maxTokens: 800, + }; + const codePrompt = `Extract the code and only the code from the dependent task output. + If it is a markdown code block, extract only the code inside. + DEPENDENT TASK OUTPUT: ${dependentTaskOutputs} + CODE:`; + const code = await this.generateText(codePrompt, task, params); + + const filePrompt = `Come up with a file name (eg. 'getWeather.ts') for the following skill. + If there is a file name to save in the task, please use it. (eg. 'getWeather.ts') + TASK: ${task.task} + CODE: ${code} + FILE_NAME:`; + const filename = await this.generateText(filePrompt, task, params); + let skillsPath = `src/agents/babyelfagi/skills/addons`; + + const dirStructure: string[] = await this.getDirectoryStructure(); + const skillPaths = dirStructure.filter((path) => path.includes(filename)); + if (skillPaths.length > 0) { + skillsPath = skillPaths[0]; + } else { + skillsPath += `/${filename}`; + } + + try { + const response = await fetch('/api/local/write-file', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + filename: skillsPath, + content: code, + }), + }); + if (!response.ok) { + throw new Error('Failed to save file'); + } + return `Code saved successfully: ${filename}`; + } catch (error) { + console.error('Error saving code.', error); + return 'Error saving code.'; + } + } +} diff --git a/src/agents/elf/skills/presets/textCompletion.ts b/src/agents/elf/skills/presets/textCompletion.ts new file mode 100644 index 00000000..fb9e1689 --- /dev/null +++ b/src/agents/elf/skills/presets/textCompletion.ts @@ -0,0 +1,34 @@ +import { AgentTask } from '@/types'; +import { Skill } from '../skill'; + +export class TextCompletion extends Skill { + name = 'text_completion'; + descriptionForHuman = + "A tool that uses OpenAI's text completion API to generate, summarize, and/or analyze text."; + descriptionForModel = + "A tool that uses OpenAI's text completion API to generate, summarize, and/or analyze text."; + icon = '🤖'; + apiKeysRequired = ['openai']; + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + if (!this.valid) return ''; + + const prompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. \n### + Output must be answered in ${this.language}. + Your objective: ${objective}. \n### + Your task: ${task} \n### + Dependent tasks output: ${dependentTaskOutputs} ### + Your task: ${task}\n### + RESPONSE:`; + + return this.generateText(prompt, task, { + temperature: 0.2, + maxTokens: 800, + modelName: 'gpt-3.5-turbo-16k', + }); + } +} diff --git a/src/agents/elf/skills/presets/webLoader.ts b/src/agents/elf/skills/presets/webLoader.ts new file mode 100644 index 00000000..7913025b --- /dev/null +++ b/src/agents/elf/skills/presets/webLoader.ts @@ -0,0 +1,120 @@ +import { AgentTask } from '@/types'; +import { Skill } from '../skill'; +import axios from 'axios'; +import { largeTextExtract } from '@/agents/babydeeragi/tools/largeTextExtract'; +import { translate } from '@/utils/translate'; + +export class WebLoader extends Skill { + name = 'web_loader'; + descriptionForHuman = + 'This skill extracts URLs from the task and returns the contents of the web pages of those URLs.'; + descriptionForModel = + 'This skill extracts URLs from the task and returns the contents of the web pages of those URLs.'; + icon = '🌐'; + + async execute(task: AgentTask, objective: string): Promise { + if (typeof objective !== 'string') { + throw new Error('Invalid inputs'); + } + + let statusMessage = '- Extracting URLs from the task.\n'; + const callback = (message: string) => { + statusMessage += message; + this.messageCallback({ + type: 'search-logs', + text: '```markdown\n' + statusMessage + '\n```', + title: `🔎 ${translate('SEARCH_LOGS', 'message')}`, + id: task.id, + icon: '🌐', + open: false, + }); + }; + + const urlString = await this.extractUrlsFromTask(task, callback); + const urls = urlString.split(',').map((url) => url.trim()); + const contents = await this.fetchContentsFromUrls(urls, callback); + const info = await this.extractInfoFromContents( + contents, + objective, + task, + callback, + ); + + return info.join('\n\n'); + } + + private async extractUrlsFromTask( + task: AgentTask, + callback: (message: string) => void, + ): Promise { + const prompt = `- Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; + const urlString = await this.generateText(prompt, task, undefined, true); + callback(` - URLs: ${urlString}\n`); + return urlString; + } + + private async fetchContentsFromUrls( + urls: string[], + callback: (message: string) => void, + ): Promise<{ url: string; content: string }[]> { + const MAX_URLS = 10; + return await Promise.all( + urls.slice(0, MAX_URLS).map(async (url) => { + callback(`- Reading: ${url} ...\n`); + const content = await this.webScrapeTool(url); + if (content.length === 0) { + callback(` - Content: No content found.\n`); + return { url, content: '' }; + } + callback( + ` - Content: ${content.slice(0, 100)} ...\n - Content length: ${ + content.length + }\n`, + ); + return { url, content }; + }), + ); + } + + private async extractInfoFromContents( + contents: { url: string; content: string }[], + objective: string, + task: AgentTask, + callback: (message: string) => void, + ): Promise { + callback(`- Extracting relevant information from the web pages.\n`); + return await Promise.all( + contents.map(async (item) => { + return ( + `URL: ${item.url}\n\n` + + (await largeTextExtract( + objective, + item.content, + task, + this.isRunningRef, + callback, + this.abortController.signal, + )) + ); + }), + ); + } + + private async webScrapeTool(url: string): Promise { + try { + const response = await axios.post( + '/api/tools/scrape', + { url }, + { signal: this.abortController.signal }, + ); + return response?.data?.response; + } catch (error: any) { + if (error.name === 'AbortError') { + console.error('Request aborted', error.message); + } else { + console.error(error.message); + } + return ''; + } + } +} diff --git a/src/agents/elf/skills/presets/webSearch.ts b/src/agents/elf/skills/presets/webSearch.ts new file mode 100644 index 00000000..f0ec2d77 --- /dev/null +++ b/src/agents/elf/skills/presets/webSearch.ts @@ -0,0 +1,37 @@ +import { AgentTask } from '@/types'; +import { webBrowsing } from '@/agents/babydeeragi/tools/webBrowsing'; +import { Skill } from '../skill'; + +// This skill is Specialized for web browsing +// using webBrowsing tool in babydeeragi +export class WebSearch extends Skill { + name = 'web_search'; + descriptionForHuman = 'A tool that performs web searches.'; + descriptionForModel = 'A tool that performs web searches.'; + icon = '🔎'; + apiKeysRequired = []; + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + if (!this.valid) return ''; + + const taskOutput = + (await webBrowsing( + objective, + task, + dependentTaskOutputs, + this.messageCallback, + undefined, + this.isRunningRef, + this.verbose, + undefined, + this.language, + this.abortController.signal, + )) ?? ''; + + return taskOutput; + } +} diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts new file mode 100644 index 00000000..c3e712c8 --- /dev/null +++ b/src/agents/elf/skills/skill.ts @@ -0,0 +1,134 @@ +import { AgentTask, LLMParams, AgentMessage } from '@/types'; +import { setupMessage } from '@/utils/message'; +import { getUserApiKey } from '@/utils/settings'; +import axios from 'axios'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { HumanChatMessage } from 'langchain/schema'; + +export type SkillType = 'normal' | 'dev'; +export type SkillExecutionLocation = 'client' | 'server'; + +export class Skill { + name: string = 'base_kill'; + descriptionForHuman: string = 'This is the base skill.'; + descriptionForModel: string = 'This is the base skill.'; + icon: string = '🛠️'; + type: SkillType = 'normal'; // If available only in the local development environment, be sure to use dev type. + apiKeysRequired: Array> = []; + valid: boolean; + apiKeys: { [key: string]: string }; + executionLocation: SkillExecutionLocation = 'client'; // 'client' or 'server' + // for UI + handleMessage: (message: AgentMessage) => void; + verbose: boolean; + language: string = 'en'; + + // This index signature allows dynamic assignment of properties + [key: string]: any; + + constructor( + apiKeys: { [key: string]: string }, + handleMessage: (message: AgentMessage) => Promise, + verbose: boolean = false, + language: string = 'en', + ) { + this.apiKeys = apiKeys; + this.handleMessage = handleMessage; + this.verbose = verbose; + this.language = language; + + const missingKeys = this.checkRequiredKeys(apiKeys); + if (missingKeys.length > 0) { + console.log(`Missing API keys for ${this.name}: ${missingKeys}`); + this.valid = false; + } else { + this.valid = true; + } + for (const key of this.apiKeysRequired) { + if (Array.isArray(key)) { + for (const subkey of key) { + if (subkey in apiKeys) { + this[`${subkey}_apiKey`] = apiKeys[subkey]; + } + } + } else if (key in apiKeys) { + this[`${key}_apiKey`] = apiKeys[key]; + } + } + + this.valid = + this.type === 'dev' ? process.env.NODE_ENV === 'development' : true; + } + + checkRequiredKeys(apiKeys: { + [key: string]: string; + }): Array> { + const missingKeys: Array> = []; + for (const key of this.apiKeysRequired) { + if (Array.isArray(key)) { + if (!key.some((k) => k in apiKeys)) { + missingKeys.push(key); + } + } else if (!(key in apiKeys)) { + missingKeys.push(key); + } + } + return missingKeys; + } + + async execute( + task: AgentTask, + dependentTaskOutputs: string, + objective: string, + ): Promise { + // This method should be overridden by subclasses + throw new Error("Method 'execute' must be implemented"); + } + + async generateText( + prompt: string, + task: AgentTask, + params?: LLMParams, + ignoreCallback: boolean = false, + ): Promise { + let chunk = ''; + const messageCallback = ignoreCallback ? () => {} : this.messageCallback; + const llm = new ChatOpenAI( + { + openAIApiKey: this.apiKeys.openai, + modelName: params?.modelName ?? 'gpt-3.5-turbo', + temperature: params?.temperature ?? 0.7, + maxTokens: params?.maxTokens ?? 1500, + topP: params?.topP ?? 1, + frequencyPenalty: params?.frequencyPenalty ?? 0, + presencePenalty: params?.presencePenalty ?? 0, + streaming: params?.streaming === undefined ? true : params.streaming, + callbacks: [ + { + handleLLMNewToken(token: string) { + chunk += token; + messageCallback?.( + setupMessage('task-execute', chunk, undefined, '🤖', task.id), + ); + }, + }, + ], + }, + { baseOptions: { signal: this.abortController.signal } }, + ); + + try { + const response = await llm.call([new HumanChatMessage(prompt)]); + messageCallback?.( + setupMessage('task-output', response.text, undefined, '✅', task.id), + ); + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return `Task aborted.`; + } + console.log('error: ', error); + return 'Failed to generate text.'; + } + } +} diff --git a/src/components/Agent/LabelBlock.tsx b/src/components/Agent/LabelBlock.tsx index 6320aa1b..478a0a59 100644 --- a/src/components/Agent/LabelBlock.tsx +++ b/src/components/Agent/LabelBlock.tsx @@ -8,9 +8,10 @@ export interface LabelBlockProps { } export const LabelBlock: React.FC = ({ block }) => { - const { icon, type, title, content } = block.messages[0]; + const { icon, type, style, title, content } = block.messages[0]; const emoji = icon || getEmoji(type); const blockTitle = title || getTitle(type); + const blockContent = style === 'log' ? '```\n' + content + '\n```' : content; const renderEmoji = () => (
@@ -21,7 +22,7 @@ export const LabelBlock: React.FC = ({ block }) => { const renderContent = () => (
- {`### ${blockTitle}\n${content}`} + {`### ${blockTitle}\n${blockContent}`}
); diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index cccb158a..fa101766 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -45,6 +45,7 @@ export function useAgent({ const [input, setInput] = useState(''); const [language] = useState(i18n?.language); const [isRunning, setIsRunning] = useState(false); + const messageMap = useRef(new Map()); // Handle input change const handleInputChange = ( @@ -76,19 +77,34 @@ export function useAgent({ if (value) { const message = new TextDecoder().decode(value); - const agentMessages: AgentMessage[] = message + const newAgentMessages: AgentMessage[] = message .trim() .split('\n') .map((m) => parseMessage(m)); - setAgentMessages((prevMessages) => [ - ...prevMessages, - ...agentMessages, - ]); + newAgentMessages.forEach((newMsg) => { + if (newMsg.id) { + const existingMsg = messageMap.current.get(newMsg.id); + if ( + existingMsg && + existingMsg.id === newMsg.id && + existingMsg.type === newMsg.type + ) { + existingMsg.content += newMsg.content; + } else { + messageMap.current.set(newMsg.id, newMsg); + } + } + }); + + const updatedNewMessages = Array.from(messageMap.current.values()); + setAgentMessages(updatedNewMessages); // Call onResponse with the new message if (onResponse) { - onResponse(new MessageEvent('message', { data: message })); + onResponse( + new MessageEvent('message', { data: updatedNewMessages }), + ); } } @@ -118,6 +134,7 @@ export function useAgent({ abortControllerRef.current = abortController; setIsRunning(true); setInput(''); + messageMap.current = new Map(); if (onSubmit) { onSubmit(); diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts index 89b21498..9a0373e8 100644 --- a/src/pages/api/agent/index.ts +++ b/src/pages/api/agent/index.ts @@ -2,7 +2,7 @@ import type { NextRequest } from 'next/server'; import { NextApiResponse } from 'next'; import { LangChainStream, StreamingTextResponse } from 'ai'; import { AgentStream } from '@/agents/base/AgentStream'; -import { Executer } from '@/agents/base/Executer'; +import { BabyElfAGI } from '@/agents/elf/executer'; export const config = { runtime: 'edge', @@ -19,7 +19,7 @@ export default async function handler(req: NextRequest, res: NextApiResponse) { // llm.call([new HumanChatMessage(input)], {}, [handlers]).catch(console.error); - const executer = new Executer(input, id, handlers, language || 'en'); + const executer = new BabyElfAGI(input, id, handlers, language || 'en'); executer.run(); return new StreamingTextResponse(stream); diff --git a/src/utils/elf/objective.ts b/src/utils/elf/objective.ts new file mode 100644 index 00000000..c507cd50 --- /dev/null +++ b/src/utils/elf/objective.ts @@ -0,0 +1,81 @@ +import { getUserApiKey } from '@/utils/settings'; +import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; + +const JSON_FILES = ['example3', 'example4', 'example_deer']; +const JSON_FILES_FOR_DEV = [ + 'example3', + 'example4', + 'example_deer', + 'example_code', + 'example_code_review', +]; +const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; + +async function fetchJsonFiles(targetJsonFiles: string[]) { + let loadedObjectives: any[] = []; + + for (const jsonFile of targetJsonFiles) { + const response = await fetch( + `${BASE_URL}/api/json-provider?file=${jsonFile}`, + ); + const data = await response.json(); + loadedObjectives.push(data); + } + + return loadedObjectives; +} + +const getObjectivesExamples = async () => { + const targetJsonFiles = + process.env.NODE_ENV === 'development' ? JSON_FILES_FOR_DEV : JSON_FILES; + const loadedObjectives = await fetchJsonFiles(targetJsonFiles); + + return loadedObjectives; +}; + +async function getEmbedding( + text: string, + modelName: string = 'text-embedding-ada-002', +) { + const embedding = new OpenAIEmbeddings({ + modelName, + openAIApiKey: getUserApiKey(), + }); + return await embedding.embedQuery(text); +} + +function calculateSimilarity(embedding1: number[], embedding2: number[]) { + const dotProduct = embedding1.reduce( + (sum, a, i) => sum + a * embedding2[i], + 0, + ); + const magnitude1 = Math.sqrt(embedding1.reduce((sum, a) => sum + a * a, 0)); + const magnitude2 = Math.sqrt(embedding2.reduce((sum, a) => sum + a * a, 0)); + return dotProduct / (magnitude1 * magnitude2); +} + +export async function findMostRelevantObjective(userInput: string) { + const userInputEmbedding = await getEmbedding( + userInput, + 'text-embedding-ada-002', + ); + const examples = await getObjectivesExamples(); + + let maxSimilarity = -Infinity; + let mostRelevantObjective = null; + + for (const example of examples) { + const objectiveEmbedding = await getEmbedding(example.objective); + const similarity = calculateSimilarity( + objectiveEmbedding, + userInputEmbedding, + ); + + if (similarity > maxSimilarity) { + maxSimilarity = similarity; + mostRelevantObjective = example; + } + } + + return mostRelevantObjective; +} diff --git a/src/utils/elf/print.ts b/src/utils/elf/print.ts new file mode 100644 index 00000000..b44bccfa --- /dev/null +++ b/src/utils/elf/print.ts @@ -0,0 +1,122 @@ +import { AgentTask, AgentMessage } from '@/types'; +import { getToolIcon, setupMessage, setupMessageWithTask } from '../message'; +import { v4 as uuidv4 } from 'uuid'; + +export class Printer { + messageCallback: (message: AgentMessage) => void; + verbose: boolean = false; + + constructor( + messageCallback: (message: AgentMessage) => void, + verbose: boolean = false, + ) { + this.messageCallback = messageCallback; + this.verbose = verbose; + } + + printObjective(objective: string) { + this.handleMessage({ + content: objective, + type: 'objective', + }); + + if (!this.verbose) return; + console.log('%c*****OBJECTIVE*****\n%c%s', 'color:fuchsia', '', objective); + } + + printNextTask(task: AgentTask) { + const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ + task.tool + }]**`; + // this + // .messageCallback + // // setupMessage('next-task', nextTask, task.tool, undefined, task.id), + // (); + + if (!this.verbose) return; + console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); + console.log(task); + } + + printTaskExecute(task: AgentTask) { + // this.messageCallback(setupMessageWithTask(task)); + + if (!this.verbose) return; + console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); + console.log(task); + } + + printTaskList(taskList: AgentTask[], id?: string) { + const useSkill = taskList[0].skill !== undefined; + let message = + '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; + if (useSkill) { + message = + '| ID | Status | Task | Skill | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; + } + + taskList.forEach((task) => { + const dependentTask = task.dependentTaskIds + ? `${task.dependentTaskIds.join(', ')}` + : '-'; + const status = task.status === 'complete' ? '✅' : '⬜️'; + if (useSkill) { + message += `| ${task.id} | ${status} | ${task.task} | ${task.icon} | ${dependentTask} |\n`; + return; + } + message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( + task.tool, + )} | ${dependentTask} |\n`; + }); + + this.messageCallback({ + id, + content: message, + type: 'task-list', + status: 'complete', + }); + + if (!this.verbose) return; + console.log('%c*****TASK LIST*****\n%c%s', 'color:fuchsia', '', message); + } + + printTaskOutput(output: string, task: AgentTask) { + if (task.tool !== 'text-completion') { + // code block for non-text-completion tools + // output = '```\n' + output + '\n```'; + } + // this.messageCallback( + // setupMessage('task-output', output, task?.tool, undefined, task?.id), + // ); + + if (!this.verbose) return; + console.log('%c*****TASK OUTPUT*****\n%c%s', 'color:fuchsia', '', output); + } + + printTaskCompleted() { + // this.messageCallback(setupMessage('done', 'Done!')); + + if (!this.verbose) return; + console.log('%c*****DONE*****\n%c', 'color:fuchsia', ''); + } + + printAllTaskCompleted() { + this.handleMessage({ + content: 'All task completed!', + type: 'finish', + }); + + if (!this.verbose) return; + console.log('%c*****ALL TASK COMPLETED*****%c', 'color:fuchsia', ''); + } + + // handleMessage() is called by the agent to send a message to the frontend + async handleMessage(message: AgentMessage) { + const msg = { + ...message, + id: message.id || uuidv4(), + status: message.status || 'complete', + }; + await this.messageCallback(msg); + } +} From c363fd3451188d73c8543c734eab72761f216c25 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Fri, 11 Aug 2023 17:26:00 +0900 Subject: [PATCH 07/42] Refactor code for executing tasks in parallel --- src/agents/elf/executer.ts | 227 +++++++++++----------- src/agents/elf/registory/skillRegistry.ts | 25 ++- src/agents/elf/registory/taskRegistry.ts | 19 +- src/agents/elf/skills/index.ts | 1 - src/agents/elf/skills/skill.ts | 63 ++++-- src/components/Agent/LabelBlock.tsx | 39 +++- src/hooks/useAgent.ts | 1 + src/utils/elf/print.ts | 10 - src/utils/message.ts | 17 +- 9 files changed, 215 insertions(+), 187 deletions(-) diff --git a/src/agents/elf/executer.ts b/src/agents/elf/executer.ts index 2be36995..8c4dc359 100644 --- a/src/agents/elf/executer.ts +++ b/src/agents/elf/executer.ts @@ -47,120 +47,115 @@ export class BabyElfAGI extends Executer { this.printer.printTaskList(this.taskRegistry.tasks, id); } - // async loop() { - // // Initialize task outputs - // let taskOutputs: TaskOutputs = {}; - // for (let task of this.taskRegistry.tasks) { - // taskOutputs[task.id] = { completed: false, output: undefined }; - // } - - // // Loop until all tasks are completed - // while (!Object.values(taskOutputs).every((task) => task.completed)) { - // if (!this.isRunningRef.current) { - // break; - // } - // this.handlers.handleMessage({ type: 'preparing' }); - - // // Get the tasks that are ready to be executed - // const tasks = this.taskRegistry.getTasks(); - - // // Update taskOutputs to include new tasks - // tasks.forEach((task) => { - // if (!(task.id in taskOutputs)) { - // taskOutputs[task.id] = { completed: false, output: undefined }; - // } - // }); - - // // Filter taskoutput not completed - // const incompleteTasks = tasks.filter((task) => { - // return !taskOutputs[task.id].completed; - // }); - - // // Filter tasks that have all their dependencies completed - // const MaxExecutableTasks = 5; - // const executableTasks = incompleteTasks - // .filter((task) => { - // if (!task.dependentTaskIds) return true; - // return task.dependentTaskIds.every((id) => { - // return taskOutputs[id]?.completed === true; - // }); - // }) - // .slice(0, MaxExecutableTasks); - - // // Execute all executable tasks in parallel - // const taskPromises = executableTasks.map(async (task, i) => { - // // Update task status to running - // this.taskRegistry.updateTasks({ - // id: task.id, - // updates: { status: 'running' }, - // }); - // this.printer.printTaskExecute(task); - // this.currentStatusCallback(); - // const output = await this.taskRegistry.executeTask( - // i, - // task, - // taskOutputs, - // this.objective, - // this.skillRegistry, - // ); - - // taskOutputs[task.id] = { completed: true, output: output }; - // this.taskRegistry.updateTasks({ - // id: task.id, - // updates: { status: 'complete', result: output }, - // }); - // this.printer.printTaskOutput(output, task); - // this.sessionSummary += `# ${task.id}: ${task.task}\n${output}\n\n`; - - // // Reflect on the output of the tasks and possibly add new tasks or update existing ones - // if (REFLECTION) { - // const skillDescriptions = this.skillRegistry.getSkillDescriptions(); - // const [newTasks, insertAfterIds, tasksToUpdate] = - // await this.taskRegistry.reflectOnOutput( - // this.objective, - // output, - // skillDescriptions, - // ); - - // // Insert new tasks - // for (let i = 0; i < newTasks.length; i++) { - // const newTask = newTasks[i]; - // const afterId = insertAfterIds[i]; - // this.taskRegistry.addTask(newTask, afterId); - // } - - // // Update existing tasks - // for (const taskToUpdate of tasksToUpdate) { - // this.taskRegistry.updateTasks({ - // id: taskToUpdate.id, - // updates: taskToUpdate, - // }); - // } - // } - // }); - - // // Wait for all tasks to complete - // await Promise.all(taskPromises); - // } - // } - - // async finishup() { - // const tasks = this.taskRegistry.getTasks(); - // const lastTask = tasks[tasks.length - 1]; - // this.handlers.handleMessage({ - // type: 'final-result', - // text: lastTask.result ?? '', - // title: translate('FINAL_TASK_RESULT', 'message'), - // icon: '✍️', - // id: 9999, - // }); - - // this.handlers.handleMessage({ - // type: 'session-summary', - // text: this.sessionSummary, - // id: 9999, - // }); - - // super.finishup(); - // } + async loop() { + // Initialize task outputs + let taskOutputs: TaskOutputs = {}; + for (let task of this.taskRegistry.tasks) { + taskOutputs[task.id] = { completed: false, output: undefined }; + } + + // Loop until all tasks are completed + while (!Object.values(taskOutputs).every((task) => task.completed)) { + // this.handlers.handleMessage({ type: 'preparing' }); + + // Get the tasks that are ready to be executed + const tasks = this.taskRegistry.getTasks(); + + // Update taskOutputs to include new tasks + tasks.forEach((task) => { + if (!(task.id in taskOutputs)) { + taskOutputs[task.id] = { completed: false, output: undefined }; + } + }); + + // Filter taskoutput not completed + const incompleteTasks = tasks.filter((task) => { + return !taskOutputs[task.id].completed; + }); + + // Filter tasks that have all their dependencies completed + const MaxExecutableTasks = 5; + const executableTasks = incompleteTasks + .filter((task) => { + if (!task.dependentTaskIds) return true; + return task.dependentTaskIds.every((id) => { + return taskOutputs[id]?.completed === true; + }); + }) + .slice(0, MaxExecutableTasks); + + // Execute all executable tasks in parallel + const taskPromises = executableTasks.map(async (task, i) => { + // Update task status to running + this.taskRegistry.updateTasks({ + id: task.id, + updates: { status: 'running' }, + }); + this.printer.printTaskExecute(task); + // this.currentStatusCallback(); + const output = await this.taskRegistry.executeTask( + i, + task, + taskOutputs, + this.objective, + this.skillRegistry, + ); + + taskOutputs[task.id] = { completed: true, output: output }; + this.taskRegistry.updateTasks({ + id: task.id, + updates: { status: 'complete', result: output }, + }); + this.printer.printTaskOutput(output, task); + this.sessionSummary += `# ${task.id}: ${task.task}\n${output}\n\n`; + + // Reflect on the output of the tasks and possibly add new tasks or update existing ones + if (REFLECTION) { + const skillDescriptions = this.skillRegistry.getSkillDescriptions(); + const [newTasks, insertAfterIds, tasksToUpdate] = + await this.taskRegistry.reflectOnOutput( + this.objective, + output, + skillDescriptions, + ); + + // Insert new tasks + for (let i = 0; i < newTasks.length; i++) { + const newTask = newTasks[i]; + const afterId = insertAfterIds[i]; + this.taskRegistry.addTask(newTask, afterId); + } + + // Update existing tasks + for (const taskToUpdate of tasksToUpdate) { + this.taskRegistry.updateTasks({ + id: taskToUpdate.id, + updates: taskToUpdate, + }); + } + } + }); + + // Wait for all tasks to complete + await Promise.all(taskPromises); + } + } + + async finishup() { + const tasks = this.taskRegistry.getTasks(); + const lastTask = tasks[tasks.length - 1]; + this.handlers.handleMessage({ + type: 'result', + content: lastTask.result ?? '', + id: uuidv4(), + }); + + this.handlers.handleMessage({ + type: 'session-summary', + content: this.sessionSummary, + id: uuidv4(), + }); + + super.finishup(); + } } diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index 82123c58..017922b4 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -1,18 +1,16 @@ import { AgentMessage } from '@/types'; import { - AirtableSaver, - CodeReader, - CodeReviewer, - CodeWriter, - DirectoryStructure, - SkillSaver, + // CodeReader, + // CodeReviewer, + // CodeWriter, + // DirectoryStructure, + // SkillSaver, TextCompletion, - WebLoader, - WebSearch, - YoutubeSearch, + // WebLoader, + // WebSearch, + // YoutubeSearch, } from '../skills'; import { Skill } from '../skills/skill'; -import { getUserApiKey } from '@/utils/settings'; export class SkillRegistry { skillClasses: (typeof Skill)[]; @@ -65,8 +63,7 @@ export class SkillRegistry { static getSkillClasses(): (typeof Skill)[] { const skills: (typeof Skill)[] = [ - Skill, - // TextCompletion, + TextCompletion, // WebSearch, // AirtableSaver, // CodeReader, @@ -81,8 +78,8 @@ export class SkillRegistry { } static apiKeys = { - openai: getUserApiKey() || process.env.OPENAI_API_KEY || '', - airtable: 'keyXXXXXXXXXXXXXX', // Your Airtable API key here + openai: process.env.OPENAI_API_KEY || '', + airtable: process.env.AIRTABLE_API_KEY || '', }; getSkill(name: string): Skill { diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts index b4ab0bda..61716eb8 100644 --- a/src/agents/elf/registory/taskRegistry.ts +++ b/src/agents/elf/registory/taskRegistry.ts @@ -7,7 +7,6 @@ import { getUserApiKey } from '@/utils/settings'; import { translate } from '@/utils/translate'; import { SkillRegistry } from './skillRegistry'; import { findMostRelevantObjective } from '@/utils/elf/objective'; -import axios from 'axios'; export class TaskRegistry { tasks: AgentTask[]; @@ -106,23 +105,7 @@ export class TaskRegistry { ? task.dependentTaskIds.map((id) => taskOutputs[id].output).join('\n') : ''; - if (skill.executionLocation === 'server') { - // Call the API endpoint if the skill needs to be executed on the server side - const response = await axios.post('/api/execute-skill', { - task: JSON.stringify(task), - dependent_task_outputs: dependentTaskOutputs, - objective, - }); - return response.data.taskOutput; - } else { - // Execute the skill on the client side - let taskOutput = await skill.execute( - task, - dependentTaskOutputs, - objective, - ); - return taskOutput; - } + return await skill.execute(task, dependentTaskOutputs, objective); } getTasks(): AgentTask[] { diff --git a/src/agents/elf/skills/index.ts b/src/agents/elf/skills/index.ts index b6154137..3100b5cf 100644 --- a/src/agents/elf/skills/index.ts +++ b/src/agents/elf/skills/index.ts @@ -8,5 +8,4 @@ export * from './presets/objectiveSaver'; export * from './presets/codeWriter'; export * from './presets/codeReviewer'; export * from './presets/webLoader'; -export * from './addons/airtableSaver'; export * from './addons/youtubeSearch'; diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts index c3e712c8..a031053b 100644 --- a/src/agents/elf/skills/skill.ts +++ b/src/agents/elf/skills/skill.ts @@ -1,9 +1,7 @@ import { AgentTask, LLMParams, AgentMessage } from '@/types'; -import { setupMessage } from '@/utils/message'; -import { getUserApiKey } from '@/utils/settings'; -import axios from 'axios'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { HumanChatMessage } from 'langchain/schema'; +import { v4 as uuidv4 } from 'uuid'; export type SkillType = 'normal' | 'dev'; export type SkillExecutionLocation = 'client' | 'server'; @@ -88,40 +86,63 @@ export class Skill { async generateText( prompt: string, task: AgentTask, - params?: LLMParams, + params: LLMParams = {}, ignoreCallback: boolean = false, ): Promise { - let chunk = ''; - const messageCallback = ignoreCallback ? () => {} : this.messageCallback; + const messageCallback = ignoreCallback ? () => {} : this.handleMessage; + const id = uuidv4(); + const defaultParams = { + modelName: 'gpt-3.5-turbo', + temperature: 0.7, + maxTokens: 1500, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + streaming: true, + }; + const llmParams = { ...defaultParams, ...params }; const llm = new ChatOpenAI( { openAIApiKey: this.apiKeys.openai, - modelName: params?.modelName ?? 'gpt-3.5-turbo', - temperature: params?.temperature ?? 0.7, - maxTokens: params?.maxTokens ?? 1500, - topP: params?.topP ?? 1, - frequencyPenalty: params?.frequencyPenalty ?? 0, - presencePenalty: params?.presencePenalty ?? 0, - streaming: params?.streaming === undefined ? true : params.streaming, + ...llmParams, callbacks: [ { handleLLMNewToken(token: string) { - chunk += token; - messageCallback?.( - setupMessage('task-execute', chunk, undefined, '🤖', task.id), - ); + messageCallback?.({ + id, + content: token, + title: `${task.task}`, + style: 'task', + type: task.skill, + icon: '🤖', + taskId: task.id.toString(), + status: 'running', + options: { + dependentTaskIds: task.dependentTaskIds?.join(',') ?? '', + }, + }); }, }, ], }, - { baseOptions: { signal: this.abortController.signal } }, + // { baseOptions: { signal: this.abortController.signal } }, ); try { const response = await llm.call([new HumanChatMessage(prompt)]); - messageCallback?.( - setupMessage('task-output', response.text, undefined, '✅', task.id), - ); + messageCallback?.({ + id, + taskId: task.id.toString(), + content: response.text, + title: task.task, + icon: '✅', + style: 'task', + type: task.skill, + status: 'complete', + options: { + dependentTaskIds: task.dependentTaskIds?.join(',') ?? '', + }, + }); return response.text; } catch (error: any) { if (error.name === 'AbortError') { diff --git a/src/components/Agent/LabelBlock.tsx b/src/components/Agent/LabelBlock.tsx index 478a0a59..549f7189 100644 --- a/src/components/Agent/LabelBlock.tsx +++ b/src/components/Agent/LabelBlock.tsx @@ -1,7 +1,9 @@ +import { useEffect, useRef } from 'react'; import { Block } from '@/types'; import { getEmoji, getTitle } from '@/utils/message'; import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; import remarkGfm from 'remark-gfm'; +import { translate } from '@/utils/translate'; export interface LabelBlockProps { block: Block; @@ -12,6 +14,26 @@ export const LabelBlock: React.FC = ({ block }) => { const emoji = icon || getEmoji(type); const blockTitle = title || getTitle(type); const blockContent = style === 'log' ? '```\n' + content + '\n```' : content; + const sessionSummary = + block.messages[0].type === 'session-summary' + ? block.messages[0].content + : null; + + const linkRef = useRef(null); + + useEffect(() => { + if (sessionSummary) { + const file = new Blob(['\uFEFF' + sessionSummary], { + type: 'text/plain;charset=utf-8', + }); + const url = URL.createObjectURL(file); + if (linkRef.current) { + const link = linkRef.current; + link.href = url; + link.download = 'session_summary.txt'; + } + } + }, [sessionSummary]); const renderEmoji = () => (
@@ -21,9 +43,20 @@ export const LabelBlock: React.FC = ({ block }) => { const renderContent = () => (
- - {`### ${blockTitle}\n${blockContent}`} - + {sessionSummary ? ( + <> + + {`### ${blockTitle}\n`} + + + ⬇️ {translate('DOWNLOAD_SESSION_SUMMARY', 'message')} + + + ) : ( + + {`### ${blockTitle}\n${blockContent}`} + + )}
); diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index fa101766..3ad4caad 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -91,6 +91,7 @@ export function useAgent({ existingMsg.type === newMsg.type ) { existingMsg.content += newMsg.content; + existingMsg.status = newMsg.status; } else { messageMap.current.set(newMsg.id, newMsg); } diff --git a/src/utils/elf/print.ts b/src/utils/elf/print.ts index b44bccfa..0c05e28f 100644 --- a/src/utils/elf/print.ts +++ b/src/utils/elf/print.ts @@ -39,8 +39,6 @@ export class Printer { } printTaskExecute(task: AgentTask) { - // this.messageCallback(setupMessageWithTask(task)); - if (!this.verbose) return; console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); console.log(task); @@ -81,14 +79,6 @@ export class Printer { } printTaskOutput(output: string, task: AgentTask) { - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - // output = '```\n' + output + '\n```'; - } - // this.messageCallback( - // setupMessage('task-output', output, task?.tool, undefined, task?.id), - // ); - if (!this.verbose) return; console.log('%c*****TASK OUTPUT*****\n%c%s', 'color:fuchsia', '', output); } diff --git a/src/utils/message.ts b/src/utils/message.ts index 74827bce..2c7e8c59 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -359,6 +359,10 @@ export const getEmoji = (type?: string) => { return '📝'; case 'task': return '📄'; + case 'session-summary': + return '📄'; + case 'result': + return '📜'; default: return '🤖'; } @@ -369,11 +373,15 @@ export const getTitle = (type?: string) => { case 'objective': return translate('OBJECTIVE', 'message'); case 'finish': - return translate('FINISH', 'message'); + return translate('FINISHED', 'message'); case 'task-list': return translate('TASK_LIST', 'message'); case 'task': return translate('TASK', 'message'); + case 'session-summary': + return translate('SESSION_SUMMARY', 'message'); + case 'result': + return translate('FINAL_TASK_RESULT', 'message'); default: return type?.toUpperCase() || 'Untitled'; } @@ -385,9 +393,10 @@ export const groupMessages = (messages: AgentMessage[]) => { let block: Block | null = null; let prevMessage: AgentMessage | null = null; messages.forEach((message) => { - if (!block || block.id !== message.id) { + const id = message.taskId !== undefined ? message.taskId : message.id; + if (!block || block.id !== id) { block = { - id: message.id, + id: id, status: message.status, messages: [message], style: message.style === 'task' ? 'task' : 'label', @@ -395,7 +404,7 @@ export const groupMessages = (messages: AgentMessage[]) => { messageGroups.push(block); } else if ( prevMessage && - prevMessage.id === message.id && + prevMessage.id === id && prevMessage.type === message.type ) { block.messages[block.messages.length - 1].content += message.content; From a8cd966dac3bd90706ae5ed2f8d1312017e9ad37 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Fri, 11 Aug 2023 17:42:15 +0900 Subject: [PATCH 08/42] Refactor getExportText to getExportAgentMessage --- src/components/Agent/AgentView.tsx | 13 +++++-------- src/utils/message.ts | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 6553b04c..517c981a 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -16,7 +16,7 @@ import { AgentParameter } from './AgentParameter'; import { ProjectTile } from './ProjectTile'; import { AgentMessageHeader } from './AgentMessageHeader'; import { - getExportText, + getExportAgentMessage, getMessageBlocks, loadingAgentMessage, groupMessages, @@ -197,7 +197,7 @@ export const AgentView: FC = () => { }; const copyHandler = () => { - navigator.clipboard.writeText(getExportText(messages, selectedAgent.id)); + navigator.clipboard.writeText(getExportAgentMessage(agentBlocks)); toast.success(translate('COPIED_TO_CLIPBOARD', 'agent')); va.track('CopyToClipboard'); @@ -209,12 +209,9 @@ export const AgentView: FC = () => { objective.length > 0 ? `${objective.replace(/\s/g, '_')}.txt` : 'download.txt'; - const file = new Blob( - ['\uFEFF' + getExportText(messages, selectedAgent.id)], - { - type: 'text/plain;charset=utf-8', - }, - ); + const file = new Blob(['\uFEFF' + getExportAgentMessage(agentBlocks)], { + type: 'text/plain;charset=utf-8', + }); element.href = URL.createObjectURL(file); element.download = filename; document.body.appendChild(element); diff --git a/src/utils/message.ts b/src/utils/message.ts index 2c7e8c59..6fa740aa 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -420,3 +420,18 @@ export const groupMessages = (messages: AgentMessage[]) => { return messageGroups; }; + +export const getExportAgentMessage = (blocks: Block[]) => { + const text = blocks + .map((block) => { + const title = getTitle(block.messages[0].type); + const emoji = getEmoji(block.messages[0].type); + const messages = block.messages + .map((message) => message.content) + .join('\n'); + return `## ${emoji} ${title}\n${messages}`; + }) + .join('\n\n'); + + return text; +}; From 7772acc8b1f0677aed82ae6f9cb87d6f280b0d19 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 12 Aug 2023 12:16:03 +0900 Subject: [PATCH 09/42] Refactor web search skill to use new web browsing tool --- src/agents/base/TestExecuter.ts | 3 +- src/agents/elf/registory/skillRegistry.ts | 4 +- src/agents/elf/registory/taskRegistry.ts | 3 +- src/agents/elf/skills/presets/webSearch.ts | 8 +- src/agents/elf/skills/skill.ts | 6 +- .../elf/tools/search/largeTextExtract.ts | 39 +++++ src/agents/elf/tools/search/prompt.ts | 34 +++++ .../search/relevantInfoExtraction/agent.ts | 44 ++++++ .../search/relevantInfoExtraction/prompt.ts | 18 +++ src/agents/elf/tools/search/webBrowsing.ts | 142 ++++++++++++++++++ src/agents/elf/tools/search/webSearch.ts | 72 +++++++++ src/agents/elf/tools/textCompletionTool.ts | 57 +++++++ src/agents/elf/tools/webScrape.ts | 46 ++++++ src/components/Agent/AgentView.tsx | 2 +- src/components/Agent/TaskBlock.tsx | 23 +-- src/types/index.ts | 2 +- src/utils/message.ts | 27 ++-- 17 files changed, 490 insertions(+), 40 deletions(-) create mode 100644 src/agents/elf/tools/search/largeTextExtract.ts create mode 100644 src/agents/elf/tools/search/prompt.ts create mode 100644 src/agents/elf/tools/search/relevantInfoExtraction/agent.ts create mode 100644 src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts create mode 100644 src/agents/elf/tools/search/webBrowsing.ts create mode 100644 src/agents/elf/tools/search/webSearch.ts create mode 100644 src/agents/elf/tools/textCompletionTool.ts create mode 100644 src/agents/elf/tools/webScrape.ts diff --git a/src/agents/base/TestExecuter.ts b/src/agents/base/TestExecuter.ts index 671ddbf4..cbebafc5 100644 --- a/src/agents/base/TestExecuter.ts +++ b/src/agents/base/TestExecuter.ts @@ -21,11 +21,10 @@ export class TestExecuter extends Executer { const task = new Promise((resolve) => { setTimeout(async () => { const id = `task + ${i}`; - await this.handleMessage({ + await this.handlers.handleMessage({ content: `Test message ${i}`, title: `Task description ${i}`, type: 'task', - style: 'task', taskId: `${i}`, id, }); diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index 017922b4..f09f911c 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -7,7 +7,7 @@ import { // SkillSaver, TextCompletion, // WebLoader, - // WebSearch, + WebSearch, // YoutubeSearch, } from '../skills'; import { Skill } from '../skills/skill'; @@ -64,7 +64,7 @@ export class SkillRegistry { static getSkillClasses(): (typeof Skill)[] { const skills: (typeof Skill)[] = [ TextCompletion, - // WebSearch, + WebSearch, // AirtableSaver, // CodeReader, // CodeWriter, diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts index 61716eb8..011488c7 100644 --- a/src/agents/elf/registory/taskRegistry.ts +++ b/src/agents/elf/registory/taskRegistry.ts @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { AgentTask, AgentMessage, TaskOutputs } from '@/types'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { parseTasks } from '@/utils/task'; @@ -133,7 +132,7 @@ export class TaskRegistry { } reorderTasks(): void { - this.tasks = _.sortBy(this.tasks, ['priority', 'task_id']); + this.tasks.sort((a, b) => a.id - b.id); } async reflectOnOutput( diff --git a/src/agents/elf/skills/presets/webSearch.ts b/src/agents/elf/skills/presets/webSearch.ts index f0ec2d77..7bac9bcf 100644 --- a/src/agents/elf/skills/presets/webSearch.ts +++ b/src/agents/elf/skills/presets/webSearch.ts @@ -1,5 +1,5 @@ import { AgentTask } from '@/types'; -import { webBrowsing } from '@/agents/babydeeragi/tools/webBrowsing'; +import { webBrowsing } from '@/agents/elf/tools/search/webBrowsing'; import { Skill } from '../skill'; // This skill is Specialized for web browsing @@ -17,19 +17,15 @@ export class WebSearch extends Skill { objective: string, ): Promise { if (!this.valid) return ''; - const taskOutput = (await webBrowsing( objective, task, dependentTaskOutputs, - this.messageCallback, - undefined, - this.isRunningRef, + this.handleMessage, this.verbose, undefined, this.language, - this.abortController.signal, )) ?? ''; return taskOutput; diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts index a031053b..e0647d44 100644 --- a/src/agents/elf/skills/skill.ts +++ b/src/agents/elf/skills/skill.ts @@ -4,7 +4,6 @@ import { HumanChatMessage } from 'langchain/schema'; import { v4 as uuidv4 } from 'uuid'; export type SkillType = 'normal' | 'dev'; -export type SkillExecutionLocation = 'client' | 'server'; export class Skill { name: string = 'base_kill'; @@ -15,7 +14,6 @@ export class Skill { apiKeysRequired: Array> = []; valid: boolean; apiKeys: { [key: string]: string }; - executionLocation: SkillExecutionLocation = 'client'; // 'client' or 'server' // for UI handleMessage: (message: AgentMessage) => void; verbose: boolean; @@ -112,7 +110,6 @@ export class Skill { id, content: token, title: `${task.task}`, - style: 'task', type: task.skill, icon: '🤖', taskId: task.id.toString(), @@ -136,11 +133,10 @@ export class Skill { content: response.text, title: task.task, icon: '✅', - style: 'task', type: task.skill, status: 'complete', options: { - dependentTaskIds: task.dependentTaskIds?.join(',') ?? '', + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', }, }); return response.text; diff --git a/src/agents/elf/tools/search/largeTextExtract.ts b/src/agents/elf/tools/search/largeTextExtract.ts new file mode 100644 index 00000000..210b2a78 --- /dev/null +++ b/src/agents/elf/tools/search/largeTextExtract.ts @@ -0,0 +1,39 @@ +import { AgentMessage, AgentTask } from '@/types'; +import { relevantInfoExtractionAgent } from './relevantInfoExtraction/agent'; + +export const largeTextExtract = async ( + id: string, + objective: string, + largeString: string, + task: AgentTask, + callback?: (message: AgentMessage) => void, +) => { + const chunkSize = 15000; + const overlap = 500; + let notes = ''; + + // for status message + const total = Math.ceil(largeString.length / (chunkSize - overlap)); + + for (let i = 0; i < largeString.length; i += chunkSize - overlap) { + callback?.({ + content: ` - chunk ${i / (chunkSize - overlap) + 1} of ${total}\n`, + style: 'log', + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'running', + }); + + const chunk = largeString.slice(i, i + chunkSize); + + const response = await relevantInfoExtractionAgent( + objective, + task.task, + notes, + chunk, + ); + notes += response; + } + return notes; +}; diff --git a/src/agents/elf/tools/search/prompt.ts b/src/agents/elf/tools/search/prompt.ts new file mode 100644 index 00000000..fbc7a107 --- /dev/null +++ b/src/agents/elf/tools/search/prompt.ts @@ -0,0 +1,34 @@ +export const searchQueryPrompt = (task: string, dependent_task: string) => { + return `You are an AI assistant tasked with generating a Google search query based on the following task: ${task}. + ${ + dependent_task.length > 0 + ? `Consider the results of dependent tasks: ${dependent_task}.` + : '' + } + If the task looks like a search query, return the identical search query as your response. + Search Query:`; +}; + +export const analystPrompt = (results: string, language: string) => { + return `You are an expert analyst. Rewrite the following information as one report without removing any facts. + Report must be answered in ${language}. + \n###INFORMATION:${results}.\n###REPORT:`; +}; + +export const textCompletionToolPrompt = ( + objective: string, + language: string, + task: string, + dependentTaskOutput: string, +) => { + let prompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. + Your objective: ${objective}. Your task: ${task} + Output must be answered in ${language}.\n + `; + if (dependentTaskOutput !== '') { + prompt += `Your dependent tasks: ${dependentTaskOutput}\n OUTPUT:`; + } else { + prompt += '\nOUTPUT:'; + } + return prompt; +}; diff --git a/src/agents/elf/tools/search/relevantInfoExtraction/agent.ts b/src/agents/elf/tools/search/relevantInfoExtraction/agent.ts new file mode 100644 index 00000000..b5b4f60f --- /dev/null +++ b/src/agents/elf/tools/search/relevantInfoExtraction/agent.ts @@ -0,0 +1,44 @@ +import { OpenAIChat } from 'langchain/llms/openai'; +import { relevantInfoExtractionPrompt } from './prompt'; +import { LLMChain } from 'langchain/chains'; +import axios from 'axios'; + +// TODO: Only client-side requests are allowed. +// To use the environment variable API key, the request must be implemented from the server side. + +export const relevantInfoExtractionAgent = async ( + objective: string, + task: string, + notes: string, + chunk: string, + signal?: AbortSignal, +) => { + const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model + const prompt = relevantInfoExtractionPrompt(); + const llm = new OpenAIChat( + { + modelName, + temperature: 0.7, + maxTokens: 800, + topP: 1, + stop: ['###'], + }, + { baseOptions: { signal: signal } }, + ); + const chain = new LLMChain({ llm: llm, prompt }); + try { + const response = await chain.call({ + objective, + task, + notes, + chunk, + }); + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return null; + } + console.log('error: ', error); + return 'Failed to extract relevant information.'; + } +}; diff --git a/src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts b/src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts new file mode 100644 index 00000000..59e8dade --- /dev/null +++ b/src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts @@ -0,0 +1,18 @@ +import { + ChatPromptTemplate, + HumanMessagePromptTemplate, + SystemMessagePromptTemplate, +} from 'langchain/prompts'; + +export const relevantInfoExtractionPrompt = () => { + const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; + const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; + const prompt = ChatPromptTemplate.fromPromptMessages([ + SystemMessagePromptTemplate.fromTemplate(systemTemplate), + HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), + ]); + + prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; + + return prompt; +}; diff --git a/src/agents/elf/tools/search/webBrowsing.ts b/src/agents/elf/tools/search/webBrowsing.ts new file mode 100644 index 00000000..ed50a114 --- /dev/null +++ b/src/agents/elf/tools/search/webBrowsing.ts @@ -0,0 +1,142 @@ +import { simplifySearchResults } from '@/agents/common/tools/webSearch'; +import { AgentTask, AgentMessage } from '@/types'; +import { analystPrompt, searchQueryPrompt } from './prompt'; +import { textCompletionTool } from '../textCompletionTool'; +import { largeTextExtract } from './largeTextExtract'; +import { v4 as uuidv4 } from 'uuid'; +import { webSearch } from './webSearch'; +import { webScrape } from '../webScrape'; + +export const webBrowsing = async ( + objective: string, + task: AgentTask, + dependentTasksOutput: string, + messageCallback: (message: AgentMessage) => void, + verbose: boolean = false, + modelName: string = 'gpt-3.5-turbo', + language: string = 'en', +) => { + let id = uuidv4(); + const prompt = searchQueryPrompt( + task.task, + dependentTasksOutput.slice(0, 3500), + ); + const searchQuery = await textCompletionTool(prompt, id, task, modelName); + const trimmedQuery = searchQuery?.replace(/^"|"$/g, ''); // remove quotes from the search query + + let message = `Search query: ${trimmedQuery}\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + const searchResults = await webSearch(trimmedQuery || ''); + if (!searchResults) { + return 'Failed to search.'; + } + + const simplifiedSearchResults = simplifySearchResults(searchResults); + message = `✅ Completed search. \nNow reading content.\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + let results = ''; + let index = 1; + let completedCount = 0; + const MaxCompletedCount = 3; + // Loop through search results + for (const searchResult of simplifiedSearchResults) { + if (completedCount >= MaxCompletedCount) break; + + // Extract the URL from the search result + const url = searchResult.link; + let title = `${index}. Reading: ${url} ...`; + + message = `${title}\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + const content = await webScrape(url); + if (!content) { + let message = ` - Failed to read content. Skipped. \n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + continue; + } + + title = `${index}. Extracting relevant info...`; + message = ` - Content reading completed. Length:${content?.length}. Now extracting relevant info...\n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + if (content?.length === 0) { + let message = ` - Content too short. Skipped. \n`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + index += 1; + continue; + } + + message = ` - Extracting relevant information\n`; + title = `${index}. Extracting relevant info...`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + const info = await largeTextExtract( + id, + objective, + content.slice(0, 20000), + task, + messageCallback, + ); + + message = ` - Relevant info: ${info + .slice(0, 100) + .replace(/\r?\n/g, '')} ...\n`; + + title = `${index}. Relevant info...`; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + results += `${info}. `; + index += 1; + completedCount += 1; + } + + message = 'Analyzing results...\n'; + callbackSearchStatus(id, message, task, messageCallback, verbose); + + const outputId = uuidv4(); + const ap = analystPrompt(results, language); + const analyzedResults = await textCompletionTool( + ap, + outputId, + task, + modelName, + messageCallback, + ); + + // callback to search logs + const msg: AgentMessage = { + id, + taskId: task.id.toString(), + type: task.skill, + content: message, + title: task.task, + status: 'complete', + }; + messageCallback(msg); + + return analyzedResults; +}; + +const callbackSearchStatus = ( + id: string, + message: string, + task: AgentTask, + messageCallback: (message: AgentMessage) => void, + verbose: boolean = false, +) => { + if (verbose) { + console.log(message); + } + + messageCallback({ + id, + taskId: task.id.toString(), + type: task.skill, + icon: '🔎', + style: 'log', + content: message, + title: task.task, + status: 'running', + }); +}; diff --git a/src/agents/elf/tools/search/webSearch.ts b/src/agents/elf/tools/search/webSearch.ts new file mode 100644 index 00000000..8b583dbb --- /dev/null +++ b/src/agents/elf/tools/search/webSearch.ts @@ -0,0 +1,72 @@ +export const webSearch = async (query: string) => { + try { + // Change environment variable name for typo + if (!process.env.SERP_API_KEY && process.env.SEARP_API_KEY !== undefined) { + throw new Error( + 'The environment variable name has been changed due to a typo: SEARP_API_KEY. Please fix it to SERP_API_KEY.', + ); + } + + if (process.env.SERP_API_KEY) { + const response = await fetch( + `https://serpapi.com/search?api_key=${process.env.SERP_API_KEY}&engine=google&q=${query}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + const data = await response.json(); + return data.organic_results; + } else if ( + process.env.GOOGLE_SEARCH_API_KEY && + process.env.GOOGLE_CUSTOM_INDEX_ID + ) { + const response = await fetch( + 'https://www.googleapis.com/customsearch/v1', + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + key: process.env.GOOGLE_SEARCH_API_KEY, + cx: process.env.GOOGLE_CUSTOM_INDEX_ID, + q: query, + }), + }, + ); + const data = await response.json(); + return data.items; + } + } catch (error) { + console.log('error: ', error); + return; + } +}; + +type SearchResult = { + position: number; + title: string; + link: string; + snippet: string; +}; + +export const simplifySearchResults = (searchResults: any[]): SearchResult[] => { + if (!Array.isArray(searchResults)) { + return []; + } + + const simplifiedResults: SearchResult[] = []; + searchResults.forEach((item, index) => { + simplifiedResults.push({ + position: index, + title: item.title, + link: item.link, + snippet: item.snippet, + }); + }); + + return simplifiedResults; +}; diff --git a/src/agents/elf/tools/textCompletionTool.ts b/src/agents/elf/tools/textCompletionTool.ts new file mode 100644 index 00000000..92b1328d --- /dev/null +++ b/src/agents/elf/tools/textCompletionTool.ts @@ -0,0 +1,57 @@ +import { AgentMessage, AgentTask } from '@/types'; +import { ChatOpenAI } from 'langchain/chat_models/openai'; +import { HumanChatMessage } from 'langchain/schema'; + +export const textCompletionTool = async ( + prompt: string, + id: string, + task: AgentTask, + modelName: string, + messageCallnback?: (message: AgentMessage) => void, +) => { + if (prompt.length > 3200) { + modelName = 'gpt-3.5-turbo-16k-0613'; + } + + const llm = new ChatOpenAI({ + modelName: modelName, + temperature: 0.2, + maxTokens: 800, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + messageCallnback?.({ + content: token, + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'running', + }); + }, + }, + ], + }); + + try { + const response = await llm.call([new HumanChatMessage(prompt)]); + messageCallnback?.({ + content: response.text, + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'complete', + }); + + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return null; + } + console.log('error: ', error); + return 'Failed to generate text.'; + } +}; diff --git a/src/agents/elf/tools/webScrape.ts b/src/agents/elf/tools/webScrape.ts new file mode 100644 index 00000000..ab8dc11b --- /dev/null +++ b/src/agents/elf/tools/webScrape.ts @@ -0,0 +1,46 @@ +import { load } from 'cheerio'; + +export const webScrape = async (url: string) => { + const content = await fetchUrlContent(url); + + if (!content) { + return null; + } + + const text = extractText(content); + + return text; +}; + +const fetchUrlContent = async (url: string) => { + try { + const response = await fetch(url); + const data = await response.text(); + return data; + } catch (error) { + console.error(`Error while fetching the URL: ${error}`); + return ''; + } +}; + +const extractText = (content: string): string => { + const $ = load(content); + const body = $('body'); + const text = getTextFromNode(body, $); + return text.trim(); +}; + +const getTextFromNode = (node: any, $: any): string => { + let text = ''; + node.contents().each((index: number, element: any) => { + if ( + element.type === 'text' && + !['img', 'script', 'style'].includes(element.name) + ) { + text += $(element).text().trim().replace(/\s\s+/g, ' '); + } else if (element.type === 'tag') { + text += getTextFromNode($(element), $); + } + }); + return text; +}; diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 517c981a..16947ec6 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -257,7 +257,7 @@ export const AgentView: FC = () => { (message) => message.type === 'complete' || message.type === 'end-of-iterations', ).length > 0; - const output = getExportText(messages); + const output = getExportAgentMessage(agentBlocks); axios.post('/api/feedback', { objective: feedbackObjective, diff --git a/src/components/Agent/TaskBlock.tsx b/src/components/Agent/TaskBlock.tsx index d5495452..0b899b11 100644 --- a/src/components/Agent/TaskBlock.tsx +++ b/src/components/Agent/TaskBlock.tsx @@ -5,22 +5,27 @@ import { AgentTaskStatus } from './AgentTastStatus'; import { getEmoji } from '@/utils/message'; import Markdown from './Markdown'; import { AgentCollapsible } from './AgentCollapsible'; - export interface AgentTaskProps { block: Block; } const renderIcon = (message: AgentMessage, block: Block) => { - return block.status === 'complete' + return message.style === 'log' + ? '' + : block.status === 'complete' ? '✅' : message.icon || getEmoji(message.type); }; -const renderContent = (message: AgentMessage, lastContent: string) => { - return message.type === 'log' ? ( - +const renderContent = (message: AgentMessage, firstContent: string) => { + const title = + message.status === 'complete' + ? firstContent.split('\n')[0] + : firstContent.trim().split('\n').slice(-1)[0]; + return message.style === 'log' ? ( +
- +
) : ( @@ -32,12 +37,12 @@ const renderContent = (message: AgentMessage, lastContent: string) => { export const TaskBlock: FC = ({ block }) => { const message = block.messages[0]; - const icon = message.icon || getEmoji(message.type); + let icon = message.icon || getEmoji(message.type); const title = message.taskId ? `${message.taskId}. ${message.title}` : message.title; const dependentTaskIds = message?.options?.dependentTaskIds ?? ''; - const lastContent = block.messages[block.messages.length - 1].content; + const firstContent = block.messages[0].content; return (
@@ -64,7 +69,7 @@ export const TaskBlock: FC = ({ block }) => { {renderIcon(message, block)}
- {renderContent(message, lastContent)} + {renderContent(message, firstContent)}
diff --git a/src/types/index.ts b/src/types/index.ts index 18a0e5d5..4f654453 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -154,7 +154,7 @@ export type AgentMessage = { title?: string; icon?: string; taskId?: string; - style?: 'label' | 'task' | 'log'; + style?: 'text' | 'log'; status?: 'complete' | 'incomplete' | 'running'; options?: { [key: string]: string }; }; diff --git a/src/utils/message.ts b/src/utils/message.ts index 6fa740aa..cd3abe64 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -344,7 +344,7 @@ export const parseMessage = (json: string): AgentMessage => { return { ...message, - style: message.style ?? 'label', + style: message.style ?? 'text', status: message.status ?? 'incomplete', }; }; @@ -391,29 +391,32 @@ export const groupMessages = (messages: AgentMessage[]) => { const messageGroups: Block[] = []; let block: Block | null = null; - let prevMessage: AgentMessage | null = null; messages.forEach((message) => { const id = message.taskId !== undefined ? message.taskId : message.id; - if (!block || block.id !== id) { + const existingBlock = messageGroups.find((block) => block.id === id); + if (!existingBlock) { block = { id: id, status: message.status, messages: [message], - style: message.style === 'task' ? 'task' : 'label', + style: message.taskId ? 'task' : 'label', }; messageGroups.push(block); } else if ( - prevMessage && - prevMessage.id === id && - prevMessage.type === message.type + existingBlock && + existingBlock.id === id && + existingBlock.messages[existingBlock.messages.length - 1].type === + message.type && + existingBlock.messages[existingBlock.messages.length - 1].id === + message.id ) { - block.messages[block.messages.length - 1].content += message.content; - block.status = message.status; + existingBlock.messages[existingBlock.messages.length - 1].content += + message.content; + existingBlock.status = message.status; } else { - block.messages.push(message); - block.status = message.status; + existingBlock.messages.push(message); + existingBlock.status = message.status; } - prevMessage = block?.messages[block.messages.length - 1]; }); console.log(messageGroups); From 084c6c700a499fc5c2291b5d8838253cdf36ef43 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 12 Aug 2023 16:21:58 +0900 Subject: [PATCH 10/42] Add WebLoader skill to skill registry and task registry and update WebLoader skill to handle messages --- src/agents/elf/registory/skillRegistry.ts | 4 +- src/agents/elf/registory/taskRegistry.ts | 4 +- src/agents/elf/skills/presets/webLoader.ts | 81 ++++++++++------------ src/agents/elf/skills/skill.ts | 1 + src/hooks/useAgent.ts | 3 +- src/utils/elf/print.ts | 1 + 6 files changed, 43 insertions(+), 51 deletions(-) diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index f09f911c..5dd5a8c1 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -6,7 +6,7 @@ import { // DirectoryStructure, // SkillSaver, TextCompletion, - // WebLoader, + WebLoader, WebSearch, // YoutubeSearch, } from '../skills'; @@ -65,6 +65,7 @@ export class SkillRegistry { const skills: (typeof Skill)[] = [ TextCompletion, WebSearch, + WebLoader, // AirtableSaver, // CodeReader, // CodeWriter, @@ -72,7 +73,6 @@ export class SkillRegistry { // DirectoryStructure, // YoutubeSearch, // CodeReviewer, - // WebLoader, ]; return skills; } diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts index 011488c7..1958fe77 100644 --- a/src/agents/elf/registory/taskRegistry.ts +++ b/src/agents/elf/registory/taskRegistry.ts @@ -3,7 +3,6 @@ import { ChatOpenAI } from 'langchain/chat_models/openai'; import { parseTasks } from '@/utils/task'; import { HumanChatMessage, SystemChatMessage } from 'langchain/schema'; import { getUserApiKey } from '@/utils/settings'; -import { translate } from '@/utils/translate'; import { SkillRegistry } from './skillRegistry'; import { findMostRelevantObjective } from '@/utils/elf/objective'; @@ -62,9 +61,8 @@ export class TaskRegistry { handleLLMNewToken(token: string) { const message: AgentMessage = { id, - title: translate('TASK_LIST', 'message'), content: token, - icon: '📝', + type: 'task-list', style: 'log', }; handleMessage(message); diff --git a/src/agents/elf/skills/presets/webLoader.ts b/src/agents/elf/skills/presets/webLoader.ts index 7913025b..c4100bfb 100644 --- a/src/agents/elf/skills/presets/webLoader.ts +++ b/src/agents/elf/skills/presets/webLoader.ts @@ -1,8 +1,9 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import axios from 'axios'; -import { largeTextExtract } from '@/agents/babydeeragi/tools/largeTextExtract'; -import { translate } from '@/utils/translate'; +import { largeTextExtract } from '@/agents/elf/tools/search/largeTextExtract'; +import { webScrape } from '../../tools/webScrape'; +import { v4 as uuidv4 } from 'uuid'; +import { text } from 'stream/consumers'; export class WebLoader extends Skill { name = 'web_loader'; @@ -17,28 +18,39 @@ export class WebLoader extends Skill { throw new Error('Invalid inputs'); } - let statusMessage = '- Extracting URLs from the task.\n'; - const callback = (message: string) => { - statusMessage += message; - this.messageCallback({ - type: 'search-logs', - text: '```markdown\n' + statusMessage + '\n```', - title: `🔎 ${translate('SEARCH_LOGS', 'message')}`, - id: task.id, - icon: '🌐', - open: false, + let message = '- Extracting URLs from the task.\n'; + const callback = ( + message: string, + status: 'running' | 'complete' | 'incomplete' = 'running', + ) => { + this.handleMessage({ + id: this.id, + taskId: task.id.toString(), + content: message, + title: task.task, + icon: this.icon, + type: this.name, + style: 'log', + status, }); }; + callback(message); const urlString = await this.extractUrlsFromTask(task, callback); const urls = urlString.split(',').map((url) => url.trim()); const contents = await this.fetchContentsFromUrls(urls, callback); - const info = await this.extractInfoFromContents( - contents, - objective, - task, - callback, - ); + const info = await this.extractInfoFromContents(contents, objective, task); + this.handleMessage({ + id: uuidv4(), + taskId: task.id.toString(), + content: info.join('\n\n'), + title: task.task, + icon: this.icon, + type: this.name, + style: 'text', + status: 'complete', + }); + callback('- Completed: Extract info from contents', 'complete'); return info.join('\n\n'); } @@ -47,7 +59,7 @@ export class WebLoader extends Skill { task: AgentTask, callback: (message: string) => void, ): Promise { - const prompt = `- Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; + const prompt = `Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; const urlString = await this.generateText(prompt, task, undefined, true); callback(` - URLs: ${urlString}\n`); return urlString; @@ -61,8 +73,8 @@ export class WebLoader extends Skill { return await Promise.all( urls.slice(0, MAX_URLS).map(async (url) => { callback(`- Reading: ${url} ...\n`); - const content = await this.webScrapeTool(url); - if (content.length === 0) { + const content = await webScrape(url); + if (!content || content.length === 0) { callback(` - Content: No content found.\n`); return { url, content: '' }; } @@ -80,41 +92,20 @@ export class WebLoader extends Skill { contents: { url: string; content: string }[], objective: string, task: AgentTask, - callback: (message: string) => void, ): Promise { - callback(`- Extracting relevant information from the web pages.\n`); return await Promise.all( contents.map(async (item) => { return ( `URL: ${item.url}\n\n` + (await largeTextExtract( + this.id, objective, item.content, task, - this.isRunningRef, - callback, - this.abortController.signal, + this.handleMessage, )) ); }), ); } - - private async webScrapeTool(url: string): Promise { - try { - const response = await axios.post( - '/api/tools/scrape', - { url }, - { signal: this.abortController.signal }, - ); - return response?.data?.response; - } catch (error: any) { - if (error.name === 'AbortError') { - console.error('Request aborted', error.message); - } else { - console.error(error.message); - } - return ''; - } - } } diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts index e0647d44..aa797bf9 100644 --- a/src/agents/elf/skills/skill.ts +++ b/src/agents/elf/skills/skill.ts @@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; export type SkillType = 'normal' | 'dev'; export class Skill { + id: string = uuidv4(); name: string = 'base_kill'; descriptionForHuman: string = 'This is the base skill.'; descriptionForModel: string = 'This is the base skill.'; diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index 3ad4caad..cae3857b 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -88,7 +88,8 @@ export function useAgent({ if ( existingMsg && existingMsg.id === newMsg.id && - existingMsg.type === newMsg.type + existingMsg.type === newMsg.type && + existingMsg.style === newMsg.style ) { existingMsg.content += newMsg.content; existingMsg.status = newMsg.status; diff --git a/src/utils/elf/print.ts b/src/utils/elf/print.ts index 0c05e28f..7c6ea03a 100644 --- a/src/utils/elf/print.ts +++ b/src/utils/elf/print.ts @@ -71,6 +71,7 @@ export class Printer { id, content: message, type: 'task-list', + style: 'text', status: 'complete', }); From 376a305f811b8bd31a5ceddf0a6559500e02f277 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 12 Aug 2023 20:40:09 +0900 Subject: [PATCH 11/42] Add YoutubeSearch skill and callback message --- src/agents/elf/registory/skillRegistry.ts | 4 +- src/agents/elf/skills/addons/youtubeSearch.ts | 43 +++++------- src/agents/elf/skills/skill.ts | 67 +++++++++++-------- src/agents/elf/tools/search/webBrowsing.ts | 2 + src/components/Agent/TaskBlock.tsx | 10 +-- 5 files changed, 61 insertions(+), 65 deletions(-) diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index 5dd5a8c1..4617e244 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -8,7 +8,7 @@ import { TextCompletion, WebLoader, WebSearch, - // YoutubeSearch, + YoutubeSearch, } from '../skills'; import { Skill } from '../skills/skill'; @@ -66,12 +66,12 @@ export class SkillRegistry { TextCompletion, WebSearch, WebLoader, + YoutubeSearch, // AirtableSaver, // CodeReader, // CodeWriter, // SkillSaver, // DirectoryStructure, - // YoutubeSearch, // CodeReviewer, ]; return skills; diff --git a/src/agents/elf/skills/addons/youtubeSearch.ts b/src/agents/elf/skills/addons/youtubeSearch.ts index ceb0a196..99337580 100644 --- a/src/agents/elf/skills/addons/youtubeSearch.ts +++ b/src/agents/elf/skills/addons/youtubeSearch.ts @@ -1,6 +1,6 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import axios from 'axios'; +import { webSearch } from '../../tools/search/webSearch'; export class YoutubeSearch extends Skill { name = 'youtube_search'; @@ -15,37 +15,26 @@ export class YoutubeSearch extends Skill { objective: string, ): Promise { const prompt = `Generate query for YouTube search based on the dependent task outputs and the objective. - Dependent tasks output: ${dependentTaskOutputs} - Objective: ${objective} + Dont include "Youtube video". Only include the query. + Dependent tasks output: ${dependentTaskOutputs} + Objective: ${objective} `; const query = await this.generateText(prompt, task); - const searchResults = await this.webSearchTool(`site:youtube.com ${query}`); + const searchResults = await webSearch(`site:youtube.com ${query}`); const youtubeLinks = this.extractYoutubeLinks(searchResults); + const result = JSON.stringify(youtubeLinks, null, 2); - return '```json\n' + JSON.stringify(youtubeLinks, null, 2) + '\n```'; - } - - webSearchTool = async (query: string) => { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); + this.callbackMessage({ + taskId: task.id.toString(), + content: '```json\n\n' + result + '\n\n```', + title: task.task, + type: task.skill, + style: 'text', + status: 'complete', + }); - return response?.data.response; - }; + return result; + } extractYoutubeLinks = (searchResults: any[]) => { const youtubeLinks = searchResults diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts index aa797bf9..c3323313 100644 --- a/src/agents/elf/skills/skill.ts +++ b/src/agents/elf/skills/skill.ts @@ -88,7 +88,7 @@ export class Skill { params: LLMParams = {}, ignoreCallback: boolean = false, ): Promise { - const messageCallback = ignoreCallback ? () => {} : this.handleMessage; + const callback = ignoreCallback ? () => {} : this.callbackMessage; const id = uuidv4(); const defaultParams = { modelName: 'gpt-3.5-turbo', @@ -100,40 +100,36 @@ export class Skill { streaming: true, }; const llmParams = { ...defaultParams, ...params }; - const llm = new ChatOpenAI( - { - openAIApiKey: this.apiKeys.openai, - ...llmParams, - callbacks: [ - { - handleLLMNewToken(token: string) { - messageCallback?.({ - id, - content: token, - title: `${task.task}`, - type: task.skill, - icon: '🤖', - taskId: task.id.toString(), - status: 'running', - options: { - dependentTaskIds: task.dependentTaskIds?.join(',') ?? '', - }, - }); - }, + const llm = new ChatOpenAI({ + openAIApiKey: this.apiKeys.openai, + ...llmParams, + callbacks: [ + { + handleLLMNewToken(token: string) { + callback?.({ + id, + content: token, + title: `${task.task}`, + type: task.skill, + icon: task.icon, + taskId: task.id.toString(), + status: 'running', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, + }); }, - ], - }, - // { baseOptions: { signal: this.abortController.signal } }, - ); + }, + ], + }); try { const response = await llm.call([new HumanChatMessage(prompt)]); - messageCallback?.({ - id, + this.callbackMessage({ taskId: task.id.toString(), - content: response.text, + content: '', title: task.task, - icon: '✅', + icon: task.icon, type: task.skill, status: 'complete', options: { @@ -149,4 +145,17 @@ export class Skill { return 'Failed to generate text.'; } } + + callbackMessage = (message: AgentMessage) => { + const baseMessage: AgentMessage = { + id: this.id, + content: '', + icon: this.icon, + type: this.name, + style: 'text', + status: 'running', + }; + const mergedMessage = { ...baseMessage, ...message }; + this.handleMessage(mergedMessage); + }; } diff --git a/src/agents/elf/tools/search/webBrowsing.ts b/src/agents/elf/tools/search/webBrowsing.ts index ed50a114..3c8a1c7d 100644 --- a/src/agents/elf/tools/search/webBrowsing.ts +++ b/src/agents/elf/tools/search/webBrowsing.ts @@ -105,12 +105,14 @@ export const webBrowsing = async ( ); // callback to search logs + message = 'Completed analyzing results.'; const msg: AgentMessage = { id, taskId: task.id.toString(), type: task.skill, content: message, title: task.task, + style: 'log', status: 'complete', }; messageCallback(msg); diff --git a/src/components/Agent/TaskBlock.tsx b/src/components/Agent/TaskBlock.tsx index 0b899b11..04085fe3 100644 --- a/src/components/Agent/TaskBlock.tsx +++ b/src/components/Agent/TaskBlock.tsx @@ -10,10 +10,8 @@ export interface AgentTaskProps { } const renderIcon = (message: AgentMessage, block: Block) => { - return message.style === 'log' + return message.style === 'log' || block.status === 'complete' ? '' - : block.status === 'complete' - ? '✅' : message.icon || getEmoji(message.type); }; @@ -37,7 +35,7 @@ const renderContent = (message: AgentMessage, firstContent: string) => { export const TaskBlock: FC = ({ block }) => { const message = block.messages[0]; - let icon = message.icon || getEmoji(message.type); + const icon = message.icon || getEmoji(message.type); const title = message.taskId ? `${message.taskId}. ${message.title}` : message.title; @@ -68,9 +66,7 @@ export const TaskBlock: FC = ({ block }) => {
{renderIcon(message, block)}
-
- {renderContent(message, firstContent)} -
+
{renderContent(message, firstContent)}
))} From 2b85753759552ed59371df51e2044ee4e880f359 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sun, 13 Aug 2023 10:43:54 +0900 Subject: [PATCH 12/42] Update skils --- src/agents/elf/executer.ts | 2 +- src/agents/elf/registory/skillRegistry.ts | 20 +++++++-------- src/agents/elf/skills/addons/airtableSaver.ts | 19 ++++++++++---- src/agents/elf/skills/presets/codeReader.ts | 8 +++++- src/agents/elf/skills/presets/codeReviewer.ts | 4 ++- src/agents/elf/skills/presets/codeWriter.ts | 3 ++- .../elf/skills/presets/directoryStructure.ts | 9 ++++--- .../elf/skills/presets/objectiveSaver.ts | 4 +-- src/agents/elf/skills/presets/skillSaver.ts | 17 ++++++++++--- src/agents/elf/skills/skill.ts | 25 ++++++++++++++++++- src/components/Agent/AgentView.tsx | 3 ++- 11 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/agents/elf/executer.ts b/src/agents/elf/executer.ts index 8c4dc359..9e728086 100644 --- a/src/agents/elf/executer.ts +++ b/src/agents/elf/executer.ts @@ -92,7 +92,7 @@ export class BabyElfAGI extends Executer { updates: { status: 'running' }, }); this.printer.printTaskExecute(task); - // this.currentStatusCallback(); + const output = await this.taskRegistry.executeTask( i, task, diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index 4617e244..2410659b 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -1,14 +1,14 @@ import { AgentMessage } from '@/types'; import { - // CodeReader, - // CodeReviewer, - // CodeWriter, - // DirectoryStructure, - // SkillSaver, TextCompletion, WebLoader, WebSearch, YoutubeSearch, + CodeReader, + CodeReviewer, + CodeWriter, + DirectoryStructure, + SkillSaver, } from '../skills'; import { Skill } from '../skills/skill'; @@ -68,11 +68,11 @@ export class SkillRegistry { WebLoader, YoutubeSearch, // AirtableSaver, - // CodeReader, - // CodeWriter, - // SkillSaver, - // DirectoryStructure, - // CodeReviewer, + CodeReader, + CodeWriter, + SkillSaver, + DirectoryStructure, + CodeReviewer, ]; return skills; } diff --git a/src/agents/elf/skills/addons/airtableSaver.ts b/src/agents/elf/skills/addons/airtableSaver.ts index 5a352ed2..da35e550 100644 --- a/src/agents/elf/skills/addons/airtableSaver.ts +++ b/src/agents/elf/skills/addons/airtableSaver.ts @@ -1,4 +1,3 @@ -import Airtable from 'airtable'; import { Skill, SkillType } from '../skill'; import { AgentTask } from '@/types'; @@ -17,19 +16,29 @@ export class AirtableSaver extends Skill { async execute( task: AgentTask, - dependentTaskOutputs: any, + dependentTaskOutputs: string, objective: string, ): Promise { if (!this.valid) { return ''; } - const airtable = new Airtable({ apiKey: this.apiKeys['airtable'] }); - const base = airtable.base(this.baseId); const fields = { Notes: dependentTaskOutputs }; // Your fields here + const url = `https://api.airtable.com/v0/${this.baseId}/${this.tableName}`; + const options = { + method: 'POST', + headers: { + Authorization: `Bearer ${this.apiKeys['airtable']}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ fields }), + }; try { - await base(this.tableName).create([{ fields }]); + const response = await fetch(url, options); + if (!response.ok) { + throw new Error('Record creation failed'); + } return 'Record creation successful'; } catch (error: any) { return `Record creation failed: ${error.message}`; diff --git a/src/agents/elf/skills/presets/codeReader.ts b/src/agents/elf/skills/presets/codeReader.ts index 78de89a3..a88d5399 100644 --- a/src/agents/elf/skills/presets/codeReader.ts +++ b/src/agents/elf/skills/presets/codeReader.ts @@ -37,7 +37,9 @@ export class CodeReader extends Skill { try { const response = await fetch( - `/api/local/read-file?filename=${encodeURIComponent(filePath)}`, + `${this.BASE_URL}/api/local/read-file?filename=${encodeURIComponent( + filePath, + )}`, { method: 'GET', }, @@ -47,6 +49,10 @@ export class CodeReader extends Skill { } const fileContent = await response.json(); console.log(`File content:\n${JSON.stringify(fileContent)}`); + this.callbackMessage({ + content: 'Read file successfully.', + status: 'complete', + }); return JSON.stringify(fileContent); } catch (error) { console.error( diff --git a/src/agents/elf/skills/presets/codeReviewer.ts b/src/agents/elf/skills/presets/codeReviewer.ts index aae59138..e1ad0c6a 100644 --- a/src/agents/elf/skills/presets/codeReviewer.ts +++ b/src/agents/elf/skills/presets/codeReviewer.ts @@ -31,7 +31,9 @@ export class CodeReviewer extends Skill { const prompt = this.generatePrompt(dependentTaskOutputs); try { - return this.generateText(prompt, task, { modelName: MODEL_NAME }); + const result = this.generateText(prompt, task, { modelName: MODEL_NAME }); + this.sendCompletionMessage(); + return result; } catch (error) { console.error('Failed to generate text:', error); throw new Error( diff --git a/src/agents/elf/skills/presets/codeWriter.ts b/src/agents/elf/skills/presets/codeWriter.ts index f6ea5e62..483d269c 100644 --- a/src/agents/elf/skills/presets/codeWriter.ts +++ b/src/agents/elf/skills/presets/codeWriter.ts @@ -42,11 +42,12 @@ export class CodeWriter extends Skill { `; try { - return await this.generateText(prompt, task, { + const result = await this.generateText(prompt, task, { modelName: MODEL_NAME, temperature: TEMPERATURE, maxTokens: MAX_TOKENS, }); + return result; } catch (error) { console.error('Error generating text:', error); throw error; diff --git a/src/agents/elf/skills/presets/directoryStructure.ts b/src/agents/elf/skills/presets/directoryStructure.ts index 229a1fa0..db6acabc 100644 --- a/src/agents/elf/skills/presets/directoryStructure.ts +++ b/src/agents/elf/skills/presets/directoryStructure.ts @@ -15,9 +15,12 @@ export class DirectoryStructure extends Skill { dependentTaskOutputs: string, objective: string, ): Promise { - const response = await fetch('/api/local/directory-structure', { - method: 'GET', - }); + const response = await fetch( + `${this.BASE_URL}/api/local/directory-structure`, + { + method: 'GET', + }, + ); if (!response.ok) { throw new Error('Failed to get directory structure'); } diff --git a/src/agents/elf/skills/presets/objectiveSaver.ts b/src/agents/elf/skills/presets/objectiveSaver.ts index b75ba66a..126c09f7 100644 --- a/src/agents/elf/skills/presets/objectiveSaver.ts +++ b/src/agents/elf/skills/presets/objectiveSaver.ts @@ -26,13 +26,13 @@ export class ObjectiveSaver extends Skill { const examplesPath = `data/example_objectives/`; try { - const response = await fetch('/api/local/write-file', { + const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - filename: `${examplesPath}/${filename}`, + filename: `${this.BASE_URL}/${examplesPath}/${filename}`, content: `${code}`, }), }); diff --git a/src/agents/elf/skills/presets/skillSaver.ts b/src/agents/elf/skills/presets/skillSaver.ts index 21d39c4d..e55678e8 100644 --- a/src/agents/elf/skills/presets/skillSaver.ts +++ b/src/agents/elf/skills/presets/skillSaver.ts @@ -33,8 +33,8 @@ export class SkillSaver extends Skill { TASK: ${task.task} CODE: ${code} FILE_NAME:`; - const filename = await this.generateText(filePrompt, task, params); - let skillsPath = `src/agents/babyelfagi/skills/addons`; + const filename = await this.generateText(filePrompt, task, params, true); + let skillsPath = `src/agents/elf/skills/addons`; const dirStructure: string[] = await this.getDirectoryStructure(); const skillPaths = dirStructure.filter((path) => path.includes(filename)); @@ -45,7 +45,7 @@ export class SkillSaver extends Skill { } try { - const response = await fetch('/api/local/write-file', { + const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -58,9 +58,18 @@ export class SkillSaver extends Skill { if (!response.ok) { throw new Error('Failed to save file'); } - return `Code saved successfully: ${filename}`; + const message = `Code saved successfully: ${filename}`; + this.callbackMessage({ + content: message, + status: 'complete', + }); + return message; } catch (error) { console.error('Error saving code.', error); + this.callbackMessage({ + content: 'Error saving code.', + status: 'complete', + }); return 'Error saving code.'; } } diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts index c3323313..8745bab2 100644 --- a/src/agents/elf/skills/skill.ts +++ b/src/agents/elf/skills/skill.ts @@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; export type SkillType = 'normal' | 'dev'; export class Skill { - id: string = uuidv4(); + id: string; name: string = 'base_kill'; descriptionForHuman: string = 'This is the base skill.'; descriptionForModel: string = 'This is the base skill.'; @@ -20,6 +20,8 @@ export class Skill { verbose: boolean; language: string = 'en'; + BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; + // This index signature allows dynamic assignment of properties [key: string]: any; @@ -33,6 +35,7 @@ export class Skill { this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; + this.id = uuidv4(); const missingKeys = this.checkRequiredKeys(apiKeys); if (missingKeys.length > 0) { @@ -82,6 +85,13 @@ export class Skill { throw new Error("Method 'execute' must be implemented"); } + async sendCompletionMessage() { + this.handleMessage({ + content: '', + status: 'complete', + }); + } + async generateText( prompt: string, task: AgentTask, @@ -158,4 +168,17 @@ export class Skill { const mergedMessage = { ...baseMessage, ...message }; this.handleMessage(mergedMessage); }; + + async getDirectoryStructure(): Promise { + const response = await fetch( + `${this.BASE_URL}/api/local/directory-structure`, + { + method: 'GET', + }, + ); + if (!response.ok) { + throw new Error('Failed to get directory structure'); + } + return await response.json(); + } } diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 16947ec6..9b1a5994 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -370,6 +370,7 @@ export const AgentView: FC = () => { const { input, + setInput, agentMessages, isRunning, handleInputChange, @@ -417,7 +418,7 @@ export const AgentView: FC = () => { {(selectedAgent.id === 'babydeeragi' || selectedAgent.id === 'babyelfagi') && ( setObjective(value)} + onClick={(value) => setInput(value)} agent={selectedAgent.id} /> )} From 94136accd9ba1a4ccb58572754824121b581b67a Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sun, 13 Aug 2023 11:10:58 +0900 Subject: [PATCH 13/42] Improve groupMessages function to handle isRunning status --- src/agents/elf/skills/addons/saveReport.ts | 1 + src/components/Agent/AgentView.tsx | 4 ++-- src/utils/message.ts | 11 +++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 src/agents/elf/skills/addons/saveReport.ts diff --git a/src/agents/elf/skills/addons/saveReport.ts b/src/agents/elf/skills/addons/saveReport.ts new file mode 100644 index 00000000..5877f617 --- /dev/null +++ b/src/agents/elf/skills/addons/saveReport.ts @@ -0,0 +1 @@ +There is no code provided in the dependent task output. \ No newline at end of file diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 9b1a5994..6cfc26c9 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -386,9 +386,9 @@ export const AgentView: FC = () => { const [agentBlocks, setAgentBlocks] = useState([]); useEffect(() => { - const newGroupedMessages = groupMessages(agentMessages); + const newGroupedMessages = groupMessages(agentMessages, isRunning); setAgentBlocks(newGroupedMessages); - }, [agentMessages]); + }, [agentMessages, isRunning]); return (
diff --git a/src/utils/message.ts b/src/utils/message.ts index cd3abe64..05d87187 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -387,7 +387,7 @@ export const getTitle = (type?: string) => { } }; -export const groupMessages = (messages: AgentMessage[]) => { +export const groupMessages = (messages: AgentMessage[], isRunning: boolean) => { const messageGroups: Block[] = []; let block: Block | null = null; @@ -419,7 +419,14 @@ export const groupMessages = (messages: AgentMessage[]) => { } }); - console.log(messageGroups); + // if isRunning is false, set all running messageGroups to incomplete + if (!isRunning) { + messageGroups.forEach((messageGroup) => { + if (messageGroup.status === 'running') { + messageGroup.status = 'incomplete'; + } + }); + } return messageGroups; }; From 32f3ac911d3d080e20f6b231a79cbb36413911d6 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sun, 13 Aug 2023 15:10:53 +0900 Subject: [PATCH 14/42] Refactor AgentView to use AgentLoading component --- src/agents/elf/registory/taskRegistry.ts | 1 + src/agents/elf/skills/addons/saveReport.txt | 1 + src/components/Agent/AgentLoading.tsx | 25 ++++++++++++++++ src/components/Agent/AgentView.tsx | 32 +++++++++------------ src/utils/message.ts | 25 ++++++++++++++++ 5 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 src/agents/elf/skills/addons/saveReport.txt create mode 100644 src/components/Agent/AgentLoading.tsx diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts index 1958fe77..42bf7fa1 100644 --- a/src/agents/elf/registory/taskRegistry.ts +++ b/src/agents/elf/registory/taskRegistry.ts @@ -64,6 +64,7 @@ export class TaskRegistry { content: token, type: 'task-list', style: 'log', + status: 'running', }; handleMessage(message); }, diff --git a/src/agents/elf/skills/addons/saveReport.txt b/src/agents/elf/skills/addons/saveReport.txt new file mode 100644 index 00000000..dacc059a --- /dev/null +++ b/src/agents/elf/skills/addons/saveReport.txt @@ -0,0 +1 @@ +There is no code in the dependent task output. \ No newline at end of file diff --git a/src/components/Agent/AgentLoading.tsx b/src/components/Agent/AgentLoading.tsx new file mode 100644 index 00000000..14779fbb --- /dev/null +++ b/src/components/Agent/AgentLoading.tsx @@ -0,0 +1,25 @@ +import { UpdateIcon } from '@radix-ui/react-icons'; +import { FC } from 'react'; + +interface AgentLoadingProps { + message: string; +} + +const AgentLoading: FC = ({ message }) => { + return ( +
+
+
+ +
+
{message}
+
+
+ ); +}; + +export default AgentLoading; diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 6cfc26c9..7cc02329 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -18,7 +18,7 @@ import { AgentMessageHeader } from './AgentMessageHeader'; import { getExportAgentMessage, getMessageBlocks, - loadingAgentMessage, + getAgentLoadingMessage, groupMessages, } from '../../utils/message'; import { BabyAGI } from '@/agents/babyagi'; @@ -37,6 +37,7 @@ import { BabyElfAGI } from '@/agents/babyelfagi/executer'; import { SkillsList } from './SkillList'; import { useAgent } from '@/hooks/useAgent'; import { AgentBlock } from './AgentBlock'; +import AgentLoading from './AgentLoading'; export const AgentView: FC = () => { const [model, setModel] = useState(MODELS[1]); @@ -67,16 +68,6 @@ export const AgentView: FC = () => { } = useExecution(); const { isExecuting, setExecuting } = useExecutionStatus(); - const scrollToBottom = useCallback(() => { - const behavior = isExecuting ? 'smooth' : 'auto'; - messagesEndRef.current?.scrollIntoView({ behavior: behavior }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messageBlocks]); - - useEffect(() => { - scrollToBottom(); - }, [scrollToBottom]); - useEffect(() => { if (selectedExecutionId) { const selectedExecution = executions.find( @@ -102,7 +93,7 @@ export const AgentView: FC = () => { updateExec(updatedExecution); } - const blocks = getMessageBlocks(messages, isExecuting); + const blocks = getMessageBlocks(messages, isRunning); setMessageBlocks(blocks); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -390,13 +381,18 @@ export const AgentView: FC = () => { setAgentBlocks(newGroupedMessages); }, [agentMessages, isRunning]); + const scrollToBottom = useCallback(() => { + const behavior = isRunning ? 'smooth' : 'auto'; + messagesEndRef.current?.scrollIntoView({ behavior: behavior }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agentBlocks, isRunning]); + + useEffect(() => { + scrollToBottom(); + }, [scrollToBottom]); + return (
-
-

- for development use only -

-
{agentMessages.length === 0 ? ( <> { ))} {isRunning && ( - + )}
{ return text; }; + +export const getAgentLoadingMessage = (blocks: Block[]) => { + const runningBlocks = blocks.filter((block) => block.status === 'running'); + const lastMessageTypes = runningBlocks.map( + (block) => block.messages[block.messages.length - 1].type, + ); + const blocksWithTaskId = runningBlocks.filter( + (block) => block.messages[block.messages.length - 1].taskId !== undefined, + ); + const taskExecuteIds = blocksWithTaskId.map( + (block) => block.messages[block.messages.length - 1].taskId, + ); + const execteMessage = `${translate( + 'EXECUTING', + 'message', + )} [${taskExecuteIds.join(', ')}]`; + + if (lastMessageTypes.includes('task-list')) { + return translate('CREATING', 'message'); + } else if (blocksWithTaskId.length > 0) { + return execteMessage; + } else { + return translate('THINKING', 'message'); + } +}; From 7f122bec0b6a63bab9759423322d537206274167 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sun, 13 Aug 2023 16:45:48 +0900 Subject: [PATCH 15/42] Refactor skill loading logic in BabyElfAGI constructor --- src/agents/elf/executer.ts | 12 +++- src/agents/elf/registory/skillRegistry.ts | 13 ++++- src/agents/elf/registory/taskRegistry.ts | 50 ++++++++--------- src/agents/elf/skills/addons/saveReport.ts | 1 - src/agents/elf/skills/addons/saveReport.txt | 1 - src/agents/elf/skills/index.ts | 1 + src/components/Agent/AgentView.tsx | 62 +++++++++++---------- src/pages/api/agent/index.ts | 22 ++++---- src/utils/constants.ts | 4 +- 9 files changed, 94 insertions(+), 72 deletions(-) delete mode 100644 src/agents/elf/skills/addons/saveReport.ts delete mode 100644 src/agents/elf/skills/addons/saveReport.txt diff --git a/src/agents/elf/executer.ts b/src/agents/elf/executer.ts index 9e728086..39b45c2f 100644 --- a/src/agents/elf/executer.ts +++ b/src/agents/elf/executer.ts @@ -1,7 +1,6 @@ -import { AgentStatus, AgentMessage, TaskOutputs } from '@/types'; // You need to define these types +import { AgentMessage, TaskOutputs } from '@/types'; // You need to define these types import { Executer } from '../base/Executer'; import { SkillRegistry, TaskRegistry } from './registory'; -import { translate } from '@/utils/translate'; import { v4 as uuidv4 } from 'uuid'; const REFLECTION = false; // If you want to use reflection, set this to true. now support only client side reflection. @@ -20,6 +19,7 @@ export class BabyElfAGI extends Executer { }, language: string = 'en', verbose: boolean = true, + specifiedSkills: string[] = [], ) { super(objective, modelName, handlers, language); @@ -27,8 +27,14 @@ export class BabyElfAGI extends Executer { this.handlers.handleMessage, verbose, this.language, + specifiedSkills, + ); + const useSpecifiedSkills = specifiedSkills.length > 0; + this.taskRegistry = new TaskRegistry( + this.language, + this.verbose, + useSpecifiedSkills, ); - this.taskRegistry = new TaskRegistry(this.language, this.verbose); } async prepare() { diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index 2410659b..1d7be7ad 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -9,6 +9,7 @@ import { CodeWriter, DirectoryStructure, SkillSaver, + AirtableSaver, } from '../skills'; import { Skill } from '../skills/skill'; @@ -25,6 +26,7 @@ export class SkillRegistry { handleMessage: (message: AgentMessage) => Promise, verbose: boolean = false, language: string = 'en', + specifiedSkills: string[] = [], ) { this.skillClasses = SkillRegistry.getSkillClasses(); this.apiKeys = SkillRegistry.apiKeys; @@ -41,6 +43,15 @@ export class SkillRegistry { this.verbose, this.language, ); + + // If the skill is specified, load it. + if (specifiedSkills.length > 0) { + if (specifiedSkills.includes(skill.name)) { + this.skills.push(skill); + } + continue; + } + if ( skill.type === 'dev' ? process.env.NODE_ENV === 'development' : true ) { @@ -67,7 +78,7 @@ export class SkillRegistry { WebSearch, WebLoader, YoutubeSearch, - // AirtableSaver, + AirtableSaver, CodeReader, CodeWriter, SkillSaver, diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts index 42bf7fa1..3d71c15d 100644 --- a/src/agents/elf/registory/taskRegistry.ts +++ b/src/agents/elf/registory/taskRegistry.ts @@ -10,11 +10,13 @@ export class TaskRegistry { tasks: AgentTask[]; verbose: boolean = false; language: string = 'en'; + useSpecifiedSkills: boolean = false; - constructor(language = 'en', verbose = false) { + constructor(language = 'en', verbose = false, useSpecifiedSkills = false) { this.tasks = []; this.verbose = verbose; this.language = language; + this.useSpecifiedSkills = useSpecifiedSkills; } async createTaskList( @@ -23,7 +25,6 @@ export class TaskRegistry { skillDescriptions: string, modelName: string = 'gpt-3.5-turbo', handleMessage: (message: AgentMessage) => Promise, - abortController?: AbortController, ): Promise { const relevantObjective = await findMostRelevantObjective(objective); const exapmleObjective = relevantObjective.objective; @@ -48,31 +49,28 @@ export class TaskRegistry { const messages = new HumanChatMessage(prompt); let result = ''; - const model = new ChatOpenAI( - { - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - verbose: this.verbose, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - const message: AgentMessage = { - id, - content: token, - type: 'task-list', - style: 'log', - status: 'running', - }; - handleMessage(message); - }, + const model = new ChatOpenAI({ + modelName: this.useSpecifiedSkills ? modelName : 'gpt-4', + temperature: 0, + maxTokens: 1500, + topP: 1, + verbose: this.verbose, + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + const message: AgentMessage = { + id, + content: token, + type: 'task-list', + style: 'log', + status: 'running', + }; + handleMessage(message); }, - ], - }, - { baseOptions: { signal: abortController?.signal } }, - ); + }, + ], + }); try { const response = await model.call([systemMessage, messages]); diff --git a/src/agents/elf/skills/addons/saveReport.ts b/src/agents/elf/skills/addons/saveReport.ts deleted file mode 100644 index 5877f617..00000000 --- a/src/agents/elf/skills/addons/saveReport.ts +++ /dev/null @@ -1 +0,0 @@ -There is no code provided in the dependent task output. \ No newline at end of file diff --git a/src/agents/elf/skills/addons/saveReport.txt b/src/agents/elf/skills/addons/saveReport.txt deleted file mode 100644 index dacc059a..00000000 --- a/src/agents/elf/skills/addons/saveReport.txt +++ /dev/null @@ -1 +0,0 @@ -There is no code in the dependent task output. \ No newline at end of file diff --git a/src/agents/elf/skills/index.ts b/src/agents/elf/skills/index.ts index 3100b5cf..07f7b5de 100644 --- a/src/agents/elf/skills/index.ts +++ b/src/agents/elf/skills/index.ts @@ -9,3 +9,4 @@ export * from './presets/codeWriter'; export * from './presets/codeReviewer'; export * from './presets/webLoader'; export * from './addons/youtubeSearch'; +export * from './addons/airtableSaver'; diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 7cc02329..bc3a7504 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -11,7 +11,6 @@ import { Block, } from '@/types'; import { AgentInput } from './AgentInput'; -import AgentMessage from './AgentMessage'; import { AgentParameter } from './AgentParameter'; import { ProjectTile } from './ProjectTile'; import { AgentMessageHeader } from './AgentMessageHeader'; @@ -23,7 +22,13 @@ import { } from '../../utils/message'; import { BabyAGI } from '@/agents/babyagi'; import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; -import { AGENT, ITERATIONS, MODELS, SETTINGS_KEY } from '@/utils/constants'; +import { + AGENT, + ITERATIONS, + MODELS, + SETTINGS_KEY, + SPECIFIED_SKILLS, +} from '@/utils/constants'; import { toast } from 'sonner'; import { v4 as uuidv4 } from 'uuid'; import { useExecution } from '@/hooks/useExecution'; @@ -33,7 +38,7 @@ import axios from 'axios'; import { taskCompletedNotification } from '@/utils/notification'; import { useTranslation } from 'next-i18next'; import { IntroGuide } from './IntroGuide'; -import { BabyElfAGI } from '@/agents/babyelfagi/executer'; +import { BabyElfAGI } from '@/agents/elf/executer'; import { SkillsList } from './SkillList'; import { useAgent } from '@/hooks/useAgent'; import { AgentBlock } from './AgentBlock'; @@ -334,29 +339,30 @@ export const AgentView: FC = () => { }; const skills = () => { - if (selectedAgent.id === 'babyelfagi') { - const elf = new BabyElfAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - false, - ); - const skills = elf.skillRegistry.getAllSkills(); - const skillInfos = skills.map((skill) => { - const skillInfo = { - name: skill.name, - description: skill.descriptionForHuman, - icon: skill.icon, - badge: skill.type, - }; - return skillInfo; - }); - return skillInfos; - } - return []; + const specificSkills = + selectedAgent.id === 'babydeeragi' ? SPECIFIED_SKILLS : []; + const elf = new BabyElfAGI( + '', + '', + { + handleMessage: async (message) => {}, + handleEnd: async () => {}, + }, + 'en', + false, + specificSkills, + ); + const skills = elf.skillRegistry.getAllSkills(); + const skillInfos = skills.map((skill) => { + const skillInfo = { + name: skill.name, + description: skill.descriptionForHuman, + icon: skill.icon, + badge: skill.type, + }; + return skillInfo; + }); + return skillInfos; }; const { @@ -405,9 +411,7 @@ export const AgentView: FC = () => { agent={selectedAgent} setAgent={setSelectedAgent} /> - {selectedAgent.id === 'babyelfagi' && ( - - )} +
diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts index 9a0373e8..72cd1398 100644 --- a/src/pages/api/agent/index.ts +++ b/src/pages/api/agent/index.ts @@ -1,8 +1,9 @@ import type { NextRequest } from 'next/server'; import { NextApiResponse } from 'next'; -import { LangChainStream, StreamingTextResponse } from 'ai'; +import { StreamingTextResponse } from 'ai'; import { AgentStream } from '@/agents/base/AgentStream'; import { BabyElfAGI } from '@/agents/elf/executer'; +import { SPECIFIED_SKILLS } from '@/utils/constants'; export const config = { runtime: 'edge', @@ -10,16 +11,17 @@ export const config = { export default async function handler(req: NextRequest, res: NextApiResponse) { const { stream, handlers } = AgentStream(); - const { input, id, language } = await req.json(); + const { input, id, language, verbose, agent_id } = await req.json(); - // const llm = new ChatOpenAI({ - // streaming: true, - // verbose: true, - // }); - - // llm.call([new HumanChatMessage(input)], {}, [handlers]).catch(console.error); - - const executer = new BabyElfAGI(input, id, handlers, language || 'en'); + const specifiedSkills = agent_id === 'babydeeragi' ? SPECIFIED_SKILLS : []; + const executer = new BabyElfAGI( + input, + id, + handlers, + language || 'en', + verbose, + specifiedSkills, + ); executer.run(); return new StreamingTextResponse(stream); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 3a5ebe7d..63ab1d55 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -48,7 +48,7 @@ export const AGENT = [ message: '🤖/🔎+📄/🧑‍💻', badge: 'STABLE', }, - { id: 'babyagi', name: 'BabyAGI', icon: '👶', message: '🤖' }, + // { id: 'babyagi', name: 'BabyAGI', icon: '👶', message: '🤖' }, ]; export const THEME = [ @@ -56,3 +56,5 @@ export const THEME = [ { id: 'light', name: 'LIGHT', icon: '🌞' }, { id: 'dark', name: 'DARK', icon: '🌚' }, ]; + +export const SPECIFIED_SKILLS = ['text_completion', 'web_search']; // for BabyDeerAGI From 62ddaf07cce9ca9f999a593a073b73b5d725b6be Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 14 Aug 2023 11:19:02 +0900 Subject: [PATCH 16/42] Convert messages to AgentMessages in AgentView.tsx and message.ts --- src/components/Agent/AgentView.tsx | 8 ++++--- src/utils/message.ts | 36 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index b5e8316f..09567b39 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -19,6 +19,7 @@ import { getMessageBlocks, getAgentLoadingMessage, groupMessages, + convertToAgentMessages, } from '../../utils/message'; import { BabyAGI } from '@/agents/babyagi'; import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; @@ -73,11 +74,11 @@ export const AgentView: FC = () => { (exe) => exe.id === selectedExecutionId, ); if (selectedExecution) { - setMessages(selectedExecution.messages); + const messages = convertToAgentMessages(selectedExecution.messages); + setAgentMessages(messages); } } else { - setMessages([]); - setObjective(''); + reset(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedExecutionId]); @@ -327,6 +328,7 @@ export const AgentView: FC = () => { input, setInput, agentMessages, + setAgentMessages, isRunning, handleInputChange, handleSubmit, diff --git a/src/utils/message.ts b/src/utils/message.ts index 4eb10338..a31329d3 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -9,6 +9,7 @@ import { Block, } from '@/types'; import { translate } from './translate'; +import { v4 as uuidv4 } from 'uuid'; export const setupMessage = ( type: MessageType, @@ -470,3 +471,38 @@ export const getAgentLoadingMessage = (blocks: Block[]) => { return translate('THINKING', 'message'); } }; + +const convertToAgentMessage = (message: Message): AgentMessage => { + // 0 is for objective, 9999 is for finish + const hasTask = message.id !== 0 && message.id !== 9999 && message.id; + const style = message.type === 'search-logs' ? 'log' : 'text'; + // remove task number from task title + const taskTitle = message.text.replace(/^\d+\.\s/, ''); + // objective title + const title = + message.type === 'objective' + ? undefined + : hasTask + ? taskTitle + : message.title; + + return { + id: message.id?.toString() ?? uuidv4(), + taskId: hasTask ? message.id?.toString() : undefined, + type: message.type, + content: message.text, + title, + icon: message.icon, + style: style, + status: 'complete', + options: { + dependentTaskIds: message.dependentTaskIds?.join(', ') ?? '', + }, + }; +}; + +export const convertToAgentMessages = (messages: Message[]): AgentMessage[] => { + return messages + .filter((message) => message.type !== 'task-execute') + .map((message) => convertToAgentMessage(message)); +}; From ba9d25b421a7e7f08ffd0ea689ae5b67cb6f85d8 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 14 Aug 2023 12:16:53 +0900 Subject: [PATCH 17/42] Set agent messages if available, otherwise set agentMessages --- src/agents/base/AgentStream.ts | 58 +++---------------------- src/components/Agent/AgentView.tsx | 49 ++++++++++----------- src/components/Sidebar/ExecutionRow.tsx | 4 +- src/types/index.ts | 3 +- src/utils/constants.ts | 32 +++++++++++++- 5 files changed, 63 insertions(+), 83 deletions(-) diff --git a/src/agents/base/AgentStream.ts b/src/agents/base/AgentStream.ts index dcbe0145..e4d5633a 100644 --- a/src/agents/base/AgentStream.ts +++ b/src/agents/base/AgentStream.ts @@ -1,65 +1,13 @@ -import { AgentMessage } from '@/types'; import { AIStreamCallbacks, createCallbacksTransformer } from 'ai'; +import { AgentMessage } from '@/types'; export function AgentStream(callbacks?: AIStreamCallbacks) { const stream = new TransformStream(); const writer = stream.writable.getWriter(); - const runs = new Set(); - - const handleError = async (e: Error, runId: string) => { - runs.delete(runId); - await writer.ready; - await writer.abort(e); - }; - - const handleStart = async (runId: string) => { - runs.add(runId); - }; - - const handleEnd = async (runId: string) => { - runs.delete(runId); - - if (runs.size === 0) { - await writer.ready; - await writer.close(); - } - }; - return { stream: stream.readable.pipeThrough(createCallbacksTransformer(callbacks)), handlers: { - // handleLLMNewToken: async (token: string) => { - // await writer.ready; - // await writer.write(token); - // }, - // handleLLMStart: async (_llm: any, _prompts: string[], runId: string) => { - // handleStart(runId); - // }, - // handleLLMEnd: async (_output: any, runId: string) => { - // await handleEnd(runId); - // }, - // handleLLMError: async (e: Error, runId: string) => { - // await handleError(e, runId); - // }, - // handleChainStart: async (_chain: any, _inputs: any, runId: string) => { - // handleStart(runId); - // }, - // handleChainEnd: async (_outputs: any, runId: string) => { - // await handleEnd(runId); - // }, - // handleChainError: async (e: Error, runId: string) => { - // await handleError(e, runId); - // }, - // handleToolStart: async (_tool: any, _input: string, runId: string) => { - // handleStart(runId); - // }, - // handleToolEnd: async (_output: string, runId: string) => { - // await handleEnd(runId); - // }, - // handleToolError: async (e: Error, runId: string) => { - // await handleError(e, runId); - // }, handleMessage: async (message: AgentMessage) => { await writer.ready; await writer.write( @@ -72,6 +20,10 @@ export function AgentStream(callbacks?: AIStreamCallbacks) { await writer.ready; await writer.close(); }, + handleError: async (e: Error) => { + await writer.ready; + await writer.abort(e); + }, }, }; } diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 09567b39..1a5c8537 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -74,8 +74,12 @@ export const AgentView: FC = () => { (exe) => exe.id === selectedExecutionId, ); if (selectedExecution) { - const messages = convertToAgentMessages(selectedExecution.messages); - setAgentMessages(messages); + if (selectedExecution.messages) { + const messages = convertToAgentMessages(selectedExecution.messages); + setAgentMessages(messages); + } else { + setAgentMessages(selectedExecution.agentMessages); + } } } else { reset(); @@ -83,22 +87,6 @@ export const AgentView: FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedExecutionId]); - useEffect(() => { - const execution = executions.find((exe) => exe.id === selectedExecutionId); - if (execution) { - const updatedExecution: Execution = { - ...execution, - messages: messages, - }; - updateExec(updatedExecution); - } - - const blocks = getMessageBlocks(messages, isRunning); - setMessageBlocks(blocks); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messages]); - useEffect(() => { setLanguage(i18n.language); }, [i18n]); @@ -107,16 +95,17 @@ export const AgentView: FC = () => { const saveNewData = async () => { const execution: Execution = { id: uuidv4(), - name: objective, + name: input, date: new Date().toISOString(), params: { - objective: objective, + objective: input, model: model, iterations: iterations, firstTask: firstTask, agent: selectedAgent.id as AgentType, }, - messages: messages, + messages: undefined, + agentMessages: agentMessages, }; selectExecution(execution.id); @@ -206,9 +195,9 @@ export const AgentView: FC = () => { let selectedExecution = executions.find( (exe) => exe.id === selectedExecutionId, ); - if (selectedExecution) { - setMessages(selectedExecution.messages); - } + // if (selectedExecution) { + // setMessages(selectedExecution.messages); + // } const feedbackObjective = selectedExecution?.params.objective; const feedbackModel = selectedExecution?.params.model.id; const feedbackAgent = selectedExecution?.params.agent; @@ -343,8 +332,18 @@ export const AgentView: FC = () => { const [agentBlocks, setAgentBlocks] = useState([]); useEffect(() => { + const execution = executions.find((exe) => exe.id === selectedExecutionId); + if (execution) { + const updatedExecution: Execution = { + ...execution, + agentMessages, + }; + updateExec(updatedExecution); + } + const newGroupedMessages = groupMessages(agentMessages, isRunning); setAgentBlocks(newGroupedMessages); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [agentMessages, isRunning]); const scrollToBottom = useCallback(() => { @@ -371,7 +370,7 @@ export const AgentView: FC = () => { agent={selectedAgent} setAgent={setSelectedAgent} /> - + {selectedAgent.id !== 'babyagi' && }
diff --git a/src/components/Sidebar/ExecutionRow.tsx b/src/components/Sidebar/ExecutionRow.tsx index cfcaad02..30e79a96 100644 --- a/src/components/Sidebar/ExecutionRow.tsx +++ b/src/components/Sidebar/ExecutionRow.tsx @@ -3,7 +3,7 @@ import { useExecutionStatus } from '@/hooks/useExecutionStatus'; import { Execution } from '@/types'; import { FC } from 'react'; import { ExtraButton } from './ExtraButton'; -import { AGENT } from '@/utils/constants'; +import { ALL_AGENTS } from '@/utils/constants'; interface ExecutionRowProps { execution: Execution; @@ -23,7 +23,7 @@ export const ExecutionRow: FC = ({ execution }) => { selectExecution(undefined); }; - const agent = AGENT.find((agent) => agent.id === execution.params.agent); + const agent = ALL_AGENTS.find((agent) => agent.id === execution.params.agent); return ( - ) : null} - {!evaluation || evaluation === 'bad' ? ( - - ) : null} -
+
diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index f1b5a1c3..4d82398a 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -1,157 +1,93 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import va from '@vercel/analytics'; -import { - AgentStatus, - AgentType, - Execution, - Message, - MessageBlock, - SelectItem, - UserSettings, - Block, -} from '@/types'; +import { Execution, SelectItem, Block } from '@/types'; import { AgentInput } from './AgentInput'; import { AgentParameter } from './AgentParameter'; import { ProjectTile } from './ProjectTile'; import { AgentMessageHeader } from './AgentMessageHeader'; import { getExportAgentMessage, - getMessageBlocks, getAgentLoadingMessage, groupMessages, convertToAgentMessages, } from '../../utils/message'; -import { BabyAGI } from '@/agents/babyagi'; -import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; -import { AGENT, ITERATIONS, MODELS, SETTINGS_KEY } from '@/utils/constants'; -import { toast } from 'sonner'; -import { v4 as uuidv4 } from 'uuid'; -import { useExecution } from '@/hooks/useExecution'; -import { translate } from '../../utils/translate'; -import axios from 'axios'; -import { taskCompletedNotification } from '@/utils/notification'; +import { AGENT, ITERATIONS, MODELS } from '@/utils/constants'; +import { translate } from '@/utils/translate'; import { useTranslation } from 'next-i18next'; import { IntroGuide } from './IntroGuide'; -import { BabyElfAGI } from '@/agents/elf/executer'; import { SkillsList } from './SkillList'; -import { useAgent } from '@/hooks/useAgent'; import { AgentBlock } from './AgentBlock'; import AgentLoading from './AgentLoading'; -import { useSkills } from '@/hooks/useSkills'; +import { + useAgent, + useSkills, + useExecutionManagement, + useApiKeyCheck, + useNotifications, + useErrorHandler, + useResetAndDeselect, + useClipboard, + useFileDownload, + useFeedback, + useScrollControl, + useCurrentEvaluation, +} from '@/hooks'; export const AgentView: FC = () => { + // Custom hooks + const { i18n } = useTranslation(); + + // useState hooks const [model, setModel] = useState(MODELS[1]); const [iterations, setIterations] = useState(ITERATIONS[0]); const [objective, setObjective] = useState(''); const [firstTask, setFirstTask] = useState( translate('FIRST_TASK_PLACEHOLDER', 'constants'), ); - const [messages, setMessages] = useState([]); - const [messageBlocks, setMessageBlocks] = useState([]); - const [agentStatus, setAgentStatus] = useState({ - type: 'ready', - }); - const [agent, setAgent] = useState( - null, - ); const [selectedAgent, setSelectedAgent] = useState(AGENT[0]); - const { i18n } = useTranslation(); const [language, setLanguage] = useState(i18n.language); - const skills = useSkills(selectedAgent.id); + const [agentBlocks, setAgentBlocks] = useState([]); + // useRef hooks const messagesEndRef = useRef(null); + + // Custom hooks + const skills = useSkills(selectedAgent.id); const { - addExecution, + saveNewData, updateExec, executions, selectedExecutionId, selectExecution, - } = useExecution(); - - useEffect(() => { - if (selectedExecutionId) { - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - if (selectedExecution.messages) { - const messages = convertToAgentMessages(selectedExecution.messages); - setAgentMessages(messages); - } else { - setAgentMessages(selectedExecution.agentMessages); - } - } - } else { - reset(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedExecutionId]); - - useEffect(() => { - setLanguage(i18n.language); - }, [i18n]); - - // manage data - const saveNewData = async () => { - const execution: Execution = { - id: uuidv4(), - name: input, - date: new Date().toISOString(), - params: { - objective: input, - model: model, - iterations: iterations, - firstTask: firstTask, - agent: selectedAgent.id as AgentType, - }, - messages: undefined, - agentMessages: agentMessages, - }; - - selectExecution(execution.id); - await new Promise((resolve) => { - addExecution(execution); - resolve(null); - }); - - return execution; - }; - - // handler functions - const messageHandler = (message: Message) => { - setMessages((currentMessages) => { - if (selectedAgent.id !== 'babyagi') { - // if the message.type and id are the same, overwrite the message - const index = currentMessages.findIndex( - (msg) => msg.type === message.type && msg.id === message.id, - ); - if (index !== -1) { - const newMessages = [...currentMessages]; - newMessages[index] = message; - return newMessages; - } - } - - const updatedMessages = [...currentMessages, message]; - - // show toast notification - if (message.type === 'complete' || message.type === 'end-of-iterations') { - toast.success(translate('ALL_TASKS_COMPLETED_TOAST', 'agent')); - taskCompletedNotification(objective); - } else if (message.type === 'done') { - toast.success(translate('TASK_COMPLETED_TOAST', 'agent')); - } - - return updatedMessages; - }); - }; + } = useExecutionManagement(); + const { checkAndAlertApiKeySetting } = useApiKeyCheck(); + const { notifyTaskCompletion } = useNotifications(); + const { errorHandler } = useErrorHandler(); + const { copyToClipboard } = useClipboard(); + const { downloadFile } = useFileDownload(); + const { currentEvaluation } = useCurrentEvaluation( + executions, + selectedExecutionId, + ); + // Functions const stopHandler = () => { va.track('Stop'); }; const startHandler = async () => { - saveNewData(); + if (checkAndAlertApiKeySetting()) { + return; + } + + saveNewData( + input, + model, + iterations, + firstTask, + selectedAgent, + agentMessages, + ); va.track('Start', { model: model.id, agent: selectedAgent.id, @@ -159,158 +95,20 @@ export const AgentView: FC = () => { }); }; - const clearHandler = () => { - reset(); - selectExecution(undefined); - setAgentStatus({ type: 'ready' }); - - va.track('New'); + const finishHandler = async () => { + notifyTaskCompletion(input); }; const copyHandler = () => { - navigator.clipboard.writeText(getExportAgentMessage(agentBlocks)); - toast.success(translate('COPIED_TO_CLIPBOARD', 'agent')); - - va.track('CopyToClipboard'); + copyToClipboard(getExportAgentMessage(agentBlocks)); }; const downloadHandler = () => { - const element = document.createElement('a'); const filename = objective.length > 0 ? `${objective.replace(/\s/g, '_')}.txt` : 'download.txt'; - const file = new Blob(['\uFEFF' + getExportAgentMessage(agentBlocks)], { - type: 'text/plain;charset=utf-8', - }); - element.href = URL.createObjectURL(file); - element.download = filename; - document.body.appendChild(element); - element.click(); - - va.track('Download'); - }; - - const feedbackHandler = (isGood: boolean) => { - let selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - // if (selectedExecution) { - // setMessages(selectedExecution.messages); - // } - const feedbackObjective = selectedExecution?.params.objective; - const feedbackModel = selectedExecution?.params.model.id; - const feedbackAgent = selectedExecution?.params.agent; - const feedbackIterations = Number(selectedExecution?.params.iterations.id); - - let lastResult = messages - .filter( - (message) => - message.type === 'task-output' || message.type === 'task-result', - ) - .pop()?.text; - if (feedbackAgent === 'babybeeagi') { - lastResult = messages - .filter((message) => message.type === 'task-result-summary') - .pop()?.text; - } - const lastTaskList = messages - .filter((message) => message.type === 'task-list') - .pop()?.text; - const sessionSummary = messages - .filter((message) => message.type === 'session-summary') - .pop()?.text; - const iterationNumber = messages.filter( - (message) => message.type === 'done', - ).length; - const finished = - messages.filter( - (message) => - message.type === 'complete' || message.type === 'end-of-iterations', - ).length > 0; - const output = getExportAgentMessage(agentBlocks); - - axios.post('/api/feedback', { - objective: feedbackObjective, - evaluation: isGood ? 'good' : 'bad', - model: feedbackModel, - agent: feedbackAgent, - iterations: feedbackIterations, - last_result: lastResult, - task_list: lastTaskList, - session_summary: sessionSummary, - iteration_number: iterationNumber, - finished: finished, - output: output, - }); - - toast.success(translate('FEEDBACK_SUBMITTED_TOAST', 'constants')); - - // update execution - if (selectedExecution) { - selectedExecution.evaluation = isGood ? 'good' : 'bad'; - updateExec(selectedExecution); - } - }; - - const userInputHandler = async (id: number, text: string) => { - if (agent instanceof BabyDeerAGI) { - agent.userInput(id, text); - } - }; - - const needSettingsAlert = () => { - const useUserApiKey = process.env.NEXT_PUBLIC_USE_USER_API_KEY; - if (useUserApiKey === 'false') { - return false; - } - - const userSettings = localStorage.getItem(SETTINGS_KEY); - if (userSettings) { - const { openAIApiKey } = JSON.parse(userSettings) as UserSettings; - if (openAIApiKey && openAIApiKey?.length > 0) { - return false; - } - } - return true; - }; - - const enabledGPT4 = async () => { - const userSettings = localStorage.getItem(SETTINGS_KEY); - if (!userSettings) { - return false; - } - - const { enabledGPT4 } = JSON.parse(userSettings) as UserSettings; - if (enabledGPT4 === undefined) { - return true; // If no value is given, its enabled by default - } - - return enabledGPT4; - }; - - const currentEvaluation = () => { - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - return selectedExecution.evaluation; - } - return undefined; - }; - - const currentAgentId = () => { - if (isRunning) { - return selectedAgent.id; - } - - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - return selectedExecution.params.agent; - } - return undefined; + downloadFile(filename, getExportAgentMessage(agentBlocks)); }; const { @@ -329,9 +127,45 @@ export const AgentView: FC = () => { modelName: model.id, onSubmit: startHandler, onCancel: stopHandler, + onFinish: finishHandler, + onError: errorHandler, }); + const { clearHandler } = useResetAndDeselect(reset, selectExecution); + const { feedbackHandler } = useFeedback( + updateExec, + executions, + selectedExecutionId, + agentMessages, + setAgentMessages, + ); + const { scrollToBottom } = useScrollControl( + messagesEndRef, + agentBlocks, + isRunning, + ); - const [agentBlocks, setAgentBlocks] = useState([]); + useEffect(() => { + if (selectedExecutionId) { + const selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + if (selectedExecution.messages) { + const messages = convertToAgentMessages(selectedExecution.messages); + setAgentMessages(messages); + } else { + setAgentMessages(selectedExecution.agentMessages); + } + } + } else { + reset(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedExecutionId]); + + useEffect(() => { + setLanguage(i18n.language); + }, [i18n]); useEffect(() => { const execution = executions.find((exe) => exe.id === selectedExecutionId); @@ -348,12 +182,6 @@ export const AgentView: FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [agentMessages, isRunning]); - const scrollToBottom = useCallback(() => { - const behavior = isRunning ? 'smooth' : 'auto'; - messagesEndRef.current?.scrollIntoView({ behavior: behavior }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [agentBlocks, isRunning]); - useEffect(() => { scrollToBottom(); }, [scrollToBottom]); diff --git a/src/components/Agent/FeedbackButtons.tsx b/src/components/Agent/FeedbackButtons.tsx new file mode 100644 index 00000000..b10b879b --- /dev/null +++ b/src/components/Agent/FeedbackButtons.tsx @@ -0,0 +1,37 @@ +import { FC } from 'react'; +import { ThumbsUp, ThumbsDown } from 'react-feather'; + +type FeedbackButtonsProps = { + handleFeedback: (value: boolean) => void; + evaluation?: 'good' | 'bad'; +}; + +export const FeedbackButtons: FC = ({ + handleFeedback, + evaluation, +}) => ( +
+ {!evaluation || evaluation === 'good' ? ( + + ) : null} + {!evaluation || evaluation === 'bad' ? ( + + ) : null} +
+); diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 00000000..539ca50e --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,12 @@ +export { useAgent } from './useAgent'; +export { useSkills } from './useSkills'; +export { useExecutionManagement } from './useExecutionManagement'; +export { useApiKeyCheck } from './useApiKeyCheck'; +export { useNotifications } from './useNotifications'; +export { useErrorHandler } from './useErrorHandler'; +export { useResetAndDeselect } from './useResetAndDeselect'; +export { useClipboard } from './useClipboard'; +export { useFileDownload } from './useFileDownload'; +export { useFeedback } from './useFeedback'; +export { useScrollControl } from './useScrollControl'; +export { useCurrentEvaluation } from './useCurrentEvaluation'; diff --git a/src/hooks/useApiKeyCheck.ts b/src/hooks/useApiKeyCheck.ts new file mode 100644 index 00000000..577bfa1a --- /dev/null +++ b/src/hooks/useApiKeyCheck.ts @@ -0,0 +1,32 @@ +// src/hooks/useApiKeyCheck.ts +import { SETTINGS_KEY } from '@/utils/constants'; +import { UserSettings } from '@/types'; +import { translate } from '../utils/translate'; + +export const useApiKeyCheck = () => { + const checkApiKeySetting = () => { + const useUserApiKey = process.env.NEXT_PUBLIC_USE_USER_API_KEY; + if (useUserApiKey === 'false') { + return false; + } + + const userSettings = localStorage.getItem(SETTINGS_KEY); + if (userSettings) { + const { openAIApiKey } = JSON.parse(userSettings) as UserSettings; + if (openAIApiKey && openAIApiKey?.length > 0) { + return false; + } + } + return true; + }; + + const checkAndAlertApiKeySetting = () => { + if (checkApiKeySetting()) { + alert(translate('ALERT_SET_UP_API_KEY', 'agent')); + return true; + } + return false; + }; + + return { checkAndAlertApiKeySetting }; +}; diff --git a/src/hooks/useClipboard.tsx b/src/hooks/useClipboard.tsx new file mode 100644 index 00000000..8a6f92b8 --- /dev/null +++ b/src/hooks/useClipboard.tsx @@ -0,0 +1,14 @@ +// src/hooks/useClipboard.ts +import { toast } from 'sonner'; +import va from '@vercel/analytics'; +import { translate } from '../utils/translate'; + +export const useClipboard = () => { + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + toast.success(translate('COPIED_TO_CLIPBOARD', 'agent')); + va.track('CopyToClipboard'); + }; + + return { copyToClipboard }; +}; diff --git a/src/hooks/useCurrentEvaluation.ts b/src/hooks/useCurrentEvaluation.ts new file mode 100644 index 00000000..aacaebb7 --- /dev/null +++ b/src/hooks/useCurrentEvaluation.ts @@ -0,0 +1,18 @@ +import { Execution } from '@/types'; + +export const useCurrentEvaluation = ( + executions: Execution[], + selectedExecutionId: string | undefined, +) => { + const currentEvaluation = () => { + const selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + return selectedExecution.evaluation; + } + return undefined; + }; + + return { currentEvaluation }; +}; diff --git a/src/hooks/useErrorHandler.ts b/src/hooks/useErrorHandler.ts new file mode 100644 index 00000000..10a5ccc3 --- /dev/null +++ b/src/hooks/useErrorHandler.ts @@ -0,0 +1,13 @@ +import va from '@vercel/analytics'; +import { toast } from 'sonner'; + +export const useErrorHandler = () => { + const errorHandler = (error: Event | ErrorEvent) => { + const errorMessage = + error instanceof ErrorEvent ? error.message : 'Unknown Error'; + toast.error(errorMessage); + va.track('Error', { error: errorMessage }); + }; + + return { errorHandler }; +}; diff --git a/src/hooks/useExecutionManagement.tsx b/src/hooks/useExecutionManagement.tsx new file mode 100644 index 00000000..fa2170dc --- /dev/null +++ b/src/hooks/useExecutionManagement.tsx @@ -0,0 +1,55 @@ +// src/hooks/useExecutionManagement.ts +import { v4 as uuidv4 } from 'uuid'; +import { useExecution } from '@/hooks/useExecution'; +import { AgentType, SelectItem } from '@/types'; +import { AgentMessage } from '../types'; + +export const useExecutionManagement = () => { + const { + addExecution, + updateExec, + executions, + selectedExecutionId, + selectExecution, + } = useExecution(); + + const saveNewData = async ( + input: string, + model: SelectItem, + iterations: SelectItem, + firstTask: string, + selectedAgent: SelectItem, + agentMessages: AgentMessage[], + ) => { + const execution = { + id: uuidv4(), + name: input, + date: new Date().toISOString(), + params: { + objective: input, + model: model, + iterations: iterations, + firstTask: firstTask, + agent: selectedAgent.id as AgentType, + }, + messages: undefined, + agentMessages: agentMessages, + }; + + selectExecution(execution.id); + await new Promise((resolve) => { + addExecution(execution); + resolve(null); + }); + + return execution; + }; + + return { + saveNewData, + updateExec, + executions, + selectedExecutionId, + selectExecution, + }; +}; diff --git a/src/hooks/useFeedback.ts b/src/hooks/useFeedback.ts new file mode 100644 index 00000000..a62c265c --- /dev/null +++ b/src/hooks/useFeedback.ts @@ -0,0 +1,61 @@ +// src/hooks/useFeedback.ts +import axios from 'axios'; +import { toast } from 'sonner'; +import { translate } from '../utils/translate'; +import { AgentMessage, Execution } from '@/types'; + +export const useFeedback = ( + updateExec: Function, + executions: Execution[], + selectedExecutionId: string | undefined, + agentMessages: AgentMessage[], + setAgentMessages: Function, +) => { + const feedbackHandler = (isGood: boolean) => { + let selectedExecution = executions.find( + (exe) => exe.id === selectedExecutionId, + ); + if (selectedExecution) { + setAgentMessages(selectedExecution.agentMessages); + } + const feedbackObjective = selectedExecution?.params.objective; + const feedbackModel = selectedExecution?.params.model.id; + const feedbackAgent = selectedExecution?.params.agent; + const feedbackIterations = Number(selectedExecution?.params.iterations.id); + + let lastResult = agentMessages + .filter((message) => message.type === 'result') + .pop()?.content; + const lastTaskList = agentMessages + .filter((message) => message.type === 'task-list') + .pop()?.content; + const sessionSummary = agentMessages + .filter((message) => message.type === 'session-summary') + .pop()?.content; + const finished = lastResult !== undefined; + const output = sessionSummary; + + axios.post('/api/feedback', { + objective: feedbackObjective, + evaluation: isGood ? 'good' : 'bad', + model: feedbackModel, + agent: feedbackAgent, + iterations: feedbackIterations, + last_result: lastResult, + task_list: lastTaskList, + session_summary: sessionSummary, + finished: finished, + output: output, + }); + + toast.success(translate('FEEDBACK_SUBMITTED_TOAST', 'constants')); + + // update execution + if (selectedExecution) { + selectedExecution.evaluation = isGood ? 'good' : 'bad'; + updateExec(selectedExecution); + } + }; + + return { feedbackHandler }; +}; diff --git a/src/hooks/useFileDownload.tsx b/src/hooks/useFileDownload.tsx new file mode 100644 index 00000000..724fc560 --- /dev/null +++ b/src/hooks/useFileDownload.tsx @@ -0,0 +1,19 @@ +// src/hooks/useFileDownload.ts +import va from '@vercel/analytics'; + +export const useFileDownload = () => { + const downloadFile = (filename: string, content: string) => { + const element = document.createElement('a'); + const file = new Blob(['\uFEFF' + content], { + type: 'text/plain;charset=utf-8', + }); + element.href = URL.createObjectURL(file); + element.download = filename; + document.body.appendChild(element); + element.click(); + + va.track('Download'); + }; + + return { downloadFile }; +}; diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts new file mode 100644 index 00000000..8201ee1e --- /dev/null +++ b/src/hooks/useNotifications.ts @@ -0,0 +1,13 @@ +// src/hooks/useNotifications.ts +import { toast } from 'sonner'; +import { taskCompletedNotification } from '@/utils/notification'; +import { translate } from '../utils/translate'; + +export const useNotifications = () => { + const notifyTaskCompletion = (input: string) => { + toast.success(translate('ALL_TASKS_COMPLETED_TOAST', 'agent')); + taskCompletedNotification(input); + }; + + return { notifyTaskCompletion }; +}; diff --git a/src/hooks/useResetAndDeselect.tsx b/src/hooks/useResetAndDeselect.tsx new file mode 100644 index 00000000..4fcb6d77 --- /dev/null +++ b/src/hooks/useResetAndDeselect.tsx @@ -0,0 +1,16 @@ +// src/hooks/useResetAndDeselect.ts +import va from '@vercel/analytics'; + +export const useResetAndDeselect = ( + reset: Function, + selectExecution: Function, +) => { + const clearHandler = () => { + reset(); + selectExecution(undefined); + + va.track('New'); + }; + + return { clearHandler }; +}; diff --git a/src/hooks/useScrollControl.tsx b/src/hooks/useScrollControl.tsx new file mode 100644 index 00000000..65ae1071 --- /dev/null +++ b/src/hooks/useScrollControl.tsx @@ -0,0 +1,16 @@ +// src/hooks/useScrollControl.ts +import { useCallback } from 'react'; + +export const useScrollControl = ( + messagesEndRef: React.RefObject, + agentBlocks: any[], + isRunning: boolean, +) => { + const scrollToBottom = useCallback(() => { + const behavior = isRunning ? 'smooth' : 'auto'; + messagesEndRef.current?.scrollIntoView({ behavior: behavior }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [agentBlocks, isRunning]); + + return { scrollToBottom }; +}; From 38fbc5761df25f7d5dd1037069c0006f83de5a93 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Thu, 17 Aug 2023 17:32:11 +0900 Subject: [PATCH 21/42] Add abort signal to agent executer --- package-lock.json | 92 +++++++++---------- package.json | 2 +- src/agents/base/AgentStream.ts | 20 ++++ src/agents/base/Executer.ts | 4 + src/agents/elf/executer.ts | 45 ++++++--- src/agents/elf/registory/skillRegistry.ts | 5 +- src/agents/elf/registory/taskRegistry.ts | 52 ++++++----- src/agents/elf/skills/presets/webLoader.ts | 1 + src/agents/elf/skills/presets/webSearch.ts | 4 +- src/agents/elf/skills/skill.ts | 48 +++++----- .../elf/tools/search/largeTextExtract.ts | 2 + src/agents/elf/tools/search/webBrowsing.ts | 5 + src/agents/elf/tools/textCompletionTool.ts | 52 ++++++----- src/hooks/useAgent.ts | 3 +- src/pages/api/agent/index.ts | 13 ++- 15 files changed, 215 insertions(+), 133 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d7e3a91..8d307de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "install": "^0.13.0", "langchain": "^0.0.64", "lodash": "^4.17.21", - "next": "13.4.8", + "next": "^13.4.16", "next-i18next": "^13.2.2", "next-themes": "^0.2.1", "npm": "^9.6.6", @@ -417,9 +417,9 @@ "peer": true }, "node_modules/@next/env": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.8.tgz", - "integrity": "sha512-twuSf1klb3k9wXI7IZhbZGtFCWvGD4wXTY2rmvzIgVhXhs7ISThrbNyutBx3jWIL8Y/Hk9+woytFz5QsgtcRKQ==" + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.16.tgz", + "integrity": "sha512-pCU0sJBqdfKP9mwDadxvZd+eLz3fZrTlmmDHY12Hdpl3DD0vy8ou5HWKVfG0zZS6tqhL4wnQqRbspdY5nqa7MA==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.2.4", @@ -430,9 +430,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.8.tgz", - "integrity": "sha512-MSFplVM4dTWOuKAUv0XR9gY7AWtMSBu9os9f+kp+s5rWhM1I2CdR3obFttd6366nS/W/VZxbPM5oEIdlIa46zA==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.16.tgz", + "integrity": "sha512-Rl6i1uUq0ciRa3VfEpw6GnWAJTSKo9oM2OrkGXPsm7rMxdd2FR5NkKc0C9xzFCI4+QtmBviWBdF2m3ur3Nqstw==", "cpu": [ "arm64" ], @@ -445,9 +445,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.8.tgz", - "integrity": "sha512-Reox+UXgonon9P0WNDE6w85DGtyBqGitl/ryznOvn6TvfxEaZIpTgeu3ZrJLU9dHSMhiK7YAM793mE/Zii2/Qw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.16.tgz", + "integrity": "sha512-o1vIKYbZORyDmTrPV1hApt9NLyWrS5vr2p5hhLGpOnkBY1cz6DAXjv8Lgan8t6X87+83F0EUDlu7klN8ieZ06A==", "cpu": [ "x64" ], @@ -460,9 +460,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.8.tgz", - "integrity": "sha512-kdyzYvAYtqQVgzIKNN7e1rLU8aZv86FDSRqPlOkKZlvqudvTO0iohuTPmnEEDlECeBM6qRPShNffotDcU/R2KA==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.16.tgz", + "integrity": "sha512-JRyAl8lCfyTng4zoOmE6hNI2f1MFUr7JyTYCHl1RxX42H4a5LMwJhDVQ7a9tmDZ/yj+0hpBn+Aan+d6lA3v0UQ==", "cpu": [ "arm64" ], @@ -475,9 +475,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.8.tgz", - "integrity": "sha512-oWxx4yRkUGcR81XwbI+T0zhZ3bDF6V1aVLpG+C7hSG50ULpV8gC39UxVO22/bv93ZlcfMY4zl8xkz9Klct6dpQ==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.16.tgz", + "integrity": "sha512-9gqVqNzUMWbUDgDiND18xoUqhwSm2gmksqXgCU0qaOKt6oAjWz8cWYjgpPVD0WICKFylEY/gvPEP1fMZDVFZ/g==", "cpu": [ "arm64" ], @@ -490,9 +490,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.8.tgz", - "integrity": "sha512-anhtvuO6eE9YRhYnaEGTfbpH3L5gT/9qPFcNoi6xS432r/4DAtpJY8kNktqkTVevVIC/pVumqO8tV59PR3zbNg==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.16.tgz", + "integrity": "sha512-KcQGwchAKmZVPa8i5PLTxvTs1/rcFnSltfpTm803Tr/BtBV3AxCkHLfhtoyVtVzx/kl/oue8oS+DSmbepQKwhw==", "cpu": [ "x64" ], @@ -505,9 +505,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.8.tgz", - "integrity": "sha512-aR+J4wWfNgH1DwCCBNjan7Iumx0lLtn+2/rEYuhIrYLY4vnxqSVGz9u3fXcgUwo6Q9LT8NFkaqK1vPprdq+BXg==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.16.tgz", + "integrity": "sha512-2RbMZNxYnJmW8EPHVBsGZPq5zqWAyBOc/YFxq/jIQ/Yn3RMFZ1dZVCjtIcsiaKmgh7mjA/W0ApbumutHNxRqqQ==", "cpu": [ "x64" ], @@ -520,9 +520,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.8.tgz", - "integrity": "sha512-OWBKIrJwQBTqrat0xhxEB/jcsjJR3+diD9nc/Y8F1mRdQzsn4bPsomgJyuqPVZs6Lz3K18qdIkvywmfSq75SsQ==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.16.tgz", + "integrity": "sha512-thDcGonELN7edUKzjzlHrdoKkm7y8IAdItQpRvvMxNUXa4d9r0ElofhTZj5emR7AiXft17hpen+QAkcWpqG7Jg==", "cpu": [ "arm64" ], @@ -535,9 +535,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.8.tgz", - "integrity": "sha512-agiPWGjUndXGTOn4ChbKipQXRA6/UPkywAWIkx7BhgGv48TiJfHTK6MGfBoL9tS6B4mtW39++uy0wFPnfD0JWg==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.16.tgz", + "integrity": "sha512-f7SE1Mo4JAchUWl0LQsbtySR9xCa+x55C0taetjUApKtcLR3AgAjASrrP+oE1inmLmw573qRnE1eZN8YJfEBQw==", "cpu": [ "ia32" ], @@ -550,9 +550,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.8.tgz", - "integrity": "sha512-UIRKoByVKbuR6SnFG4JM8EMFlJrfEGuUQ1ihxzEleWcNwRMMiVaCj1KyqfTOW8VTQhJ0u8P1Ngg6q1RwnIBTtw==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.16.tgz", + "integrity": "sha512-WamDZm1M/OEM4QLce3lOmD1XdLEl37zYZwlmOLhmF7qYJ2G6oYm9+ejZVv+LakQIsIuXhSpVlOvrxIAHqwRkPQ==", "cpu": [ "x64" ], @@ -5686,11 +5686,11 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.8.tgz", - "integrity": "sha512-lxUjndYKjZHGK3CWeN2RI+/6ni6EUvjiqGWXAYPxUfGIdFGQ5XoisrqAJ/dF74aP27buAfs8MKIbIMMdxjqSBg==", + "version": "13.4.16", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.16.tgz", + "integrity": "sha512-1xaA/5DrfpPu0eV31Iro7JfPeqO8uxQWb1zYNTe+KDKdzqkAGapLcDYHMLNKXKB7lHjZ7LfKUOf9dyuzcibrhA==", "dependencies": { - "@next/env": "13.4.8", + "@next/env": "13.4.16", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -5706,19 +5706,18 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.8", - "@next/swc-darwin-x64": "13.4.8", - "@next/swc-linux-arm64-gnu": "13.4.8", - "@next/swc-linux-arm64-musl": "13.4.8", - "@next/swc-linux-x64-gnu": "13.4.8", - "@next/swc-linux-x64-musl": "13.4.8", - "@next/swc-win32-arm64-msvc": "13.4.8", - "@next/swc-win32-ia32-msvc": "13.4.8", - "@next/swc-win32-x64-msvc": "13.4.8" + "@next/swc-darwin-arm64": "13.4.16", + "@next/swc-darwin-x64": "13.4.16", + "@next/swc-linux-arm64-gnu": "13.4.16", + "@next/swc-linux-arm64-musl": "13.4.16", + "@next/swc-linux-x64-gnu": "13.4.16", + "@next/swc-linux-x64-musl": "13.4.16", + "@next/swc-win32-arm64-msvc": "13.4.16", + "@next/swc-win32-ia32-msvc": "13.4.16", + "@next/swc-win32-x64-msvc": "13.4.16" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "fibers": ">= 3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -5727,9 +5726,6 @@ "@opentelemetry/api": { "optional": true }, - "fibers": { - "optional": true - }, "sass": { "optional": true } diff --git a/package.json b/package.json index b2d0b2c4..ad468fd6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "install": "^0.13.0", "langchain": "^0.0.64", "lodash": "^4.17.21", - "next": "13.4.8", + "next": "^13.4.16", "next-i18next": "^13.2.2", "next-themes": "^0.2.1", "npm": "^9.6.6", diff --git a/src/agents/base/AgentStream.ts b/src/agents/base/AgentStream.ts index e4d5633a..f6a1c936 100644 --- a/src/agents/base/AgentStream.ts +++ b/src/agents/base/AgentStream.ts @@ -4,11 +4,24 @@ import { AgentMessage } from '@/types'; export function AgentStream(callbacks?: AIStreamCallbacks) { const stream = new TransformStream(); const writer = stream.writable.getWriter(); + let observers: ((isActive: boolean) => void)[] = []; + + let wasActive: boolean | null = null; + const isWriterActive = () => writer.desiredSize !== null; + const notifyObservers = () => { + const isActive = isWriterActive(); + if (wasActive !== isActive) { + observers.forEach((observer) => observer(isActive)); + wasActive = isActive; + } + }; return { stream: stream.readable.pipeThrough(createCallbacksTransformer(callbacks)), handlers: { handleMessage: async (message: AgentMessage) => { + notifyObservers(); + if (!isWriterActive()) return; await writer.ready; await writer.write( `${JSON.stringify({ @@ -17,13 +30,20 @@ export function AgentStream(callbacks?: AIStreamCallbacks) { ); }, handleEnd: async () => { + notifyObservers(); + if (!isWriterActive()) return; await writer.ready; await writer.close(); }, handleError: async (e: Error) => { + notifyObservers(); + if (!isWriterActive()) return; await writer.ready; await writer.abort(e); }, }, + addObserver: (observer: (isActive: boolean) => void) => { + observers.push(observer); + }, }; } diff --git a/src/agents/base/Executer.ts b/src/agents/base/Executer.ts index 3186d27e..8301e602 100644 --- a/src/agents/base/Executer.ts +++ b/src/agents/base/Executer.ts @@ -10,6 +10,7 @@ export class Executer { }; language: string; verbose: boolean; + signal?: AbortSignal; printer: Printer; taskList: AgentTask[] = []; @@ -23,12 +24,14 @@ export class Executer { }, language: string = 'en', varbose: boolean = false, + signal?: AbortSignal, ) { this.objective = objective; this.modelName = modelName; this.handlers = handlers; this.language = language; this.verbose = varbose; + this.signal = signal; this.printer = new Printer(this.handlers.handleMessage, this.verbose); } @@ -47,6 +50,7 @@ export class Executer { async loop() {} async finishup() { + if (this.signal?.aborted) return; // Objective completed this.printer.printAllTaskCompleted(); this.handlers.handleEnd(); diff --git a/src/agents/elf/executer.ts b/src/agents/elf/executer.ts index e729f4ce..187f7c24 100644 --- a/src/agents/elf/executer.ts +++ b/src/agents/elf/executer.ts @@ -18,11 +18,17 @@ export class BabyElfAGI extends Executer { handleEnd: () => Promise; }, language: string = 'en', - verbose: boolean = true, + verbose: boolean = false, specifiedSkills: string[] = [], userApiKey?: string, + signal?: AbortSignal, ) { - super(objective, modelName, handlers, language, verbose); + super(objective, modelName, handlers, language, verbose, signal); + + signal?.addEventListener('abort', () => { + this.verbose && + console.log('Abort signal received. Stopping execution...'); + }); this.skillRegistry = new SkillRegistry( this.handlers.handleMessage, @@ -30,13 +36,16 @@ export class BabyElfAGI extends Executer { this.language, specifiedSkills, userApiKey, + this.signal, ); + const useSpecifiedSkills = specifiedSkills.length > 0; this.taskRegistry = new TaskRegistry( this.language, this.verbose, useSpecifiedSkills, userApiKey, + this.signal, ); } @@ -62,11 +71,11 @@ export class BabyElfAGI extends Executer { for (let task of this.taskRegistry.tasks) { taskOutputs[task.id] = { completed: false, output: undefined }; } - // Loop until all tasks are completed - while (!Object.values(taskOutputs).every((task) => task.completed)) { - // this.handlers.handleMessage({ type: 'preparing' }); - + while ( + !this.signal?.aborted && + !Object.values(taskOutputs).every((task) => task.completed) + ) { // Get the tasks that are ready to be executed const tasks = this.taskRegistry.getTasks(); @@ -102,13 +111,19 @@ export class BabyElfAGI extends Executer { }); this.printer.printTaskExecute(task); - const output = await this.taskRegistry.executeTask( - i, - task, - taskOutputs, - this.objective, - this.skillRegistry, - ); + let output = ''; + try { + output = await this.taskRegistry.executeTask( + i, + task, + taskOutputs, + this.objective, + this.skillRegistry, + ); + } catch (error) { + console.error(error); + return; + } taskOutputs[task.id] = { completed: true, output: output }; this.taskRegistry.updateTasks({ @@ -151,6 +166,8 @@ export class BabyElfAGI extends Executer { } async finishup() { + super.finishup(); + const tasks = this.taskRegistry.getTasks(); const lastTask = tasks[tasks.length - 1]; this.handlers.handleMessage({ @@ -167,4 +184,6 @@ export class BabyElfAGI extends Executer { super.finishup(); } + + async close() {} } diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts index 89d4636c..a3396ce6 100644 --- a/src/agents/elf/registory/skillRegistry.ts +++ b/src/agents/elf/registory/skillRegistry.ts @@ -18,6 +18,7 @@ export class SkillRegistry { skills: Skill[] = []; apiKeys: { [key: string]: string }; userApiKey?: string; + signal?: AbortSignal; // for UI handleMessage: (message: AgentMessage) => Promise; verbose: boolean; @@ -29,11 +30,12 @@ export class SkillRegistry { language: string = 'en', specifiedSkills: string[] = [], userApiKey?: string, + signal?: AbortSignal, ) { this.skillClasses = SkillRegistry.getSkillClasses(); this.apiKeys = SkillRegistry.apiKeys; this.userApiKey = userApiKey; - // + this.signal = signal; this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; @@ -49,6 +51,7 @@ export class SkillRegistry { this.handleMessage, this.verbose, this.language, + this.signal, ); // If the skill is specified, load it. diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts index 8172eb1e..97e719de 100644 --- a/src/agents/elf/registory/taskRegistry.ts +++ b/src/agents/elf/registory/taskRegistry.ts @@ -4,26 +4,27 @@ import { parseTasks } from '@/utils/task'; import { HumanChatMessage, SystemChatMessage } from 'langchain/schema'; import { SkillRegistry } from './skillRegistry'; import { findMostRelevantObjective } from '@/utils/elf/objective'; - export class TaskRegistry { tasks: AgentTask[]; verbose: boolean = false; language: string = 'en'; useSpecifiedSkills: boolean = false; userApiKey?: string; - abortController?: AbortController; + signal?: AbortSignal; constructor( language = 'en', verbose = false, useSpecifiedSkills = false, userApiKey?: string, + signal?: AbortSignal, ) { this.tasks = []; this.verbose = verbose; this.language = language; this.userApiKey = userApiKey; this.useSpecifiedSkills = useSpecifiedSkills; + this.signal = signal; } async createTaskList( @@ -60,29 +61,32 @@ export class TaskRegistry { const messages = new HumanChatMessage(prompt); let result = ''; - const model = new ChatOpenAI({ - openAIApiKey: this.userApiKey, - modelName: this.useSpecifiedSkills ? modelName : 'gpt-4', - temperature: 0, - maxTokens: 1500, - topP: 1, - verbose: false, // You can set this to true to see the lanchain logs - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - const message: AgentMessage = { - id, - content: token, - type: 'task-list', - style: 'log', - status: 'running', - }; - handleMessage(message); + const model = new ChatOpenAI( + { + openAIApiKey: this.userApiKey, + modelName: this.useSpecifiedSkills ? modelName : 'gpt-4', + temperature: 0, + maxTokens: 1500, + topP: 1, + verbose: false, // You can set this to true to see the lanchain logs + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + const message: AgentMessage = { + id, + content: token, + type: 'task-list', + style: 'log', + status: 'running', + }; + handleMessage(message); + }, }, - }, - ], - }); + ], + }, + { baseOptions: { signal: this.signal } }, + ); try { const response = await model.call([systemMessage, messages]); diff --git a/src/agents/elf/skills/presets/webLoader.ts b/src/agents/elf/skills/presets/webLoader.ts index 9c0b6f65..29d224e5 100644 --- a/src/agents/elf/skills/presets/webLoader.ts +++ b/src/agents/elf/skills/presets/webLoader.ts @@ -103,6 +103,7 @@ export class WebLoader extends Skill { task, this.apiKeys.openai, this.handleMessage, + this.abortSignal, )) ); }), diff --git a/src/agents/elf/skills/presets/webSearch.ts b/src/agents/elf/skills/presets/webSearch.ts index 133080cd..069fcd21 100644 --- a/src/agents/elf/skills/presets/webSearch.ts +++ b/src/agents/elf/skills/presets/webSearch.ts @@ -16,7 +16,8 @@ export class WebSearch extends Skill { dependentTaskOutputs: string, objective: string, ): Promise { - if (!this.valid) return ''; + if (!this.valid || this.signal?.aborted) return ''; + const taskOutput = (await webBrowsing( objective, @@ -27,6 +28,7 @@ export class WebSearch extends Skill { undefined, this.language, this.apiKeys.openai, + this.signal, )) ?? ''; return taskOutput; diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts index 77bac6e1..c78161e1 100644 --- a/src/agents/elf/skills/skill.ts +++ b/src/agents/elf/skills/skill.ts @@ -19,6 +19,7 @@ export class Skill { handleMessage: (message: AgentMessage) => void; verbose: boolean; language: string = 'en'; + signal?: AbortSignal; BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; @@ -30,11 +31,13 @@ export class Skill { handleMessage: (message: AgentMessage) => Promise, verbose: boolean = false, language: string = 'en', + abortSignal?: AbortSignal, ) { this.apiKeys = apiKeys; this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; + this.signal = abortSignal; this.id = uuidv4(); const missingKeys = this.checkRequiredKeys(apiKeys); @@ -111,28 +114,31 @@ export class Skill { streaming: true, }; const llmParams = { ...defaultParams, ...params }; - const llm = new ChatOpenAI({ - openAIApiKey: this.apiKeys.openai, - ...llmParams, - callbacks: [ - { - handleLLMNewToken(token: string) { - callback?.({ - id, - content: token, - title: `${task.task}`, - type: task.skill, - icon: task.icon, - taskId: task.id.toString(), - status: 'running', - options: { - dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', - }, - }); + const llm = new ChatOpenAI( + { + openAIApiKey: this.apiKeys.openai, + ...llmParams, + callbacks: [ + { + handleLLMNewToken(token: string) { + callback?.({ + id, + content: token, + title: `${task.task}`, + type: task.skill, + icon: task.icon, + taskId: task.id.toString(), + status: 'running', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, + }); + }, }, - }, - ], - }); + ], + }, + { baseOptions: { signal: this.abortSignal } }, + ); try { const response = await llm.call([new HumanChatMessage(prompt)]); diff --git a/src/agents/elf/tools/search/largeTextExtract.ts b/src/agents/elf/tools/search/largeTextExtract.ts index 0fa636f7..8ba26f1b 100644 --- a/src/agents/elf/tools/search/largeTextExtract.ts +++ b/src/agents/elf/tools/search/largeTextExtract.ts @@ -8,6 +8,7 @@ export const largeTextExtract = async ( task: AgentTask, userApiKey?: string, callback?: (message: AgentMessage) => void, + signal?: AbortSignal, ) => { const chunkSize = 15000; const overlap = 500; @@ -34,6 +35,7 @@ export const largeTextExtract = async ( notes, chunk, userApiKey, + signal, ); notes += response; } diff --git a/src/agents/elf/tools/search/webBrowsing.ts b/src/agents/elf/tools/search/webBrowsing.ts index 69c0fc3f..065fcd2d 100644 --- a/src/agents/elf/tools/search/webBrowsing.ts +++ b/src/agents/elf/tools/search/webBrowsing.ts @@ -16,6 +16,7 @@ export const webBrowsing = async ( modelName: string = 'gpt-3.5-turbo', language: string = 'en', userApiKey?: string, + signal?: AbortSignal, ) => { let id = uuidv4(); const prompt = searchQueryPrompt( @@ -28,6 +29,7 @@ export const webBrowsing = async ( task, modelName, userApiKey, + signal, ); const trimmedQuery = searchQuery?.replace(/^"|"$/g, ''); // remove quotes from the search query @@ -48,6 +50,7 @@ export const webBrowsing = async ( const MaxCompletedCount = 3; // Loop through search results for (const searchResult of simplifiedSearchResults) { + if (signal?.aborted) return ''; if (completedCount >= MaxCompletedCount) break; // Extract the URL from the search result @@ -85,6 +88,7 @@ export const webBrowsing = async ( task, userApiKey, messageCallback, + signal, ); message = ` - Relevant info: ${info @@ -110,6 +114,7 @@ export const webBrowsing = async ( task, modelName, userApiKey, + signal, messageCallback, ); diff --git a/src/agents/elf/tools/textCompletionTool.ts b/src/agents/elf/tools/textCompletionTool.ts index 849bfc06..8a492f00 100644 --- a/src/agents/elf/tools/textCompletionTool.ts +++ b/src/agents/elf/tools/textCompletionTool.ts @@ -8,35 +8,39 @@ export const textCompletionTool = async ( task: AgentTask, modelName: string, userApiKey?: string, + signal?: AbortSignal, messageCallnback?: (message: AgentMessage) => void, ) => { if (prompt.length > 3200) { modelName = 'gpt-3.5-turbo-16k-0613'; } - const llm = new ChatOpenAI({ - openAIApiKey: userApiKey, - modelName, - temperature: 0.2, - maxTokens: 800, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - messageCallnback?.({ - content: token, - type: task.skill, - id: id, - taskId: task.id.toString(), - status: 'running', - }); + const llm = new ChatOpenAI( + { + openAIApiKey: userApiKey, + modelName, + temperature: 0.2, + maxTokens: 800, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + messageCallnback?.({ + content: token, + type: task.skill, + id: id, + taskId: task.id.toString(), + status: 'running', + }); + }, }, - }, - ], - }); + ], + }, + { baseOptions: { AbortSignal: signal } }, + ); try { const response = await llm.call([new HumanChatMessage(prompt)]); @@ -48,6 +52,10 @@ export const textCompletionTool = async ( status: 'complete', }); + if (messageCallnback instanceof AbortSignal) { + throw new Error('messageCallnback cannot be of type AbortSignal'); + } + return response.text; } catch (error: any) { if (error.name === 'AbortError') { diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index def93f67..77019fc3 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -72,8 +72,9 @@ export function useAgent({ model_name: modelName, language, user_key: userKey, + verbose: true, }), - signal: abortController.signal, // Add the abort signal + signal: abortController.signal, }); const reader = response.body?.getReader(); diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts index 110232c3..c34e8111 100644 --- a/src/pages/api/agent/index.ts +++ b/src/pages/api/agent/index.ts @@ -9,7 +9,7 @@ export const config = { }; export default async function handler(req: NextRequest) { - const { stream, handlers } = AgentStream(); + const { stream, handlers, addObserver } = AgentStream(); const { input, agent_id, model_name, language, verbose, user_key } = await req.json(); @@ -21,6 +21,16 @@ export default async function handler(req: NextRequest) { } const specifiedSkills = agent_id === 'babydeeragi' ? SPECIFIED_SKILLS : []; + + // req.signal is not working, so we use AbortController. + const abortController = new AbortController(); + const signal = abortController.signal; + + // Add an observer to abort the request when the client disconnects. + addObserver((isActive) => { + if (!isActive) abortController.abort(); + }); + const executer = new BabyElfAGI( input, model_name, @@ -29,6 +39,7 @@ export default async function handler(req: NextRequest) { verbose, specifiedSkills, user_key, + signal, ); executer.run(); From 02b10708257c9b0144e7f6e7470518a0d464d77c Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Fri, 18 Aug 2023 15:42:21 +0900 Subject: [PATCH 22/42] Refactor search and extraction tools --- src/agents/elf/skills/addons/youtubeSearch.ts | 2 +- src/agents/elf/skills/presets/webLoader.ts | 2 +- src/agents/elf/skills/presets/webSearch.ts | 2 +- .../search/relevantInfoExtraction/agent.ts | 46 --------------- .../search/relevantInfoExtraction/prompt.ts | 18 ------ .../{search => utils}/largeTextExtract.ts | 4 +- .../elf/tools/utils/relevantInfoExtraction.ts | 59 +++++++++++++++++++ .../textCompletion.ts} | 2 +- .../elf/tools/{search => }/webBrowsing.ts | 12 ++-- .../elf/tools/{search => }/webSearch.ts | 0 .../elf/tools/search => utils}/prompt.ts | 0 11 files changed, 71 insertions(+), 76 deletions(-) delete mode 100644 src/agents/elf/tools/search/relevantInfoExtraction/agent.ts delete mode 100644 src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts rename src/agents/elf/tools/{search => utils}/largeTextExtract.ts (87%) create mode 100644 src/agents/elf/tools/utils/relevantInfoExtraction.ts rename src/agents/elf/tools/{textCompletionTool.ts => utils/textCompletion.ts} (97%) rename src/agents/elf/tools/{search => }/webBrowsing.ts (92%) rename src/agents/elf/tools/{search => }/webSearch.ts (100%) rename src/{agents/elf/tools/search => utils}/prompt.ts (100%) diff --git a/src/agents/elf/skills/addons/youtubeSearch.ts b/src/agents/elf/skills/addons/youtubeSearch.ts index 99337580..596ed689 100644 --- a/src/agents/elf/skills/addons/youtubeSearch.ts +++ b/src/agents/elf/skills/addons/youtubeSearch.ts @@ -1,6 +1,6 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import { webSearch } from '../../tools/search/webSearch'; +import { webSearch } from '../../tools/webSearch'; export class YoutubeSearch extends Skill { name = 'youtube_search'; diff --git a/src/agents/elf/skills/presets/webLoader.ts b/src/agents/elf/skills/presets/webLoader.ts index 29d224e5..dcfeffd3 100644 --- a/src/agents/elf/skills/presets/webLoader.ts +++ b/src/agents/elf/skills/presets/webLoader.ts @@ -1,6 +1,6 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import { largeTextExtract } from '@/agents/elf/tools/search/largeTextExtract'; +import { largeTextExtract } from '@/agents/elf/tools/utils/largeTextExtract'; import { webScrape } from '../../tools/webScrape'; import { v4 as uuidv4 } from 'uuid'; diff --git a/src/agents/elf/skills/presets/webSearch.ts b/src/agents/elf/skills/presets/webSearch.ts index 069fcd21..9e94c06f 100644 --- a/src/agents/elf/skills/presets/webSearch.ts +++ b/src/agents/elf/skills/presets/webSearch.ts @@ -1,5 +1,5 @@ import { AgentTask } from '@/types'; -import { webBrowsing } from '@/agents/elf/tools/search/webBrowsing'; +import { webBrowsing } from '@/agents/elf/tools/webBrowsing'; import { Skill } from '../skill'; // This skill is Specialized for web browsing diff --git a/src/agents/elf/tools/search/relevantInfoExtraction/agent.ts b/src/agents/elf/tools/search/relevantInfoExtraction/agent.ts deleted file mode 100644 index 537ca2a6..00000000 --- a/src/agents/elf/tools/search/relevantInfoExtraction/agent.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { OpenAIChat } from 'langchain/llms/openai'; -import { relevantInfoExtractionPrompt } from './prompt'; -import { LLMChain } from 'langchain/chains'; -import axios from 'axios'; - -// TODO: Only client-side requests are allowed. -// To use the environment variable API key, the request must be implemented from the server side. - -export const relevantInfoExtractionAgent = async ( - objective: string, - task: string, - notes: string, - chunk: string, - userApiKey?: string, - signal?: AbortSignal, -) => { - const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model - const prompt = relevantInfoExtractionPrompt(); - const llm = new OpenAIChat( - { - modelName, - temperature: 0.7, - maxTokens: 800, - topP: 1, - stop: ['###'], - }, - { baseOptions: { signal: signal } }, - ); - const chain = new LLMChain({ llm: llm, prompt }); - try { - const response = await chain.call({ - openAIApiKey: userApiKey, - objective, - task, - notes, - chunk, - }); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return 'Failed to extract relevant information.'; - } -}; diff --git a/src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts b/src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts deleted file mode 100644 index 59e8dade..00000000 --- a/src/agents/elf/tools/search/relevantInfoExtraction/prompt.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; - -export const relevantInfoExtractionPrompt = () => { - const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; - const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate(systemTemplate), - HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), - ]); - - prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; - - return prompt; -}; diff --git a/src/agents/elf/tools/search/largeTextExtract.ts b/src/agents/elf/tools/utils/largeTextExtract.ts similarity index 87% rename from src/agents/elf/tools/search/largeTextExtract.ts rename to src/agents/elf/tools/utils/largeTextExtract.ts index 8ba26f1b..44695252 100644 --- a/src/agents/elf/tools/search/largeTextExtract.ts +++ b/src/agents/elf/tools/utils/largeTextExtract.ts @@ -1,5 +1,5 @@ import { AgentMessage, AgentTask } from '@/types'; -import { relevantInfoExtractionAgent } from './relevantInfoExtraction/agent'; +import { relevantInfoExtraction } from './relevantInfoExtraction'; export const largeTextExtract = async ( id: string, @@ -29,7 +29,7 @@ export const largeTextExtract = async ( const chunk = largeString.slice(i, i + chunkSize); - const response = await relevantInfoExtractionAgent( + const response = await relevantInfoExtraction( objective, task.task, notes, diff --git a/src/agents/elf/tools/utils/relevantInfoExtraction.ts b/src/agents/elf/tools/utils/relevantInfoExtraction.ts new file mode 100644 index 00000000..beedf4d4 --- /dev/null +++ b/src/agents/elf/tools/utils/relevantInfoExtraction.ts @@ -0,0 +1,59 @@ +import { OpenAIChat } from 'langchain/llms/openai'; +import { LLMChain } from 'langchain/chains'; +import { + ChatPromptTemplate, + HumanMessagePromptTemplate, + SystemMessagePromptTemplate, +} from 'langchain/prompts'; + +export const relevantInfoExtraction = async ( + objective: string, + task: string, + notes: string, + chunk: string, + userApiKey?: string, + signal?: AbortSignal, +) => { + const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model + const prompt = relevantInfoExtractionPrompt(); + const llm = new OpenAIChat( + { + modelName, + temperature: 0.7, + maxTokens: 800, + topP: 1, + stop: ['###'], + }, + { baseOptions: { signal: signal } }, + ); + const chain = new LLMChain({ llm: llm, prompt }); + try { + const response = await chain.call({ + openAIApiKey: userApiKey, + objective, + task, + notes, + chunk, + }); + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return null; + } + console.log('error: ', error); + return 'Failed to extract relevant information.'; + } +}; + +const relevantInfoExtractionPrompt = () => { + const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; + const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; + const prompt = ChatPromptTemplate.fromPromptMessages([ + SystemMessagePromptTemplate.fromTemplate(systemTemplate), + HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), + ]); + + prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; + + return prompt; +}; diff --git a/src/agents/elf/tools/textCompletionTool.ts b/src/agents/elf/tools/utils/textCompletion.ts similarity index 97% rename from src/agents/elf/tools/textCompletionTool.ts rename to src/agents/elf/tools/utils/textCompletion.ts index 8a492f00..8f8e3192 100644 --- a/src/agents/elf/tools/textCompletionTool.ts +++ b/src/agents/elf/tools/utils/textCompletion.ts @@ -2,7 +2,7 @@ import { AgentMessage, AgentTask } from '@/types'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { HumanChatMessage } from 'langchain/schema'; -export const textCompletionTool = async ( +export const textCompletion = async ( prompt: string, id: string, task: AgentTask, diff --git a/src/agents/elf/tools/search/webBrowsing.ts b/src/agents/elf/tools/webBrowsing.ts similarity index 92% rename from src/agents/elf/tools/search/webBrowsing.ts rename to src/agents/elf/tools/webBrowsing.ts index 065fcd2d..18f577ec 100644 --- a/src/agents/elf/tools/search/webBrowsing.ts +++ b/src/agents/elf/tools/webBrowsing.ts @@ -1,11 +1,11 @@ import { simplifySearchResults } from '@/agents/common/tools/webSearch'; import { AgentTask, AgentMessage } from '@/types'; -import { analystPrompt, searchQueryPrompt } from './prompt'; -import { textCompletionTool } from '../textCompletionTool'; -import { largeTextExtract } from './largeTextExtract'; +import { analystPrompt, searchQueryPrompt } from '../../../utils/prompt'; +import { textCompletion } from './utils/textCompletion'; +import { largeTextExtract } from './utils/largeTextExtract'; import { v4 as uuidv4 } from 'uuid'; import { webSearch } from './webSearch'; -import { webScrape } from '../webScrape'; +import { webScrape } from './webScrape'; export const webBrowsing = async ( objective: string, @@ -23,7 +23,7 @@ export const webBrowsing = async ( task.task, dependentTasksOutput.slice(0, 3500), ); - const searchQuery = await textCompletionTool( + const searchQuery = await textCompletion( prompt, id, task, @@ -108,7 +108,7 @@ export const webBrowsing = async ( const outputId = uuidv4(); const ap = analystPrompt(results, language); - const analyzedResults = await textCompletionTool( + const analyzedResults = await textCompletion( ap, outputId, task, diff --git a/src/agents/elf/tools/search/webSearch.ts b/src/agents/elf/tools/webSearch.ts similarity index 100% rename from src/agents/elf/tools/search/webSearch.ts rename to src/agents/elf/tools/webSearch.ts diff --git a/src/agents/elf/tools/search/prompt.ts b/src/utils/prompt.ts similarity index 100% rename from src/agents/elf/tools/search/prompt.ts rename to src/utils/prompt.ts From 01da1156247139feeca3629c404d30e41a0da0d2 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Fri, 18 Aug 2023 16:40:56 +0900 Subject: [PATCH 23/42] Delete the old mod and also delete the related code --- README.md | 9 - src/agents/babybeeagi/agent.ts | 567 ------------------ .../babybeeagi/chains/taskManagement.ts | 54 -- src/agents/babybeeagi/chains/taskOverview.ts | 24 - src/agents/babybeeagi/chains/taskSummarize.ts | 14 - src/agents/babybeeagi/service.ts | 85 --- src/agents/babybeeagi/tools/textCompletion.ts | 19 - src/agents/babycatagi/agent.ts | 556 ----------------- .../chains/relevantInfoExtraction.ts | 23 - src/agents/babycatagi/chains/taskCreation.ts | 36 -- src/agents/babycatagi/service.ts | 77 --- src/agents/babycatagi/tools/textCompletion.ts | 43 -- .../agents/relevantInfoExtraction/agent.ts | 78 --- .../agents/relevantInfoExtraction/prompt.ts | 18 - .../babydeeragi/agents/taskCreation/agent.ts | 123 ---- .../babydeeragi/agents/taskCreation/prompt.ts | 34 -- src/agents/babydeeragi/executer.ts | 209 ------- src/agents/babydeeragi/prompt.ts | 34 -- .../babydeeragi/tools/largeTextExtract.ts | 63 -- src/agents/babydeeragi/tools/webBrowsing.ts | 221 ------- .../example_objectives/example1.json | 29 - src/agents/babyelfagi/executer.ts | 125 ++-- .../babyelfagi/registory/skillRegistry.ts | 59 +- .../babyelfagi/registory/taskRegistry.ts | 158 ++--- .../babyelfagi/skills/addons/airtableSaver.ts | 19 +- .../babyelfagi/skills/addons/youtubeSearch.ts | 43 +- src/agents/babyelfagi/skills/index.ts | 2 +- .../babyelfagi/skills/presets/codeReader.ts | 8 +- .../babyelfagi/skills/presets/codeReviewer.ts | 4 +- .../babyelfagi/skills/presets/codeWriter.ts | 3 +- .../skills/presets/directoryStructure.ts | 9 +- .../skills/presets/objectiveSaver.ts | 4 +- .../babyelfagi/skills/presets/skillSaver.ts | 17 +- .../babyelfagi/skills/presets/webLoader.ts | 82 ++- .../babyelfagi/skills/presets/webSearch.ts | 13 +- src/agents/babyelfagi/skills/skill.ts | 178 +++--- .../tools/utils/largeTextExtract.ts | 0 .../tools/utils/relevantInfoExtraction.ts | 0 .../tools/utils/textCompletion.ts | 0 .../{elf => babyelfagi}/tools/webBrowsing.ts | 2 +- .../{elf => babyelfagi}/tools/webScrape.ts | 0 .../{elf => babyelfagi}/tools/webSearch.ts | 0 src/agents/base/AgentExecuter.ts | 72 +-- src/agents/base/Executer.ts | 58 -- src/agents/common/tools/textCompletionTool.ts | 87 --- src/agents/common/tools/webScrape.ts | 46 -- src/agents/common/tools/webSearch.ts | 66 -- src/agents/elf/executer.ts | 189 ------ src/agents/elf/registory/index.ts | 2 - src/agents/elf/registory/skillRegistry.ts | 129 ---- src/agents/elf/registory/taskRegistry.ts | 249 -------- src/agents/elf/skills/addons/airtableSaver.ts | 47 -- src/agents/elf/skills/addons/youtubeSearch.ts | 54 -- src/agents/elf/skills/index.ts | 12 - src/agents/elf/skills/presets/codeReader.ts | 65 -- src/agents/elf/skills/presets/codeReviewer.ts | 44 -- src/agents/elf/skills/presets/codeWriter.ts | 56 -- .../elf/skills/presets/directoryStructure.ts | 29 - .../elf/skills/presets/objectiveSaver.ts | 48 -- src/agents/elf/skills/presets/skillSaver.ts | 76 --- .../elf/skills/presets/textCompletion.ts | 34 -- src/agents/elf/skills/presets/webLoader.ts | 112 ---- src/agents/elf/skills/presets/webSearch.ts | 36 -- src/agents/elf/skills/skill.ts | 191 ------ src/components/Agent/Agent.tsx | 512 ---------------- src/components/Agent/AgentLabelBlock.tsx | 80 --- src/components/Agent/AgentMessage.tsx | 106 ---- src/components/Agent/AgentMessageBlock.tsx | 31 - src/components/Agent/AgentMessageFooter.tsx | 13 - src/components/Agent/AgentMessageInput.tsx | 62 -- src/components/Agent/AgentTask.tsx | 97 --- src/components/Agent/Input.tsx | 190 ------ src/components/Agent/MessageSummary.tsx | 25 - src/components/Agent/MessageSummaryCard.tsx | 74 --- src/hooks/useSkills.tsx | 2 +- src/pages/api/agent/index.ts | 2 +- src/pages/api/agents/create.ts | 23 - src/pages/api/agents/management.ts | 32 - src/pages/api/agents/overview.ts | 24 - src/pages/api/agents/summarize.ts | 18 - src/pages/api/deer/completion.ts | 25 - src/pages/api/deer/create.ts | 36 -- src/pages/api/deer/extract.ts | 34 -- src/pages/api/elf/completion.ts | 34 -- src/pages/api/elf/embedding.ts | 19 - src/pages/api/execute-skill.ts | 25 - src/pages/api/tools/completion.ts | 15 - src/pages/api/tools/extract.ts | 21 - src/pages/api/tools/scrape.ts | 15 - src/pages/api/tools/search.ts | 15 - src/pages/dev/index.tsx | 116 ---- src/pages/index.tsx | 13 +- src/types/index.ts | 3 - src/utils/elf/objective.ts | 85 --- src/utils/elf/print.ts | 113 ---- src/utils/objective.ts | 60 +- src/utils/print.ts | 74 +-- 97 files changed, 449 insertions(+), 6289 deletions(-) delete mode 100644 src/agents/babybeeagi/agent.ts delete mode 100644 src/agents/babybeeagi/chains/taskManagement.ts delete mode 100644 src/agents/babybeeagi/chains/taskOverview.ts delete mode 100644 src/agents/babybeeagi/chains/taskSummarize.ts delete mode 100644 src/agents/babybeeagi/service.ts delete mode 100644 src/agents/babybeeagi/tools/textCompletion.ts delete mode 100644 src/agents/babycatagi/agent.ts delete mode 100644 src/agents/babycatagi/chains/relevantInfoExtraction.ts delete mode 100644 src/agents/babycatagi/chains/taskCreation.ts delete mode 100644 src/agents/babycatagi/service.ts delete mode 100644 src/agents/babycatagi/tools/textCompletion.ts delete mode 100644 src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts delete mode 100644 src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts delete mode 100644 src/agents/babydeeragi/agents/taskCreation/agent.ts delete mode 100644 src/agents/babydeeragi/agents/taskCreation/prompt.ts delete mode 100644 src/agents/babydeeragi/executer.ts delete mode 100644 src/agents/babydeeragi/prompt.ts delete mode 100644 src/agents/babydeeragi/tools/largeTextExtract.ts delete mode 100644 src/agents/babydeeragi/tools/webBrowsing.ts delete mode 100644 src/agents/babyelfagi/example_objectives/example1.json rename src/agents/{elf => babyelfagi}/tools/utils/largeTextExtract.ts (100%) rename src/agents/{elf => babyelfagi}/tools/utils/relevantInfoExtraction.ts (100%) rename src/agents/{elf => babyelfagi}/tools/utils/textCompletion.ts (100%) rename src/agents/{elf => babyelfagi}/tools/webBrowsing.ts (98%) rename src/agents/{elf => babyelfagi}/tools/webScrape.ts (100%) rename src/agents/{elf => babyelfagi}/tools/webSearch.ts (100%) delete mode 100644 src/agents/base/Executer.ts delete mode 100644 src/agents/common/tools/textCompletionTool.ts delete mode 100644 src/agents/common/tools/webScrape.ts delete mode 100644 src/agents/common/tools/webSearch.ts delete mode 100644 src/agents/elf/executer.ts delete mode 100644 src/agents/elf/registory/index.ts delete mode 100644 src/agents/elf/registory/skillRegistry.ts delete mode 100644 src/agents/elf/registory/taskRegistry.ts delete mode 100644 src/agents/elf/skills/addons/airtableSaver.ts delete mode 100644 src/agents/elf/skills/addons/youtubeSearch.ts delete mode 100644 src/agents/elf/skills/index.ts delete mode 100644 src/agents/elf/skills/presets/codeReader.ts delete mode 100644 src/agents/elf/skills/presets/codeReviewer.ts delete mode 100644 src/agents/elf/skills/presets/codeWriter.ts delete mode 100644 src/agents/elf/skills/presets/directoryStructure.ts delete mode 100644 src/agents/elf/skills/presets/objectiveSaver.ts delete mode 100644 src/agents/elf/skills/presets/skillSaver.ts delete mode 100644 src/agents/elf/skills/presets/textCompletion.ts delete mode 100644 src/agents/elf/skills/presets/webLoader.ts delete mode 100644 src/agents/elf/skills/presets/webSearch.ts delete mode 100644 src/agents/elf/skills/skill.ts delete mode 100644 src/components/Agent/Agent.tsx delete mode 100644 src/components/Agent/AgentLabelBlock.tsx delete mode 100644 src/components/Agent/AgentMessage.tsx delete mode 100644 src/components/Agent/AgentMessageBlock.tsx delete mode 100644 src/components/Agent/AgentMessageFooter.tsx delete mode 100644 src/components/Agent/AgentMessageInput.tsx delete mode 100644 src/components/Agent/AgentTask.tsx delete mode 100644 src/components/Agent/Input.tsx delete mode 100644 src/components/Agent/MessageSummary.tsx delete mode 100644 src/components/Agent/MessageSummaryCard.tsx delete mode 100644 src/pages/api/agents/create.ts delete mode 100644 src/pages/api/agents/management.ts delete mode 100644 src/pages/api/agents/overview.ts delete mode 100644 src/pages/api/agents/summarize.ts delete mode 100644 src/pages/api/deer/completion.ts delete mode 100644 src/pages/api/deer/create.ts delete mode 100644 src/pages/api/deer/extract.ts delete mode 100644 src/pages/api/elf/completion.ts delete mode 100644 src/pages/api/elf/embedding.ts delete mode 100644 src/pages/api/execute-skill.ts delete mode 100644 src/pages/api/tools/completion.ts delete mode 100644 src/pages/api/tools/extract.ts delete mode 100644 src/pages/api/tools/scrape.ts delete mode 100644 src/pages/api/tools/search.ts delete mode 100644 src/pages/dev/index.tsx delete mode 100644 src/utils/elf/objective.ts delete mode 100644 src/utils/elf/print.ts diff --git a/README.md b/README.md index 58f24679..6bb30207 100644 --- a/README.md +++ b/README.md @@ -25,15 +25,6 @@ This is a port of [babyagi](https://github.com/yoheinakajima/babyagi) with [Lang ## 🚗 Roadmap -- [x] The BabyAGI can search and scrape the web. ([🐝 BabyBeeAGI](https://twitter.com/yoheinakajima/status/1652732735344246784)) -- [x] Exporting Execution Results -- [x] Execution history -- [x] Faster speeds and fewer errors. ([😺 BabyCatAGI](https://twitter.com/yoheinakajima/status/1657448504112091136)) -- [x] i18n support ( 🇧🇷, 🇩🇪, 🇺🇸, 🇪🇸, 🇫🇷, 🇮🇳, 🇭🇺, 🇯🇵, 🇷🇺, 🇹🇭, ... and much more) -- [x] User feedback -- [x] Improv UX for task creation (only BabyCatAGI🐱 & Client request) -- [x] Notification that all tasks have been completed. 🔔 -- [x] Display the current task and task list. 📌 - [x] Collapsible Sidebar ⏩️ - [x] User input & parallel tasking. ([🦌 BabyDeerAGI](https://twitter.com/yoheinakajima/status/1666313838868992001)) - [x] API updates support (gpt-3.5-turbo-0613/gpt-3.5-turbo-16k-0613/gpt-4-0613) diff --git a/src/agents/babybeeagi/agent.ts b/src/agents/babybeeagi/agent.ts deleted file mode 100644 index a99739a4..00000000 --- a/src/agents/babybeeagi/agent.ts +++ /dev/null @@ -1,567 +0,0 @@ -import { AgentStatus, Message, TaskStatus, ToolType } from '@/types'; -import { textCompletion } from './tools/textCompletion'; -import { overviewAgent, summarizerAgent, taskManagementAgent } from './service'; -import { getToolIcon, setupMessage } from '@/utils/message'; -import axios from 'axios'; -import { parseTasks } from '@/utils/task'; -import { getUserApiKey } from '@/utils/settings'; -import { t } from 'i18next'; -import { translate } from '@/utils/translate'; - -export interface AgentTask { - id: number; - task: string; - tool: ToolType; - dependentTaskId?: number; - status: TaskStatus; - result?: string; - resultSummary?: string; -} - -export class BabyBeeAGI { - objective: string; - modelName: string; - firstTask: string; - taskList: AgentTask[] = []; - sessionSummary: string = ''; - taskIdCounter: number = 1; - isRunning: boolean; - verbose: boolean; - language: string = 'en'; - messageCallback: (message: Message) => void; - statusCallback: (status: AgentStatus) => void; - cancelCallback: () => void; - abortController?: AbortController; - - constructor( - objective: string, - modelName: string, - firstTask: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancel: () => void, - language: string = 'en', - verbose: boolean = false, - ) { - this.objective = objective; - this.taskList = []; - this.verbose = verbose; - this.language = language; - this.modelName = modelName; - this.firstTask = firstTask; - this.cancelCallback = cancel; - this.messageCallback = messageCallback; - this.statusCallback = statusCallback; - this.isRunning = false; - } - - // print logs - printBabyBee() { - if (!this.verbose) return; - console.log( - '%c*****BABY BEE AGI*****\n\n%c%s', - 'color:orange', - '', - 'Baby Bee AGI is running...', - ); - } - - printObjective() { - this.messageCallback(setupMessage('objective', this.objective)); - if (!this.verbose) return; - console.log( - '%c*****OBJECTIVE*****\n\n%c%s', - 'color:blue', - '', - this.objective, - ); - } - - printTaskList() { - if (!this.isRunning) return; - - let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - this.taskList.forEach((task) => { - const dependentTask = task.dependentTaskId - ? `${task.dependentTaskId}` - : '-'; - const status = task.status === 'complete' ? '✅' : '⬜️'; - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; - }); - - this.messageCallback(setupMessage('task-list', message)); - - if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n\n%c', 'color:fuchsia', ''); - console.log(message); - } - - printSessionSummary() { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('session-summary', this.sessionSummary)); - - if (!this.verbose) return; - console.log('%c*****SESSION SUMMARY*****\n\n%c', 'color:orange', ''); - console.log(this.sessionSummary); - } - - printNextTask(task: AgentTask) { - if (!this.isRunning) return; - - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - this.messageCallback(setupMessage('next-task', nextTask)); - - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n\n%s', 'color:green', '', nextTask); - } - - printResult(result: string, task: AgentTask) { - if (!this.isRunning) return; - - let output = result; - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - output = '```\n' + output + '\n```'; - } - this.messageCallback(setupMessage('task-result', output, task?.tool)); - - if (!this.verbose) return; - output = result.length > 2000 ? result.slice(0, 2000) + '...' : result; - console.log('%c*****TASK RESULT*****\n%c%s', 'color:purple', '', output); - } - - printResultSummary(summary: string) { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('task-result-summary', summary)); - - if (!this.verbose) return; - console.log( - '%c*****TASK RESULT SUMMARY*****\n%c%s', - 'color:purple', - '', - summary, - ); - } - - printDone() { - if (!this.isRunning) return; - - this.messageCallback( - setupMessage( - 'done', - `Number of tasks completed: ${this.taskIdCounter.toString()}`, - ), - ); - - if (!this.verbose) return; - console.log('%c*****DONE*****%c', 'color:blue', ''); - } - - printAllTaskCompleted() { - if (!this.isRunning) return; - - this.messageCallback( - setupMessage('complete', translate('ALL_TASK_COMPLETED_TOAST', 'agent')), - ); - if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:blue', ''); - } - - // Tools functions - async webSearchTool(query: string) { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data.response; - } - - async webScrapeTool(url: string) { - const response = await axios - .post( - '/api/tools/scrape', - { - url, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async textCompletionTool(prompt: string) { - if (getUserApiKey()) { - return await textCompletion( - prompt, - 'gpt-3.5-turbo-0613', - getUserApiKey(), - ); - } - - const response = await axios - .post( - '/api/tools/completion', - { - prompt, - apiKey: getUserApiKey(), - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - // Task list functions - async addTask(task: AgentTask) { - this.taskList.push(task); - } - - async getTask(id: number) { - return this.taskList.find((task) => task.id === id); - } - - async getCompletedTasks() { - return this.taskList.filter((task) => task.status === 'complete'); - } - - async summarizeTask(value: string) { - const text = value.length > 4000 ? value.slice(0, 4000) + '...' : value; - - if (getUserApiKey()) { - return await summarizerAgent(text, this.language, getUserApiKey()); - } - - const response = await axios - .post( - '/api/agents/summarize', - { - text, - language: this.language, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async overviewTask(lastTaskId: number) { - const completedTasks = await this.getCompletedTasks(); - let completedTasksText = ''; - completedTasks.forEach((task) => { - completedTasksText += `${task.id}. ${task.task} - ${task.resultSummary}\n`; - }); - - if (getUserApiKey()) { - return await overviewAgent( - this.objective, - this.sessionSummary, - lastTaskId, - completedTasksText, - getUserApiKey(), - ); - } - - const response = await axios - .post( - '/api/agents/overview', - { - objective: this.objective, - session_summary: this.sessionSummary, - last_task_id: lastTaskId, - completed_tasks_text: completedTasksText, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async managementTask( - result: string, - taskDescription: string, - incompleteTasks: string[], - currntTaskId: number, - ) { - let taskList = this.taskList; - // copy task list - const originalTaskList = taskList.slice(); - // minified task list - const minifiedTaskList = taskList.map((task) => { - const { result, ...rest } = task; - return rest; - }); - const websearchVar = process.env.SERP_API_KEY ? '[web-search] ' : ''; // if search api key is not set, don't add [web-search] to the task description - const res = result.slice(0, 4000); // come up with a better solution lator - - let managedResult = ''; - if (getUserApiKey()) { - managedResult = await taskManagementAgent( - minifiedTaskList, - this.objective, - res, - websearchVar, - this.modelName, - this.language, - getUserApiKey(), - ); - } else { - const response = await axios - .post( - '/api/agents/management', - { - task_list: minifiedTaskList, - objective: this.objective, - result: res, - websearch_var: websearchVar, - model_name: this.modelName, - language: this.language, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - managedResult = response?.data?.response; - } - - // update task list - try { - taskList = parseTasks(managedResult); - } catch (error) { - console.error(error); - - // TODO: handle error - return taskList; - } - - // Add the 'result' field back in - for (let i = 0; i < taskList.length; i++) { - const originalTask = originalTaskList[i]; - if (originalTask?.result) { - taskList[i].result = originalTask.result; - } - } - - const currentTask = taskList[currntTaskId - 1]; - if (currentTask) { - taskList[currntTaskId - 1].result = managedResult; - } - - return taskList; - } - - // Agent functions - async executeTask(task: AgentTask, taskList: AgentTask[], objective: string) { - // Check if task is already completed - let dependentTask; - if (task.dependentTaskId) { - dependentTask = await this.getTask(task.dependentTaskId); - if (!dependentTask || dependentTask.status !== 'complete') { - return; - } - } - - // Execute task - this.statusCallback({ type: 'executing' }); - this.printNextTask(task); - let taskPrompt = `Complete your assign task based on the objective:\n\n${objective}, Your task: ${task.task}`; - if (task.dependentTaskId) { - if (dependentTask) { - const dependentTaskResult = dependentTask.resultSummary; // Use summary instead of result to avoid long text (original code use result) - // console.log('dependentTaskResult: ', dependentTaskResult); - taskPrompt += `\nThe previous task ${dependentTask.id}. ${dependentTask.task} result: ${dependentTaskResult}`; - } - } - - // taskPrompt += '\nResponses should be no more than 1000 characters.'; // Added message (Not in original code) - taskPrompt += '\nResponse:'; - let result = ''; - - switch (task.tool) { - case 'text-completion': - result = - (await this.textCompletionTool(taskPrompt)) ?? - 'Failed to complete text'; - break; - case 'web-search': - const search = (await this.webSearchTool(task.task)) ?? ''; - result = JSON.stringify(search); - break; - case 'web-scrape': - result = - (await this.webScrapeTool(task.task)) ?? 'Failed to scrape web page'; - break; - default: - result = 'Unknown tool'; - break; - } - - this.printResult(result, task); - - this.statusCallback({ type: 'updating' }); - // Update task status and result - task.status = 'complete'; - task.result = result; - task.resultSummary = await this.summarizeTask(result); - - this.printResultSummary(task.resultSummary ?? ''); - - this.statusCallback({ type: 'summarizing' }); - // Update session summary - this.sessionSummary = await this.overviewTask(task.id); - - this.printSessionSummary(); - - // Increment task id counter - this.taskIdCounter += 1; - - const incompleteTasks = taskList - .filter((task) => task.status === 'incomplete') - .map((task) => task.task); - - this.statusCallback({ type: 'managing' }); - // Update task manager agent of tasks - this.taskList = await this.managementTask( - result, - task.task, - incompleteTasks, - task.id, - ); - } - - async stop() { - this.isRunning = false; - this.cancelCallback(); - this.abortController?.abort(); - } - - async start() { - // Add the first task - const task: AgentTask = { - id: this.taskIdCounter, // 1 - task: this.firstTask, - tool: 'text-completion', - status: 'incomplete', - }; - - this.addTask(task); - this.taskIdCounter = 0; - this.printBabyBee(); - this.printObjective(); - - // Start the loop - this.isRunning = true; - await this.loop(); - - if (!this.isRunning) { - this.statusCallback({ type: 'finished' }); - return; - } - - // Objective completed - this.printAllTaskCompleted(); - this.statusCallback({ type: 'finished' }); - this.cancelCallback(); - this.isRunning = false; - } - - async loop() { - // Continue the loop while there are incomplete tasks - while ( - this.taskList.some((task) => task.status === 'incomplete') && - this.isRunning - ) { - this.statusCallback({ type: 'preparing' }); - // Filter out incomplete tasks - const incompleteTasks = this.taskList.filter( - (task) => task.status === 'incomplete', - ); - - if (incompleteTasks.length === 0) { - break; - } - - // sort tasks by id - incompleteTasks.sort((a, b) => a.id - b.id); - - // Pull the first task - const task = incompleteTasks[0]; - - if (!this.isRunning) break; - - // Execute the task & call task manager from function - await this.executeTask(task, incompleteTasks, this.objective); - - this.statusCallback({ type: 'closing' }); - // Print task list - this.printTaskList(); - this.printDone(); - - await new Promise((resolve) => setTimeout(resolve, 1000)); // Sleep before checking the task list again - } - } -} diff --git a/src/agents/babybeeagi/chains/taskManagement.ts b/src/agents/babybeeagi/chains/taskManagement.ts deleted file mode 100644 index d5263164..00000000 --- a/src/agents/babybeeagi/chains/taskManagement.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskManagementChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskManagementTemplate = `You are a task management AI tasked with cleaning the formatting of and reprioritizing the following tasks: {minified_task_list}. - Consider the ultimate objective of your team: {objective}. - Do not remove any tasks. Task description must be answered in {language}. Return the result as a JSON-formatted list of dictionaries.\n - Create new tasks based on the result of last task if necessary for the objective. Limit tasks types to those that can be completed with the available tools listed below. Task description should be detailed. - The maximum task list length is 7. Do not add an 8th task. - The last completed task has the following result: {result}. - Current tool option is [text-completion] {websearch_var} and [web-scrape] only. - For tasks using [web-scrape], provide only the URL to scrape as the task description. Do not provide placeholder URLs, but use ones provided by a search step or the initial objective. - For tasks using [web-search], provide the search query, and only the search query to use (eg. not 'research waterproof shoes, but 'waterproof shoes') - dependent_task_id should always be null or a number. - Do not reorder completed tasks. Only reorder and dedupe incomplete tasks.\n - Make sure all task IDs are in chronological order.\n - Do not provide example URLs for [web-scrape].\n - Do not include the result from the last task in the JSON, that will be added after..\n - The last step is always to provide a final summary report of all tasks.\n - An example of the desired output format is: `; - - // json format is invalid in prompt template. so escape { to {{ and } to }} - const jsonExamples = - '[' + - '{{"id": 1, "task": "https://untapped.vc", "tool": "web-scrape", "dependent_task_id": null, "status": "incomplete", "result": null, "result_summary": null}},' + - '{{"id": 2, "task": "Analyze the contents of...", "tool": "text-completion", "dependent_task_id": 1, "status": "incomplete", "result": null, "result_summary": null}},' + - '{{"id": 3, "task": "Untapped Capital", "tool": "web-search", "dependent_task_id": null, "status": "incomplete", "result": null, "result_summary": null}}' + - '].'; - - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate( - 'You are a task management AI tasked with cleaning the formatting of and reprioritizing. The response will return only JSON.', - ), - HumanMessagePromptTemplate.fromTemplate( - taskManagementTemplate + jsonExamples, - ), - ]); - - prompt.inputVariables = [ - 'minified_task_list', - 'objective', - 'result', - 'websearch_var', - 'language', - ]; - - return new TaskManagementChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babybeeagi/chains/taskOverview.ts b/src/agents/babybeeagi/chains/taskOverview.ts deleted file mode 100644 index f94993ea..00000000 --- a/src/agents/babybeeagi/chains/taskOverview.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { PromptTemplate } from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskOverviewChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskOverviewTemplate = - `Here is the current session summary:\n{session_summary}` + - ` to create new tasks with the following objective: {objective},` + - ` The last completed task is task {last_task_id}.` + - ` Please update the session summary with the information of the last task:` + - ` {completed_tasks_text}` + - ` Updated session summary, which should describe all tasks in chronological order:`; - const prompt = new PromptTemplate({ - template: taskOverviewTemplate, - inputVariables: [ - 'objective', - 'session_summary', - 'last_task_id', - 'completed_tasks_text', - ], - }); - return new TaskOverviewChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babybeeagi/chains/taskSummarize.ts b/src/agents/babybeeagi/chains/taskSummarize.ts deleted file mode 100644 index 4e761451..00000000 --- a/src/agents/babybeeagi/chains/taskSummarize.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PromptTemplate } from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskSummarizeChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskSummarizeTemplate = - 'Please summarize the following text. The Summary must be answered in {language}. If specific figures are available, they must be included.:\n{text}\nSummary:'; - const prompt = new PromptTemplate({ - template: taskSummarizeTemplate, - inputVariables: ['text', 'language'], - }); - return new TaskSummarizeChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babybeeagi/service.ts b/src/agents/babybeeagi/service.ts deleted file mode 100644 index a8117973..00000000 --- a/src/agents/babybeeagi/service.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { TaskSummarizeChain } from './chains/taskSummarize'; -import { TaskOverviewChain } from './chains/taskOverview'; -import { TaskManagementChain } from './chains/taskManagement'; -import { AgentTask } from './agent'; -import { OpenAI, OpenAIChat } from 'langchain/llms/openai'; -import { stringifyTasks } from '@/utils/task'; - -export const summarizerAgent = async ( - text: string, - language: string, - openAIApiKey?: string, -) => { - const model = new OpenAI({ - openAIApiKey, - modelName: 'gpt-3.5-turbo-0613', - temperature: 0.5, - maxTokens: 100, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const chain = TaskSummarizeChain.fromLLM({ llm: model }); - const response = await chain.call({ - text, - language, - }); - return response.text; -}; - -export const overviewAgent = async ( - objective: string, - sessionSummary: string, - lastTaskId: number, - completedTasksText: string, - openAIApiKey?: string, -) => { - const model = new OpenAI({ - openAIApiKey, - modelName: 'gpt-3.5-turbo-0613', - temperature: 0.5, - maxTokens: 200, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const chain = TaskOverviewChain.fromLLM({ llm: model }); - const response = await chain.call({ - objective, - session_summary: sessionSummary, - last_task_id: lastTaskId, - completed_tasks_text: completedTasksText, - }); - return response.text; -}; - -export const taskManagementAgent = async ( - minifiedTaskList: AgentTask[], - objective: string, - result: string, - websearchVar: string, - modelName: string, - language: string, - openAIApiKey?: string, -) => { - const model = new OpenAIChat({ - openAIApiKey, - modelName, - temperature: 0.2, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const chain = TaskManagementChain.fromLLM({ llm: model }); - - const response = await chain.call({ - // minified_task_list: stringifyTasks(minifiedTaskList), - minified_task_list: stringifyTasks([]), - objective, - result, - websearch_var: websearchVar, - language, - }); - return response.text; -}; diff --git a/src/agents/babybeeagi/tools/textCompletion.ts b/src/agents/babybeeagi/tools/textCompletion.ts deleted file mode 100644 index 9f7e6295..00000000 --- a/src/agents/babybeeagi/tools/textCompletion.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { OpenAIChat } from 'langchain/llms/openai'; - -export const textCompletion = async ( - text: string, - modelName: string, - openAIApiKey?: string, -) => { - const tool = new OpenAIChat({ - openAIApiKey: openAIApiKey, - modelName: modelName, - temperature: 0.5, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - const result = await tool.call(text); - return result; -}; diff --git a/src/agents/babycatagi/agent.ts b/src/agents/babycatagi/agent.ts deleted file mode 100644 index 28c46733..00000000 --- a/src/agents/babycatagi/agent.ts +++ /dev/null @@ -1,556 +0,0 @@ -import { AgentStatus, AgentTask, Message } from '@/types'; -import { textCompletion } from './tools/textCompletion'; -import { getToolIcon, setupMessage } from '@/utils/message'; -import axios from 'axios'; -import { parseTasks } from '@/utils/task'; -import { getUserApiKey } from '@/utils/settings'; -import { extractRelevantInfoAgent, taskCreationAgent } from './service'; -import { simplifySearchResults } from '../common/tools/webSearch'; - -export class BabyCatAGI { - objective: string; - modelName: string; - taskList: AgentTask[] = []; - sessionSummary: string = ''; - taskIdCounter: number = 1; - isRunning: boolean; - verbose: boolean; - language: string = 'en'; - messageCallback: (message: Message) => void; - statusCallback: (status: AgentStatus) => void; - cancelCallback: () => void; - abortController?: AbortController; - chunk: string = ''; - - constructor( - objective: string, - modelName: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancel: () => void, - language: string = 'en', - verbose: boolean = false, - ) { - this.objective = objective; - this.taskList = []; - this.verbose = verbose; - this.modelName = modelName; - this.language = language; - this.cancelCallback = cancel; - this.messageCallback = messageCallback; - this.statusCallback = statusCallback; - this.isRunning = false; - } - - // print logs - printBabyCat() { - if (!this.verbose) return; - console.log( - '%c*****BABY CAT AGI*****\n\n%c%s', - 'color:orange', - '', - 'Baby Cat AGI is running...', - ); - } - - printObjective() { - this.messageCallback(setupMessage('objective', this.objective)); - if (!this.verbose) return; - console.log( - '%c*****OBJECTIVE*****\n\n%c%s', - 'color:blue', - '', - this.objective, - ); - } - - printTaskList() { - if (!this.isRunning) return; - - let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - this.taskList.forEach((task) => { - const dependentTask = task.dependentTaskIds - ? `${task.dependentTaskIds.join(', ')}` - : '-'; - const status = task.status === 'complete' ? '✅' : '⬜️'; - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; - }); - - this.messageCallback(setupMessage('task-list', message)); - - if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n\n%c', 'color:fuchsia', ''); - console.log(message); - } - - printSessionSummary() { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('session-summary', this.sessionSummary)); - - if (!this.verbose) return; - console.log('%c*****SESSION SUMMARY*****\n\n%c', 'color:orange', ''); - console.log(this.sessionSummary); - } - - printNextTask(task: AgentTask) { - if (!this.isRunning) return; - - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - this.messageCallback(setupMessage('next-task', nextTask)); - - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n\n%s', 'color:green', '', nextTask); - } - - printTaskOutput(output: string, task: AgentTask) { - if (!this.isRunning) return; - - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - output = '```\n' + output + '\n```'; - } - this.messageCallback(setupMessage('task-output', output, task?.tool)); - - if (!this.verbose) return; - console.log('%c*****TASK RESULT*****\n%c%s', 'color:purple', '', output); - } - - printDone() { - if (!this.isRunning) return; - - this.messageCallback( - setupMessage( - 'done', - `Number of tasks completed: ${this.taskIdCounter.toString()}`, - ), - ); - - if (!this.verbose) return; - console.log('%c*****DONE*****%c', 'color:blue', ''); - } - - printAllTaskCompleted() { - if (!this.isRunning) return; - - this.messageCallback(setupMessage('complete', 'All Tasks Completed')); - if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:blue', ''); - } - - // Tools functions - async textCompletionTool(prompt: string) { - this.abortController = new AbortController(); - this.statusCallback({ type: 'executing' }); - - this.chunk = '```markdown\n'; - const callback = (token: string) => { - this.chunk += token; - this.statusCallback({ type: 'executing-stream', message: this.chunk }); - }; - - if (getUserApiKey()) { - this.statusCallback({ type: 'executing-stream' }); - - return await textCompletion( - prompt, - 'gpt-3.5-turbo-0613', - this.abortController?.signal, - getUserApiKey(), - callback, - ); - } - - const response = await axios - .post( - '/api/tools/completion', - { - prompt, - apiKey: getUserApiKey(), - callback, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async webSearchTool(query: string) { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data.response; - } - - async webScrapeTool(url: string) { - const response = await axios - .post( - '/api/tools/scrape', - { - url, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; - } - - async callbackSearchStatus(message: string) { - this.statusCallback({ - type: 'executing-stream', - message: '```markdown\n' + message + '\n```', - }); - } - - async webSearchToolWithAgent(task: AgentTask) { - // get search results - const searchResults = await this.webSearchTool(task.task); - - if (!this.isRunning) return; - - // simplify search results - const sinmplifiedSearchResults = simplifySearchResults(searchResults); - if (this.verbose) { - console.log('Completed search. Now scraping results.\n'); - } - let statusMessage = 'Completed search. Now scraping results.\n'; - this.callbackSearchStatus(statusMessage); - - if (!this.isRunning) return; - - let result = ''; - let index = 1; - // Loop through search results - for (const searchResult of sinmplifiedSearchResults) { - if (!this.isRunning) break; - if (index >= 5) break; - - // Extract the URL from the search result - const url = searchResult.link; - if (this.verbose) { - console.log('Scraping: %s ...', url); - } - statusMessage += `${index}. Scraping: ${url} ...\n`; - this.callbackSearchStatus(statusMessage); - - const content = await this.webScrapeTool(url); - if (!content) continue; - - if (this.verbose) { - console.log( - 'Scrape completed. Length:%s. Now extracting relevant info... \n', - content.length, - ); - } - statusMessage += ` - Scrape completed. Length:${content.length}. Now extracting relevant info... \n`; - this.callbackSearchStatus(statusMessage); - - if (!this.isRunning) break; - - // extract relevant text from the scraped text - const info = await this.extractRelevantInfo(content.slice(0, 5000), task); - // combine search result and scraped text - result += `${info}. `; - - if (this.verbose) { - console.log('Content: %s ...\n', result.slice(0, 100)); - } - statusMessage += ` - Content: ${result.slice(0, 100)} ...\n`; - this.callbackSearchStatus(statusMessage); - - index++; - } - - if (!this.isRunning) return; - - // callback to search logs - this.messageCallback( - setupMessage('search-logs', '```markdown\n' + statusMessage + '\n```'), - ); - - return result; - } - - async extractRelevantInfo(largeString: string, task: AgentTask) { - const chunkSize = 2800; //3000; - const overlap = 500; - let notes = ''; - - for (let i = 0; i < largeString.length; i += chunkSize - overlap) { - if (!this.isRunning) break; - - const chunk = largeString.slice(i, i + chunkSize); - if (getUserApiKey()) { - const response = await extractRelevantInfoAgent( - this.objective, - task.task, - chunk, - notes, - getUserApiKey(), - ); - notes += response; - } else { - // Server side call - const response = await axios - .post( - '/api/tools/extract', - { - objective: this.objective, - task: task.task, - chunk, - notes, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - notes += response?.data?.response; - } - } - return notes; - } - - // Task list functions - async addTask(task: AgentTask) { - this.taskList.push(task); - } - - async getTaskById(id: number) { - return this.taskList.find((task) => task.id === id); - } - - async getCompletedTasks() { - return this.taskList.filter((task) => task.status === 'complete'); - } - - // Agent functions - async taskCreationAgent() { - this.abortController = new AbortController(); - const websearchVar = process.env.SERP_API_KEY ? '[web-search] ' : ''; // if search api key is not set, don't add [web-search] to the task description - - this.chunk = '```json\n'; - const callback = (token: string) => { - this.chunk += token; - this.statusCallback({ type: 'creating-stream', message: this.chunk }); - }; - - let result = ''; - if (getUserApiKey()) { - this.statusCallback({ type: 'creating-stream' }); - - result = await taskCreationAgent( - this.objective, - websearchVar, - this.modelName, - this.language, - this.abortController?.signal, - getUserApiKey(), - callback, - ); - } else { - // Server side call - const response = await axios - .post( - '/api/agents/create', - { - objective: this.objective, - websearch_var: websearchVar, - model_name: this.modelName, - }, - { - signal: this.abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - result = response?.data?.response; - } - - if (!result) { - return []; - } - - let taskList = this.taskList; - // update task list - try { - taskList = parseTasks(result); - } catch (error) { - console.error(error); - // TODO: handle error - } - - return taskList; - } - - async executeTask(task: AgentTask, taskList: AgentTask[], objective: string) { - // Check if dependent task id is not empty - if (task.dependentTaskIds) { - let allDependentTasksCompleted = true; - for (const id of task.dependentTaskIds) { - const dependentTask = await this.getTaskById(id); - if (dependentTask?.status !== 'complete') { - allDependentTasksCompleted = false; - break; - } - } - } - - if (!this.isRunning) return; - - // Execute the task - this.statusCallback({ type: 'executing' }); - this.printNextTask(task); - - let taskPrompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. Your objective: ${objective}. Your task: ${task.task}`; - if (task.dependentTaskIds) { - let dependentTasksOutput = ''; - for (const id of task.dependentTaskIds) { - const dependentTasks = await this.getTaskById(id); - const dependentTaskOutput = dependentTasks?.output?.slice(0, 2000); - dependentTasksOutput += dependentTaskOutput; - } - taskPrompt += `Your dependent task output: ${dependentTasksOutput}\n OUTPUT:`; - } - - if (!this.isRunning) return; - - // Use the tool to complete the task - let taskOutput = ''; - switch (task.tool) { - case 'text-completion': - taskOutput = await this.textCompletionTool(taskPrompt); - break; - case 'web-search': - taskOutput = (await this.webSearchToolWithAgent(task)) ?? ''; - break; - default: - break; - } - - // Find the task index in the task list - const taskIndex = taskList.findIndex((t) => t.id === task.id); - - // Matk task as complete and update the output - taskList[taskIndex].status = 'complete'; - taskList[taskIndex].output = taskOutput; - - // print the task output - this.printTaskOutput(taskOutput, task); - - this.sessionSummary += `\n\nTask: ${task.id} - ${task.task}\n${taskOutput}`; - } - - async stop() { - this.isRunning = false; - this.cancelCallback(); - this.abortController?.abort(); - } - - async start() { - this.isRunning = true; - this.printBabyCat(); - this.printObjective(); - - // Initialize the task id counter - this.taskIdCounter = 0; - - // Run the task creation agent to create the initial tasks - this.taskList = (await this.taskCreationAgent()) ?? []; - - this.printTaskList(); - - if (!this.isRunning) return; - - // Start the loop - await this.loop(); - - if (!this.isRunning) { - this.statusCallback({ type: 'finished' }); - return; - } - - // Objective completed - this.printSessionSummary(); - this.printAllTaskCompleted(); - this.statusCallback({ type: 'finished' }); - this.cancelCallback(); - this.isRunning = false; - } - - async loop() { - // Continue the loop while there are incomplete tasks - while ( - this.taskList.some((task) => task.status === 'incomplete') && - this.isRunning - ) { - this.statusCallback({ type: 'preparing' }); - // Filter out incomplete tasks - const incompleteTasks = this.taskList.filter( - (task) => task.status === 'incomplete', - ); - - // Pull the first task - const task = incompleteTasks[0]; - - if (!this.isRunning) break; - - await this.executeTask(task, this.taskList, this.objective); - - this.taskIdCounter += 1; - this.statusCallback({ type: 'closing' }); - // Print task list - this.printTaskList(); - this.printDone(); - } - } -} diff --git a/src/agents/babycatagi/chains/relevantInfoExtraction.ts b/src/agents/babycatagi/chains/relevantInfoExtraction.ts deleted file mode 100644 index 9c06fd7c..00000000 --- a/src/agents/babycatagi/chains/relevantInfoExtraction.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class relevantInfoExtractionChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; - const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes: - `; - - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate(systemTemplate), - HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), - ]); - - prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; - - return new relevantInfoExtractionChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babycatagi/chains/taskCreation.ts b/src/agents/babycatagi/chains/taskCreation.ts deleted file mode 100644 index 6df76f63..00000000 --- a/src/agents/babycatagi/chains/taskCreation.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; -import { LLMChain, LLMChainInput } from 'langchain/chains'; - -export class TaskCreationChain extends LLMChain { - static fromLLM(fields: Omit): LLMChain { - const taskCreationTemplate = ` - You are a task creation AI tasked with creating a list of tasks as a JSON array, considering the ultimate objective of your team: {objective}. - Create new tasks based on the objective. Limit tasks types to those that can be completed with the available tools listed below. Task description should be detailed. - Task description must be answered in {language}. - Current tool option is [text-completion] {websearch_var} and only." # web-search is added automatically if SERPAPI exists - For tasks using [web-search], provide the search query, and only the search query to use (eg. not 'research waterproof shoes, but 'waterproof shoes') - dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. - Make sure all task IDs are in chronological order.\n - The last step is always to provide a final summary report including tasks executed and summary of knowledge acquired.\n - Do not create any summarizing steps outside of the last step..\n - An example of the desired output format is: - [{{\"id\": 1, \"task\": \"https://untapped.vc\", \"tool\": \"web-scrape\", \"dependent_task_ids\": [], \"status\": \"incomplete\", \"output\": null}}, - {{\"id\": 2, \"task\": \"Consider additional insights that can be reasoned from the results of...\", \"tool\": \"text-completion\", \"dependent_task_ids\": [1], \"status\": \"incomplete\", \"output\": null}}, - {{\"id\": 3, \"task\": \"Untapped Capital\", \"tool\": \"web-search\", \"dependent_task_ids\": [], \"status\": \"incomplete\", \"output\": null}}].\n - JSON TASK LIST= - `; - - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate('You are a task creation AI.'), - HumanMessagePromptTemplate.fromTemplate(taskCreationTemplate), - ]); - - prompt.inputVariables = ['objective', 'websearch_var', 'language']; - - return new TaskCreationChain({ prompt, ...fields }); - } -} diff --git a/src/agents/babycatagi/service.ts b/src/agents/babycatagi/service.ts deleted file mode 100644 index d035f22a..00000000 --- a/src/agents/babycatagi/service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { OpenAI, OpenAIChat } from 'langchain/llms/openai'; -import { relevantInfoExtractionChain } from './chains/relevantInfoExtraction'; -import { TaskCreationChain } from './chains/taskCreation'; - -export const taskCreationAgent = async ( - objective: string, - websearchVar: string, - modelName: string, - language: string, - signal?: AbortSignal, - openAIApiKey?: string, - callback?: (token: string) => void, -) => { - const model = new OpenAIChat( - { - openAIApiKey, - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - if (callback) { - callback(token); - } - }, - }, - ], - }, - { baseOptions: { signal: signal } }, - ); - const chain = TaskCreationChain.fromLLM({ llm: model }); - - try { - const response = await chain.call({ - objective, - websearch_var: websearchVar, - language, - }); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return error; - } -}; - -export const extractRelevantInfoAgent = async ( - objective: string, - task: string, - notes: string, - chunk: string, - openAIApiKey?: string, -) => { - const model = new OpenAI({ - openAIApiKey, - modelName: 'gpt-3.5-turbo-0613', - temperature: 0.7, - maxTokens: 800, - n: 1, - stop: ['###'], - }); - const chain = relevantInfoExtractionChain.fromLLM({ llm: model }); - const response = await chain.call({ - objective, - task: task, - notes, - chunk, - }); - return response.text; -}; diff --git a/src/agents/babycatagi/tools/textCompletion.ts b/src/agents/babycatagi/tools/textCompletion.ts deleted file mode 100644 index ad2dff0a..00000000 --- a/src/agents/babycatagi/tools/textCompletion.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { OpenAI } from 'langchain/llms/openai'; - -export const textCompletion = async ( - text: string, - modelName: string, - abortSignal?: AbortSignal, - openAIApiKey?: string, - callback?: (token: string) => void, -) => { - const tool = new OpenAI( - { - openAIApiKey: openAIApiKey, - modelName: modelName, - temperature: 0.2, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - if (callback) { - callback(token); - } - }, - }, - ], - }, - { baseOptions: { signal: abortSignal } }, - ); - - try { - const response = await tool.call(text); - return response; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return error; - } -}; diff --git a/src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts b/src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts deleted file mode 100644 index 8945119e..00000000 --- a/src/agents/babydeeragi/agents/relevantInfoExtraction/agent.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { getUserApiKey } from '@/utils/settings'; -import { OpenAIChat } from 'langchain/llms/openai'; -import { relevantInfoExtractionPrompt } from './prompt'; -import { LLMChain } from 'langchain/chains'; -import axios from 'axios'; - -// TODO: Only client-side requests are allowed. -// To use the environment variable API key, the request must be implemented from the server side. - -export const relevantInfoExtractionAgent = async ( - objective: string, - task: string, - notes: string, - chunk: string, - signal?: AbortSignal, -) => { - const openAIApiKey = getUserApiKey(); - const modelName = 'gpt-3.5-turbo-16k-0613'; // use a fixed model - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - if (!openAIApiKey) { - // server side request - const response = await axios - .post( - '/api/deer/extract', - { - objective, - task, - notes, - chunk, - model_name: modelName, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - return response?.data?.response; - } - - const prompt = relevantInfoExtractionPrompt(); - const llm = new OpenAIChat( - { - openAIApiKey, - modelName, - temperature: 0.7, - maxTokens: 800, - topP: 1, - stop: ['###'], - }, - { baseOptions: { signal: signal } }, - ); - const chain = new LLMChain({ llm: llm, prompt }); - try { - const response = await chain.call({ - objective, - task, - notes, - chunk, - }); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return 'Failed to extract relevant information.'; - } -}; diff --git a/src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts b/src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts deleted file mode 100644 index 59e8dade..00000000 --- a/src/agents/babydeeragi/agents/relevantInfoExtraction/prompt.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - ChatPromptTemplate, - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -} from 'langchain/prompts'; - -export const relevantInfoExtractionPrompt = () => { - const systemTemplate = `Objective: {objective}\nCurrent Task:{task}`; - const relevantInfoExtractionTemplate = `Analyze the following text and extract information relevant to our objective and current task, and only information relevant to our objective and current task. If there is no relevant information do not say that there is no relevant informaiton related to our objective. ### Then, update or start our notes provided here (keep blank if currently blank): {notes}.### Text to analyze: {chunk}.### Updated Notes:`; - const prompt = ChatPromptTemplate.fromPromptMessages([ - SystemMessagePromptTemplate.fromTemplate(systemTemplate), - HumanMessagePromptTemplate.fromTemplate(relevantInfoExtractionTemplate), - ]); - - prompt.inputVariables = ['objective', 'task', 'notes', 'chunk']; - - return prompt; -}; diff --git a/src/agents/babydeeragi/agents/taskCreation/agent.ts b/src/agents/babydeeragi/agents/taskCreation/agent.ts deleted file mode 100644 index 9c829b12..00000000 --- a/src/agents/babydeeragi/agents/taskCreation/agent.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { OpenAIChat } from 'langchain/llms/openai'; -import { taskCreationPrompt } from './prompt'; -import { LLMChain } from 'langchain/chains'; -import { AgentTask, Message } from '@/types'; -import { getUserApiKey } from '@/utils/settings'; -import { parseTasks } from '@/utils/task'; -import { translate } from '@/utils/translate'; -import axios from 'axios'; - -// TODO: Only client-side requests are allowed. -// To use the environment variable API key, the request must be implemented from the server side. - -export const taskCreationAgent = async ( - objective: string, - modelName: string, - language: string, - signal?: AbortSignal, - messageCallback?: (message: Message) => void, -) => { - let chunk = '```json\n'; - const websearchVar = - process.env.SERP_API_KEY || process.env.GOOGLE_SEARCH_API_KEY - ? '[web-search] ' - : ''; // if search api key is not set, don't add [web-search] to the task description - - const userinputVar = '[user-input] '; - const prompt = taskCreationPrompt(); - const openAIApiKey = getUserApiKey(); - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - let result = ''; - if (getUserApiKey()) { - // client side request - const model = new OpenAIChat( - { - openAIApiKey, - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - maxRetries: 3, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - const message: Message = { - type: 'task-execute', - title: translate('CREATING', 'message'), - text: chunk, - icon: '📝', - id: 0, - }; - messageCallback?.(message); - }, - }, - ], - }, - { baseOptions: { signal: signal } }, - ); - - const chain = new LLMChain({ llm: model, prompt }); - try { - const response = await chain.call({ - objective, - websearch_var: websearchVar, - user_input_var: userinputVar, - language, - }); - result = response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log(error); - return null; - } - } else { - // server side request - const response = await axios - .post( - '/api/deer/create', - { - objective: objective, - websearch_var: websearchVar, - user_input_var: userinputVar, - model_name: modelName, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - result = response?.data?.response; - } - - if (!result) { - return null; - } - - let taskList: AgentTask[] = []; - // update task list - try { - taskList = parseTasks(result); - } catch (error) { - console.log(error); - // TODO: handle error - return null; - } - - return taskList; -}; diff --git a/src/agents/babydeeragi/agents/taskCreation/prompt.ts b/src/agents/babydeeragi/agents/taskCreation/prompt.ts deleted file mode 100644 index 437630ed..00000000 --- a/src/agents/babydeeragi/agents/taskCreation/prompt.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { PromptTemplate } from 'langchain/prompts'; - -export const taskCreationPrompt = () => { - const prompt = new PromptTemplate({ - inputVariables: [ - 'objective', - 'websearch_var', - 'user_input_var', - 'language', - ], - template: ` - You are an expert task creation AI tasked with creating a list of tasks as a JSON array, considering the ultimate objective of your team: {objective}. - Create new tasks based on the objective. Limit tasks types to those that can be completed with the available tools listed below. Task description should be detailed. - Task description must be answered in {language}. - Current tool options are [text-completion] {websearch_var} {user_input_var}. - For tasks using [web-search], provide the search query, and only the search query to use (eg. not 'research waterproof shoes, but 'waterproof shoes'). Result will be a summary of relevant information from the first few articles. - When requiring multiple searches, use the [web-search] multiple times. This tool will use the dependent task result to generate the search query if necessary. - Use [user-input] sparingly and only if you need to ask a question to the user who set up the objective. The task description should be the question you want to ask the user.') - dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. - Make sure all task IDs are in chronological order.\n - EXAMPLE OBJECTIVE=Look up AI news from today (May 27, 2023) and write a poem. - TASK LIST=[ - {{\"id\":1,\"task\":\"AI news today\",\"tool\":\"web-search\",\"dependent_task_ids\":[],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}}, - {{\"id\":2,\"task\":\"Summarize a news article\",\"tool\":\"text-completion\",\"dependent_task_ids\":[1],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}}, - {{\"id\":3,\"task\":\"Pick up important news\",\"tool\":\"text-completion\",\"dependent_task_ids\":[2],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}}, - {{\"id\":4,\"task\":\"Final summary report\",\"tool\":\"text-completion\",\"dependent_task_ids\":[1,2,3],\"status\":\"incomplete\",\"result\":null,\"result_summary\":null}} - ] - OBJECTIVE={objective} - TASK LIST= - `, - }); - - return prompt; -}; diff --git a/src/agents/babydeeragi/executer.ts b/src/agents/babydeeragi/executer.ts deleted file mode 100644 index 85757ca6..00000000 --- a/src/agents/babydeeragi/executer.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { AgentExecuter } from '../base/AgentExecuter'; -import { taskCreationAgent } from './agents/taskCreation/agent'; -import { AgentTask } from '@/types'; -import { getTaskById } from '@/utils/task'; -import { webBrowsing } from './tools/webBrowsing'; -import { textCompletionToolPrompt } from './prompt'; -import { textCompletionTool } from '../common/tools/textCompletionTool'; -import { setupMessage } from '@/utils/message'; -import { toast } from 'sonner'; -import { translate } from '@/utils/translate'; - -export class BabyDeerAGI extends AgentExecuter { - sessionSummary = `OBJECTIVE: ${this.objective}\n\n`; - userInputResolvers: { [id: number]: (message: string) => void } = {}; - userInputPromises: { [id: number]: Promise } = {}; - - // Create task list by agent - async taskCreation() { - this.statusCallback({ type: 'creating' }); - this.abortController = new AbortController(); - const taskList = await taskCreationAgent( - this.objective, - this.modelName, - this.language, - this.abortController?.signal, - this.messageCallback, - ); - - if (!taskList) { - toast.error(translate('ERROR_CREATING_TASKS', 'message')); - this.stop(); - return; - } - - this.taskList = taskList; - this.printer.printTaskList(this.taskList, 0); - } - - async taskOutputWithTool(task: AgentTask) { - let taskOutput = ''; - switch (task.tool) { - case 'text-completion': - this.abortController = new AbortController(); - let dependentTasksOutput = ''; - if (task.dependentTaskIds) { - for (const id of task.dependentTaskIds) { - const dependentTask = getTaskById(this.taskList, id); - const dependentTaskOutput = dependentTask?.output; - dependentTasksOutput += `${dependentTask?.task}: ${dependentTaskOutput}\n`; - } - } - const prompt = textCompletionToolPrompt( - this.objective, - this.language, - task.task, - dependentTasksOutput.slice(0, 14000), - ); - - taskOutput = await textCompletionTool( - prompt, - this.modelName, - this.abortController?.signal, - task.id, - this.messageCallback, - ); - break; - case 'web-search': - let dependentOutput = ''; - if (task.dependentTaskIds) { - for (const dependentTaskId of task.dependentTaskIds) { - const dependentTask = getTaskById(this.taskList, dependentTaskId); - if (!dependentTask) continue; - const dependentTaskOutput = dependentTask.output; - dependentOutput += `${dependentTask.task}: ${dependentTaskOutput}\n`; - } - } - taskOutput = - (await webBrowsing( - this.objective, - task, - dependentOutput, - this.messageCallback, - this.statusCallback, - this.isRunningRef, - this.verbose, - this.modelName, - this.language, - this.abortController?.signal, - )) ?? ''; - break; - case 'user-input': - taskOutput = await this.getUserInput(task); - break; - default: - break; - } - return taskOutput; - } - - async executeTask(task: AgentTask) { - if (!this.isRunningRef.current) return; - - // Find the task index in the task list - const taskIndex = this.taskList.findIndex((t) => t.id === task.id); - - // Execute the task - this.taskList[taskIndex].status = 'running'; - this.currentStatusCallback(); - // this.printer.printNextTask(task); - this.printer.printTaskExecute(task); - - let taskOutput = await this.taskOutputWithTool(task); - - if (!this.isRunningRef.current) return; - - // print the task output - this.printer.printTaskOutput(taskOutput, task); - - if (!this.isRunningRef.current) return; - - // Update the task status - this.taskList[taskIndex].output = taskOutput; - this.taskList[taskIndex].status = 'complete'; - - this.currentStatusCallback(); - } - - // Override AgentExecuter - async prepare() { - super.prepare(); - this.userInputPromises = {}; - this.userInputResolvers = {}; - // 1. Create task list - await this.taskCreation(); - } - - async loop() { - // Continue the loop while there are incomplete tasks - while ( - this.isRunningRef.current && - this.taskList.some((task) => task.status === 'incomplete') - ) { - if (!this.isRunningRef.current) { - break; - } - - this.statusCallback({ type: 'preparing' }); - - const incompleteTasks = this.taskList.filter( - (task) => task.status === 'incomplete', - ); - // Filter tasks that have all their dependencies completed - const MaxExecutableTasks = 5; - const executableTasks = incompleteTasks - .filter((task) => { - if (!task.dependentTaskIds) return true; - return task.dependentTaskIds.every((id) => { - const dependentTask = getTaskById(this.taskList, id); - return dependentTask?.status === 'complete'; - }); - }) - .slice(0, MaxExecutableTasks); - - // Execute all executable tasks in parallel - await Promise.all(executableTasks.map((task) => this.executeTask(task))); - } - } - - async finishup() { - if (!this.isRunningRef.current) { - this.statusCallback({ type: 'finished' }); - return; - } - const id = this.taskList.length + 1; - this.printer.printTaskList(this.taskList, id); - - super.finishup(); - } - - async userInput(taskId: number, message: string): Promise { - if (this.userInputResolvers[taskId]) { - this.userInputResolvers[taskId](message); - delete this.userInputResolvers[taskId]; - delete this.userInputPromises[taskId]; - } - } - - getUserInput(task: AgentTask) { - this.messageCallback( - setupMessage('user-input', task.task, task.tool, undefined, task.id), - ); - toast.message(translate('USER_INPUT_WAITING', 'message')); - this.statusCallback({ type: 'user-input' }); - this.userInputPromises[task.id] = new Promise((resolve) => { - this.userInputResolvers[task.id] = resolve; - }); - return this.userInputPromises[task.id]; - } - - currentStatusCallback = () => { - const ids = this.taskList - .filter((t) => t.status === 'running') - .map((t) => t.id); - this.statusCallback({ - type: 'executing', - message: `(👉 ${ids.join(', ')} / ${this.taskList.length})`, - }); - }; -} diff --git a/src/agents/babydeeragi/prompt.ts b/src/agents/babydeeragi/prompt.ts deleted file mode 100644 index fbc7a107..00000000 --- a/src/agents/babydeeragi/prompt.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const searchQueryPrompt = (task: string, dependent_task: string) => { - return `You are an AI assistant tasked with generating a Google search query based on the following task: ${task}. - ${ - dependent_task.length > 0 - ? `Consider the results of dependent tasks: ${dependent_task}.` - : '' - } - If the task looks like a search query, return the identical search query as your response. - Search Query:`; -}; - -export const analystPrompt = (results: string, language: string) => { - return `You are an expert analyst. Rewrite the following information as one report without removing any facts. - Report must be answered in ${language}. - \n###INFORMATION:${results}.\n###REPORT:`; -}; - -export const textCompletionToolPrompt = ( - objective: string, - language: string, - task: string, - dependentTaskOutput: string, -) => { - let prompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. - Your objective: ${objective}. Your task: ${task} - Output must be answered in ${language}.\n - `; - if (dependentTaskOutput !== '') { - prompt += `Your dependent tasks: ${dependentTaskOutput}\n OUTPUT:`; - } else { - prompt += '\nOUTPUT:'; - } - return prompt; -}; diff --git a/src/agents/babydeeragi/tools/largeTextExtract.ts b/src/agents/babydeeragi/tools/largeTextExtract.ts deleted file mode 100644 index fe59e7e9..00000000 --- a/src/agents/babydeeragi/tools/largeTextExtract.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AgentStatus, AgentTask } from '@/types'; -import { getUserApiKey } from '@/utils/settings'; -import { relevantInfoExtractionAgent } from '../agents/relevantInfoExtraction/agent'; -import axios from 'axios'; -import React from 'react'; - -export const largeTextExtract = async ( - objective: string, - largeString: string, - task: AgentTask, - isRunningRef?: React.MutableRefObject, - callback?: (message: string) => void, - signal?: AbortSignal, -) => { - const chunkSize = 15000; - const overlap = 500; - let notes = ''; - - // for status message - const total = Math.ceil(largeString.length / (chunkSize - overlap)); - - for (let i = 0; i < largeString.length; i += chunkSize - overlap) { - if (!isRunningRef?.current) break; - - callback?.(` - chunk ${i / (chunkSize - overlap) + 1} of ${total}\n`); - - const chunk = largeString.slice(i, i + chunkSize); - // Client side call - if (getUserApiKey()) { - const response = await relevantInfoExtractionAgent( - objective, - task.task, - notes, - chunk, - ); - notes += response; - } else { - // Server side call - const response = await axios - .post( - '/api/tools/extract', - { - objective: objective, - task: task.task, - chunk, - notes, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - notes += response?.data?.response; - } - } - return notes; -}; diff --git a/src/agents/babydeeragi/tools/webBrowsing.ts b/src/agents/babydeeragi/tools/webBrowsing.ts deleted file mode 100644 index eff879cd..00000000 --- a/src/agents/babydeeragi/tools/webBrowsing.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { simplifySearchResults } from '@/agents/common/tools/webSearch'; -import { AgentStatus, AgentTask, Message } from '@/types'; -import axios from 'axios'; -import { getTaskById } from '@/utils/task'; -import { analystPrompt, searchQueryPrompt } from '../prompt'; -import { textCompletionTool } from '../../common/tools/textCompletionTool'; -import { largeTextExtract } from './largeTextExtract'; -import { translate } from '@/utils/translate'; - -export const webBrowsing = async ( - objective: string, - task: AgentTask, - dependentTasksOutput: string, - messageCallback: (message: Message) => void, - statusCallback?: (status: AgentStatus) => void, - isRunningRef?: React.MutableRefObject, - verbose: boolean = false, - modelName: string = 'gpt-3.5-turbo', - language: string = 'en', - signal?: AbortSignal, -) => { - const prompt = searchQueryPrompt( - task.task, - dependentTasksOutput.slice(0, 3500), - ); - const searchQuery = await textCompletionTool(prompt, modelName, signal); - - const trimmedQuery = searchQuery.replace(/^"|"$/g, ''); // remove quotes from the search query - - let title = `🔎 Searching: ${trimmedQuery}`; - let message = `Search query: ${trimmedQuery}\n`; - callbackSearchStatus(title, message, task, messageCallback); - const searchResults = await webSearchTool(trimmedQuery, signal); - if (!searchResults) { - return 'Failed to search.'; - } - let statusMessage = message; - - if (!isRunningRef?.current) return; - - const simplifiedSearchResults = simplifySearchResults(searchResults); - title = `📖 Reading content...`; - message = `✅ Completed search. \nNow reading content.\n`; - if (verbose) { - console.log(message); - } - - statusMessage += message; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - if (!isRunningRef.current) return; - - let results = ''; - let index = 1; - let completedCount = 0; - const MaxCompletedCount = 3; - // Loop through search results - for (const searchResult of simplifiedSearchResults) { - if (!isRunningRef.current) return; - if (completedCount >= MaxCompletedCount) break; - - // Extract the URL from the search result - const url = searchResult.link; - let title = `${index}. Reading: ${url} ...`; - - if (verbose) { - console.log(message); - } - statusMessage += `${title}\n`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - const content = (await webScrapeTool(url, signal)) ?? ''; - - title = `${index}. Extracting relevant info...`; - message = ` - Content reading completed. Length:${content.length}. Now extracting relevant info...\n`; - if (verbose) { - console.log(message); - } - - statusMessage += message; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - if (content.length === 0) { - let message = ` - Content too short. Skipped. \n`; - if (verbose) console.log(message); - statusMessage += message; - callbackSearchStatus(undefined, statusMessage, task, messageCallback); - index += 1; - continue; - } - - if (!isRunningRef.current) return; - - // extract relevant text from the scraped text - const callback = (message: string) => { - if (verbose) { - console.log(message); - } - statusMessage = `${statusMessage}${message}`; - title = `${index}. Extracting relevant info... ${message}`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - }; - - statusMessage += ` - Extracting relevant information\n`; - title = `${index}. Extracting relevant info...`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - const info = await largeTextExtract( - objective, - content.slice(0, 20000), - task, - isRunningRef, - callback, - signal, - ); - - message = ` - Relevant info: ${info - .slice(0, 100) - .replace(/\r?\n/g, '')} ...\n`; - if (verbose) { - console.log(message); - } - statusMessage += message; - title = `${index}. Relevant info...`; - callbackSearchStatus(title, statusMessage, task, messageCallback); - - results += `${info}. `; - index += 1; - completedCount += 1; - } - - if (!isRunningRef.current) return; - - callbackSearchStatus( - 'Analyzing results...', - `${statusMessage}Analyze results...`, - task, - messageCallback, - ); - - const ap = analystPrompt(results, language); - const analyzedResults = await textCompletionTool( - ap, - modelName, - signal, - task.id, - messageCallback, - ); - - // callback to search logs - const msg: Message = { - type: 'search-logs', - text: '```markdown\n' + statusMessage + '\n```', - title: `🔎 ${translate('SEARCH_LOGS', 'message')}`, - id: task.id, - icon: '🌐', - open: false, - }; - messageCallback(msg); - - return analyzedResults; -}; - -const callbackSearchStatus = ( - title: string | undefined, - message: string, - task: AgentTask, - messageCallback: (message: Message) => void, -) => { - messageCallback({ - type: 'search-logs', - title: title ?? translate('SEARCH_LOGS', 'message'), - text: '```markdown\n' + message + '\n```', - id: task.id, - icon: '🌐', - open: false, - }); -}; - -const webSearchTool = async (query: string, signal?: AbortSignal) => { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data.response; -}; - -const webScrapeTool = async (url: string, signal?: AbortSignal) => { - const response = await axios - .post( - '/api/tools/scrape', - { - url, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - - return response?.data?.response; -}; diff --git a/src/agents/babyelfagi/example_objectives/example1.json b/src/agents/babyelfagi/example_objectives/example1.json deleted file mode 100644 index 6bcd9b73..00000000 --- a/src/agents/babyelfagi/example_objectives/example1.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "objective": "Create a new skill that writes a poem based on an input.", - "examples": [ - { - "id": 1, - "task": "Read the code in textCompletion.ts using the code_reader skill to understand its structure and concepts.", - "skill": "code_reader", - "icon": "📖", - "dependent_task_ids": [], - "status": "incomplete" - }, - { - "id": 2, - "task": "Write a new skill that uses the concepts from textCompletion.ts to generate a poem based on user input.", - "skill": "text_completion", - "icon": "🤖", - "dependent_task_ids": [1], - "status": "incomplete" - }, - { - "id": 3, - "task": "Save the newly created skill using the skill_saver skill for future use.", - "skill": "skill_saver", - "icon": "💾", - "dependent_task_ids": [2], - "status": "incomplete" - } - ] -} diff --git a/src/agents/babyelfagi/executer.ts b/src/agents/babyelfagi/executer.ts index f1aa069d..165c2364 100644 --- a/src/agents/babyelfagi/executer.ts +++ b/src/agents/babyelfagi/executer.ts @@ -1,61 +1,68 @@ -import { AgentStatus, Message, TaskOutputs } from '@/types'; // You need to define these types +import { AgentMessage, TaskOutputs } from '@/types'; // You need to define these types import { AgentExecuter } from '../base/AgentExecuter'; import { SkillRegistry, TaskRegistry } from './registory'; -import { translate } from '@/utils/translate'; +import { v4 as uuidv4 } from 'uuid'; const REFLECTION = false; // If you want to use reflection, set this to true. now support only client side reflection. export class BabyElfAGI extends AgentExecuter { skillRegistry: SkillRegistry; taskRegistry: TaskRegistry; - sessionSummary: string; + sessionSummary: string = ''; constructor( objective: string, modelName: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancelCallback: () => void, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, language: string = 'en', verbose: boolean = false, + specifiedSkills: string[] = [], + userApiKey?: string, + signal?: AbortSignal, ) { - super( - objective, - modelName, - messageCallback, - statusCallback, - cancelCallback, - language, - verbose, - ); + super(objective, modelName, handlers, language, verbose, signal); + + signal?.addEventListener('abort', () => { + this.verbose && + console.log('Abort signal received. Stopping execution...'); + }); - this.abortController = new AbortController(); this.skillRegistry = new SkillRegistry( - this.messageCallback, - this.abortController, - this.isRunningRef, - verbose, + this.handlers.handleMessage, + this.verbose, + this.language, + specifiedSkills, + userApiKey, + this.signal, + ); + + const useSpecifiedSkills = specifiedSkills.length > 0; + this.taskRegistry = new TaskRegistry( this.language, + this.verbose, + useSpecifiedSkills, + userApiKey, + this.signal, ); - this.taskRegistry = new TaskRegistry(this.verbose); - this.sessionSummary = ''; } async prepare() { await super.prepare(); const skillDescriptions = this.skillRegistry.getSkillDescriptions(); - this.abortController = new AbortController(); - this.statusCallback({ type: 'creating' }); + const id = uuidv4(); + // Create task list await this.taskRegistry.createTaskList( + id, this.objective, skillDescriptions, - 'gpt-4', // Culletly using GPT-4 - this.messageCallback, - this.abortController, - this.language, + this.modelName, + this.handlers.handleMessage, ); - this.printer.printTaskList(this.taskRegistry.tasks, 0); + this.printer.printTaskList(this.taskRegistry.tasks, id); } async loop() { @@ -64,14 +71,11 @@ export class BabyElfAGI extends AgentExecuter { for (let task of this.taskRegistry.tasks) { taskOutputs[task.id] = { completed: false, output: undefined }; } - // Loop until all tasks are completed - while (!Object.values(taskOutputs).every((task) => task.completed)) { - if (!this.isRunningRef.current) { - break; - } - this.statusCallback({ type: 'preparing' }); - + while ( + !this.signal?.aborted && + !Object.values(taskOutputs).every((task) => task.completed) + ) { // Get the tasks that are ready to be executed const tasks = this.taskRegistry.getTasks(); @@ -106,14 +110,20 @@ export class BabyElfAGI extends AgentExecuter { updates: { status: 'running' }, }); this.printer.printTaskExecute(task); - this.currentStatusCallback(); - const output = await this.taskRegistry.executeTask( - i, - task, - taskOutputs, - this.objective, - this.skillRegistry, - ); + + let output = ''; + try { + output = await this.taskRegistry.executeTask( + i, + task, + taskOutputs, + this.objective, + this.skillRegistry, + ); + } catch (error) { + console.error(error); + return; + } taskOutputs[task.id] = { completed: true, output: output }; this.taskRegistry.updateTasks({ @@ -156,31 +166,24 @@ export class BabyElfAGI extends AgentExecuter { } async finishup() { + super.finishup(); + const tasks = this.taskRegistry.getTasks(); const lastTask = tasks[tasks.length - 1]; - this.messageCallback({ - type: 'final-result', - text: lastTask.result ?? '', - title: translate('FINAL_TASK_RESULT', 'message'), - icon: '✍️', - id: 9999, + this.handlers.handleMessage({ + type: 'result', + content: lastTask.result ?? '', + id: uuidv4(), }); - this.messageCallback({ + this.handlers.handleMessage({ type: 'session-summary', - text: this.sessionSummary, - id: 9999, + content: this.sessionSummary, + id: uuidv4(), }); super.finishup(); } - currentStatusCallback = () => { - const tasks = this.taskRegistry.getTasks(); - const ids = tasks.filter((t) => t.status === 'running').map((t) => t.id); - this.statusCallback({ - type: 'executing', - message: `(👉 ${ids.join(', ')} / ${tasks.length})`, - }); - }; + async close() {} } diff --git a/src/agents/babyelfagi/registory/skillRegistry.ts b/src/agents/babyelfagi/registory/skillRegistry.ts index 2813a8f6..a3396ce6 100644 --- a/src/agents/babyelfagi/registory/skillRegistry.ts +++ b/src/agents/babyelfagi/registory/skillRegistry.ts @@ -1,56 +1,67 @@ -import { Message } from '@/types'; +import { AgentMessage } from '@/types'; import { - AirtableSaver, + TextCompletion, + WebLoader, + WebSearch, + YoutubeSearch, CodeReader, CodeReviewer, CodeWriter, DirectoryStructure, SkillSaver, - TextCompletion, - WebLoader, - WebSearch, - YoutubeSearch, + AirtableSaver, } from '../skills'; import { Skill } from '../skills/skill'; -import { getUserApiKey } from '@/utils/settings'; export class SkillRegistry { skillClasses: (typeof Skill)[]; skills: Skill[] = []; apiKeys: { [key: string]: string }; + userApiKey?: string; + signal?: AbortSignal; // for UI - messageCallback: (message: Message) => void; - abortController: AbortController; - isRunningRef?: React.MutableRefObject; + handleMessage: (message: AgentMessage) => Promise; verbose: boolean; language: string = 'en'; constructor( - messageCallback?: (message: Message) => void, - abortController?: AbortController, - isRunningRef?: React.MutableRefObject, + handleMessage: (message: AgentMessage) => Promise, verbose: boolean = false, language: string = 'en', + specifiedSkills: string[] = [], + userApiKey?: string, + signal?: AbortSignal, ) { this.skillClasses = SkillRegistry.getSkillClasses(); this.apiKeys = SkillRegistry.apiKeys; - // - this.messageCallback = messageCallback || (() => {}); - this.abortController = abortController || new AbortController(); - this.isRunningRef = isRunningRef; + this.userApiKey = userApiKey; + this.signal = signal; + this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; + if (this.userApiKey) { + this.apiKeys['openai'] = this.userApiKey; + } + // Load all skills for (let SkillClass of this.skillClasses) { let skill = new SkillClass( this.apiKeys, - this.messageCallback, - this.abortController, - this.isRunningRef, + this.handleMessage, this.verbose, this.language, + this.signal, ); + + // If the skill is specified, load it. + if (specifiedSkills.length > 0) { + if (specifiedSkills.includes(skill.name)) { + this.skills.push(skill); + } + continue; + } + if ( skill.type === 'dev' ? process.env.NODE_ENV === 'development' : true ) { @@ -75,21 +86,21 @@ export class SkillRegistry { const skills: (typeof Skill)[] = [ TextCompletion, WebSearch, + WebLoader, + YoutubeSearch, AirtableSaver, CodeReader, CodeWriter, SkillSaver, DirectoryStructure, - YoutubeSearch, CodeReviewer, - WebLoader, ]; return skills; } static apiKeys = { - openai: getUserApiKey() || process.env.OPENAI_API_KEY || '', - airtable: 'keyXXXXXXXXXXXXXX', // Your Airtable API key here + openai: process.env.OPENAI_API_KEY || '', + airtable: process.env.AIRTABLE_API_KEY || '', }; getSkill(name: string): Skill { diff --git a/src/agents/babyelfagi/registory/taskRegistry.ts b/src/agents/babyelfagi/registory/taskRegistry.ts index 12306e9f..26f9912a 100644 --- a/src/agents/babyelfagi/registory/taskRegistry.ts +++ b/src/agents/babyelfagi/registory/taskRegistry.ts @@ -1,32 +1,44 @@ -import _ from 'lodash'; -import { AgentTask, Message, TaskOutputs } from '@/types'; +import { AgentTask, AgentMessage, TaskOutputs } from '@/types'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { parseTasks } from '@/utils/task'; import { HumanChatMessage, SystemChatMessage } from 'langchain/schema'; -import { getUserApiKey } from '@/utils/settings'; -import { translate } from '@/utils/translate'; import { SkillRegistry } from './skillRegistry'; import { findMostRelevantObjective } from '@/utils/objective'; -import axios from 'axios'; - export class TaskRegistry { tasks: AgentTask[]; verbose: boolean = false; - - constructor(verbose = false) { + language: string = 'en'; + useSpecifiedSkills: boolean = false; + userApiKey?: string; + signal?: AbortSignal; + + constructor( + language = 'en', + verbose = false, + useSpecifiedSkills = false, + userApiKey?: string, + signal?: AbortSignal, + ) { this.tasks = []; this.verbose = verbose; + this.language = language; + this.userApiKey = userApiKey; + this.useSpecifiedSkills = useSpecifiedSkills; + this.signal = signal; } async createTaskList( + id: string, objective: string, skillDescriptions: string, modelName: string = 'gpt-3.5-turbo', - messageCallback?: (message: Message) => void, - abortController?: AbortController, - language: string = 'en', + handleMessage: (message: AgentMessage) => Promise, ): Promise { - const relevantObjective = await findMostRelevantObjective(objective); + const relevantObjective = await findMostRelevantObjective( + objective, + this.userApiKey, + ); + const exapmleObjective = relevantObjective.objective; const exampleTaskList = relevantObjective.examples; const prompt = ` @@ -36,10 +48,10 @@ export class TaskRegistry { RULES: Do not use skills that are not listed. Always include one skill. - Do not create files unless specified in the objective. + Do not create files unless specified in the objective. dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. Make sure all task IDs are in chronological order.### - Output must be answered in ${language}. + Output must be answered in ${this.language}. EXAMPLE OBJECTIVE=${exapmleObjective} TASK LIST=${JSON.stringify(exampleTaskList)} OBJECTIVE=${objective} @@ -47,75 +59,43 @@ export class TaskRegistry { const systemPrompt = 'You are a task creation AI.'; const systemMessage = new SystemChatMessage(systemPrompt); const messages = new HumanChatMessage(prompt); - const openAIApiKey = getUserApiKey(); - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } let result = ''; - if (openAIApiKey) { - let chunk = '```json\n'; - const model = new ChatOpenAI( - { - openAIApiKey, - modelName, - temperature: 0, - maxTokens: 1500, - topP: 1, - verbose: this.verbose, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - const message: Message = { - type: 'task-execute', - title: translate('CREATING', 'message'), - text: chunk, - icon: '📝', - id: 0, - }; - messageCallback?.(message); - }, + const model = new ChatOpenAI( + { + openAIApiKey: this.userApiKey, + modelName: this.useSpecifiedSkills ? modelName : 'gpt-4', + temperature: 0, + maxTokens: 1500, + topP: 1, + verbose: false, // You can set this to true to see the lanchain logs + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + const message: AgentMessage = { + id, + content: token, + type: 'task-list', + style: 'log', + status: 'running', + }; + handleMessage(message); }, - ], - }, - { baseOptions: { signal: abortController?.signal } }, - ); + }, + ], + }, + { baseOptions: { signal: this.signal } }, + ); - try { - const response = await model.call([systemMessage, messages]); - result = response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - console.log('Task creation aborted'); - } - console.log(error); + try { + const response = await model.call([systemMessage, messages]); + result = response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + console.log('Task creation aborted'); } - } else { - // server side request - const response = await axios - .post( - '/api/elf/completion', - { - prompt: prompt, - model_name: modelName, - temperature: 0, - max_tokens: 1500, - }, - { - signal: abortController?.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - result = response?.data?.response; + console.log(error); } if (result === undefined) { @@ -137,23 +117,7 @@ export class TaskRegistry { ? task.dependentTaskIds.map((id) => taskOutputs[id].output).join('\n') : ''; - if (skill.executionLocation === 'server') { - // Call the API endpoint if the skill needs to be executed on the server side - const response = await axios.post('/api/execute-skill', { - task: JSON.stringify(task), - dependent_task_outputs: dependentTaskOutputs, - objective, - }); - return response.data.taskOutput; - } else { - // Execute the skill on the client side - let taskOutput = await skill.execute( - task, - dependentTaskOutputs, - objective, - ); - return taskOutput; - } + return await skill.execute(task, dependentTaskOutputs, objective); } getTasks(): AgentTask[] { @@ -181,7 +145,7 @@ export class TaskRegistry { } reorderTasks(): void { - this.tasks = _.sortBy(this.tasks, ['priority', 'task_id']); + this.tasks.sort((a, b) => a.id - b.id); } async reflectOnOutput( @@ -250,7 +214,7 @@ export class TaskRegistry { ); const model = new ChatOpenAI({ - openAIApiKey: getUserApiKey(), + openAIApiKey: this.userApiKey, modelName, temperature: 0.7, maxTokens: 1500, diff --git a/src/agents/babyelfagi/skills/addons/airtableSaver.ts b/src/agents/babyelfagi/skills/addons/airtableSaver.ts index 5a352ed2..da35e550 100644 --- a/src/agents/babyelfagi/skills/addons/airtableSaver.ts +++ b/src/agents/babyelfagi/skills/addons/airtableSaver.ts @@ -1,4 +1,3 @@ -import Airtable from 'airtable'; import { Skill, SkillType } from '../skill'; import { AgentTask } from '@/types'; @@ -17,19 +16,29 @@ export class AirtableSaver extends Skill { async execute( task: AgentTask, - dependentTaskOutputs: any, + dependentTaskOutputs: string, objective: string, ): Promise { if (!this.valid) { return ''; } - const airtable = new Airtable({ apiKey: this.apiKeys['airtable'] }); - const base = airtable.base(this.baseId); const fields = { Notes: dependentTaskOutputs }; // Your fields here + const url = `https://api.airtable.com/v0/${this.baseId}/${this.tableName}`; + const options = { + method: 'POST', + headers: { + Authorization: `Bearer ${this.apiKeys['airtable']}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ fields }), + }; try { - await base(this.tableName).create([{ fields }]); + const response = await fetch(url, options); + if (!response.ok) { + throw new Error('Record creation failed'); + } return 'Record creation successful'; } catch (error: any) { return `Record creation failed: ${error.message}`; diff --git a/src/agents/babyelfagi/skills/addons/youtubeSearch.ts b/src/agents/babyelfagi/skills/addons/youtubeSearch.ts index ceb0a196..596ed689 100644 --- a/src/agents/babyelfagi/skills/addons/youtubeSearch.ts +++ b/src/agents/babyelfagi/skills/addons/youtubeSearch.ts @@ -1,6 +1,6 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import axios from 'axios'; +import { webSearch } from '../../tools/webSearch'; export class YoutubeSearch extends Skill { name = 'youtube_search'; @@ -15,37 +15,26 @@ export class YoutubeSearch extends Skill { objective: string, ): Promise { const prompt = `Generate query for YouTube search based on the dependent task outputs and the objective. - Dependent tasks output: ${dependentTaskOutputs} - Objective: ${objective} + Dont include "Youtube video". Only include the query. + Dependent tasks output: ${dependentTaskOutputs} + Objective: ${objective} `; const query = await this.generateText(prompt, task); - const searchResults = await this.webSearchTool(`site:youtube.com ${query}`); + const searchResults = await webSearch(`site:youtube.com ${query}`); const youtubeLinks = this.extractYoutubeLinks(searchResults); + const result = JSON.stringify(youtubeLinks, null, 2); - return '```json\n' + JSON.stringify(youtubeLinks, null, 2) + '\n```'; - } - - webSearchTool = async (query: string) => { - const response = await axios - .post( - '/api/tools/search', - { - query, - }, - { - signal: this.abortController.signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); + this.callbackMessage({ + taskId: task.id.toString(), + content: '```json\n\n' + result + '\n\n```', + title: task.task, + type: task.skill, + style: 'text', + status: 'complete', + }); - return response?.data.response; - }; + return result; + } extractYoutubeLinks = (searchResults: any[]) => { const youtubeLinks = searchResults diff --git a/src/agents/babyelfagi/skills/index.ts b/src/agents/babyelfagi/skills/index.ts index b6154137..07f7b5de 100644 --- a/src/agents/babyelfagi/skills/index.ts +++ b/src/agents/babyelfagi/skills/index.ts @@ -8,5 +8,5 @@ export * from './presets/objectiveSaver'; export * from './presets/codeWriter'; export * from './presets/codeReviewer'; export * from './presets/webLoader'; -export * from './addons/airtableSaver'; export * from './addons/youtubeSearch'; +export * from './addons/airtableSaver'; diff --git a/src/agents/babyelfagi/skills/presets/codeReader.ts b/src/agents/babyelfagi/skills/presets/codeReader.ts index 78de89a3..a88d5399 100644 --- a/src/agents/babyelfagi/skills/presets/codeReader.ts +++ b/src/agents/babyelfagi/skills/presets/codeReader.ts @@ -37,7 +37,9 @@ export class CodeReader extends Skill { try { const response = await fetch( - `/api/local/read-file?filename=${encodeURIComponent(filePath)}`, + `${this.BASE_URL}/api/local/read-file?filename=${encodeURIComponent( + filePath, + )}`, { method: 'GET', }, @@ -47,6 +49,10 @@ export class CodeReader extends Skill { } const fileContent = await response.json(); console.log(`File content:\n${JSON.stringify(fileContent)}`); + this.callbackMessage({ + content: 'Read file successfully.', + status: 'complete', + }); return JSON.stringify(fileContent); } catch (error) { console.error( diff --git a/src/agents/babyelfagi/skills/presets/codeReviewer.ts b/src/agents/babyelfagi/skills/presets/codeReviewer.ts index aae59138..e1ad0c6a 100644 --- a/src/agents/babyelfagi/skills/presets/codeReviewer.ts +++ b/src/agents/babyelfagi/skills/presets/codeReviewer.ts @@ -31,7 +31,9 @@ export class CodeReviewer extends Skill { const prompt = this.generatePrompt(dependentTaskOutputs); try { - return this.generateText(prompt, task, { modelName: MODEL_NAME }); + const result = this.generateText(prompt, task, { modelName: MODEL_NAME }); + this.sendCompletionMessage(); + return result; } catch (error) { console.error('Failed to generate text:', error); throw new Error( diff --git a/src/agents/babyelfagi/skills/presets/codeWriter.ts b/src/agents/babyelfagi/skills/presets/codeWriter.ts index f6ea5e62..483d269c 100644 --- a/src/agents/babyelfagi/skills/presets/codeWriter.ts +++ b/src/agents/babyelfagi/skills/presets/codeWriter.ts @@ -42,11 +42,12 @@ export class CodeWriter extends Skill { `; try { - return await this.generateText(prompt, task, { + const result = await this.generateText(prompt, task, { modelName: MODEL_NAME, temperature: TEMPERATURE, maxTokens: MAX_TOKENS, }); + return result; } catch (error) { console.error('Error generating text:', error); throw error; diff --git a/src/agents/babyelfagi/skills/presets/directoryStructure.ts b/src/agents/babyelfagi/skills/presets/directoryStructure.ts index 229a1fa0..db6acabc 100644 --- a/src/agents/babyelfagi/skills/presets/directoryStructure.ts +++ b/src/agents/babyelfagi/skills/presets/directoryStructure.ts @@ -15,9 +15,12 @@ export class DirectoryStructure extends Skill { dependentTaskOutputs: string, objective: string, ): Promise { - const response = await fetch('/api/local/directory-structure', { - method: 'GET', - }); + const response = await fetch( + `${this.BASE_URL}/api/local/directory-structure`, + { + method: 'GET', + }, + ); if (!response.ok) { throw new Error('Failed to get directory structure'); } diff --git a/src/agents/babyelfagi/skills/presets/objectiveSaver.ts b/src/agents/babyelfagi/skills/presets/objectiveSaver.ts index b75ba66a..126c09f7 100644 --- a/src/agents/babyelfagi/skills/presets/objectiveSaver.ts +++ b/src/agents/babyelfagi/skills/presets/objectiveSaver.ts @@ -26,13 +26,13 @@ export class ObjectiveSaver extends Skill { const examplesPath = `data/example_objectives/`; try { - const response = await fetch('/api/local/write-file', { + const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - filename: `${examplesPath}/${filename}`, + filename: `${this.BASE_URL}/${examplesPath}/${filename}`, content: `${code}`, }), }); diff --git a/src/agents/babyelfagi/skills/presets/skillSaver.ts b/src/agents/babyelfagi/skills/presets/skillSaver.ts index 21d39c4d..e55678e8 100644 --- a/src/agents/babyelfagi/skills/presets/skillSaver.ts +++ b/src/agents/babyelfagi/skills/presets/skillSaver.ts @@ -33,8 +33,8 @@ export class SkillSaver extends Skill { TASK: ${task.task} CODE: ${code} FILE_NAME:`; - const filename = await this.generateText(filePrompt, task, params); - let skillsPath = `src/agents/babyelfagi/skills/addons`; + const filename = await this.generateText(filePrompt, task, params, true); + let skillsPath = `src/agents/elf/skills/addons`; const dirStructure: string[] = await this.getDirectoryStructure(); const skillPaths = dirStructure.filter((path) => path.includes(filename)); @@ -45,7 +45,7 @@ export class SkillSaver extends Skill { } try { - const response = await fetch('/api/local/write-file', { + const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -58,9 +58,18 @@ export class SkillSaver extends Skill { if (!response.ok) { throw new Error('Failed to save file'); } - return `Code saved successfully: ${filename}`; + const message = `Code saved successfully: ${filename}`; + this.callbackMessage({ + content: message, + status: 'complete', + }); + return message; } catch (error) { console.error('Error saving code.', error); + this.callbackMessage({ + content: 'Error saving code.', + status: 'complete', + }); return 'Error saving code.'; } } diff --git a/src/agents/babyelfagi/skills/presets/webLoader.ts b/src/agents/babyelfagi/skills/presets/webLoader.ts index 7913025b..17074aec 100644 --- a/src/agents/babyelfagi/skills/presets/webLoader.ts +++ b/src/agents/babyelfagi/skills/presets/webLoader.ts @@ -1,8 +1,8 @@ import { AgentTask } from '@/types'; import { Skill } from '../skill'; -import axios from 'axios'; -import { largeTextExtract } from '@/agents/babydeeragi/tools/largeTextExtract'; -import { translate } from '@/utils/translate'; +import { largeTextExtract } from '@/agents/babyelfagi/tools/utils/largeTextExtract'; +import { webScrape } from '../../tools/webScrape'; +import { v4 as uuidv4 } from 'uuid'; export class WebLoader extends Skill { name = 'web_loader'; @@ -17,28 +17,39 @@ export class WebLoader extends Skill { throw new Error('Invalid inputs'); } - let statusMessage = '- Extracting URLs from the task.\n'; - const callback = (message: string) => { - statusMessage += message; - this.messageCallback({ - type: 'search-logs', - text: '```markdown\n' + statusMessage + '\n```', - title: `🔎 ${translate('SEARCH_LOGS', 'message')}`, - id: task.id, - icon: '🌐', - open: false, + let message = '- Extracting URLs from the task.\n'; + const callback = ( + message: string, + status: 'running' | 'complete' | 'incomplete' = 'running', + ) => { + this.handleMessage({ + id: this.id, + taskId: task.id.toString(), + content: message, + title: task.task, + icon: this.icon, + type: this.name, + style: 'log', + status, }); }; + callback(message); const urlString = await this.extractUrlsFromTask(task, callback); const urls = urlString.split(',').map((url) => url.trim()); const contents = await this.fetchContentsFromUrls(urls, callback); - const info = await this.extractInfoFromContents( - contents, - objective, - task, - callback, - ); + const info = await this.extractInfoFromContents(contents, objective, task); + this.handleMessage({ + id: uuidv4(), + taskId: task.id.toString(), + content: info.join('\n\n'), + title: task.task, + icon: this.icon, + type: this.name, + style: 'text', + status: 'complete', + }); + callback('- Completed: Extract info from contents', 'complete'); return info.join('\n\n'); } @@ -47,7 +58,7 @@ export class WebLoader extends Skill { task: AgentTask, callback: (message: string) => void, ): Promise { - const prompt = `- Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; + const prompt = `Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; const urlString = await this.generateText(prompt, task, undefined, true); callback(` - URLs: ${urlString}\n`); return urlString; @@ -61,8 +72,8 @@ export class WebLoader extends Skill { return await Promise.all( urls.slice(0, MAX_URLS).map(async (url) => { callback(`- Reading: ${url} ...\n`); - const content = await this.webScrapeTool(url); - if (content.length === 0) { + const content = await webScrape(url); + if (!content || content.length === 0) { callback(` - Content: No content found.\n`); return { url, content: '' }; } @@ -80,41 +91,22 @@ export class WebLoader extends Skill { contents: { url: string; content: string }[], objective: string, task: AgentTask, - callback: (message: string) => void, ): Promise { - callback(`- Extracting relevant information from the web pages.\n`); return await Promise.all( contents.map(async (item) => { return ( `URL: ${item.url}\n\n` + (await largeTextExtract( + this.id, objective, item.content, task, - this.isRunningRef, - callback, - this.abortController.signal, + this.apiKeys.openai, + this.handleMessage, + this.abortSignal, )) ); }), ); } - - private async webScrapeTool(url: string): Promise { - try { - const response = await axios.post( - '/api/tools/scrape', - { url }, - { signal: this.abortController.signal }, - ); - return response?.data?.response; - } catch (error: any) { - if (error.name === 'AbortError') { - console.error('Request aborted', error.message); - } else { - console.error(error.message); - } - return ''; - } - } } diff --git a/src/agents/babyelfagi/skills/presets/webSearch.ts b/src/agents/babyelfagi/skills/presets/webSearch.ts index f0ec2d77..b5a79d32 100644 --- a/src/agents/babyelfagi/skills/presets/webSearch.ts +++ b/src/agents/babyelfagi/skills/presets/webSearch.ts @@ -1,5 +1,5 @@ import { AgentTask } from '@/types'; -import { webBrowsing } from '@/agents/babydeeragi/tools/webBrowsing'; +import { webBrowsing } from '@/agents/babyelfagi/tools/webBrowsing'; import { Skill } from '../skill'; // This skill is Specialized for web browsing @@ -9,27 +9,26 @@ export class WebSearch extends Skill { descriptionForHuman = 'A tool that performs web searches.'; descriptionForModel = 'A tool that performs web searches.'; icon = '🔎'; - apiKeysRequired = []; + apiKeysRequired = ['openai']; async execute( task: AgentTask, dependentTaskOutputs: string, objective: string, ): Promise { - if (!this.valid) return ''; + if (!this.valid || this.signal?.aborted) return ''; const taskOutput = (await webBrowsing( objective, task, dependentTaskOutputs, - this.messageCallback, - undefined, - this.isRunningRef, + this.handleMessage, this.verbose, undefined, this.language, - this.abortController.signal, + this.apiKeys.openai, + this.signal, )) ?? ''; return taskOutput; diff --git a/src/agents/babyelfagi/skills/skill.ts b/src/agents/babyelfagi/skills/skill.ts index 54cff634..c78161e1 100644 --- a/src/agents/babyelfagi/skills/skill.ts +++ b/src/agents/babyelfagi/skills/skill.ts @@ -1,14 +1,12 @@ -import { AgentTask, LLMParams, Message } from '@/types'; -import { setupMessage } from '@/utils/message'; -import { getUserApiKey } from '@/utils/settings'; -import axios from 'axios'; +import { AgentTask, LLMParams, AgentMessage } from '@/types'; import { ChatOpenAI } from 'langchain/chat_models/openai'; import { HumanChatMessage } from 'langchain/schema'; +import { v4 as uuidv4 } from 'uuid'; export type SkillType = 'normal' | 'dev'; -export type SkillExecutionLocation = 'client' | 'server'; export class Skill { + id: string; name: string = 'base_kill'; descriptionForHuman: string = 'This is the base skill.'; descriptionForModel: string = 'This is the base skill.'; @@ -17,31 +15,30 @@ export class Skill { apiKeysRequired: Array> = []; valid: boolean; apiKeys: { [key: string]: string }; - executionLocation: SkillExecutionLocation = 'client'; // 'client' or 'server' // for UI - messageCallback: (message: Message) => void; - abortController: AbortController; - isRunningRef?: React.MutableRefObject; + handleMessage: (message: AgentMessage) => void; verbose: boolean; language: string = 'en'; + signal?: AbortSignal; + + BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; // This index signature allows dynamic assignment of properties [key: string]: any; constructor( apiKeys: { [key: string]: string }, - messageCallback: (message: Message) => void, - abortController: AbortController, - isRunningRef?: React.MutableRefObject, + handleMessage: (message: AgentMessage) => Promise, verbose: boolean = false, language: string = 'en', + abortSignal?: AbortSignal, ) { this.apiKeys = apiKeys; - this.messageCallback = messageCallback; - this.abortController = abortController; - this.isRunningRef = isRunningRef; + this.handleMessage = handleMessage; this.verbose = verbose; this.language = language; + this.signal = abortSignal; + this.id = uuidv4(); const missingKeys = this.checkRequiredKeys(apiKeys); if (missingKeys.length > 0) { @@ -91,86 +88,101 @@ export class Skill { throw new Error("Method 'execute' must be implemented"); } + async sendCompletionMessage() { + this.handleMessage({ + content: '', + status: 'complete', + }); + } + async generateText( prompt: string, task: AgentTask, - params?: LLMParams, + params: LLMParams = {}, ignoreCallback: boolean = false, ): Promise { - if (getUserApiKey()) { - let chunk = ''; - const messageCallback = ignoreCallback ? () => {} : this.messageCallback; - const llm = new ChatOpenAI( - { - openAIApiKey: this.apiKeys.openai, - modelName: params?.modelName ?? 'gpt-3.5-turbo', - temperature: params?.temperature ?? 0.7, - maxTokens: params?.maxTokens ?? 1500, - topP: params?.topP ?? 1, - frequencyPenalty: params?.frequencyPenalty ?? 0, - presencePenalty: params?.presencePenalty ?? 0, - streaming: params?.streaming === undefined ? true : params.streaming, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - messageCallback?.( - setupMessage('task-execute', chunk, undefined, '🤖', task.id), - ); - }, - }, - ], - }, - { baseOptions: { signal: this.abortController.signal } }, - ); - - try { - const response = await llm.call([new HumanChatMessage(prompt)]); - messageCallback?.( - setupMessage('task-output', response.text, undefined, '✅', task.id), - ); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return `Task aborted.`; - } - console.log('error: ', error); - return 'Failed to generate text.'; - } - } else { - // server side request - const response = await axios - .post( - '/api/elf/completion', - { - prompt: prompt, - model_name: params?.modelName ?? 'gpt-3.5-turbo', - temperature: params?.temperature ?? 0.7, - max_tokens: params?.maxTokens ?? 1500, - top_p: params?.topP ?? 1, - frequency_penalty: params?.frequencyPenalty ?? 0, - presence_penalty: params?.presencePenalty ?? 0, - }, + const callback = ignoreCallback ? () => {} : this.callbackMessage; + const id = uuidv4(); + const defaultParams = { + apiKey: this.apiKeys.openai, + modelName: 'gpt-3.5-turbo', + temperature: 0.7, + maxTokens: 1500, + topP: 1, + frequencyPenalty: 0, + presencePenalty: 0, + streaming: true, + }; + const llmParams = { ...defaultParams, ...params }; + const llm = new ChatOpenAI( + { + openAIApiKey: this.apiKeys.openai, + ...llmParams, + callbacks: [ { - signal: this.abortController.signal, + handleLLMNewToken(token: string) { + callback?.({ + id, + content: token, + title: `${task.task}`, + type: task.skill, + icon: task.icon, + taskId: task.id.toString(), + status: 'running', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, + }); + }, }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - return undefined; - } - console.log('error: ', error); - return undefined; - }); + ], + }, + { baseOptions: { signal: this.abortSignal } }, + ); - return response?.data.response; + try { + const response = await llm.call([new HumanChatMessage(prompt)]); + this.callbackMessage({ + taskId: task.id.toString(), + content: '', + title: task.task, + icon: task.icon, + type: task.skill, + status: 'complete', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, + }); + return response.text; + } catch (error: any) { + if (error.name === 'AbortError') { + return `Task aborted.`; + } + console.log('error: ', error); + return 'Failed to generate text.'; } } + callbackMessage = (message: AgentMessage) => { + const baseMessage: AgentMessage = { + id: this.id, + content: '', + icon: this.icon, + type: this.name, + style: 'text', + status: 'running', + }; + const mergedMessage = { ...baseMessage, ...message }; + this.handleMessage(mergedMessage); + }; + async getDirectoryStructure(): Promise { - const response = await fetch('/api/local/directory-structure', { - method: 'GET', - }); + const response = await fetch( + `${this.BASE_URL}/api/local/directory-structure`, + { + method: 'GET', + }, + ); if (!response.ok) { throw new Error('Failed to get directory structure'); } diff --git a/src/agents/elf/tools/utils/largeTextExtract.ts b/src/agents/babyelfagi/tools/utils/largeTextExtract.ts similarity index 100% rename from src/agents/elf/tools/utils/largeTextExtract.ts rename to src/agents/babyelfagi/tools/utils/largeTextExtract.ts diff --git a/src/agents/elf/tools/utils/relevantInfoExtraction.ts b/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts similarity index 100% rename from src/agents/elf/tools/utils/relevantInfoExtraction.ts rename to src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts diff --git a/src/agents/elf/tools/utils/textCompletion.ts b/src/agents/babyelfagi/tools/utils/textCompletion.ts similarity index 100% rename from src/agents/elf/tools/utils/textCompletion.ts rename to src/agents/babyelfagi/tools/utils/textCompletion.ts diff --git a/src/agents/elf/tools/webBrowsing.ts b/src/agents/babyelfagi/tools/webBrowsing.ts similarity index 98% rename from src/agents/elf/tools/webBrowsing.ts rename to src/agents/babyelfagi/tools/webBrowsing.ts index 18f577ec..6a8918f3 100644 --- a/src/agents/elf/tools/webBrowsing.ts +++ b/src/agents/babyelfagi/tools/webBrowsing.ts @@ -1,4 +1,4 @@ -import { simplifySearchResults } from '@/agents/common/tools/webSearch'; +import { simplifySearchResults } from '@/agents/babyelfagi/tools/webSearch'; import { AgentTask, AgentMessage } from '@/types'; import { analystPrompt, searchQueryPrompt } from '../../../utils/prompt'; import { textCompletion } from './utils/textCompletion'; diff --git a/src/agents/elf/tools/webScrape.ts b/src/agents/babyelfagi/tools/webScrape.ts similarity index 100% rename from src/agents/elf/tools/webScrape.ts rename to src/agents/babyelfagi/tools/webScrape.ts diff --git a/src/agents/elf/tools/webSearch.ts b/src/agents/babyelfagi/tools/webSearch.ts similarity index 100% rename from src/agents/elf/tools/webSearch.ts rename to src/agents/babyelfagi/tools/webSearch.ts diff --git a/src/agents/base/AgentExecuter.ts b/src/agents/base/AgentExecuter.ts index 57fff585..f8c2d541 100644 --- a/src/agents/base/AgentExecuter.ts +++ b/src/agents/base/AgentExecuter.ts @@ -1,76 +1,58 @@ -import { AgentStatus, AgentTask, Message } from '@/types'; +import { AgentMessage, AgentTask } from '@/types'; import { Printer } from '@/utils/print'; export class AgentExecuter { objective: string; modelName: string; - messageCallback: (message: Message) => void; - statusCallback: (status: AgentStatus) => void; - cancelCallback: () => void; - language: string = 'en'; - verbose: boolean = false; + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }; + language: string; + verbose: boolean; + signal?: AbortSignal; - taskIdCounter: number = 0; - retryCounter: number = 0; - taskList: AgentTask[] = []; - isRunningRef = { current: false }; - - abortController?: AbortController; printer: Printer; + taskList: AgentTask[] = []; constructor( objective: string, modelName: string, - messageCallback: (message: Message) => void, - statusCallback: (status: AgentStatus) => void, - cancelCallback: () => void, + handlers: { + handleMessage: (message: AgentMessage) => Promise; + handleEnd: () => Promise; + }, language: string = 'en', - verbose: boolean = false, + varbose: boolean = false, + signal?: AbortSignal, ) { this.objective = objective; this.modelName = modelName; - this.messageCallback = messageCallback; - this.statusCallback = statusCallback; - this.cancelCallback = cancelCallback; + this.handlers = handlers; this.language = language; - this.verbose = verbose; - this.printer = new Printer(messageCallback, verbose); + this.verbose = varbose; + this.signal = signal; + this.printer = new Printer(this.handlers.handleMessage, this.verbose); } - async start() { - this.isRunningRef.current = true; - this.taskIdCounter = 0; - this.retryCounter = 0; + async run() { this.taskList = []; await this.prepare(); await this.loop(); await this.finishup(); } - async stop() { - this.isRunningRef.current = false; - this.cancelCallback(); - this.abortController?.abort(); - } - - async finishup() { - if (!this.isRunningRef.current) { - this.statusCallback({ type: 'finished' }); - return; - } - - // Objective completed - this.printer.printAllTaskCompleted(); - this.statusCallback({ type: 'finished' }); - this.cancelCallback(); - this.isRunningRef.current = false; - } - // prepare() is called before loop() async prepare() { this.printer.printObjective(this.objective); } + async loop() {} - async userInput(taskId: number, message: string) {} + async finishup() { + if (this.signal?.aborted) return; + // Objective completed + this.printer.printAllTaskCompleted(); + this.handlers.handleEnd(); + } } diff --git a/src/agents/base/Executer.ts b/src/agents/base/Executer.ts deleted file mode 100644 index 8301e602..00000000 --- a/src/agents/base/Executer.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { AgentMessage, AgentTask } from '@/types'; -import { Printer } from '@/utils/elf/print'; - -export class Executer { - objective: string; - modelName: string; - handlers: { - handleMessage: (message: AgentMessage) => Promise; - handleEnd: () => Promise; - }; - language: string; - verbose: boolean; - signal?: AbortSignal; - - printer: Printer; - taskList: AgentTask[] = []; - - constructor( - objective: string, - modelName: string, - handlers: { - handleMessage: (message: AgentMessage) => Promise; - handleEnd: () => Promise; - }, - language: string = 'en', - varbose: boolean = false, - signal?: AbortSignal, - ) { - this.objective = objective; - this.modelName = modelName; - this.handlers = handlers; - this.language = language; - this.verbose = varbose; - this.signal = signal; - this.printer = new Printer(this.handlers.handleMessage, this.verbose); - } - - async run() { - this.taskList = []; - await this.prepare(); - await this.loop(); - await this.finishup(); - } - - // prepare() is called before loop() - async prepare() { - this.printer.printObjective(this.objective); - } - - async loop() {} - - async finishup() { - if (this.signal?.aborted) return; - // Objective completed - this.printer.printAllTaskCompleted(); - this.handlers.handleEnd(); - } -} diff --git a/src/agents/common/tools/textCompletionTool.ts b/src/agents/common/tools/textCompletionTool.ts deleted file mode 100644 index f86e3e24..00000000 --- a/src/agents/common/tools/textCompletionTool.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { getUserApiKey } from '@/utils/settings'; -import { Message } from '@/types'; -import { setupMessage } from '@/utils/message'; -import axios from 'axios'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { HumanChatMessage } from 'langchain/schema'; - -export const textCompletionTool = async ( - prompt: string, - modelName: string, - signal?: AbortSignal, - id?: number, - messageCallnback?: (message: Message) => void, -) => { - if (prompt.length > 3200) { - modelName = 'gpt-3.5-turbo-16k-0613'; - } - let chunk = ''; - const openAIApiKey = getUserApiKey(); - - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - if (!openAIApiKey) { - // server side request - const response = await axios - .post( - '/api/deer/completion', - { - prompt, - model_name: modelName, - }, - { - signal: signal, - }, - ) - .catch((error) => { - if (error.name === 'AbortError') { - console.log('Request aborted', error.message); - } else { - console.log(error.message); - } - }); - return response?.data?.response.text; - } - - const llm = new ChatOpenAI( - { - openAIApiKey: openAIApiKey, - modelName: modelName, - temperature: 0.2, - maxTokens: 800, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - chunk += token; - messageCallnback?.( - setupMessage('task-execute', chunk, undefined, '🤖', id), - ); - }, - }, - ], - }, - { baseOptions: { signal: signal } }, - ); - - try { - const response = await llm.call([new HumanChatMessage(prompt)]); - // - messageCallnback?.( - setupMessage('task-output', response.text, undefined, '✅', id), - ); - - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return null; - } - console.log('error: ', error); - return 'Failed to generate text.'; - } -}; diff --git a/src/agents/common/tools/webScrape.ts b/src/agents/common/tools/webScrape.ts deleted file mode 100644 index 3b30f5dc..00000000 --- a/src/agents/common/tools/webScrape.ts +++ /dev/null @@ -1,46 +0,0 @@ -import axios from 'axios'; -import { load } from 'cheerio'; - -export const webScrape = async (url: string) => { - const content = await fetchUrlContent(url); - - if (!content) { - return null; - } - - const text = extractText(content); - - return text; -}; - -const fetchUrlContent = async (url: string) => { - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - console.error(`Error while fetching the URL: ${error}`); - return ''; - } -}; - -const extractText = (content: string): string => { - const $ = load(content); - const body = $('body'); - const text = getTextFromNode(body, $); - return text.trim(); -}; - -const getTextFromNode = (node: any, $: any): string => { - let text = ''; - node.contents().each((index: number, element: any) => { - if ( - element.type === 'text' && - !['img', 'script', 'style'].includes(element.name) - ) { - text += $(element).text().trim().replace(/\s\s+/g, ' '); - } else if (element.type === 'tag') { - text += getTextFromNode($(element), $); - } - }); - return text; -}; diff --git a/src/agents/common/tools/webSearch.ts b/src/agents/common/tools/webSearch.ts deleted file mode 100644 index c5d3b3c7..00000000 --- a/src/agents/common/tools/webSearch.ts +++ /dev/null @@ -1,66 +0,0 @@ -import axios from 'axios'; - -export const webSearch = async (query: string) => { - try { - // Change environment variable name for typo - if (!process.env.SERP_API_KEY && process.env.SEARP_API_KEY !== undefined) { - throw new Error( - 'The environment variable name has been changed due to a typo: SEARP_API_KEY. Please fix it to SERP_API_KEY.', - ); - } - - if (process.env.SERP_API_KEY) { - const response = await axios.get('https://serpapi.com/search', { - params: { - api_key: process.env.SERP_API_KEY, - engine: 'google', - q: query, - }, - }); - return response.data.organic_results; - } else if ( - process.env.GOOGLE_SEARCH_API_KEY && - process.env.GOOGLE_CUSTOM_INDEX_ID - ) { - const response = await axios.get( - 'https://www.googleapis.com/customsearch/v1', - { - params: { - key: process.env.GOOGLE_SEARCH_API_KEY, - cx: process.env.GOOGLE_CUSTOM_INDEX_ID, - q: query, - }, - }, - ); - return response.data.items; - } - } catch (error) { - console.log('error: ', error); - return; - } -}; - -type SearchResult = { - position: number; - title: string; - link: string; - snippet: string; -}; - -export const simplifySearchResults = (searchResults: any[]): SearchResult[] => { - if (!Array.isArray(searchResults)) { - return []; - } - - const simplifiedResults: SearchResult[] = []; - searchResults.forEach((item, index) => { - simplifiedResults.push({ - position: index, - title: item.title, - link: item.link, - snippet: item.snippet, - }); - }); - - return simplifiedResults; -}; diff --git a/src/agents/elf/executer.ts b/src/agents/elf/executer.ts deleted file mode 100644 index 187f7c24..00000000 --- a/src/agents/elf/executer.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { AgentMessage, TaskOutputs } from '@/types'; // You need to define these types -import { Executer } from '../base/Executer'; -import { SkillRegistry, TaskRegistry } from './registory'; -import { v4 as uuidv4 } from 'uuid'; - -const REFLECTION = false; // If you want to use reflection, set this to true. now support only client side reflection. - -export class BabyElfAGI extends Executer { - skillRegistry: SkillRegistry; - taskRegistry: TaskRegistry; - sessionSummary: string = ''; - - constructor( - objective: string, - modelName: string, - handlers: { - handleMessage: (message: AgentMessage) => Promise; - handleEnd: () => Promise; - }, - language: string = 'en', - verbose: boolean = false, - specifiedSkills: string[] = [], - userApiKey?: string, - signal?: AbortSignal, - ) { - super(objective, modelName, handlers, language, verbose, signal); - - signal?.addEventListener('abort', () => { - this.verbose && - console.log('Abort signal received. Stopping execution...'); - }); - - this.skillRegistry = new SkillRegistry( - this.handlers.handleMessage, - this.verbose, - this.language, - specifiedSkills, - userApiKey, - this.signal, - ); - - const useSpecifiedSkills = specifiedSkills.length > 0; - this.taskRegistry = new TaskRegistry( - this.language, - this.verbose, - useSpecifiedSkills, - userApiKey, - this.signal, - ); - } - - async prepare() { - await super.prepare(); - - const skillDescriptions = this.skillRegistry.getSkillDescriptions(); - const id = uuidv4(); - // Create task list - await this.taskRegistry.createTaskList( - id, - this.objective, - skillDescriptions, - this.modelName, - this.handlers.handleMessage, - ); - this.printer.printTaskList(this.taskRegistry.tasks, id); - } - - async loop() { - // Initialize task outputs - let taskOutputs: TaskOutputs = {}; - for (let task of this.taskRegistry.tasks) { - taskOutputs[task.id] = { completed: false, output: undefined }; - } - // Loop until all tasks are completed - while ( - !this.signal?.aborted && - !Object.values(taskOutputs).every((task) => task.completed) - ) { - // Get the tasks that are ready to be executed - const tasks = this.taskRegistry.getTasks(); - - // Update taskOutputs to include new tasks - tasks.forEach((task) => { - if (!(task.id in taskOutputs)) { - taskOutputs[task.id] = { completed: false, output: undefined }; - } - }); - - // Filter taskoutput not completed - const incompleteTasks = tasks.filter((task) => { - return !taskOutputs[task.id].completed; - }); - - // Filter tasks that have all their dependencies completed - const MaxExecutableTasks = 5; - const executableTasks = incompleteTasks - .filter((task) => { - if (!task.dependentTaskIds) return true; - return task.dependentTaskIds.every((id) => { - return taskOutputs[id]?.completed === true; - }); - }) - .slice(0, MaxExecutableTasks); - - // Execute all executable tasks in parallel - const taskPromises = executableTasks.map(async (task, i) => { - // Update task status to running - this.taskRegistry.updateTasks({ - id: task.id, - updates: { status: 'running' }, - }); - this.printer.printTaskExecute(task); - - let output = ''; - try { - output = await this.taskRegistry.executeTask( - i, - task, - taskOutputs, - this.objective, - this.skillRegistry, - ); - } catch (error) { - console.error(error); - return; - } - - taskOutputs[task.id] = { completed: true, output: output }; - this.taskRegistry.updateTasks({ - id: task.id, - updates: { status: 'complete', result: output }, - }); - this.printer.printTaskOutput(output, task); - this.sessionSummary += `# ${task.id}: ${task.task}\n${output}\n\n`; - - // Reflect on the output of the tasks and possibly add new tasks or update existing ones - if (REFLECTION) { - const skillDescriptions = this.skillRegistry.getSkillDescriptions(); - const [newTasks, insertAfterIds, tasksToUpdate] = - await this.taskRegistry.reflectOnOutput( - this.objective, - output, - skillDescriptions, - ); - - // Insert new tasks - for (let i = 0; i < newTasks.length; i++) { - const newTask = newTasks[i]; - const afterId = insertAfterIds[i]; - this.taskRegistry.addTask(newTask, afterId); - } - - // Update existing tasks - for (const taskToUpdate of tasksToUpdate) { - this.taskRegistry.updateTasks({ - id: taskToUpdate.id, - updates: taskToUpdate, - }); - } - } - }); - - // Wait for all tasks to complete - await Promise.all(taskPromises); - } - } - - async finishup() { - super.finishup(); - - const tasks = this.taskRegistry.getTasks(); - const lastTask = tasks[tasks.length - 1]; - this.handlers.handleMessage({ - type: 'result', - content: lastTask.result ?? '', - id: uuidv4(), - }); - - this.handlers.handleMessage({ - type: 'session-summary', - content: this.sessionSummary, - id: uuidv4(), - }); - - super.finishup(); - } - - async close() {} -} diff --git a/src/agents/elf/registory/index.ts b/src/agents/elf/registory/index.ts deleted file mode 100644 index 3d67ffbc..00000000 --- a/src/agents/elf/registory/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './skillRegistry'; -export * from './taskRegistry'; diff --git a/src/agents/elf/registory/skillRegistry.ts b/src/agents/elf/registory/skillRegistry.ts deleted file mode 100644 index a3396ce6..00000000 --- a/src/agents/elf/registory/skillRegistry.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { AgentMessage } from '@/types'; -import { - TextCompletion, - WebLoader, - WebSearch, - YoutubeSearch, - CodeReader, - CodeReviewer, - CodeWriter, - DirectoryStructure, - SkillSaver, - AirtableSaver, -} from '../skills'; -import { Skill } from '../skills/skill'; - -export class SkillRegistry { - skillClasses: (typeof Skill)[]; - skills: Skill[] = []; - apiKeys: { [key: string]: string }; - userApiKey?: string; - signal?: AbortSignal; - // for UI - handleMessage: (message: AgentMessage) => Promise; - verbose: boolean; - language: string = 'en'; - - constructor( - handleMessage: (message: AgentMessage) => Promise, - verbose: boolean = false, - language: string = 'en', - specifiedSkills: string[] = [], - userApiKey?: string, - signal?: AbortSignal, - ) { - this.skillClasses = SkillRegistry.getSkillClasses(); - this.apiKeys = SkillRegistry.apiKeys; - this.userApiKey = userApiKey; - this.signal = signal; - this.handleMessage = handleMessage; - this.verbose = verbose; - this.language = language; - - if (this.userApiKey) { - this.apiKeys['openai'] = this.userApiKey; - } - - // Load all skills - for (let SkillClass of this.skillClasses) { - let skill = new SkillClass( - this.apiKeys, - this.handleMessage, - this.verbose, - this.language, - this.signal, - ); - - // If the skill is specified, load it. - if (specifiedSkills.length > 0) { - if (specifiedSkills.includes(skill.name)) { - this.skills.push(skill); - } - continue; - } - - if ( - skill.type === 'dev' ? process.env.NODE_ENV === 'development' : true - ) { - this.skills.push(skill); - } - } - - this.skills.filter((skill) => skill.valid); - - // Print the names and descriptions of all loaded skills - let loadedSkills = this.skills - .map((skill) => { - return `${skill.icon} ${skill.name}: ${skill.descriptionForHuman}`; - }) - .join('\n'); - if (this.verbose) { - console.log(`Loaded skills:\n${loadedSkills}`); - } - } - - static getSkillClasses(): (typeof Skill)[] { - const skills: (typeof Skill)[] = [ - TextCompletion, - WebSearch, - WebLoader, - YoutubeSearch, - AirtableSaver, - CodeReader, - CodeWriter, - SkillSaver, - DirectoryStructure, - CodeReviewer, - ]; - return skills; - } - - static apiKeys = { - openai: process.env.OPENAI_API_KEY || '', - airtable: process.env.AIRTABLE_API_KEY || '', - }; - - getSkill(name: string): Skill { - const skill = this.skills.find((skill) => { - return skill.name === name; - }); - if (!skill) { - throw new Error( - `Skill '${name}' not found. Please make sure the skill is loaded and all required API keys are set.`, - ); - } - return skill; - } - - getAllSkills(): Skill[] { - return this.skills; - } - - getSkillDescriptions(): string { - return this.skills - .map((skill) => { - return `${skill.icon} ${skill.name}: ${skill.descriptionForModel}`; - }) - .join(','); - } -} diff --git a/src/agents/elf/registory/taskRegistry.ts b/src/agents/elf/registory/taskRegistry.ts deleted file mode 100644 index 97e719de..00000000 --- a/src/agents/elf/registory/taskRegistry.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { AgentTask, AgentMessage, TaskOutputs } from '@/types'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { parseTasks } from '@/utils/task'; -import { HumanChatMessage, SystemChatMessage } from 'langchain/schema'; -import { SkillRegistry } from './skillRegistry'; -import { findMostRelevantObjective } from '@/utils/elf/objective'; -export class TaskRegistry { - tasks: AgentTask[]; - verbose: boolean = false; - language: string = 'en'; - useSpecifiedSkills: boolean = false; - userApiKey?: string; - signal?: AbortSignal; - - constructor( - language = 'en', - verbose = false, - useSpecifiedSkills = false, - userApiKey?: string, - signal?: AbortSignal, - ) { - this.tasks = []; - this.verbose = verbose; - this.language = language; - this.userApiKey = userApiKey; - this.useSpecifiedSkills = useSpecifiedSkills; - this.signal = signal; - } - - async createTaskList( - id: string, - objective: string, - skillDescriptions: string, - modelName: string = 'gpt-3.5-turbo', - handleMessage: (message: AgentMessage) => Promise, - ): Promise { - const relevantObjective = await findMostRelevantObjective( - objective, - this.userApiKey, - ); - - const exapmleObjective = relevantObjective.objective; - const exampleTaskList = relevantObjective.examples; - const prompt = ` - You are an expert task list creation AI tasked with creating a list of tasks as a JSON array, considering the ultimate objective of your team: ${objective}. - Create a very short task list based on the objective, the final output of the last task will be provided back to the user. Limit tasks types to those that can be completed with the available skills listed below. Task description should be detailed.### - AVAILABLE SKILLS: ${skillDescriptions}.### - RULES: - Do not use skills that are not listed. - Always include one skill. - Do not create files unless specified in the objective. - dependent_task_ids should always be an empty array, or an array of numbers representing the task ID it should pull results from. - Make sure all task IDs are in chronological order.### - Output must be answered in ${this.language}. - EXAMPLE OBJECTIVE=${exapmleObjective} - TASK LIST=${JSON.stringify(exampleTaskList)} - OBJECTIVE=${objective} - TASK LIST=`; - const systemPrompt = 'You are a task creation AI.'; - const systemMessage = new SystemChatMessage(systemPrompt); - const messages = new HumanChatMessage(prompt); - - let result = ''; - const model = new ChatOpenAI( - { - openAIApiKey: this.userApiKey, - modelName: this.useSpecifiedSkills ? modelName : 'gpt-4', - temperature: 0, - maxTokens: 1500, - topP: 1, - verbose: false, // You can set this to true to see the lanchain logs - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - const message: AgentMessage = { - id, - content: token, - type: 'task-list', - style: 'log', - status: 'running', - }; - handleMessage(message); - }, - }, - ], - }, - { baseOptions: { signal: this.signal } }, - ); - - try { - const response = await model.call([systemMessage, messages]); - result = response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - console.log('Task creation aborted'); - } - console.log(error); - } - - if (result === undefined) { - return; - } - - this.tasks = parseTasks(result); - } - - async executeTask( - i: number, - task: AgentTask, - taskOutputs: TaskOutputs, - objective: string, - skillRegistry: SkillRegistry, - ): Promise { - const skill = skillRegistry.getSkill(task.skill ?? ''); - const dependentTaskOutputs = task.dependentTaskIds - ? task.dependentTaskIds.map((id) => taskOutputs[id].output).join('\n') - : ''; - - return await skill.execute(task, dependentTaskOutputs, objective); - } - - getTasks(): AgentTask[] { - return this.tasks; - } - - getTask(taskId: number): AgentTask | undefined { - return this.tasks.find((task) => task.id === taskId); - } - - addTask(task: AgentTask, afterTaskId: number): void { - let index = this.tasks.findIndex((t) => t.id === afterTaskId); - if (index !== -1) { - this.tasks.splice(index + 1, 0, task); - } else { - this.tasks.push(task); - } - } - - updateTasks(taskUpdate: { id: number; updates: Partial }): void { - let task = this.getTask(taskUpdate.id); - if (task) { - Object.assign(task, taskUpdate.updates); - } - } - - reorderTasks(): void { - this.tasks.sort((a, b) => a.id - b.id); - } - - async reflectOnOutput( - objective: string, - taskOutput: string, - skillDescriptions: string, - modelName: string = 'gpt-3.5-turbo-16k', - ): Promise<[AgentTask[], number[], AgentTask[]]> { - const example = [ - [ - { - id: 3, - task: 'New task 1 description', - skill: 'text_completion', - icon: '🤖', - dependent_task_ids: [], - status: 'complete', - }, - { - id: 4, - task: 'New task 2 description', - skill: 'text_completion', - icon: '🤖', - dependent_task_ids: [], - status: 'incomplete', - }, - ], - [2, 3], - [ - { - id: 5, - task: 'Complete the objective and provide a final report', - skill: 'text_completion', - icon: '🤖', - dependent_task_ids: [1, 2, 3, 4], - status: 'incomplete', - }, - ], - ]; - - const prompt = `You are an expert task manager, review the task output to decide at least one new task to add. - As you add a new task, see if there are any tasks that need to be updated (such as updating dependencies). - Use the current task list as reference. - considering the ultimate objective of your team: ${objective}. - Do not add duplicate tasks to those in the current task list. - Only provide JSON as your response without further comments. - Every new and updated task must include all variables, even they are empty array. - Dependent IDs must be smaller than the ID of the task. - New tasks IDs should be no larger than the last task ID. - Always select at least one skill. - Task IDs should be unique and in chronological order. - Do not change the status of complete tasks. - Only add skills from the AVAILABLE SKILLS, using the exact same spelling. - Provide your array as a JSON array with double quotes. The first object is new tasks to add as a JSON array, the second array lists the ID numbers where the new tasks should be added after (number of ID numbers matches array), The number of elements in the first and second arrays will always be the same. - And the third array provides the tasks that need to be updated. - Make sure to keep dependent_task_ids key, even if an empty array. - OBJECIVE: ${objective}. - AVAILABLE SKILLS: ${skillDescriptions}. - Here is the last task output: ${taskOutput} - Here is the current task list: ${JSON.stringify(this.tasks)} - EXAMPLE OUTPUT FORMAT = ${JSON.stringify(example)} - OUTPUT = `; - - console.log( - '\nReflecting on task output to generate new tasks if necessary...\n', - ); - - const model = new ChatOpenAI({ - openAIApiKey: this.userApiKey, - modelName, - temperature: 0.7, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - - const response = await model.call([ - new SystemChatMessage('You are a task creation AI.'), - new HumanChatMessage(prompt), - ]); - - const result = response.text; - console.log('\n' + result); - - // Check if the returned result has the expected structure - if (typeof result === 'string') { - try { - const taskList = JSON.parse(result); - console.log(taskList); - return [taskList[0], taskList[1], taskList[2]]; - } catch (error) { - console.error(error); - } - } else { - throw new Error('Invalid task list structure in the output'); - } - - return [[], [], []]; - } -} diff --git a/src/agents/elf/skills/addons/airtableSaver.ts b/src/agents/elf/skills/addons/airtableSaver.ts deleted file mode 100644 index da35e550..00000000 --- a/src/agents/elf/skills/addons/airtableSaver.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Skill, SkillType } from '../skill'; -import { AgentTask } from '@/types'; - -export class AirtableSaver extends Skill { - name = 'airtable_saver'; - descriptionForHuman = 'Saves data to Airtable'; - descriptionForModel = - 'Saves data to Airtable. If objective does not include airtable, this skill dont use anytime.'; - icon = '📦'; - type: SkillType = 'dev'; - - apiKeysRequired = ['airtable']; - - baseId = 'appXXXXXXX'; // Your base ID here - tableName = 'Table 1'; // Your table name here - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - if (!this.valid) { - return ''; - } - - const fields = { Notes: dependentTaskOutputs }; // Your fields here - const url = `https://api.airtable.com/v0/${this.baseId}/${this.tableName}`; - const options = { - method: 'POST', - headers: { - Authorization: `Bearer ${this.apiKeys['airtable']}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ fields }), - }; - - try { - const response = await fetch(url, options); - if (!response.ok) { - throw new Error('Record creation failed'); - } - return 'Record creation successful'; - } catch (error: any) { - return `Record creation failed: ${error.message}`; - } - } -} diff --git a/src/agents/elf/skills/addons/youtubeSearch.ts b/src/agents/elf/skills/addons/youtubeSearch.ts deleted file mode 100644 index 596ed689..00000000 --- a/src/agents/elf/skills/addons/youtubeSearch.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill } from '../skill'; -import { webSearch } from '../../tools/webSearch'; - -export class YoutubeSearch extends Skill { - name = 'youtube_search'; - descriptionForHuman = 'This skill searches YouTube for videos.'; - descriptionForModel = - 'This skill searches YouTube for videos. Returns a list of links.'; - icon = '📺'; - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - const prompt = `Generate query for YouTube search based on the dependent task outputs and the objective. - Dont include "Youtube video". Only include the query. - Dependent tasks output: ${dependentTaskOutputs} - Objective: ${objective} - `; - const query = await this.generateText(prompt, task); - const searchResults = await webSearch(`site:youtube.com ${query}`); - const youtubeLinks = this.extractYoutubeLinks(searchResults); - const result = JSON.stringify(youtubeLinks, null, 2); - - this.callbackMessage({ - taskId: task.id.toString(), - content: '```json\n\n' + result + '\n\n```', - title: task.task, - type: task.skill, - style: 'text', - status: 'complete', - }); - - return result; - } - - extractYoutubeLinks = (searchResults: any[]) => { - const youtubeLinks = searchResults - .filter((result) => { - return result?.link.includes('youtube.com/watch?v='); - }) - .map((result) => { - return { - position: result?.position || 0, - title: result?.title || '', - link: result?.link || '', - snippet: result?.snippet || '', - }; - }); - return youtubeLinks; - }; -} diff --git a/src/agents/elf/skills/index.ts b/src/agents/elf/skills/index.ts deleted file mode 100644 index 07f7b5de..00000000 --- a/src/agents/elf/skills/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from './skill'; -export * from './presets/textCompletion'; -export * from './presets/webSearch'; -export * from './presets/codeReader'; -export * from './presets/skillSaver'; -export * from './presets/directoryStructure'; -export * from './presets/objectiveSaver'; -export * from './presets/codeWriter'; -export * from './presets/codeReviewer'; -export * from './presets/webLoader'; -export * from './addons/youtubeSearch'; -export * from './addons/airtableSaver'; diff --git a/src/agents/elf/skills/presets/codeReader.ts b/src/agents/elf/skills/presets/codeReader.ts deleted file mode 100644 index a88d5399..00000000 --- a/src/agents/elf/skills/presets/codeReader.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill, SkillType } from '../skill'; - -export class CodeReader extends Skill { - name = 'code_reader'; - descriptionForHuman = - "A skill that finds a file's location in its own program's directory and returns its contents."; - descriptionForModel = - "A skill that finds a file's location in its own program's directory and returns its contents."; - icon = '📖'; - type: SkillType = 'dev'; - apiKeysRequired = ['openai']; - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - if (!this.valid) return ''; - - const dirStructure = await this.getDirectoryStructure(); - - const prompt = `Find a specific file in a directory and return only the file path, based on the task description below. ### - First, find the file name in the task description below. Then, find the file path in the directory structure below. Finally, return the file path.### - Don't use only paths and don't put them in quotes. - The directory structure of src is as follows: \n${JSON.stringify( - dirStructure, - )} - Your task description: ${task.task}\n### - RESPONSE:`; - let filePath = await this.generateText(prompt, task, { - temperature: 0.2, - modelName: 'gpt-4', - }); - - console.log(`AI suggested file path: ${filePath}`); - - try { - const response = await fetch( - `${this.BASE_URL}/api/local/read-file?filename=${encodeURIComponent( - filePath, - )}`, - { - method: 'GET', - }, - ); - if (!response.ok) { - throw new Error('Failed to read file'); - } - const fileContent = await response.json(); - console.log(`File content:\n${JSON.stringify(fileContent)}`); - this.callbackMessage({ - content: 'Read file successfully.', - status: 'complete', - }); - return JSON.stringify(fileContent); - } catch (error) { - console.error( - "File not found. Please check the AI's suggested file path.", - error, - ); - return "File not found. Please check the AI's suggested file path."; - } - } -} diff --git a/src/agents/elf/skills/presets/codeReviewer.ts b/src/agents/elf/skills/presets/codeReviewer.ts deleted file mode 100644 index e1ad0c6a..00000000 --- a/src/agents/elf/skills/presets/codeReviewer.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill, SkillType } from '../skill'; - -// Define constants -const DESCRIPTION = - 'A skill that reviews code and provides comments to improve its quality.'; -const ICON = '👨‍💻'; -const MODEL_NAME = 'gpt-4'; - -export class CodeReviewer extends Skill { - readonly name = 'code_reviewer'; - readonly descriptionForHuman = DESCRIPTION; - readonly descriptionForModel = DESCRIPTION; - readonly icon = ICON; - readonly type: SkillType = 'dev'; - readonly apiKeysRequired = ['openai']; - - generatePrompt(code: string): string { - return `Code review comments for the following code:\n\n${code}\n\nComments:`; - } - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - ): Promise { - if (!this.valid) - throw new Error( - 'Skill is not valid. Please check the required API keys.', - ); - - const prompt = this.generatePrompt(dependentTaskOutputs); - - try { - const result = this.generateText(prompt, task, { modelName: MODEL_NAME }); - this.sendCompletionMessage(); - return result; - } catch (error) { - console.error('Failed to generate text:', error); - throw new Error( - 'Failed to generate text. Please check your input and try again.', - ); - } - } -} diff --git a/src/agents/elf/skills/presets/codeWriter.ts b/src/agents/elf/skills/presets/codeWriter.ts deleted file mode 100644 index 483d269c..00000000 --- a/src/agents/elf/skills/presets/codeWriter.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill, SkillType } from '../skill'; - -/** - * This skill uses the GPT-4 model to write code - * It takes in a task, dependent task outputs, and an objective, and returns a string - */ - -// Constants for the GPT-4 model -const MODEL_NAME = 'gpt-4'; -const TEMPERATURE = 0.2; -const MAX_TOKENS = 800; - -export class CodeWriter extends Skill { - readonly name = 'code_writer'; - readonly descriptionForHuman = - "A tool that uses OpenAI's text completion API to write code. This tool does not save the code."; - readonly descriptionForModel = - "A tool that uses OpenAI's text completion API to write code. This tool does not save the code. This skill must be a dependent task on the code_reader skill."; - readonly icon = '🖊️'; - readonly type: SkillType = 'dev'; - readonly apiKeysRequired = ['openai']; - - // The execute function takes in a task, dependent task outputs, and an objective, and returns a string - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - if (!this.valid) { - throw new Error('Invalid state'); - } - - const prompt = ` - You are a genius AI programmer. - Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. - Dependent tasks output include reference code. - Your objective: ${objective}. - Your task: ${task} - Dependent tasks output: ${dependentTaskOutputs} - RESPONSE: - `; - - try { - const result = await this.generateText(prompt, task, { - modelName: MODEL_NAME, - temperature: TEMPERATURE, - maxTokens: MAX_TOKENS, - }); - return result; - } catch (error) { - console.error('Error generating text:', error); - throw error; - } - } -} diff --git a/src/agents/elf/skills/presets/directoryStructure.ts b/src/agents/elf/skills/presets/directoryStructure.ts deleted file mode 100644 index db6acabc..00000000 --- a/src/agents/elf/skills/presets/directoryStructure.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill, SkillType } from '../skill'; - -export class DirectoryStructure extends Skill { - name = 'directory_structure'; - descriptionForHuman = - "A skill that outputs the directory structure of the 'src' folder."; - descriptionForModel = - "A skill that outputs the directory structure of the 'src' folder."; - icon = '📂'; - type: SkillType = 'dev'; - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - const response = await fetch( - `${this.BASE_URL}/api/local/directory-structure`, - { - method: 'GET', - }, - ); - if (!response.ok) { - throw new Error('Failed to get directory structure'); - } - return await response.json(); - } -} diff --git a/src/agents/elf/skills/presets/objectiveSaver.ts b/src/agents/elf/skills/presets/objectiveSaver.ts deleted file mode 100644 index 126c09f7..00000000 --- a/src/agents/elf/skills/presets/objectiveSaver.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill, SkillType } from '../skill'; - -export class ObjectiveSaver extends Skill { - name = 'objective_saver'; - descriptionForHuman = - 'A skill that saves a new example_objective based on the concepts from skillSaver.ts'; - descriptionForModel = - 'A skill that saves a new example_objective based on the concepts from skillSaver.ts '; - icon = '💽'; - type: SkillType = 'dev'; - apiKeysRequired = ['openai']; - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - if (!this.valid) return ''; - - const code = dependentTaskOutputs; - const prompt = `Come up with a file name (eg. 'research_shoes.json') for the following objective:${code}\n###\nFILE_NAME:`; - const filename = await this.generateText(prompt, task, { - temperature: 0.2, - }); - const examplesPath = `data/example_objectives/`; - - try { - const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: `${this.BASE_URL}/${examplesPath}/${filename}`, - content: `${code}`, - }), - }); - if (!response.ok) { - throw new Error('Failed to save file'); - } - return `Code saved successfully: ${filename}`; - } catch (error) { - console.error('Error saving code.', error); - return 'Error saving code.'; - } - } -} diff --git a/src/agents/elf/skills/presets/skillSaver.ts b/src/agents/elf/skills/presets/skillSaver.ts deleted file mode 100644 index e55678e8..00000000 --- a/src/agents/elf/skills/presets/skillSaver.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill, SkillType } from '../skill'; - -export class SkillSaver extends Skill { - name = 'skill_saver'; - descriptionForHuman = - 'A skill that saves code written in a previous step into a file within the skills folder. Not for writing code.'; - descriptionForModel = - 'A skill that saves code written in a previous step into a file within the skills folder. Not for writing code. If objective does not include save the skill, this skill dont use anytime.'; - icon = '💾'; - type: SkillType = 'dev'; - apiKeysRequired = ['openai']; - - async execute( - task: AgentTask, - dependentTaskOutputs: any, - objective: string, - ): Promise { - if (!this.valid) return ''; - - const params = { - temperature: 0.2, - maxTokens: 800, - }; - const codePrompt = `Extract the code and only the code from the dependent task output. - If it is a markdown code block, extract only the code inside. - DEPENDENT TASK OUTPUT: ${dependentTaskOutputs} - CODE:`; - const code = await this.generateText(codePrompt, task, params); - - const filePrompt = `Come up with a file name (eg. 'getWeather.ts') for the following skill. - If there is a file name to save in the task, please use it. (eg. 'getWeather.ts') - TASK: ${task.task} - CODE: ${code} - FILE_NAME:`; - const filename = await this.generateText(filePrompt, task, params, true); - let skillsPath = `src/agents/elf/skills/addons`; - - const dirStructure: string[] = await this.getDirectoryStructure(); - const skillPaths = dirStructure.filter((path) => path.includes(filename)); - if (skillPaths.length > 0) { - skillsPath = skillPaths[0]; - } else { - skillsPath += `/${filename}`; - } - - try { - const response = await fetch(`${this.BASE_URL}/api/local/write-file`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - filename: skillsPath, - content: code, - }), - }); - if (!response.ok) { - throw new Error('Failed to save file'); - } - const message = `Code saved successfully: ${filename}`; - this.callbackMessage({ - content: message, - status: 'complete', - }); - return message; - } catch (error) { - console.error('Error saving code.', error); - this.callbackMessage({ - content: 'Error saving code.', - status: 'complete', - }); - return 'Error saving code.'; - } - } -} diff --git a/src/agents/elf/skills/presets/textCompletion.ts b/src/agents/elf/skills/presets/textCompletion.ts deleted file mode 100644 index fb9e1689..00000000 --- a/src/agents/elf/skills/presets/textCompletion.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill } from '../skill'; - -export class TextCompletion extends Skill { - name = 'text_completion'; - descriptionForHuman = - "A tool that uses OpenAI's text completion API to generate, summarize, and/or analyze text."; - descriptionForModel = - "A tool that uses OpenAI's text completion API to generate, summarize, and/or analyze text."; - icon = '🤖'; - apiKeysRequired = ['openai']; - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - if (!this.valid) return ''; - - const prompt = `Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. \n### - Output must be answered in ${this.language}. - Your objective: ${objective}. \n### - Your task: ${task} \n### - Dependent tasks output: ${dependentTaskOutputs} ### - Your task: ${task}\n### - RESPONSE:`; - - return this.generateText(prompt, task, { - temperature: 0.2, - maxTokens: 800, - modelName: 'gpt-3.5-turbo-16k', - }); - } -} diff --git a/src/agents/elf/skills/presets/webLoader.ts b/src/agents/elf/skills/presets/webLoader.ts deleted file mode 100644 index dcfeffd3..00000000 --- a/src/agents/elf/skills/presets/webLoader.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { AgentTask } from '@/types'; -import { Skill } from '../skill'; -import { largeTextExtract } from '@/agents/elf/tools/utils/largeTextExtract'; -import { webScrape } from '../../tools/webScrape'; -import { v4 as uuidv4 } from 'uuid'; - -export class WebLoader extends Skill { - name = 'web_loader'; - descriptionForHuman = - 'This skill extracts URLs from the task and returns the contents of the web pages of those URLs.'; - descriptionForModel = - 'This skill extracts URLs from the task and returns the contents of the web pages of those URLs.'; - icon = '🌐'; - - async execute(task: AgentTask, objective: string): Promise { - if (typeof objective !== 'string') { - throw new Error('Invalid inputs'); - } - - let message = '- Extracting URLs from the task.\n'; - const callback = ( - message: string, - status: 'running' | 'complete' | 'incomplete' = 'running', - ) => { - this.handleMessage({ - id: this.id, - taskId: task.id.toString(), - content: message, - title: task.task, - icon: this.icon, - type: this.name, - style: 'log', - status, - }); - }; - - callback(message); - const urlString = await this.extractUrlsFromTask(task, callback); - const urls = urlString.split(',').map((url) => url.trim()); - const contents = await this.fetchContentsFromUrls(urls, callback); - const info = await this.extractInfoFromContents(contents, objective, task); - this.handleMessage({ - id: uuidv4(), - taskId: task.id.toString(), - content: info.join('\n\n'), - title: task.task, - icon: this.icon, - type: this.name, - style: 'text', - status: 'complete', - }); - callback('- Completed: Extract info from contents', 'complete'); - - return info.join('\n\n'); - } - - private async extractUrlsFromTask( - task: AgentTask, - callback: (message: string) => void, - ): Promise { - const prompt = `Extracting URLs from the task.\nReturn a comma-separated URL List.\nTASK: ${task.task}\nURLS:`; - const urlString = await this.generateText(prompt, task, undefined, true); - callback(` - URLs: ${urlString}\n`); - return urlString; - } - - private async fetchContentsFromUrls( - urls: string[], - callback: (message: string) => void, - ): Promise<{ url: string; content: string }[]> { - const MAX_URLS = 10; - return await Promise.all( - urls.slice(0, MAX_URLS).map(async (url) => { - callback(`- Reading: ${url} ...\n`); - const content = await webScrape(url); - if (!content || content.length === 0) { - callback(` - Content: No content found.\n`); - return { url, content: '' }; - } - callback( - ` - Content: ${content.slice(0, 100)} ...\n - Content length: ${ - content.length - }\n`, - ); - return { url, content }; - }), - ); - } - - private async extractInfoFromContents( - contents: { url: string; content: string }[], - objective: string, - task: AgentTask, - ): Promise { - return await Promise.all( - contents.map(async (item) => { - return ( - `URL: ${item.url}\n\n` + - (await largeTextExtract( - this.id, - objective, - item.content, - task, - this.apiKeys.openai, - this.handleMessage, - this.abortSignal, - )) - ); - }), - ); - } -} diff --git a/src/agents/elf/skills/presets/webSearch.ts b/src/agents/elf/skills/presets/webSearch.ts deleted file mode 100644 index 9e94c06f..00000000 --- a/src/agents/elf/skills/presets/webSearch.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { AgentTask } from '@/types'; -import { webBrowsing } from '@/agents/elf/tools/webBrowsing'; -import { Skill } from '../skill'; - -// This skill is Specialized for web browsing -// using webBrowsing tool in babydeeragi -export class WebSearch extends Skill { - name = 'web_search'; - descriptionForHuman = 'A tool that performs web searches.'; - descriptionForModel = 'A tool that performs web searches.'; - icon = '🔎'; - apiKeysRequired = ['openai']; - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - if (!this.valid || this.signal?.aborted) return ''; - - const taskOutput = - (await webBrowsing( - objective, - task, - dependentTaskOutputs, - this.handleMessage, - this.verbose, - undefined, - this.language, - this.apiKeys.openai, - this.signal, - )) ?? ''; - - return taskOutput; - } -} diff --git a/src/agents/elf/skills/skill.ts b/src/agents/elf/skills/skill.ts deleted file mode 100644 index c78161e1..00000000 --- a/src/agents/elf/skills/skill.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { AgentTask, LLMParams, AgentMessage } from '@/types'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { HumanChatMessage } from 'langchain/schema'; -import { v4 as uuidv4 } from 'uuid'; - -export type SkillType = 'normal' | 'dev'; - -export class Skill { - id: string; - name: string = 'base_kill'; - descriptionForHuman: string = 'This is the base skill.'; - descriptionForModel: string = 'This is the base skill.'; - icon: string = '🛠️'; - type: SkillType = 'normal'; // If available only in the local development environment, be sure to use dev type. - apiKeysRequired: Array> = []; - valid: boolean; - apiKeys: { [key: string]: string }; - // for UI - handleMessage: (message: AgentMessage) => void; - verbose: boolean; - language: string = 'en'; - signal?: AbortSignal; - - BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; - - // This index signature allows dynamic assignment of properties - [key: string]: any; - - constructor( - apiKeys: { [key: string]: string }, - handleMessage: (message: AgentMessage) => Promise, - verbose: boolean = false, - language: string = 'en', - abortSignal?: AbortSignal, - ) { - this.apiKeys = apiKeys; - this.handleMessage = handleMessage; - this.verbose = verbose; - this.language = language; - this.signal = abortSignal; - this.id = uuidv4(); - - const missingKeys = this.checkRequiredKeys(apiKeys); - if (missingKeys.length > 0) { - console.log(`Missing API keys for ${this.name}: ${missingKeys}`); - this.valid = false; - } else { - this.valid = true; - } - for (const key of this.apiKeysRequired) { - if (Array.isArray(key)) { - for (const subkey of key) { - if (subkey in apiKeys) { - this[`${subkey}_apiKey`] = apiKeys[subkey]; - } - } - } else if (key in apiKeys) { - this[`${key}_apiKey`] = apiKeys[key]; - } - } - - this.valid = - this.type === 'dev' ? process.env.NODE_ENV === 'development' : true; - } - - checkRequiredKeys(apiKeys: { - [key: string]: string; - }): Array> { - const missingKeys: Array> = []; - for (const key of this.apiKeysRequired) { - if (Array.isArray(key)) { - if (!key.some((k) => k in apiKeys)) { - missingKeys.push(key); - } - } else if (!(key in apiKeys)) { - missingKeys.push(key); - } - } - return missingKeys; - } - - async execute( - task: AgentTask, - dependentTaskOutputs: string, - objective: string, - ): Promise { - // This method should be overridden by subclasses - throw new Error("Method 'execute' must be implemented"); - } - - async sendCompletionMessage() { - this.handleMessage({ - content: '', - status: 'complete', - }); - } - - async generateText( - prompt: string, - task: AgentTask, - params: LLMParams = {}, - ignoreCallback: boolean = false, - ): Promise { - const callback = ignoreCallback ? () => {} : this.callbackMessage; - const id = uuidv4(); - const defaultParams = { - apiKey: this.apiKeys.openai, - modelName: 'gpt-3.5-turbo', - temperature: 0.7, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - streaming: true, - }; - const llmParams = { ...defaultParams, ...params }; - const llm = new ChatOpenAI( - { - openAIApiKey: this.apiKeys.openai, - ...llmParams, - callbacks: [ - { - handleLLMNewToken(token: string) { - callback?.({ - id, - content: token, - title: `${task.task}`, - type: task.skill, - icon: task.icon, - taskId: task.id.toString(), - status: 'running', - options: { - dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', - }, - }); - }, - }, - ], - }, - { baseOptions: { signal: this.abortSignal } }, - ); - - try { - const response = await llm.call([new HumanChatMessage(prompt)]); - this.callbackMessage({ - taskId: task.id.toString(), - content: '', - title: task.task, - icon: task.icon, - type: task.skill, - status: 'complete', - options: { - dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', - }, - }); - return response.text; - } catch (error: any) { - if (error.name === 'AbortError') { - return `Task aborted.`; - } - console.log('error: ', error); - return 'Failed to generate text.'; - } - } - - callbackMessage = (message: AgentMessage) => { - const baseMessage: AgentMessage = { - id: this.id, - content: '', - icon: this.icon, - type: this.name, - style: 'text', - status: 'running', - }; - const mergedMessage = { ...baseMessage, ...message }; - this.handleMessage(mergedMessage); - }; - - async getDirectoryStructure(): Promise { - const response = await fetch( - `${this.BASE_URL}/api/local/directory-structure`, - { - method: 'GET', - }, - ); - if (!response.ok) { - throw new Error('Failed to get directory structure'); - } - return await response.json(); - } -} diff --git a/src/components/Agent/Agent.tsx b/src/components/Agent/Agent.tsx deleted file mode 100644 index 2438614b..00000000 --- a/src/components/Agent/Agent.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react'; -import va from '@vercel/analytics'; -import { - AgentStatus, - AgentType, - Execution, - Message, - MessageBlock, - SelectItem, - UserSettings, -} from '@/types'; -import { Input } from './Input'; -import AgentMessage from './AgentMessage'; -import { AgentParameter } from './AgentParameter'; -import { ProjectTile } from './ProjectTile'; -import { AgentMessageHeader } from './AgentMessageHeader'; -import { - getExportText, - getMessageBlocks, - loadingAgentMessage, -} from '../../utils/message'; -import { BabyAGI } from '@/agents/babyagi'; -import { BabyDeerAGI } from '@/agents/babydeeragi/executer'; -import { AGENT, ITERATIONS, MODELS, SETTINGS_KEY } from '@/utils/constants'; -import { toast } from 'sonner'; -import { v4 as uuidv4 } from 'uuid'; -import { useExecution } from '@/hooks/useExecution'; -import { useExecutionStatus } from '@/hooks/useExecutionStatus'; -import { translate } from '../../utils/translate'; -import axios from 'axios'; -import { taskCompletedNotification } from '@/utils/notification'; -import { useTranslation } from 'next-i18next'; -import { AgentMessageBlock } from './AgentMessageBlock'; -import { AgentTask } from './AgentTask'; -import { IntroGuide } from './IntroGuide'; -import { BabyElfAGI } from '@/agents/babyelfagi/executer'; -import { SkillsList } from './SkillList'; - -export const Agent: FC = () => { - const [model, setModel] = useState(MODELS[1]); - const [iterations, setIterations] = useState(ITERATIONS[0]); - const [objective, setObjective] = useState(''); - const [firstTask, setFirstTask] = useState( - translate('FIRST_TASK_PLACEHOLDER', 'constants'), - ); - const [messages, setMessages] = useState([]); - const [messageBlocks, setMessageBlocks] = useState([]); - const [agentStatus, setAgentStatus] = useState({ - type: 'ready', - }); - const [agent, setAgent] = useState( - null, - ); - const [selectedAgent, setSelectedAgent] = useState(AGENT[0]); - const { i18n } = useTranslation(); - const [language, setLanguage] = useState(i18n.language); - - const messagesEndRef = useRef(null); - const { - addExecution, - updateExec, - executions, - selectedExecutionId, - selectExecution, - } = useExecution(); - const { isExecuting, setExecuting } = useExecutionStatus(); - - const scrollToBottom = useCallback(() => { - const behavior = isExecuting ? 'smooth' : 'auto'; - messagesEndRef.current?.scrollIntoView({ behavior: behavior }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messageBlocks]); - - useEffect(() => { - scrollToBottom(); - }, [scrollToBottom]); - - useEffect(() => { - if (selectedExecutionId) { - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - setMessages(selectedExecution.messages); - } - } else { - setMessages([]); - setObjective(''); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedExecutionId]); - - useEffect(() => { - const execution = executions.find((exe) => exe.id === selectedExecutionId); - if (execution) { - const updatedExecution: Execution = { - ...execution, - messages: messages, - }; - updateExec(updatedExecution); - } - - const blocks = getMessageBlocks(messages, isExecuting); - setMessageBlocks(blocks); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [messages]); - - useEffect(() => { - setLanguage(i18n.language); - }, [i18n]); - - // manage data - const saveNewData = async () => { - const execution: Execution = { - id: uuidv4(), - name: objective, - date: new Date().toISOString(), - params: { - objective: objective, - model: model, - iterations: iterations, - firstTask: firstTask, - agent: selectedAgent.id as AgentType, - }, - messages: messages, - }; - - selectExecution(execution.id); - await new Promise((resolve) => { - addExecution(execution); - resolve(null); - }); - - return execution; - }; - - // handler functions - const messageHandler = (message: Message) => { - setMessages((currentMessages) => { - if (selectedAgent.id !== 'babyagi') { - // if the message.type and id are the same, overwrite the message - const index = currentMessages.findIndex( - (msg) => msg.type === message.type && msg.id === message.id, - ); - if (index !== -1) { - const newMessages = [...currentMessages]; - newMessages[index] = message; - return newMessages; - } - } - - const updatedMessages = [...currentMessages, message]; - - // show toast notification - if (message.type === 'complete' || message.type === 'end-of-iterations') { - toast.success(translate('ALL_TASKS_COMPLETED_TOAST', 'agent')); - taskCompletedNotification(objective); - } else if (message.type === 'done') { - toast.success(translate('TASK_COMPLETED_TOAST', 'agent')); - } - - return updatedMessages; - }); - }; - - const inputHandler = (value: string) => { - setObjective(value); - }; - - const cancelHandle = () => { - setAgent(null); - setExecuting(false); - }; - - const startHandler = async () => { - if (needSettingsAlert()) { - alert(translate('ALERT_SET_UP_API_KEY', 'agent')); - return; - } - if (model.id === 'gpt-4') { - const enabled = await enabledGPT4(); - if (!enabled) { - alert(translate('ALERT_GPT_4_DISABLED', 'constants')); - return; - } - } - - setMessages([]); - setExecuting(true); - const execution = await saveNewData(); - const verbose = false; // You can set this to true to see the agent's internal state - - // switch agent - let agent = null; - switch (selectedAgent.id) { - case 'babyagi': - agent = new BabyAGI( - objective, - model.id, - Number(iterations.id), - firstTask, - execution.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - case 'babydeeragi': - agent = new BabyDeerAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - case 'babyelfagi': - agent = new BabyElfAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - verbose, - ); - break; - } - setAgent(agent); - agent?.start(); - - va.track('Start', { - model: model.id, - agent: selectedAgent.id, - iterations: iterations.id, - }); - }; - - const stopHandler = () => { - // refresh message blocks - const blocks = getMessageBlocks(messages, false); - setMessageBlocks(blocks); - - setExecuting(false); - agent?.stop(); - - va.track('Stop'); - }; - - const clearHandler = () => { - setMessages([]); - selectExecution(undefined); - setAgentStatus({ type: 'ready' }); - - va.track('New'); - }; - - const copyHandler = () => { - navigator.clipboard.writeText(getExportText(messages, selectedAgent.id)); - toast.success(translate('COPIED_TO_CLIPBOARD', 'agent')); - - va.track('CopyToClipboard'); - }; - - const downloadHandler = () => { - const element = document.createElement('a'); - const filename = - objective.length > 0 - ? `${objective.replace(/\s/g, '_')}.txt` - : 'download.txt'; - const file = new Blob( - ['\uFEFF' + getExportText(messages, selectedAgent.id)], - { - type: 'text/plain;charset=utf-8', - }, - ); - element.href = URL.createObjectURL(file); - element.download = filename; - document.body.appendChild(element); - element.click(); - - va.track('Download'); - }; - - const feedbackHandler = (isGood: boolean) => { - let selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - setMessages(selectedExecution.messages); - } - const feedbackObjective = selectedExecution?.params.objective; - const feedbackModel = selectedExecution?.params.model.id; - const feedbackAgent = selectedExecution?.params.agent; - const feedbackIterations = Number(selectedExecution?.params.iterations.id); - - let lastResult = messages - .filter( - (message) => - message.type === 'task-output' || message.type === 'task-result', - ) - .pop()?.text; - if (feedbackAgent === 'babybeeagi') { - lastResult = messages - .filter((message) => message.type === 'task-result-summary') - .pop()?.text; - } - const lastTaskList = messages - .filter((message) => message.type === 'task-list') - .pop()?.text; - const sessionSummary = messages - .filter((message) => message.type === 'session-summary') - .pop()?.text; - const iterationNumber = messages.filter( - (message) => message.type === 'done', - ).length; - const finished = - messages.filter( - (message) => - message.type === 'complete' || message.type === 'end-of-iterations', - ).length > 0; - const output = getExportText(messages); - - axios.post('/api/feedback', { - objective: feedbackObjective, - evaluation: isGood ? 'good' : 'bad', - model: feedbackModel, - agent: feedbackAgent, - iterations: feedbackIterations, - last_result: lastResult, - task_list: lastTaskList, - session_summary: sessionSummary, - iteration_number: iterationNumber, - finished: finished, - output: output, - }); - - toast.success(translate('FEEDBACK_SUBMITTED_TOAST', 'constants')); - - // update execution - if (selectedExecution) { - selectedExecution.evaluation = isGood ? 'good' : 'bad'; - updateExec(selectedExecution); - } - }; - - const userInputHandler = async (id: number, text: string) => { - if (agent instanceof BabyDeerAGI) { - agent.userInput(id, text); - } - }; - - const needSettingsAlert = () => { - const useUserApiKey = process.env.NEXT_PUBLIC_USE_USER_API_KEY; - if (useUserApiKey === 'false') { - return false; - } - - const userSettings = localStorage.getItem(SETTINGS_KEY); - if (userSettings) { - const { openAIApiKey } = JSON.parse(userSettings) as UserSettings; - if (openAIApiKey && openAIApiKey?.length > 0) { - return false; - } - } - return true; - }; - - const enabledGPT4 = async () => { - const userSettings = localStorage.getItem(SETTINGS_KEY); - if (!userSettings) { - return false; - } - - const { enabledGPT4 } = JSON.parse(userSettings) as UserSettings; - if (enabledGPT4 === undefined) { - return true; // If no value is given, its enabled by default - } - - return enabledGPT4; - }; - - const currentEvaluation = () => { - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - return selectedExecution.evaluation; - } - return undefined; - }; - - const currentAgentId = () => { - if (isExecuting) { - return selectedAgent.id; - } - - const selectedExecution = executions.find( - (exe) => exe.id === selectedExecutionId, - ); - if (selectedExecution) { - return selectedExecution.params.agent; - } - return undefined; - }; - - const skills = () => { - if (selectedAgent.id === 'babyelfagi') { - const elf = new BabyElfAGI( - objective, - model.id, - messageHandler, - setAgentStatus, - cancelHandle, - language, - false, - ); - const skills = elf.skillRegistry.getAllSkills(); - const skillInfos = skills.map((skill) => { - const skillInfo = { - name: skill.name, - description: skill.descriptionForHuman, - icon: skill.icon, - badge: skill.type, - }; - return skillInfo; - }); - return skillInfos; - } - return []; - }; - - return ( -
- {messageBlocks.length === 0 ? ( - <> - - {selectedAgent.id === 'babyelfagi' && ( - - )} -
-
- - {(selectedAgent.id === 'babydeeragi' || - selectedAgent.id === 'babyelfagi') && ( - setObjective(value)} - agent={selectedAgent.id} - /> - )} -
-
- - ) : ( -
- - {messageBlocks.map((block, index) => - currentAgentId() === 'babydeeragi' || - currentAgentId() === 'babyelfagi' ? ( - - ) : ( - - ), - )} - {isExecuting && ( - - )} -
-
- )} - 0} - agent={selectedAgent.id as AgentType} - evaluation={currentEvaluation()} - /> -
- ); -}; diff --git a/src/components/Agent/AgentLabelBlock.tsx b/src/components/Agent/AgentLabelBlock.tsx deleted file mode 100644 index fca626a2..00000000 --- a/src/components/Agent/AgentLabelBlock.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { MessageBlock } from '@/types'; -import { translate } from '@/utils/translate'; -import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; -import remarkGfm from 'remark-gfm'; -import AgentMessage from './AgentMessage'; -import { useEffect, useRef } from 'react'; - -export interface AgentLabelBlockProps { - block: MessageBlock; -} - -export const AgentLabelBlock: React.FC = ({ block }) => { - const message = block.messages[0]; - const nextMessage = block.messages?.[1]; - const linkRef = useRef(null); - - useEffect(() => { - if (message.type === 'final-result') { - if (!nextMessage) return; - - const file = new Blob(['\uFEFF' + nextMessage?.text], { - type: 'text/plain;charset=utf-8', - }); - const url = URL.createObjectURL(file); - if (linkRef.current) { - const link = linkRef.current; - link.href = url; - link.download = 'session_summary.txt'; - } - - // If it's a development environment, save the file automatically. - if (process.env.NODE_ENV === 'development') { - fetch('/api/local/save-session-summary', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ text: nextMessage.text }), - }) - .then((response) => response.json()) - .then((data) => console.log(data)); - } - } - }, [nextMessage]); - - if ( - message.type !== 'objective' && - message.type !== 'complete' && - message.type !== 'final-result' && - message.type !== 'task-list' && - message.type !== 'task-execute' - ) - return null; - - if (message.type === 'task-execute') { - return ; - } - - return ( -
-
-
-
- {message.icon} -
-
- - {`### ${message.title}\n${message.text}`} - - {message.type === 'final-result' && ( - - ⬇️ {translate('DOWNLOAD_SESSION_SUMMARY', 'message')} - - )} -
-
-
-
- ); -}; diff --git a/src/components/Agent/AgentMessage.tsx b/src/components/Agent/AgentMessage.tsx deleted file mode 100644 index e4167d4d..00000000 --- a/src/components/Agent/AgentMessage.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Message } from '@/types'; -import { getMessageText } from '@/utils/message'; -import { UpdateIcon } from '@radix-ui/react-icons'; -import { FC, useEffect, useState } from 'react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import { translate } from '../../utils/translate'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { - oneLight, - vscDarkPlus, -} from 'react-syntax-highlighter/dist/cjs/styles/prism'; -import { AgentCollapsible } from './AgentCollapsible'; -import { useTheme } from 'next-themes'; - -interface AgentMessageProps { - message: Message; - spacing?: 'normal' | 'tight'; -} - -const AgentMessage: FC = ({ - message, - spacing = 'normal', -}) => { - const simpleTitle = message.title?.split('(')[0] ?? ''; // ex: 'Creating tasks... (*This process takes time. Please wait...*)' - const { theme, systemTheme } = useTheme(); - const [highlightStyle, setHighlightStyle] = useState(oneLight); - const py = spacing === 'normal' ? 'py-4 md:py-6' : 'py-0'; - - useEffect(() => { - const isDark = - theme === 'system' ? systemTheme === 'dark' : theme === 'dark'; - const style = isDark ? vscDarkPlus : oneLight; - setHighlightStyle(style); - }, [theme, systemTheme]); - - const contents = ( -
- - {String(children).replace(/\n$/, '')} - - ) : ( - - {children} - - ); - }, - }} - > - {getMessageText(message)} - -
- ); - - return ( -
-
- {message.type === 'loading' ? ( -
- -
- ) : message.type === 'task-execute' ? ( -
- ) : ( -
{message.icon}
- )} - {message.type === 'session-summary' ? ( -
- - {translate('SUMMARY')} - - {contents} -
- ) : message.status?.type === 'creating-stream' || - message.status?.type === 'executing-stream' || - message.type === 'search-logs' || - message.type === 'task-execute' ? ( - - {contents} - - ) : ( - contents - )} -
-
- ); -}; - -export default AgentMessage; diff --git a/src/components/Agent/AgentMessageBlock.tsx b/src/components/Agent/AgentMessageBlock.tsx deleted file mode 100644 index be2ee1fa..00000000 --- a/src/components/Agent/AgentMessageBlock.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { MessageBlock } from '@/types'; -import { FC } from 'react'; -import AgentMessage from './AgentMessage'; -import { AgentMessageInput } from './AgentMessageInput'; - -export interface AgentMessageBlockProps { - block: MessageBlock; - userInputCallback: (id: number, input: string) => Promise; -} - -export const AgentMessageBlock: FC = ({ - block, - userInputCallback, -}) => { - return ( -
- {block.messages.map((message, index) => - message.type === `user-input` ? ( - - ) : ( - - ), - )} -
- ); -}; diff --git a/src/components/Agent/AgentMessageFooter.tsx b/src/components/Agent/AgentMessageFooter.tsx deleted file mode 100644 index 747b42e3..00000000 --- a/src/components/Agent/AgentMessageFooter.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { translate } from '@/utils/translate'; -import { FC } from 'react'; -import { ThumbsDown, ThumbsUp } from 'react-feather'; - -export const AgentMessageFooter: FC = () => { - return ( -
- - - {translate('FEEDBACK_MESSAGE', 'constants')} -
- ); -}; diff --git a/src/components/Agent/AgentMessageInput.tsx b/src/components/Agent/AgentMessageInput.tsx deleted file mode 100644 index 8a75edbd..00000000 --- a/src/components/Agent/AgentMessageInput.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Message } from '@/types'; -import { translate } from '@/utils/translate'; -import { FC, FormEvent, useState } from 'react'; -import { Check } from 'react-feather'; - -export interface AgentMessageInputProps { - id: number; - message: Message; - onSubmit: (id: number, message: string) => Promise; -} - -export const AgentMessageInput: FC = ({ - id, - message, - onSubmit, -}) => { - const [text, setText] = useState(''); - const [submitted, setSubmitted] = useState(false); - - const handleSubmit = async (e: FormEvent) => { - e.preventDefault(); - onSubmit(id, text); - setSubmitted(true); - setText(''); - }; - - return ( -
-
-
-
- {message.icon} -
-
-
- {`${message.id}. `} - {message.text} -
-
- setText(e.target.value)} - disabled={submitted} - className="datk:placeholder-neutral-500 w-full rounded-l-lg border border-neutral-200 bg-white py-2 pl-4 pr-12 text-base placeholder-neutral-300 focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-black dark:placeholder-neutral-600 focus:dark:border-neutral-500" - /> - -
-
-
-
-
- ); -}; diff --git a/src/components/Agent/AgentTask.tsx b/src/components/Agent/AgentTask.tsx deleted file mode 100644 index 5df41748..00000000 --- a/src/components/Agent/AgentTask.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { MessageBlock } from '@/types'; -import { FC } from 'react'; -import { AgentCollapsible } from './AgentCollapsible'; -import remarkGfm from 'remark-gfm'; -import { AgentResult } from './AgentResult'; -import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; -import { AgentLabelBlock } from './AgentLabelBlock'; -import { AgentTaskStatus } from './AgentTastStatus'; -import { AgentMessageInput } from './AgentMessageInput'; - -export interface AgentTaskProps { - block: MessageBlock; - userInputCallback: (id: number, input: string) => Promise; -} - -export const AgentTask: FC = ({ block, userInputCallback }) => { - const message = block.messages[0]; - const nextMessage = block.messages[1]; - const id = block.id ?? 0; - - if (message === undefined) return null; - - if (nextMessage?.type === 'user-input') { - return ( - - ); - } - - // task output - const outputMessages = block.messages.filter( - (message) => - message.type === 'task-output' || message.type === 'task-execute', - ); - const logs = block.messages.filter( - (message) => message.type === 'search-logs', - ); - - return message.type === 'next-task' ? ( -
-
-
-
- {message.icon} -
-
- {message.text} -
- -
- {block.messages.length > 1 && ( - -
-
-
- {outputMessages[0]?.icon} -
-
- {outputMessages[0]?.text && ( -
- - {outputMessages[0]?.text} - -
- )} - {logs.length > 0 && - (outputMessages.length === 0 || - block.status === 'complete') && ( - -
- - {logs[0].text} - -
-
- )} -
-
-
-
- )} -
-
- ) : ( - - ); -}; diff --git a/src/components/Agent/Input.tsx b/src/components/Agent/Input.tsx deleted file mode 100644 index f2700f45..00000000 --- a/src/components/Agent/Input.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import TextareaAutosize from 'react-textarea-autosize'; -import { AgentType } from '@/types'; -import { translate } from '@/utils/translate'; -import { - ClipboardIcon, - DividerVerticalIcon, - DotFilledIcon, - DownloadIcon, - PlayIcon, - PlusIcon, - StopIcon, - UpdateIcon, -} from '@radix-ui/react-icons'; -import { ThumbsUp, ThumbsDown } from 'react-feather'; -import { FC } from 'react'; - -type InputProps = { - value: string; - onChange: (value: string) => void; - onStart: (value: string) => void; - onStop: () => void; - onClear: () => void; - onCopy: () => void; - onDownload: () => void; - onFeedback: (value: boolean) => void; - isExecuting: boolean; - hasMessages: boolean; - agent: AgentType; - evaluation?: 'good' | 'bad'; -}; - -export const Input: FC = ({ - value, - onChange, - onStart, - onStop, - onClear, - onCopy, - onDownload, - onFeedback, - isExecuting, - hasMessages, - agent, - evaluation, -}) => { - const handleKeyDown = (e: React.KeyboardEvent) => { - if (hasMessages) { - return; - } - - if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) { - e.preventDefault(); - onStart(value); - } - }; - - return ( -
-
-
-
-
- {isExecuting ? ( - - ) : hasMessages ? ( - - ) : null} -
-
- {!isExecuting && hasMessages && ( -
-
- {!evaluation || evaluation === 'good' ? ( - - ) : null} - {!evaluation || evaluation === 'bad' ? ( - - ) : null} -
-
- -
-
- - -
-
- )} -
-
-
- onChange(e.target.value)} - onKeyDown={handleKeyDown} - /> - -
- {isExecuting ? : null} -
-
-
-
- - BabyAGI UI - - {' is designed to make it easier to run and develop with '} - - babyagi - - {' in a web app, like a ChatGPT.'} -
-
- ); -}; diff --git a/src/components/Agent/MessageSummary.tsx b/src/components/Agent/MessageSummary.tsx deleted file mode 100644 index 4afdcdb2..00000000 --- a/src/components/Agent/MessageSummary.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Message } from '@/types'; -import { getMessageSummaryTitle } from '@/utils/message'; -import { FC } from 'react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; - -export interface MessageSummaryProps { - message?: Message; -} - -export const MessageSummary: FC = ({ message }) => { - return ( -
- -
- - {message?.text ?? ''} - -
-
- ); -}; diff --git a/src/components/Agent/MessageSummaryCard.tsx b/src/components/Agent/MessageSummaryCard.tsx deleted file mode 100644 index 527933f4..00000000 --- a/src/components/Agent/MessageSummaryCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as HoverCard from '@radix-ui/react-hover-card'; -import { FC } from 'react'; -import { Message } from '@/types'; -import { MessageSummary } from './MessageSummary'; -import { DrawingPinFilledIcon, InfoCircledIcon } from '@radix-ui/react-icons'; -import clsx from 'clsx'; - -export interface MessageSummaryCardProps { - messages: Message[]; -} - -export const MessageSummaryCard: FC = ({ - messages, -}) => { - const objective = messages.find((message) => message.type === 'objective'); - const task = messages - .slice() - .reverse() - .find((message) => message.type === 'next-task'); - const taskList = messages - .slice() - .reverse() - .find((message) => message.type === 'task-list'); - const list = [objective, task, taskList].filter( - (message) => message !== undefined, - ); - - const content = ( -
-
- {messages.length > 0 ? ( - list.map( - (message) => - message && ( - - ), - ) - ) : ( -
Nothing to show
- )} -
-
- ); - - return ( - - -
- -
-
- - - {content} - -
- ); -}; diff --git a/src/hooks/useSkills.tsx b/src/hooks/useSkills.tsx index 08da9484..3a8821e0 100644 --- a/src/hooks/useSkills.tsx +++ b/src/hooks/useSkills.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { BabyElfAGI } from '@/agents/elf/executer'; +import { BabyElfAGI } from '@/agents/babyelfagi/executer'; import { SPECIFIED_SKILLS } from '@/utils/constants'; type SkillInfo = { diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts index c34e8111..c9aad562 100644 --- a/src/pages/api/agent/index.ts +++ b/src/pages/api/agent/index.ts @@ -1,7 +1,7 @@ import type { NextRequest } from 'next/server'; import { StreamingTextResponse } from 'ai'; import { AgentStream } from '@/agents/base/AgentStream'; -import { BabyElfAGI } from '@/agents/elf/executer'; +import { BabyElfAGI } from '@/agents/babyelfagi/executer'; import { SPECIFIED_SKILLS } from '@/utils/constants'; export const config = { diff --git a/src/pages/api/agents/create.ts b/src/pages/api/agents/create.ts deleted file mode 100644 index 49e1a473..00000000 --- a/src/pages/api/agents/create.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { taskCreationAgent } from '@/agents/babycatagi/service'; -import { NextRequest, NextResponse } from 'next/server'; - -export const config = { - runtime: 'edge', -}; - -const handler = async (req: NextRequest) => { - try { - const { objective, websearch_var, model_name, language } = await req.json(); - const response = await taskCreationAgent( - objective, - websearch_var, - model_name, - language, - ); - return NextResponse.json({ response: response }); - } catch (error) { - return NextResponse.error(); - } -}; - -export default handler; diff --git a/src/pages/api/agents/management.ts b/src/pages/api/agents/management.ts deleted file mode 100644 index 62159aad..00000000 --- a/src/pages/api/agents/management.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { taskManagementAgent } from '@/agents/babybeeagi/service'; - -export const config = { - runtime: 'edge', -}; - -const handler = async (req: NextRequest) => { - try { - const { - minified_task_list, - objective, - result, - websearch_var, - model_name, - language, - } = await req.json(); - const response = await taskManagementAgent( - minified_task_list, - objective, - result, - websearch_var, - model_name, - language, - ); - return NextResponse.json({ response: response }); - } catch (error) { - return NextResponse.error(); - } -}; - -export default handler; diff --git a/src/pages/api/agents/overview.ts b/src/pages/api/agents/overview.ts deleted file mode 100644 index a93256dc..00000000 --- a/src/pages/api/agents/overview.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { overviewAgent } from '@/agents/babybeeagi/service'; - -export const config = { - runtime: 'edge', -}; - -const handler = async (req: NextRequest) => { - try { - const { objective, session_summary, last_task_id, completed_tasks_text } = - await req.json(); - const response = await overviewAgent( - objective, - session_summary, - last_task_id, - completed_tasks_text, - ); - return NextResponse.json({ response: response }); - } catch (error) { - return NextResponse.error(); - } -}; - -export default handler; diff --git a/src/pages/api/agents/summarize.ts b/src/pages/api/agents/summarize.ts deleted file mode 100644 index dda07d10..00000000 --- a/src/pages/api/agents/summarize.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { summarizerAgent } from '@/agents/babybeeagi/service'; - -export const config = { - runtime: 'edge', -}; - -const handler = async (req: NextRequest) => { - try { - const { text, language } = await req.json(); - const response = await summarizerAgent(text, language); - return NextResponse.json({ response: response }); - } catch (error) { - return NextResponse.error(); - } -}; - -export default handler; diff --git a/src/pages/api/deer/completion.ts b/src/pages/api/deer/completion.ts deleted file mode 100644 index 384dc27e..00000000 --- a/src/pages/api/deer/completion.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { HumanChatMessage } from 'langchain/schema'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { prompt, model_name } = await req.body; - const llm = new ChatOpenAI({ - modelName: model_name, - temperature: 0.2, - maxTokens: 800, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - - const response = await llm.call([new HumanChatMessage(prompt)]); - - return res.status(200).json({ response: response }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/api/deer/create.ts b/src/pages/api/deer/create.ts deleted file mode 100644 index 2281111d..00000000 --- a/src/pages/api/deer/create.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { taskCreationPrompt } from '@/agents/babydeeragi/agents/taskCreation/prompt'; -import { LLMChain } from 'langchain/chains'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { NextRequest, NextResponse } from 'next/server'; - -export const runtime = 'edge'; - -const handler = async (req: NextRequest) => { - try { - const { objective, websearch_var, user_input_var, model_name, language } = - await req.json(); - - const llm = new ChatOpenAI({ - modelName: model_name, - temperature: 0, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - - const prompt = taskCreationPrompt(); - const chain = new LLMChain({ llm, prompt }); - const response = await chain.call({ - objective, - websearch_var, - user_input_var, - language, - }); - return NextResponse.json({ response: response.text }); - } catch (error) { - return NextResponse.error(); - } -}; - -export default handler; diff --git a/src/pages/api/deer/extract.ts b/src/pages/api/deer/extract.ts deleted file mode 100644 index 3cb73603..00000000 --- a/src/pages/api/deer/extract.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { relevantInfoExtractionPrompt } from '@/agents/babydeeragi/agents/relevantInfoExtraction/prompt'; -import { LLMChain } from 'langchain/chains'; -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { NextRequest, NextResponse } from 'next/server'; - -export const runtime = 'edge'; - -const handler = async (req: NextRequest) => { - try { - const { objective, task, notes, chunk, model_name } = await req.json(); - const llm = new ChatOpenAI({ - modelName: model_name, - temperature: 0, - maxTokens: 1500, - topP: 1, - frequencyPenalty: 0, - presencePenalty: 0, - }); - - const prompt = relevantInfoExtractionPrompt(); - const chain = new LLMChain({ llm, prompt }); - const response = await chain.call({ - objective, - task, - notes, - chunk, - }); - return NextResponse.json({ response: response.text }); - } catch (error) { - return NextResponse.error(); - } -}; - -export default handler; diff --git a/src/pages/api/elf/completion.ts b/src/pages/api/elf/completion.ts deleted file mode 100644 index d723cacb..00000000 --- a/src/pages/api/elf/completion.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ChatOpenAI } from 'langchain/chat_models/openai'; -import { HumanChatMessage } from 'langchain/schema'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { - prompt, - model_name, - temperature, - max_tokens, - top_p, - frequency_penalty, - presence_penalty, - } = await req.body; - const llm = new ChatOpenAI({ - modelName: model_name, - temperature, - maxTokens: max_tokens, - topP: top_p || 1, - frequencyPenalty: frequency_penalty || 0, - presencePenalty: presence_penalty || 0, - verbose: true, - }); - - const response = await llm.call([new HumanChatMessage(prompt)]); - - return res.status(200).json({ response: response.text }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/api/elf/embedding.ts b/src/pages/api/elf/embedding.ts deleted file mode 100644 index 432bc0b8..00000000 --- a/src/pages/api/elf/embedding.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { model_name, text } = await req.body; - const embedding = new OpenAIEmbeddings({ - modelName: model_name, - }); - - const response = await embedding.embedQuery(text); - - return res.status(200).json({ response: response }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/api/execute-skill.ts b/src/pages/api/execute-skill.ts deleted file mode 100644 index 49f086b1..00000000 --- a/src/pages/api/execute-skill.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SkillRegistry } from '@/agents/babyelfagi/registory/skillRegistry'; -import { AgentTask } from '@/types'; -import { NextApiRequest, NextApiResponse } from 'next'; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - const { task, dependent_task_outputs, objective, skill_name } = req.body; - - const agentTask: AgentTask = JSON.parse(task); - const skillRegistry = new SkillRegistry(); - const skill = skillRegistry.getSkill(skill_name as string); // Get the skill by its name - - try { - const taskOutput = await skill.execute( - agentTask, - dependent_task_outputs, - objective, - ); - res.status(200).json({ taskOutput }); - } catch (error: unknown) { - res.status(500).json({ error: (error as Error).message }); - } -} diff --git a/src/pages/api/tools/completion.ts b/src/pages/api/tools/completion.ts deleted file mode 100644 index 9cbc2d5a..00000000 --- a/src/pages/api/tools/completion.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { textCompletion } from '@/agents/babybeeagi/tools/textCompletion'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { prompt, model_name } = await req.body; - - const response = await textCompletion(prompt, model_name); - return res.status(200).json({ response: response }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/api/tools/extract.ts b/src/pages/api/tools/extract.ts deleted file mode 100644 index 24bdfaca..00000000 --- a/src/pages/api/tools/extract.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { extractRelevantInfoAgent } from '@/agents/babycatagi/service'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { objective, task, chunk, notes } = await req.body; - - const response = await extractRelevantInfoAgent( - objective, - task, - chunk, - notes, - ); - - return res.status(200).json({ response: response }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/api/tools/scrape.ts b/src/pages/api/tools/scrape.ts deleted file mode 100644 index 1bee74dd..00000000 --- a/src/pages/api/tools/scrape.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { webScrape } from '@/agents/common/tools/webScrape'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { url } = await req.body; - - const response = await webScrape(url); - return res.status(200).json({ response: response }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/api/tools/search.ts b/src/pages/api/tools/search.ts deleted file mode 100644 index 6c1091e6..00000000 --- a/src/pages/api/tools/search.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { webSearch } from '@/agents/common/tools/webSearch'; -import { NextApiRequest, NextApiResponse } from 'next'; - -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - try { - const { query } = await req.body; - - const response = await webSearch(query); - return res.status(200).json({ response: response }); - } catch (error) { - return res.status(500).json({ error: error }); - } -}; - -export default handler; diff --git a/src/pages/dev/index.tsx b/src/pages/dev/index.tsx deleted file mode 100644 index d6c7ffd9..00000000 --- a/src/pages/dev/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { AgentView } from '@/components/Agent/AgentView'; -import { Sidebar } from '@/components/Sidebar/Sidebar'; -import Head from 'next/head'; -import { useEffect, useState } from 'react'; -import type { GetStaticProps } from 'next'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import nextI18NextConfig from '../../../next-i18next.config.js'; -import { languages } from '@/utils/languages'; -import { STATE_KEY } from '@/utils/constants'; -import { UIState } from '@/types/index.js'; -import { CollapsedButton } from '@/components/Sidebar/CollapsedButton'; - -function Home() { - const [showSidebar, setShowSidebar] = useState(false); - - useEffect(() => { - const item = localStorage.getItem(STATE_KEY); - let show = false; - if (!item) { - if (window.innerWidth <= 768) { - show = false; - } else { - show = true; - } - } else { - const state = JSON.parse(item) as UIState; - if (state?.showSidebar === undefined) { - } else { - show = state.showSidebar; - } - } - setShowSidebar(show); - saveSidebarState(show); - }, []); - - const saveSidebarState = (show: boolean) => { - const state: UIState = { showSidebar: show }; - localStorage.setItem(STATE_KEY, JSON.stringify(state)); - }; - - const menuClickHandler = () => { - setShowSidebar(!showSidebar); - saveSidebarState(!showSidebar); - }; - - // This page is for development only. - if (process.env.NODE_ENV !== 'development') { - return

This page is for development.

; - } - - return ( - <> - - BabyAGI-UI | DEV - - - - - - - - - - - - -
-
- {showSidebar && ( -
- -
- )} - -
-
- -
-
- - ); -} - -export const getStaticProps: GetStaticProps = async ({ locale = 'en' }) => { - const supportedLocales = languages.map((language) => language.code); - const chosenLocale = supportedLocales.includes(locale) ? locale : 'en'; - - return { - props: { - ...(await serverSideTranslations( - chosenLocale, - nextI18NextConfig.ns as string[], - )), - }, - }; -}; - -export default Home; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d0f9a8df..64426f0d 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,11 +1,11 @@ -import { Agent } from '@/components/Agent/Agent'; +import { AgentView } from '@/components/Agent/AgentView'; import { Sidebar } from '@/components/Sidebar/Sidebar'; import Head from 'next/head'; import { useEffect, useState } from 'react'; import type { GetStaticProps } from 'next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import nextI18NextConfig from '../../next-i18next.config.js'; -import { languages } from '../utils/languages'; +import { languages } from '@/utils/languages'; import { STATE_KEY } from '@/utils/constants'; import { UIState } from '@/types/index.js'; import { CollapsedButton } from '@/components/Sidebar/CollapsedButton'; @@ -43,10 +43,15 @@ function Home() { saveSidebarState(!showSidebar); }; + // This page is for development only. + if (process.env.NODE_ENV !== 'development') { + return

This page is for development.

; + } + return ( <> - BabyAGI-UI + BabyAGI-UI | DEV
)} - +
{ - const targetJsonFiles = - process.env.NODE_ENV === 'development' ? JSON_FILES_FOR_DEV : JSON_FILES; - const loadedObjectives = await fetchJsonFiles(targetJsonFiles); - - return loadedObjectives; -}; - -async function getEmbedding( - text: string, - modelName: string = 'text-embedding-ada-002', - userApiKey?: string, -) { - const embedding = new OpenAIEmbeddings({ - modelName, - openAIApiKey: userApiKey, - }); - return await embedding.embedQuery(text); -} - -function calculateSimilarity(embedding1: number[], embedding2: number[]) { - const dotProduct = embedding1.reduce( - (sum, a, i) => sum + a * embedding2[i], - 0, - ); - const magnitude1 = Math.sqrt(embedding1.reduce((sum, a) => sum + a * a, 0)); - const magnitude2 = Math.sqrt(embedding2.reduce((sum, a) => sum + a * a, 0)); - return dotProduct / (magnitude1 * magnitude2); -} - -export async function findMostRelevantObjective( - userInput: string, - userApiKey?: string, -) { - const userInputEmbedding = await getEmbedding( - userInput, - 'text-embedding-ada-002', - userApiKey, - ); - const examples = await getObjectivesExamples(); - - let maxSimilarity = -Infinity; - let mostRelevantObjective = null; - - for (const example of examples) { - const objectiveEmbedding = await getEmbedding(example.objective); - const similarity = calculateSimilarity( - objectiveEmbedding, - userInputEmbedding, - ); - - if (similarity > maxSimilarity) { - maxSimilarity = similarity; - mostRelevantObjective = example; - } - } - - return mostRelevantObjective; -} diff --git a/src/utils/elf/print.ts b/src/utils/elf/print.ts deleted file mode 100644 index 7c6ea03a..00000000 --- a/src/utils/elf/print.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { AgentTask, AgentMessage } from '@/types'; -import { getToolIcon, setupMessage, setupMessageWithTask } from '../message'; -import { v4 as uuidv4 } from 'uuid'; - -export class Printer { - messageCallback: (message: AgentMessage) => void; - verbose: boolean = false; - - constructor( - messageCallback: (message: AgentMessage) => void, - verbose: boolean = false, - ) { - this.messageCallback = messageCallback; - this.verbose = verbose; - } - - printObjective(objective: string) { - this.handleMessage({ - content: objective, - type: 'objective', - }); - - if (!this.verbose) return; - console.log('%c*****OBJECTIVE*****\n%c%s', 'color:fuchsia', '', objective); - } - - printNextTask(task: AgentTask) { - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - // this - // .messageCallback - // // setupMessage('next-task', nextTask, task.tool, undefined, task.id), - // (); - - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); - console.log(task); - } - - printTaskExecute(task: AgentTask) { - if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); - console.log(task); - } - - printTaskList(taskList: AgentTask[], id?: string) { - const useSkill = taskList[0].skill !== undefined; - let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - if (useSkill) { - message = - '| ID | Status | Task | Skill | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - } - - taskList.forEach((task) => { - const dependentTask = task.dependentTaskIds - ? `${task.dependentTaskIds.join(', ')}` - : '-'; - const status = task.status === 'complete' ? '✅' : '⬜️'; - if (useSkill) { - message += `| ${task.id} | ${status} | ${task.task} | ${task.icon} | ${dependentTask} |\n`; - return; - } - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; - }); - - this.messageCallback({ - id, - content: message, - type: 'task-list', - style: 'text', - status: 'complete', - }); - - if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n%c%s', 'color:fuchsia', '', message); - } - - printTaskOutput(output: string, task: AgentTask) { - if (!this.verbose) return; - console.log('%c*****TASK OUTPUT*****\n%c%s', 'color:fuchsia', '', output); - } - - printTaskCompleted() { - // this.messageCallback(setupMessage('done', 'Done!')); - - if (!this.verbose) return; - console.log('%c*****DONE*****\n%c', 'color:fuchsia', ''); - } - - printAllTaskCompleted() { - this.handleMessage({ - content: 'All task completed!', - type: 'finish', - }); - - if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:fuchsia', ''); - } - - // handleMessage() is called by the agent to send a message to the frontend - async handleMessage(message: AgentMessage) { - const msg = { - ...message, - id: message.id || uuidv4(), - status: message.status || 'complete', - }; - await this.messageCallback(msg); - } -} diff --git a/src/utils/objective.ts b/src/utils/objective.ts index 5fb7b4d4..6c9f6994 100644 --- a/src/utils/objective.ts +++ b/src/utils/objective.ts @@ -1,8 +1,5 @@ -import { getUserApiKey } from '@/utils/settings'; -import axios from 'axios'; import { OpenAIEmbeddings } from 'langchain/embeddings/openai'; -const CURRENT_OBJECTIVES_VERSION = '1.0.0'; const JSON_FILES = ['example3', 'example4', 'example_deer']; const JSON_FILES_FOR_DEV = [ 'example3', @@ -11,12 +8,15 @@ const JSON_FILES_FOR_DEV = [ 'example_code', 'example_code_review', ]; +const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; async function fetchJsonFiles(targetJsonFiles: string[]) { let loadedObjectives: any[] = []; for (const jsonFile of targetJsonFiles) { - const response = await fetch(`/api/json-provider?file=${jsonFile}`); + const response = await fetch( + `${BASE_URL}/api/json-provider?file=${jsonFile}`, + ); const data = await response.json(); loadedObjectives.push(data); } @@ -25,57 +25,23 @@ async function fetchJsonFiles(targetJsonFiles: string[]) { } const getObjectivesExamples = async () => { - const storedObjectives = localStorage.getItem('BABYAGIUI_OBJECTIVES'); - - if (storedObjectives) { - const data = JSON.parse(storedObjectives); - if (data.version === CURRENT_OBJECTIVES_VERSION) { - return data.objectives; - } - } - const targetJsonFiles = process.env.NODE_ENV === 'development' ? JSON_FILES_FOR_DEV : JSON_FILES; const loadedObjectives = await fetchJsonFiles(targetJsonFiles); - const data = { - version: CURRENT_OBJECTIVES_VERSION, - objectives: loadedObjectives, - }; - - localStorage.setItem('BABYAGIUI_OBJECTIVES', JSON.stringify(data)); - return loadedObjectives; }; async function getEmbedding( text: string, modelName: string = 'text-embedding-ada-002', + userApiKey?: string, ) { - const openAIApiKey = getUserApiKey(); - if (!openAIApiKey && process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true') { - throw new Error('User API key is not set.'); - } - - if (openAIApiKey) { - const embedding = new OpenAIEmbeddings({ - modelName, - openAIApiKey: getUserApiKey(), - }); - return await embedding.embedQuery(text); - } else { - const response = await axios.post( - '/api/elf/embedding', - { - text: text, - model_name: modelName, - }, - { - signal: new AbortController().signal, - }, - ); - return response.data.response; - } + const embedding = new OpenAIEmbeddings({ + modelName, + openAIApiKey: userApiKey, + }); + return await embedding.embedQuery(text); } function calculateSimilarity(embedding1: number[], embedding2: number[]) { @@ -88,10 +54,14 @@ function calculateSimilarity(embedding1: number[], embedding2: number[]) { return dotProduct / (magnitude1 * magnitude2); } -export async function findMostRelevantObjective(userInput: string) { +export async function findMostRelevantObjective( + userInput: string, + userApiKey?: string, +) { const userInputEmbedding = await getEmbedding( userInput, 'text-embedding-ada-002', + userApiKey, ); const examples = await getObjectivesExamples(); diff --git a/src/utils/print.ts b/src/utils/print.ts index 4990248c..8033c708 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -1,12 +1,12 @@ -import { AgentTask, Message } from '@/types'; -import { getToolIcon, setupMessage, setupMessageWithTask } from './message'; +import { AgentTask, AgentMessage } from '@/types'; +import { v4 as uuidv4 } from 'uuid'; export class Printer { - messageCallback: (message: Message) => void; + messageCallback: (message: AgentMessage) => void; verbose: boolean = false; constructor( - messageCallback: (message: Message) => void, + messageCallback: (message: AgentMessage) => void, verbose: boolean = false, ) { this.messageCallback = messageCallback; @@ -14,88 +14,80 @@ export class Printer { } printObjective(objective: string) { - this.messageCallback(setupMessage('objective', objective)); + this.handleMessage({ + content: objective, + type: 'objective', + }); if (!this.verbose) return; console.log('%c*****OBJECTIVE*****\n%c%s', 'color:fuchsia', '', objective); } printNextTask(task: AgentTask) { - const nextTask = `${task.id}. ${task.task} - **[${getToolIcon(task.tool)} ${ - task.tool - }]**`; - this.messageCallback( - setupMessage('next-task', nextTask, task.tool, undefined, task.id), - ); - if (!this.verbose) return; console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); console.log(task); } printTaskExecute(task: AgentTask) { - this.messageCallback(setupMessageWithTask(task)); - if (!this.verbose) return; console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); console.log(task); } - printTaskList(taskList: AgentTask[], id?: number) { - const useSkill = taskList[0].skill !== undefined; + printTaskList(taskList: AgentTask[], id?: string) { let message = - '| ID | Status | Task | Tool | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - if (useSkill) { - message = - '| ID | Status | Task | Skill | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; - } + '| ID | Status | Task | Skill | Dependency | \n | :-: | :-: | - | :-: | :-: | \n'; taskList.forEach((task) => { const dependentTask = task.dependentTaskIds ? `${task.dependentTaskIds.join(', ')}` : '-'; const status = task.status === 'complete' ? '✅' : '⬜️'; - if (useSkill) { - message += `| ${task.id} | ${status} | ${task.task} | ${task.icon} | ${dependentTask} |\n`; - return; - } - message += `| ${task.id} | ${status} | ${task.task} | ${getToolIcon( - task.tool, - )} | ${dependentTask} |\n`; + message += `| ${task.id} | ${status} | ${task.task} | ${task.icon} | ${dependentTask} |\n`; }); - this.messageCallback( - setupMessage('task-list', message, undefined, `📝`, id), - ); + this.messageCallback({ + id, + content: message, + type: 'task-list', + style: 'text', + status: 'complete', + }); if (!this.verbose) return; console.log('%c*****TASK LIST*****\n%c%s', 'color:fuchsia', '', message); } printTaskOutput(output: string, task: AgentTask) { - if (task.tool !== 'text-completion') { - // code block for non-text-completion tools - // output = '```\n' + output + '\n```'; - } - this.messageCallback( - setupMessage('task-output', output, task?.tool, undefined, task?.id), - ); - if (!this.verbose) return; console.log('%c*****TASK OUTPUT*****\n%c%s', 'color:fuchsia', '', output); } printTaskCompleted() { - this.messageCallback(setupMessage('done', 'Done!')); + // this.messageCallback(setupMessage('done', 'Done!')); if (!this.verbose) return; console.log('%c*****DONE*****\n%c', 'color:fuchsia', ''); } printAllTaskCompleted() { - this.messageCallback(setupMessage('complete', 'All tasks completed!')); + this.handleMessage({ + content: 'All task completed!', + type: 'finish', + }); if (!this.verbose) return; console.log('%c*****ALL TASK COMPLETED*****%c', 'color:fuchsia', ''); } + + // handleMessage() is called by the agent to send a message to the frontend + async handleMessage(message: AgentMessage) { + const msg = { + ...message, + id: message.id || uuidv4(), + status: message.status || 'complete', + }; + await this.messageCallback(msg); + } } From 7c80415e866df3b05b81666006720ee93e4f13c8 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Fri, 18 Aug 2023 16:47:49 +0900 Subject: [PATCH 24/42] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6bb30207..62df2ff6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ This is a port of [babyagi](https://github.com/yoheinakajima/babyagi) with [Lang - [x] User input & parallel tasking. ([🦌 BabyDeerAGI](https://twitter.com/yoheinakajima/status/1666313838868992001)) - [x] API updates support (gpt-3.5-turbo-0613/gpt-3.5-turbo-16k-0613/gpt-4-0613) - [x] Skills Class allows for easy skill creation ([🧝 BabyElfAGI](https://twitter.com/yoheinakajima/status/1678443482866933760)) +- [x] Aggregate the logic of the agent in the backend. +- [x] Add hooks to make it easier to handle the agent on the frontend. - [ ] Support the Llama2 model 🦙 and more ... From 382f5bea78af4ee2468d329dc8457496a28f2a76 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 19 Aug 2023 10:09:08 +0900 Subject: [PATCH 25/42] Add new message for execution stop with toast notification --- public/locales/en/agent.json | 5 ++-- src/agents/babyelfagi/executer.ts | 2 +- src/agents/babyelfagi/skills/skill.ts | 37 ++++++++++++--------------- src/components/Agent/AgentLoading.tsx | 4 +-- src/components/Agent/AgentView.tsx | 2 ++ src/hooks/useAgent.ts | 11 +++++++- src/pages/index.tsx | 2 +- 7 files changed, 35 insertions(+), 28 deletions(-) diff --git a/public/locales/en/agent.json b/public/locales/en/agent.json index 61377a03..760408c0 100644 --- a/public/locales/en/agent.json +++ b/public/locales/en/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Please set up your OpenAI API key from the settings menu.", "ALL_TASKS_COMPLETED_TOAST": "All Tasks Completed!", "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "TASK_COMPLETED_TOAST": "Task Completed!" -} \ No newline at end of file + "TASK_COMPLETED_TOAST": "Task Completed!", + "EXECUTION_STOPPED": "Execution stopped" +} diff --git a/src/agents/babyelfagi/executer.ts b/src/agents/babyelfagi/executer.ts index 165c2364..cb2482cc 100644 --- a/src/agents/babyelfagi/executer.ts +++ b/src/agents/babyelfagi/executer.ts @@ -166,7 +166,7 @@ export class BabyElfAGI extends AgentExecuter { } async finishup() { - super.finishup(); + if (this.signal?.aborted) return; const tasks = this.taskRegistry.getTasks(); const lastTask = tasks[tasks.length - 1]; diff --git a/src/agents/babyelfagi/skills/skill.ts b/src/agents/babyelfagi/skills/skill.ts index c78161e1..fe44da06 100644 --- a/src/agents/babyelfagi/skills/skill.ts +++ b/src/agents/babyelfagi/skills/skill.ts @@ -114,6 +114,19 @@ export class Skill { streaming: true, }; const llmParams = { ...defaultParams, ...params }; + + const message: AgentMessage = { + id, + content: '', + title: task.task, + type: task.skill, + icon: task.icon, + taskId: task.id.toString(), + status: 'complete', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, + }; const llm = new ChatOpenAI( { openAIApiKey: this.apiKeys.openai, @@ -122,16 +135,8 @@ export class Skill { { handleLLMNewToken(token: string) { callback?.({ - id, - content: token, - title: `${task.task}`, - type: task.skill, - icon: task.icon, - taskId: task.id.toString(), - status: 'running', - options: { - dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', - }, + ...message, + ...{ content: token, status: 'running' }, }); }, }, @@ -142,17 +147,7 @@ export class Skill { try { const response = await llm.call([new HumanChatMessage(prompt)]); - this.callbackMessage({ - taskId: task.id.toString(), - content: '', - title: task.task, - icon: task.icon, - type: task.skill, - status: 'complete', - options: { - dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', - }, - }); + this.callbackMessage(message); return response.text; } catch (error: any) { if (error.name === 'AbortError') { diff --git a/src/components/Agent/AgentLoading.tsx b/src/components/Agent/AgentLoading.tsx index 14779fbb..1fc5699d 100644 --- a/src/components/Agent/AgentLoading.tsx +++ b/src/components/Agent/AgentLoading.tsx @@ -11,9 +11,9 @@ const AgentLoading: FC = ({ message }) => { className={`bg-neutral-50 text-gray-800 dark:border-gray-900/50 dark:bg-neutral-950 dark:text-gray-100`} >
-
+
{message}
diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index 4d82398a..abe97498 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -32,6 +32,7 @@ import { useScrollControl, useCurrentEvaluation, } from '@/hooks'; +import { toast } from 'sonner'; export const AgentView: FC = () => { // Custom hooks @@ -73,6 +74,7 @@ export const AgentView: FC = () => { // Functions const stopHandler = () => { va.track('Stop'); + toast(translate('EXECUTION_STOPPED', 'agent')); }; const startHandler = async () => { diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index 77019fc3..d03203b6 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -136,7 +136,16 @@ export function useAgent({ } catch (error) { // Call onError when an error occurs if (onError) { - onError(new ErrorEvent('error', { error })); + // If the reason for the error is abort, ignore it + if (error instanceof Error && error.name === 'AbortError') { + // ignore + } else { + onError( + new ErrorEvent('error', { + error: error instanceof Error ? error.message : 'Unknown error', + }), + ); + } } } }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 64426f0d..9b7ac1bf 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -51,7 +51,7 @@ function Home() { return ( <> - BabyAGI-UI | DEV + BabyAGI-UI Date: Sat, 19 Aug 2023 10:35:39 +0900 Subject: [PATCH 26/42] Refactor print messages for better readability --- src/utils/print.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/print.ts b/src/utils/print.ts index 8033c708..79faefea 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -20,18 +20,18 @@ export class Printer { }); if (!this.verbose) return; - console.log('%c*****OBJECTIVE*****\n%c%s', 'color:fuchsia', '', objective); + console.log('\x1b[35m%s\x1b[0m', '*****OBJECTIVE*****\n' + objective); } printNextTask(task: AgentTask) { if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); + console.log('\x1b[33m%s\x1b[0m', '*****NEXT TASK*****\n'); console.log(task); } printTaskExecute(task: AgentTask) { if (!this.verbose) return; - console.log('%c*****NEXT TASK*****\n%c', 'color:fuchsia', ''); + console.log('\x1b[35m%s\x1b[0m', '*****NEXT TASK*****\n'); console.log(task); } @@ -56,19 +56,19 @@ export class Printer { }); if (!this.verbose) return; - console.log('%c*****TASK LIST*****\n%c%s', 'color:fuchsia', '', message); + console.log('\x1b[34m%s\x1b[0m', '*****TASK LIST*****\n' + message); } printTaskOutput(output: string, task: AgentTask) { if (!this.verbose) return; - console.log('%c*****TASK OUTPUT*****\n%c%s', 'color:fuchsia', '', output); + console.log('\x1b[32m%s\x1b[0m', '*****TASK OUTPUT*****\n' + output); } printTaskCompleted() { // this.messageCallback(setupMessage('done', 'Done!')); if (!this.verbose) return; - console.log('%c*****DONE*****\n%c', 'color:fuchsia', ''); + console.log('\x1b[35m%s\x1b[0m', '*****DONE*****\n'); } printAllTaskCompleted() { @@ -78,7 +78,7 @@ export class Printer { }); if (!this.verbose) return; - console.log('%c*****ALL TASK COMPLETED*****%c', 'color:fuchsia', ''); + console.log('\x1b[36m%s\x1b[0m', '*****ALL TASK COMPLETED*****\n'); } // handleMessage() is called by the agent to send a message to the frontend From dea18658aa599d602038bcaf09b684e1ffb4ba45 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 19 Aug 2023 10:41:49 +0900 Subject: [PATCH 27/42] Add "EXECUTION_STOPPED" to agent.json --- public/locales/ar/agent.json | 3 ++- public/locales/au/agent.json | 3 ++- public/locales/bg/agent.json | 3 ++- public/locales/bn/agent.json | 3 ++- public/locales/br/agent.json | 3 ++- public/locales/cs/agent.json | 3 ++- public/locales/da/agent.json | 3 ++- public/locales/de/agent.json | 3 ++- public/locales/el/agent.json | 3 ++- public/locales/es/agent.json | 3 ++- public/locales/et/agent.json | 3 ++- public/locales/fa/agent.json | 3 ++- public/locales/fi/agent.json | 3 ++- public/locales/fr/agent.json | 3 ++- public/locales/gb/agent.json | 3 ++- public/locales/gu/agent.json | 3 ++- public/locales/he/agent.json | 3 ++- public/locales/hi/agent.json | 3 ++- public/locales/hr/agent.json | 3 ++- public/locales/hu/agent.json | 3 ++- public/locales/id/agent.json | 3 ++- public/locales/it/agent.json | 3 ++- public/locales/ja/agent.json | 5 +++-- public/locales/kn/agent.json | 3 ++- public/locales/ko/agent.json | 3 ++- public/locales/lt/agent.json | 3 ++- public/locales/lv/agent.json | 3 ++- public/locales/ml/agent.json | 3 ++- public/locales/nl/agent.json | 3 ++- public/locales/no/agent.json | 3 ++- public/locales/pl/agent.json | 3 ++- public/locales/pt/agent.json | 3 ++- public/locales/ro/agent.json | 3 ++- public/locales/ru/agent.json | 3 ++- public/locales/sk/agent.json | 3 ++- public/locales/sl/agent.json | 3 ++- public/locales/sr/agent.json | 3 ++- public/locales/sv/agent.json | 3 ++- public/locales/ta/agent.json | 3 ++- public/locales/te/agent.json | 3 ++- public/locales/th/agent.json | 3 ++- public/locales/tr/agent.json | 3 ++- public/locales/uk/agent.json | 3 ++- public/locales/ur/agent.json | 3 ++- public/locales/vi/agent.json | 3 ++- public/locales/zh/agent.json | 3 ++- public/locales/zhtw/agent.json | 3 ++- 47 files changed, 95 insertions(+), 48 deletions(-) diff --git a/public/locales/ar/agent.json b/public/locales/ar/agent.json index b82ba3a9..d47874d8 100644 --- a/public/locales/ar/agent.json +++ b/public/locales/ar/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "يرجى إعداد مفتاح OpenAI API الخاص بك من قائمة الإعدادات.", "ALL_TASKS_COMPLETED_TOAST": "اكتملت جميع المهام!", "COPIED_TO_CLIPBOARD": "نسخ إلى الحافظة", - "TASK_COMPLETED_TOAST": "تمت المهمة!" + "TASK_COMPLETED_TOAST": "تمت المهمة!", + "EXECUTION_STOPPED": "تم إيقاف التنفيذ" } \ No newline at end of file diff --git a/public/locales/au/agent.json b/public/locales/au/agent.json index 61377a03..e5973862 100644 --- a/public/locales/au/agent.json +++ b/public/locales/au/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Please set up your OpenAI API key from the settings menu.", "ALL_TASKS_COMPLETED_TOAST": "All Tasks Completed!", "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "TASK_COMPLETED_TOAST": "Task Completed!" + "TASK_COMPLETED_TOAST": "Task Completed!", + "EXECUTION_STOPPED": "Execution stopped." } \ No newline at end of file diff --git a/public/locales/bg/agent.json b/public/locales/bg/agent.json index f89d3a34..58fb3ce3 100644 --- a/public/locales/bg/agent.json +++ b/public/locales/bg/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Моля, настройте вашия OpenAI API ключ от менюто с настройки.", "ALL_TASKS_COMPLETED_TOAST": "Всички задачи изпълнени!", "COPIED_TO_CLIPBOARD": "Копирано в клипборда", - "TASK_COMPLETED_TOAST": "Задачата е изпълнена!" + "TASK_COMPLETED_TOAST": "Задачата е изпълнена!", + "EXECUTION_STOPPED": "Изпълнението е спряно" } \ No newline at end of file diff --git a/public/locales/bn/agent.json b/public/locales/bn/agent.json index a1375f6a..b6376ce5 100644 --- a/public/locales/bn/agent.json +++ b/public/locales/bn/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "সেটিংস মেনু থেকে আপনার OpenAI API কী সেট আপ করুন।", "ALL_TASKS_COMPLETED_TOAST": "সমস্ত কাজ সম্পন্ন!", "COPIED_TO_CLIPBOARD": "ক্লিপবোর্ডে কপি করা হয়েছে", - "TASK_COMPLETED_TOAST": "কাজ সম্পূর্ণ!" + "TASK_COMPLETED_TOAST": "কাজ সম্পূর্ণ!", + "EXECUTION_STOPPED": "কার্যপরিচালনা থামে গেল" } \ No newline at end of file diff --git a/public/locales/br/agent.json b/public/locales/br/agent.json index 7426dc93..0e541e7a 100644 --- a/public/locales/br/agent.json +++ b/public/locales/br/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Por favor, configure sua chave de API do OpenAI no menu de configurações.", "ALL_TASKS_COMPLETED_TOAST": "Todas as tarefas concluídas!", "COPIED_TO_CLIPBOARD": "Copiado para a área de transferência", - "TASK_COMPLETED_TOAST": "Tarefa concluída!" + "TASK_COMPLETED_TOAST": "Tarefa concluída!", + "EXECUTION_STOPPED": "A execução foi interrompida." } \ No newline at end of file diff --git a/public/locales/cs/agent.json b/public/locales/cs/agent.json index 80a8bc2f..51cac022 100644 --- a/public/locales/cs/agent.json +++ b/public/locales/cs/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Nastavte svůj OpenAI API klíč z nabídky nastavení.", "ALL_TASKS_COMPLETED_TOAST": "Všechny úkoly dokončeny!", "COPIED_TO_CLIPBOARD": "Zkopírováno do schránky", - "TASK_COMPLETED_TOAST": "Úkol splněn!" + "TASK_COMPLETED_TOAST": "Úkol splněn!", + "EXECUTION_STOPPED": "Provedení zastaveno" } \ No newline at end of file diff --git a/public/locales/da/agent.json b/public/locales/da/agent.json index 3060b528..afe86e94 100644 --- a/public/locales/da/agent.json +++ b/public/locales/da/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Indstil venligst din OpenAI API-nøgle fra indstillingsmenuen.", "ALL_TASKS_COMPLETED_TOAST": "Alle opgaver udført!", "COPIED_TO_CLIPBOARD": "Kopieret til udklipsholder", - "TASK_COMPLETED_TOAST": "Opgave afsluttet!" + "TASK_COMPLETED_TOAST": "Opgave afsluttet!", + "EXECUTION_STOPPED": "Udførelse stoppede" } \ No newline at end of file diff --git a/public/locales/de/agent.json b/public/locales/de/agent.json index 6956f8d5..7d651997 100644 --- a/public/locales/de/agent.json +++ b/public/locales/de/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Bitte richten Sie Ihren OpenAI-API-Schlüssel über das Einstellungsmenü ein.", "ALL_TASKS_COMPLETED_TOAST": "Alle Aufgaben erledigt!", "COPIED_TO_CLIPBOARD": "In die Zwischenablage kopiert", - "TASK_COMPLETED_TOAST": "Aufgabe erledigt!" + "TASK_COMPLETED_TOAST": "Aufgabe erledigt!", + "EXECUTION_STOPPED": "Die Ausführung wurde gestoppt." } \ No newline at end of file diff --git a/public/locales/el/agent.json b/public/locales/el/agent.json index 0d2f506c..5dc4a090 100644 --- a/public/locales/el/agent.json +++ b/public/locales/el/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Ρυθμίστε το κλειδί OpenAI API από το μενού ρυθμίσεων.", "ALL_TASKS_COMPLETED_TOAST": "Ολοκληρώθηκαν όλες οι εργασίες!", "COPIED_TO_CLIPBOARD": "Αντιγράφηκε στο πρόχειρο", - "TASK_COMPLETED_TOAST": "Εργασία Ολοκληρώθηκε!" + "TASK_COMPLETED_TOAST": "Εργασία Ολοκληρώθηκε!", + "EXECUTION_STOPPED": "Η εκτέλεση διακόπηκε" } \ No newline at end of file diff --git a/public/locales/es/agent.json b/public/locales/es/agent.json index a458a2e7..4ac39559 100644 --- a/public/locales/es/agent.json +++ b/public/locales/es/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Configure su clave API de OpenAI desde el menú de configuración.", "ALL_TASKS_COMPLETED_TOAST": "¡Todas las tareas completadas!", "COPIED_TO_CLIPBOARD": "Copiado al portapapeles", - "TASK_COMPLETED_TOAST": "¡Tarea terminada!" + "TASK_COMPLETED_TOAST": "¡Tarea terminada!", + "EXECUTION_STOPPED": "La ejecución se detuvo" } \ No newline at end of file diff --git a/public/locales/et/agent.json b/public/locales/et/agent.json index 2fa25262..b485f58f 100644 --- a/public/locales/et/agent.json +++ b/public/locales/et/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Seadistage seadete menüüst oma OpenAI API võti.", "ALL_TASKS_COMPLETED_TOAST": "Kõik ülesanded täidetud!", "COPIED_TO_CLIPBOARD": "Kopeeriti lõikelauale", - "TASK_COMPLETED_TOAST": "Ülesanne täidetud!" + "TASK_COMPLETED_TOAST": "Ülesanne täidetud!", + "EXECUTION_STOPPED": "Täitmine peatus" } \ No newline at end of file diff --git a/public/locales/fa/agent.json b/public/locales/fa/agent.json index 0546ae75..e1dbad94 100644 --- a/public/locales/fa/agent.json +++ b/public/locales/fa/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "لطفاً کلید OpenAI API خود را از منوی تنظیمات تنظیم کنید.", "ALL_TASKS_COMPLETED_TOAST": "تمام وظایف تکمیل شد!", "COPIED_TO_CLIPBOARD": "در کلیپ بورد کپی شد", - "TASK_COMPLETED_TOAST": "کار تکمیل شد!" + "TASK_COMPLETED_TOAST": "کار تکمیل شد!", + "EXECUTION_STOPPED": "تنفیذ متوقف شد" } \ No newline at end of file diff --git a/public/locales/fi/agent.json b/public/locales/fi/agent.json index e14e53f0..54ea333b 100644 --- a/public/locales/fi/agent.json +++ b/public/locales/fi/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Määritä OpenAI API -avain asetusvalikosta.", "ALL_TASKS_COMPLETED_TOAST": "Kaikki tehtävät suoritettu!", "COPIED_TO_CLIPBOARD": "Kopioitu leikepöydälle", - "TASK_COMPLETED_TOAST": "Tehtävä suoritettu!" + "TASK_COMPLETED_TOAST": "Tehtävä suoritettu!", + "EXECUTION_STOPPED": "Suoritus keskeytetty" } \ No newline at end of file diff --git a/public/locales/fr/agent.json b/public/locales/fr/agent.json index ef426257..20278340 100644 --- a/public/locales/fr/agent.json +++ b/public/locales/fr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Veuillez configurer votre clé API OpenAI à partir du menu des paramètres.", "ALL_TASKS_COMPLETED_TOAST": "Toutes les tâches terminées !", "COPIED_TO_CLIPBOARD": "Copié dans le presse-papier", - "TASK_COMPLETED_TOAST": "Tâche terminée!" + "TASK_COMPLETED_TOAST": "Tâche terminée!", + "EXECUTION_STOPPED": "L'exécution s'est arrêtée." } \ No newline at end of file diff --git a/public/locales/gb/agent.json b/public/locales/gb/agent.json index 61377a03..795077b8 100644 --- a/public/locales/gb/agent.json +++ b/public/locales/gb/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Please set up your OpenAI API key from the settings menu.", "ALL_TASKS_COMPLETED_TOAST": "All Tasks Completed!", "COPIED_TO_CLIPBOARD": "Copied to clipboard", - "TASK_COMPLETED_TOAST": "Task Completed!" + "TASK_COMPLETED_TOAST": "Task Completed!", + "EXECUTION_STOPPED": "Execution stopped" } \ No newline at end of file diff --git a/public/locales/gu/agent.json b/public/locales/gu/agent.json index 327866fb..d62dfef3 100644 --- a/public/locales/gu/agent.json +++ b/public/locales/gu/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "કૃપા કરીને સેટિંગ્સ મેનૂમાંથી તમારી OpenAI API કી સેટ કરો.", "ALL_TASKS_COMPLETED_TOAST": "બધા કાર્યો પૂર્ણ!", "COPIED_TO_CLIPBOARD": "ક્લિપબોર્ડ પર કૉપિ કરી", - "TASK_COMPLETED_TOAST": "કાર્ય પૂર્ણ થયું!" + "TASK_COMPLETED_TOAST": "કાર્ય પૂર્ણ થયું!", + "EXECUTION_STOPPED": "પરિનિર્માણ બંધ થઇ ગયેલું છે" } \ No newline at end of file diff --git a/public/locales/he/agent.json b/public/locales/he/agent.json index 9aaa0f36..5c0f26d9 100644 --- a/public/locales/he/agent.json +++ b/public/locales/he/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "הגדר את מפתח ה-API של OpenAI מתפריט ההגדרות.", "ALL_TASKS_COMPLETED_TOAST": "כל המשימות הושלמו!", "COPIED_TO_CLIPBOARD": "הועתק ללוח", - "TASK_COMPLETED_TOAST": "משימה הושלמה!" + "TASK_COMPLETED_TOAST": "משימה הושלמה!", + "EXECUTION_STOPPED": "הביצוע נעצר" } \ No newline at end of file diff --git a/public/locales/hi/agent.json b/public/locales/hi/agent.json index 1fa131a7..6a8d64d2 100644 --- a/public/locales/hi/agent.json +++ b/public/locales/hi/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "कृपया सेटिंग मेनू से अपनी OpenAI API कुंजी सेट करें।", "ALL_TASKS_COMPLETED_TOAST": "सभी कार्य पूर्ण!", "COPIED_TO_CLIPBOARD": "क्लिपबोर्ड पर नकल", - "TASK_COMPLETED_TOAST": "कार्य पूरा हो गया!" + "TASK_COMPLETED_TOAST": "कार्य पूरा हो गया!", + "EXECUTION_STOPPED": "अंदाज़ा रोक दिया गया" } \ No newline at end of file diff --git a/public/locales/hr/agent.json b/public/locales/hr/agent.json index 7ea5d006..e1595242 100644 --- a/public/locales/hr/agent.json +++ b/public/locales/hr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Postavite svoj OpenAI API ključ iz izbornika postavki.", "ALL_TASKS_COMPLETED_TOAST": "Svi zadaci obavljeni!", "COPIED_TO_CLIPBOARD": "Kopirano u međuspremnik", - "TASK_COMPLETED_TOAST": "Zadatak obavljen!" + "TASK_COMPLETED_TOAST": "Zadatak obavljen!", + "EXECUTION_STOPPED": "Izvršenje je zaustavljeno" } \ No newline at end of file diff --git a/public/locales/hu/agent.json b/public/locales/hu/agent.json index 528007f5..339c2020 100644 --- a/public/locales/hu/agent.json +++ b/public/locales/hu/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Kérjük, állítsa be OpenAI API-kulcsát a beállítások menüből.", "ALL_TASKS_COMPLETED_TOAST": "Minden feladat teljesítve!", "COPIED_TO_CLIPBOARD": "Vágólapra másolva", - "TASK_COMPLETED_TOAST": "Feladat elvégezve!" + "TASK_COMPLETED_TOAST": "Feladat elvégezve!", + "EXECUTION_STOPPED": "Végrehajtás leállt" } \ No newline at end of file diff --git a/public/locales/id/agent.json b/public/locales/id/agent.json index 61f1a522..ad73ba01 100644 --- a/public/locales/id/agent.json +++ b/public/locales/id/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Silakan atur kunci API OpenAI Anda dari menu pengaturan.", "ALL_TASKS_COMPLETED_TOAST": "Semua Tugas Selesai!", "COPIED_TO_CLIPBOARD": "Disalin ke papan klip", - "TASK_COMPLETED_TOAST": "Tugas selesai!" + "TASK_COMPLETED_TOAST": "Tugas selesai!", + "EXECUTION_STOPPED": "Pembatasan eksekusi" } \ No newline at end of file diff --git a/public/locales/it/agent.json b/public/locales/it/agent.json index 1baa8c0c..4b8d4f12 100644 --- a/public/locales/it/agent.json +++ b/public/locales/it/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Configura la tua chiave API OpenAI dal menu delle impostazioni.", "ALL_TASKS_COMPLETED_TOAST": "Tutte le attività completate!", "COPIED_TO_CLIPBOARD": "Copiato negli appunti", - "TASK_COMPLETED_TOAST": "Compito completato!" + "TASK_COMPLETED_TOAST": "Compito completato!", + "EXECUTION_STOPPED": "Esecuzione interrotta" } \ No newline at end of file diff --git a/public/locales/ja/agent.json b/public/locales/ja/agent.json index 9f72333d..6b1c2cfb 100644 --- a/public/locales/ja/agent.json +++ b/public/locales/ja/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "設定メニューから OpenAI API キーを設定してください", "ALL_TASKS_COMPLETED_TOAST": "すべてのタスクが完了しました", "COPIED_TO_CLIPBOARD": "クリップボードにコピーされました", - "TASK_COMPLETED_TOAST": "タスクが完了しました" -} + "TASK_COMPLETED_TOAST": "タスクが完了しました", + "EXECUTION_STOPPED": "実行が停止しました" +} \ No newline at end of file diff --git a/public/locales/kn/agent.json b/public/locales/kn/agent.json index 962fcde4..8ac6c400 100644 --- a/public/locales/kn/agent.json +++ b/public/locales/kn/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "ದಯವಿಟ್ಟು ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೆನುವಿನಿಂದ ನಿಮ್ಮ OpenAI API ಕೀಯನ್ನು ಹೊಂದಿಸಿ.", "ALL_TASKS_COMPLETED_TOAST": "ಎಲ್ಲಾ ಕಾರ್ಯಗಳು ಪೂರ್ಣಗೊಂಡಿವೆ!", "COPIED_TO_CLIPBOARD": "ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಲಾಗಿದೆ", - "TASK_COMPLETED_TOAST": "ಕಾರ್ಯ ಪೂರ್ಣಗೊಂಡಿದೆ!" + "TASK_COMPLETED_TOAST": "ಕಾರ್ಯ ಪೂರ್ಣಗೊಂಡಿದೆ!", + "EXECUTION_STOPPED": "ನೆಲಸಲು ನಿಲ್ಲಿಸಲಾಗಿದೆ" } \ No newline at end of file diff --git a/public/locales/ko/agent.json b/public/locales/ko/agent.json index 20e585c8..bd5a373e 100644 --- a/public/locales/ko/agent.json +++ b/public/locales/ko/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "설정 메뉴에서 OpenAI API 키를 설정하세요.", "ALL_TASKS_COMPLETED_TOAST": "모든 작업 완료!", "COPIED_TO_CLIPBOARD": "클립보드에 복사됨", - "TASK_COMPLETED_TOAST": "작업 완료!" + "TASK_COMPLETED_TOAST": "작업 완료!", + "EXECUTION_STOPPED": "실행 중지" } \ No newline at end of file diff --git a/public/locales/lt/agent.json b/public/locales/lt/agent.json index 2c7244fb..61dd3e3b 100644 --- a/public/locales/lt/agent.json +++ b/public/locales/lt/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Nustatymų meniu nustatykite OpenAI API raktą.", "ALL_TASKS_COMPLETED_TOAST": "Visos užduotys baigtos!", "COPIED_TO_CLIPBOARD": "Nukopijuota į mainų sritį", - "TASK_COMPLETED_TOAST": "Užduotis atlikta!" + "TASK_COMPLETED_TOAST": "Užduotis atlikta!", + "EXECUTION_STOPPED": "Vykdomasis sustabdytas" } \ No newline at end of file diff --git a/public/locales/lv/agent.json b/public/locales/lv/agent.json index a8790d20..79eb4e74 100644 --- a/public/locales/lv/agent.json +++ b/public/locales/lv/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Lūdzu, iestatiet savu OpenAI API atslēgu iestatījumu izvēlnē.", "ALL_TASKS_COMPLETED_TOAST": "Visi uzdevumi izpildīti!", "COPIED_TO_CLIPBOARD": "Kopēts starpliktuvē", - "TASK_COMPLETED_TOAST": "Uzdevums izpildīts!" + "TASK_COMPLETED_TOAST": "Uzdevums izpildīts!", + "EXECUTION_STOPPED": "Izp" } \ No newline at end of file diff --git a/public/locales/ml/agent.json b/public/locales/ml/agent.json index 1d3b9851..8c4a554c 100644 --- a/public/locales/ml/agent.json +++ b/public/locales/ml/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "ക്രമീകരണ മെനുവിൽ നിന്ന് നിങ്ങളുടെ OpenAI API കീ സജ്ജീകരിക്കുക.", "ALL_TASKS_COMPLETED_TOAST": "എല്ലാ ജോലികളും പൂർത്തിയായി!", "COPIED_TO_CLIPBOARD": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി", - "TASK_COMPLETED_TOAST": "ടാസ്ക് പൂർത്തിയായി!" + "TASK_COMPLETED_TOAST": "ടാസ്ക് പൂർത്തിയായി!", + "EXECUTION_STOPPED": "നിഷ്പത്തി നിർത്തി." } \ No newline at end of file diff --git a/public/locales/nl/agent.json b/public/locales/nl/agent.json index 00e2c615..02a72418 100644 --- a/public/locales/nl/agent.json +++ b/public/locales/nl/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Stel uw OpenAI API-sleutel in via het instellingenmenu.", "ALL_TASKS_COMPLETED_TOAST": "Alle taken voltooid!", "COPIED_TO_CLIPBOARD": "Gekopieerd naar het klembord", - "TASK_COMPLETED_TOAST": "Taak volbracht!" + "TASK_COMPLETED_TOAST": "Taak volbracht!", + "EXECUTION_STOPPED": "Uitvoering gestopt" } \ No newline at end of file diff --git a/public/locales/no/agent.json b/public/locales/no/agent.json index bdbbc0ba..4d40b874 100644 --- a/public/locales/no/agent.json +++ b/public/locales/no/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Sett opp OpenAI API-nøkkelen fra innstillingsmenyen.", "ALL_TASKS_COMPLETED_TOAST": "Alle oppgaver fullført!", "COPIED_TO_CLIPBOARD": "Kopiert til utklippstavlen", - "TASK_COMPLETED_TOAST": "Oppgave fullført!" + "TASK_COMPLETED_TOAST": "Oppgave fullført!", + "EXECUTION_STOPPED": "Utførelsen stoppet" } \ No newline at end of file diff --git a/public/locales/pl/agent.json b/public/locales/pl/agent.json index d4e16cc8..b453a97f 100644 --- a/public/locales/pl/agent.json +++ b/public/locales/pl/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Skonfiguruj swój klucz API OpenAI w menu ustawień.", "ALL_TASKS_COMPLETED_TOAST": "Wszystkie zadania wykonane!", "COPIED_TO_CLIPBOARD": "Skopiowane do schowka", - "TASK_COMPLETED_TOAST": "Zadanie ukończone!" + "TASK_COMPLETED_TOAST": "Zadanie ukończone!", + "EXECUTION_STOPPED": "Wykonanie zatrzymane" } \ No newline at end of file diff --git a/public/locales/pt/agent.json b/public/locales/pt/agent.json index 68d9f580..589f5808 100644 --- a/public/locales/pt/agent.json +++ b/public/locales/pt/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Configure sua chave de API OpenAI no menu de configurações.", "ALL_TASKS_COMPLETED_TOAST": "Todas as tarefas concluídas!", "COPIED_TO_CLIPBOARD": "Copiado para a área de transferência", - "TASK_COMPLETED_TOAST": "Tarefa completa!" + "TASK_COMPLETED_TOAST": "Tarefa completa!", + "EXECUTION_STOPPED": "Execução interrompida" } \ No newline at end of file diff --git a/public/locales/ro/agent.json b/public/locales/ro/agent.json index 52114471..e11f5a42 100644 --- a/public/locales/ro/agent.json +++ b/public/locales/ro/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Vă rugăm să vă configurați cheia API OpenAI din meniul de setări.", "ALL_TASKS_COMPLETED_TOAST": "Toate sarcinile finalizate!", "COPIED_TO_CLIPBOARD": "Copiat în clipboard", - "TASK_COMPLETED_TOAST": "Sarcina finalizată!" + "TASK_COMPLETED_TOAST": "Sarcina finalizată!", + "EXECUTION_STOPPED": "Execuția s-a oprit" } \ No newline at end of file diff --git a/public/locales/ru/agent.json b/public/locales/ru/agent.json index 37aff535..e6b9097e 100644 --- a/public/locales/ru/agent.json +++ b/public/locales/ru/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Настройте свой ключ API OpenAI в меню настроек.", "ALL_TASKS_COMPLETED_TOAST": "Все задания выполнены!", "COPIED_TO_CLIPBOARD": "Скопировано в буфер обмена", - "TASK_COMPLETED_TOAST": "Задача выполнена!" + "TASK_COMPLETED_TOAST": "Задача выполнена!", + "EXECUTION_STOPPED": "Исполнение остановлено" } \ No newline at end of file diff --git a/public/locales/sk/agent.json b/public/locales/sk/agent.json index acc7d36a..ca5597c0 100644 --- a/public/locales/sk/agent.json +++ b/public/locales/sk/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Nastavte svoj kľúč OpenAI API z ponuky nastavení.", "ALL_TASKS_COMPLETED_TOAST": "Všetky úlohy dokončené!", "COPIED_TO_CLIPBOARD": "Skopírované do schránky", - "TASK_COMPLETED_TOAST": "Úloha dokončená!" + "TASK_COMPLETED_TOAST": "Úloha dokončená!", + "EXECUTION_STOPPED": "Vykonanie zastavené" } \ No newline at end of file diff --git a/public/locales/sl/agent.json b/public/locales/sl/agent.json index 865b2d91..c99a158f 100644 --- a/public/locales/sl/agent.json +++ b/public/locales/sl/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "V meniju z nastavitvami nastavite ključ OpenAI API.", "ALL_TASKS_COMPLETED_TOAST": "Vse naloge opravljene!", "COPIED_TO_CLIPBOARD": "Kopirano v odložišče", - "TASK_COMPLETED_TOAST": "Naloga opravljena!" + "TASK_COMPLETED_TOAST": "Naloga opravljena!", + "EXECUTION_STOPPED": "Izvedba je bila ustavljena." } \ No newline at end of file diff --git a/public/locales/sr/agent.json b/public/locales/sr/agent.json index 3b835439..0cfe3174 100644 --- a/public/locales/sr/agent.json +++ b/public/locales/sr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Подесите свој ОпенАИ АПИ кључ из менија подешавања.", "ALL_TASKS_COMPLETED_TOAST": "Сви задаци завршени!", "COPIED_TO_CLIPBOARD": "Копирано у међуспремник", - "TASK_COMPLETED_TOAST": "Задатак завршен!" + "TASK_COMPLETED_TOAST": "Задатак завршен!", + "EXECUTION_STOPPED": "Izvršenje je zaustavljeno" } \ No newline at end of file diff --git a/public/locales/sv/agent.json b/public/locales/sv/agent.json index b1e6cab2..b93bdfbc 100644 --- a/public/locales/sv/agent.json +++ b/public/locales/sv/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Vänligen ställ in din OpenAI API-nyckel från inställningsmenyn.", "ALL_TASKS_COMPLETED_TOAST": "Alla uppgifter slutförda!", "COPIED_TO_CLIPBOARD": "Kopierade till urklipp", - "TASK_COMPLETED_TOAST": "Uppgift slutförd!" + "TASK_COMPLETED_TOAST": "Uppgift slutförd!", + "EXECUTION_STOPPED": "Utförande avbrutet." } \ No newline at end of file diff --git a/public/locales/ta/agent.json b/public/locales/ta/agent.json index 120f18e5..a0222567 100644 --- a/public/locales/ta/agent.json +++ b/public/locales/ta/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "அமைப்புகள் மெனுவிலிருந்து உங்கள் OpenAI API விசையை அமைக்கவும்.", "ALL_TASKS_COMPLETED_TOAST": "அனைத்து பணிகளும் முடிந்தது!", "COPIED_TO_CLIPBOARD": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது", - "TASK_COMPLETED_TOAST": "பணி முடிந்தது!" + "TASK_COMPLETED_TOAST": "பணி முடிந்தது!", + "EXECUTION_STOPPED": "மேற்படுக்கை நிறுத்தப்பட்டது" } \ No newline at end of file diff --git a/public/locales/te/agent.json b/public/locales/te/agent.json index f7575491..72c86f33 100644 --- a/public/locales/te/agent.json +++ b/public/locales/te/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "దయచేసి సెట్టింగ్‌ల మెను నుండి మీ OpenAI API కీని సెటప్ చేయండి.", "ALL_TASKS_COMPLETED_TOAST": "అన్ని పనులు పూర్తయ్యాయి!", "COPIED_TO_CLIPBOARD": "క్లిప్‌బోర్డ్‌కి కాపీ చేయబడింది", - "TASK_COMPLETED_TOAST": "పని పూర్తయింది!" + "TASK_COMPLETED_TOAST": "పని పూర్తయింది!", + "EXECUTION_STOPPED": "నిష్పత్తి నిలిపివేయబడింది" } \ No newline at end of file diff --git a/public/locales/th/agent.json b/public/locales/th/agent.json index 839c954a..b3598766 100644 --- a/public/locales/th/agent.json +++ b/public/locales/th/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "โปรดตั้งค่าคีย์ OpenAI API ของคุณจากเมนูการตั้งค่า", "ALL_TASKS_COMPLETED_TOAST": "เสร็จสิ้นภารกิจทั้งหมด!", "COPIED_TO_CLIPBOARD": "คัดลอกไปที่คลิปบอร์ดแล้ว", - "TASK_COMPLETED_TOAST": "ภารกิจเสร็จสิ้น!" + "TASK_COMPLETED_TOAST": "ภารกิจเสร็จสิ้น!", + "EXECUTION_STOPPED": "การดำเนินการถูกระงับ" } \ No newline at end of file diff --git a/public/locales/tr/agent.json b/public/locales/tr/agent.json index d23a5953..ec8d1b25 100644 --- a/public/locales/tr/agent.json +++ b/public/locales/tr/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Lütfen ayarlar menüsünden OpenAI API anahtarınızı ayarlayın.", "ALL_TASKS_COMPLETED_TOAST": "Tüm Görevler Tamamlandı!", "COPIED_TO_CLIPBOARD": "Panoya kopyalandı", - "TASK_COMPLETED_TOAST": "Görev tamamlandı!" + "TASK_COMPLETED_TOAST": "Görev tamamlandı!", + "EXECUTION_STOPPED": "İşlem durdu" } \ No newline at end of file diff --git a/public/locales/uk/agent.json b/public/locales/uk/agent.json index 2dd4a827..9f7909a0 100644 --- a/public/locales/uk/agent.json +++ b/public/locales/uk/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Налаштуйте ключ OpenAI API у меню налаштувань.", "ALL_TASKS_COMPLETED_TOAST": "Усі завдання виконано!", "COPIED_TO_CLIPBOARD": "Скопійовано в буфер обміну", - "TASK_COMPLETED_TOAST": "Завдання виконано!" + "TASK_COMPLETED_TOAST": "Завдання виконано!", + "EXECUTION_STOPPED": "Виконання припинено" } \ No newline at end of file diff --git a/public/locales/ur/agent.json b/public/locales/ur/agent.json index 20d1b90c..c95db984 100644 --- a/public/locales/ur/agent.json +++ b/public/locales/ur/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "براہ کرم ترتیبات کے مینو سے اپنی OpenAI API کلید ترتیب دیں۔", "ALL_TASKS_COMPLETED_TOAST": "تمام کام مکمل!", "COPIED_TO_CLIPBOARD": "کلپ بورڈ پر کاپی ہو گیا۔", - "TASK_COMPLETED_TOAST": "کام مکمل ہو گیا!" + "TASK_COMPLETED_TOAST": "کام مکمل ہو گیا!", + "EXECUTION_STOPPED": "تنفیذ روک دیا گیا" } \ No newline at end of file diff --git a/public/locales/vi/agent.json b/public/locales/vi/agent.json index e9b92806..b8874573 100644 --- a/public/locales/vi/agent.json +++ b/public/locales/vi/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "Vui lòng thiết lập khóa API OpenAI của bạn từ menu cài đặt.", "ALL_TASKS_COMPLETED_TOAST": "Tất cả các nhiệm vụ đã hoàn thành!", "COPIED_TO_CLIPBOARD": "Sao chép vào clipboard", - "TASK_COMPLETED_TOAST": "Nhiệm vụ hoàn thành!" + "TASK_COMPLETED_TOAST": "Nhiệm vụ hoàn thành!", + "EXECUTION_STOPPED": "Thực hiện đã dừng lại" } \ No newline at end of file diff --git a/public/locales/zh/agent.json b/public/locales/zh/agent.json index 34900be5..8e2925c9 100644 --- a/public/locales/zh/agent.json +++ b/public/locales/zh/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "请从设置菜单中设置您的 OpenAI API 密钥。", "ALL_TASKS_COMPLETED_TOAST": "所有任务完成!", "COPIED_TO_CLIPBOARD": "复制到剪贴板", - "TASK_COMPLETED_TOAST": "任务完成!" + "TASK_COMPLETED_TOAST": "任务完成!", + "EXECUTION_STOPPED": "执行已停止" } \ No newline at end of file diff --git a/public/locales/zhtw/agent.json b/public/locales/zhtw/agent.json index 5e0b1bb0..4b11a236 100644 --- a/public/locales/zhtw/agent.json +++ b/public/locales/zhtw/agent.json @@ -2,5 +2,6 @@ "ALERT_SET_UP_API_KEY": "請從設置菜單中設置您的OpenAI API密鑰。", "ALL_TASKS_COMPLETED_TOAST": "所有任務已完成!", "COPIED_TO_CLIPBOARD": "已複製到剪貼簿。", - "TASK_COMPLETED_TOAST": "任務已完成!" + "TASK_COMPLETED_TOAST": "任務已完成!", + "EXECUTION_STOPPED": "執行停止了" } \ No newline at end of file From e4c7a4abe8baf13aec81972114612e4d3dcd46fe Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 19 Aug 2023 11:49:28 +0900 Subject: [PATCH 28/42] Add ExamplePage component to project --- src/pages/example/index.tsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/pages/example/index.tsx diff --git a/src/pages/example/index.tsx b/src/pages/example/index.tsx new file mode 100644 index 00000000..b6681c83 --- /dev/null +++ b/src/pages/example/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useAgent } from '@/hooks/useAgent'; +const ExamplePage: React.FC = () => { + const { agentMessages, input, handleSubmit, handleInputChange } = useAgent(); + return ( +
+ {agentMessages.map((m) => ( +
+ {m.title && ( +
+ {m.icon}: {m.title} +
+ )} +
{m.content}
+
+ ))} +
+ +
+
+ ); +}; + +export default ExamplePage; From 84d59406f249f70156a6a4a1292f9b75808cba9d Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 19 Aug 2023 12:00:26 +0900 Subject: [PATCH 29/42] Refactor code to include API endpoint in useAgent hook --- src/pages/example/index.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/example/index.tsx b/src/pages/example/index.tsx index b6681c83..fde8a82b 100644 --- a/src/pages/example/index.tsx +++ b/src/pages/example/index.tsx @@ -1,9 +1,18 @@ import React from 'react'; import { useAgent } from '@/hooks/useAgent'; const ExamplePage: React.FC = () => { - const { agentMessages, input, handleSubmit, handleInputChange } = useAgent(); + const { agentMessages, input, handleSubmit, handleInputChange } = useAgent({ + api: '/api/agent', + }); return (
+
+ +
{agentMessages.map((m) => (
{m.title && ( @@ -14,13 +23,6 @@ const ExamplePage: React.FC = () => {
{m.content}
))} -
- -
); }; From 8ad90f06f4a623850642cc8a54c3ed5862c4e1b1 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Sat, 19 Aug 2023 12:20:33 +0900 Subject: [PATCH 30/42] Refactor agent view and useAgent hooks to add verbose option --- src/components/Agent/AgentView.tsx | 1 + src/hooks/useAgent.ts | 4 +++- src/pages/index.tsx | 5 ----- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/Agent/AgentView.tsx b/src/components/Agent/AgentView.tsx index abe97498..b4b3140a 100644 --- a/src/components/Agent/AgentView.tsx +++ b/src/components/Agent/AgentView.tsx @@ -127,6 +127,7 @@ export const AgentView: FC = () => { api: '/api/agent', agentId: selectedAgent.id, modelName: model.id, + verbose: false, onSubmit: startHandler, onCancel: stopHandler, onFinish: finishHandler, diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index d03203b6..b3701070 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -13,6 +13,7 @@ export type UseAgentOptions = { onFinish?: () => void; onSubmit?: () => void; onCancel?: () => void; + verbose?: boolean; }; export type UseAgentHelpers = { @@ -42,6 +43,7 @@ export function useAgent({ onFinish, onSubmit, onCancel, + verbose = false, }: UseAgentOptions = {}): UseAgentHelpers { const abortControllerRef = useRef(null); const [agentMessages, setAgentMessages] = useState([]); @@ -72,7 +74,7 @@ export function useAgent({ model_name: modelName, language, user_key: userKey, - verbose: true, + verbose, }), signal: abortController.signal, }); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 9b7ac1bf..9bb67db9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -43,11 +43,6 @@ function Home() { saveSidebarState(!showSidebar); }; - // This page is for development only. - if (process.env.NODE_ENV !== 'development') { - return

This page is for development.

; - } - return ( <> From f3f2849095c4e7a1f4df36c9e12f7ddb8f497e68 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 12:16:48 +0900 Subject: [PATCH 31/42] Refactor code for printing task execution with skill information --- .../babyelfagi/skills/addons/youtubeSearch.ts | 1 - .../babyelfagi/skills/presets/codeWriter.ts | 2 +- .../babyelfagi/skills/presets/webLoader.ts | 4 ---- src/agents/babyelfagi/skills/skill.ts | 3 --- src/agents/babyelfagi/tools/webBrowsing.ts | 3 --- src/components/Agent/TaskBlock.tsx | 21 +++++++++++-------- src/hooks/useSkills.tsx | 2 +- src/pages/example/index.tsx | 7 +------ src/utils/message.ts | 4 ++++ src/utils/print.ts | 8 +++++++ 10 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/agents/babyelfagi/skills/addons/youtubeSearch.ts b/src/agents/babyelfagi/skills/addons/youtubeSearch.ts index 596ed689..ff8a0ecb 100644 --- a/src/agents/babyelfagi/skills/addons/youtubeSearch.ts +++ b/src/agents/babyelfagi/skills/addons/youtubeSearch.ts @@ -27,7 +27,6 @@ export class YoutubeSearch extends Skill { this.callbackMessage({ taskId: task.id.toString(), content: '```json\n\n' + result + '\n\n```', - title: task.task, type: task.skill, style: 'text', status: 'complete', diff --git a/src/agents/babyelfagi/skills/presets/codeWriter.ts b/src/agents/babyelfagi/skills/presets/codeWriter.ts index 483d269c..4aafec87 100644 --- a/src/agents/babyelfagi/skills/presets/codeWriter.ts +++ b/src/agents/babyelfagi/skills/presets/codeWriter.ts @@ -32,7 +32,7 @@ export class CodeWriter extends Skill { } const prompt = ` - You are a genius AI programmer. + You are a genius AI programmer. Complete your assigned task based on the objective and only based on information provided in the dependent task output, if provided. Dependent tasks output include reference code. Your objective: ${objective}. diff --git a/src/agents/babyelfagi/skills/presets/webLoader.ts b/src/agents/babyelfagi/skills/presets/webLoader.ts index 17074aec..3e0cdf76 100644 --- a/src/agents/babyelfagi/skills/presets/webLoader.ts +++ b/src/agents/babyelfagi/skills/presets/webLoader.ts @@ -26,8 +26,6 @@ export class WebLoader extends Skill { id: this.id, taskId: task.id.toString(), content: message, - title: task.task, - icon: this.icon, type: this.name, style: 'log', status, @@ -43,8 +41,6 @@ export class WebLoader extends Skill { id: uuidv4(), taskId: task.id.toString(), content: info.join('\n\n'), - title: task.task, - icon: this.icon, type: this.name, style: 'text', status: 'complete', diff --git a/src/agents/babyelfagi/skills/skill.ts b/src/agents/babyelfagi/skills/skill.ts index fe44da06..d787871f 100644 --- a/src/agents/babyelfagi/skills/skill.ts +++ b/src/agents/babyelfagi/skills/skill.ts @@ -118,9 +118,7 @@ export class Skill { const message: AgentMessage = { id, content: '', - title: task.task, type: task.skill, - icon: task.icon, taskId: task.id.toString(), status: 'complete', options: { @@ -162,7 +160,6 @@ export class Skill { const baseMessage: AgentMessage = { id: this.id, content: '', - icon: this.icon, type: this.name, style: 'text', status: 'running', diff --git a/src/agents/babyelfagi/tools/webBrowsing.ts b/src/agents/babyelfagi/tools/webBrowsing.ts index 6a8918f3..ab62a9b1 100644 --- a/src/agents/babyelfagi/tools/webBrowsing.ts +++ b/src/agents/babyelfagi/tools/webBrowsing.ts @@ -125,7 +125,6 @@ export const webBrowsing = async ( taskId: task.id.toString(), type: task.skill, content: message, - title: task.task, style: 'log', status: 'complete', }; @@ -149,10 +148,8 @@ const callbackSearchStatus = ( id, taskId: task.id.toString(), type: task.skill, - icon: '🔎', style: 'log', content: message, - title: task.task, status: 'running', }); }; diff --git a/src/components/Agent/TaskBlock.tsx b/src/components/Agent/TaskBlock.tsx index 04085fe3..d463e837 100644 --- a/src/components/Agent/TaskBlock.tsx +++ b/src/components/Agent/TaskBlock.tsx @@ -1,5 +1,6 @@ -import { Block, AgentMessage } from '@/types'; import { FC } from 'react'; +import { Block, AgentMessage } from '@/types'; +import { useSkills } from '@/hooks'; import { AgentResult } from './AgentResult'; import { AgentTaskStatus } from './AgentTastStatus'; import { getEmoji } from '@/utils/message'; @@ -15,11 +16,11 @@ const renderIcon = (message: AgentMessage, block: Block) => { : message.icon || getEmoji(message.type); }; -const renderContent = (message: AgentMessage, firstContent: string) => { +const renderContent = (message: AgentMessage) => { const title = message.status === 'complete' - ? firstContent.split('\n')[0] - : firstContent.trim().split('\n').slice(-1)[0]; + ? message.content.split('\n')[0] + : message.content.trim().split('\n').slice(-1)[0]; return message.style === 'log' ? (
@@ -35,12 +36,14 @@ const renderContent = (message: AgentMessage, firstContent: string) => { export const TaskBlock: FC = ({ block }) => { const message = block.messages[0]; - const icon = message.icon || getEmoji(message.type); const title = message.taskId ? `${message.taskId}. ${message.title}` : message.title; const dependentTaskIds = message?.options?.dependentTaskIds ?? ''; - const firstContent = block.messages[0].content; + const icon = message.icon || getEmoji(message.type); + const renderMessages = block.messages.filter( + (message) => message.content !== '', + ); return (
@@ -54,19 +57,19 @@ export const TaskBlock: FC = ({ block }) => {
- {block.messages.length > 0 && ( + {renderMessages.length > 0 && ( - {block.messages.map((message, index) => ( + {renderMessages.map((message, index) => (
{renderIcon(message, block)}
-
{renderContent(message, firstContent)}
+
{renderContent(message)}
))} diff --git a/src/hooks/useSkills.tsx b/src/hooks/useSkills.tsx index 3a8821e0..1ecb7f61 100644 --- a/src/hooks/useSkills.tsx +++ b/src/hooks/useSkills.tsx @@ -9,7 +9,7 @@ type SkillInfo = { badge: string; }; -export const useSkills = (selectedAgentId: string) => { +export const useSkills = (selectedAgentId?: string) => { const [skillInfos, setSkillInfos] = useState([]); useEffect(() => { diff --git a/src/pages/example/index.tsx b/src/pages/example/index.tsx index fde8a82b..2f2057ce 100644 --- a/src/pages/example/index.tsx +++ b/src/pages/example/index.tsx @@ -15,12 +15,7 @@ const ExamplePage: React.FC = () => { {agentMessages.map((m) => (
- {m.title && ( -
- {m.icon}: {m.title} -
- )} -
{m.content}
+ {m.type}: {m.content}
))}
diff --git a/src/utils/message.ts b/src/utils/message.ts index a31329d3..e74bbda0 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -369,6 +369,10 @@ export const getEmoji = (type?: string) => { } }; +export const getSkillEmoji = (skill?: string) => { + const skills = useSkills(); +}; + export const getTitle = (type?: string) => { switch (type) { case 'objective': diff --git a/src/utils/print.ts b/src/utils/print.ts index 79faefea..6e68ddf8 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -30,6 +30,14 @@ export class Printer { } printTaskExecute(task: AgentTask) { + this.handleMessage({ + taskId: task.id.toString(), + content: '', + type: task.skill, + title: task.task, + icon: task.icon || '', + }); + if (!this.verbose) return; console.log('\x1b[35m%s\x1b[0m', '*****NEXT TASK*****\n'); console.log(task); From 0eb29a273b6e4bdf0ec19c15338ce8a03c32a203 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 12:18:50 +0900 Subject: [PATCH 32/42] Remove unused function getSkillEmoji --- src/utils/message.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/message.ts b/src/utils/message.ts index e74bbda0..a31329d3 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -369,10 +369,6 @@ export const getEmoji = (type?: string) => { } }; -export const getSkillEmoji = (skill?: string) => { - const skills = useSkills(); -}; - export const getTitle = (type?: string) => { switch (type) { case 'objective': From 12967a82cde7e27199feee1a48c717f52a137d1d Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 15:14:46 +0900 Subject: [PATCH 33/42] Refactor error handling in useAgent and validate environment variables in handler --- src/hooks/useAgent.ts | 13 ++------ src/hooks/useErrorHandler.ts | 4 +-- src/pages/api/agent/index.ts | 59 +++++++++++++++++++++++------------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index b3701070..bdf4a1c4 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -9,7 +9,7 @@ export type UseAgentOptions = { agentId?: string; modelName?: string; onResponse?: (event: MessageEvent) => void; - onError?: (event: Event | ErrorEvent) => void; + onError?: (error: Error) => void; onFinish?: () => void; onSubmit?: () => void; onCancel?: () => void; @@ -142,11 +142,7 @@ export function useAgent({ if (error instanceof Error && error.name === 'AbortError') { // ignore } else { - onError( - new ErrorEvent('error', { - error: error instanceof Error ? error.message : 'Unknown error', - }), - ); + onError(error as Error); } } } @@ -173,11 +169,6 @@ export function useAgent({ try { await sendRequest(abortController); - } catch (error) { - // Call onError when an error occurs - if (onError) { - onError(new ErrorEvent('error', { error })); - } } finally { setIsRunning(false); } diff --git a/src/hooks/useErrorHandler.ts b/src/hooks/useErrorHandler.ts index 10a5ccc3..508222e6 100644 --- a/src/hooks/useErrorHandler.ts +++ b/src/hooks/useErrorHandler.ts @@ -2,9 +2,9 @@ import va from '@vercel/analytics'; import { toast } from 'sonner'; export const useErrorHandler = () => { - const errorHandler = (error: Event | ErrorEvent) => { + const errorHandler = (error: Error) => { const errorMessage = - error instanceof ErrorEvent ? error.message : 'Unknown Error'; + error instanceof Error ? error.message : 'Unknown Error'; toast.error(errorMessage); va.track('Error', { error: errorMessage }); }; diff --git a/src/pages/api/agent/index.ts b/src/pages/api/agent/index.ts index c9aad562..f3dfa5d5 100644 --- a/src/pages/api/agent/index.ts +++ b/src/pages/api/agent/index.ts @@ -1,4 +1,4 @@ -import type { NextRequest } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; import { StreamingTextResponse } from 'ai'; import { AgentStream } from '@/agents/base/AgentStream'; import { BabyElfAGI } from '@/agents/babyelfagi/executer'; @@ -8,29 +8,17 @@ export const config = { runtime: 'edge', }; -export default async function handler(req: NextRequest) { +export default async function handler(req: NextRequest, res: NextResponse) { const { stream, handlers, addObserver } = AgentStream(); + const requestData = await req.json(); const { input, agent_id, model_name, language, verbose, user_key } = - await req.json(); + requestData; - if ( - process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true' && - (user_key === undefined || user_key === null || !user_key.startsWith('sk-')) - ) { - throw new Error('Invalid user key'); - } - - const specifiedSkills = agent_id === 'babydeeragi' ? SPECIFIED_SKILLS : []; - - // req.signal is not working, so we use AbortController. - const abortController = new AbortController(); - const signal = abortController.signal; - - // Add an observer to abort the request when the client disconnects. - addObserver((isActive) => { - if (!isActive) abortController.abort(); - }); + validateEnvironmentVariables(); + validateUserKey(user_key); + const specifiedSkills = getSpecifiedSkills(agent_id); + const abortController = setupAbortController(addObserver); const executer = new BabyElfAGI( input, model_name, @@ -39,9 +27,38 @@ export default async function handler(req: NextRequest) { verbose, specifiedSkills, user_key, - signal, + abortController.signal, ); executer.run(); return new StreamingTextResponse(stream); } + +function validateEnvironmentVariables() { + if (!process.env.BASE_URL) { + throw new Error('You must set BASE_URL environment variable'); + } +} + +function validateUserKey(user_key: string) { + if ( + process.env.NEXT_PUBLIC_USE_USER_API_KEY === 'true' && + (user_key === undefined || user_key === null || !user_key.startsWith('sk-')) + ) { + throw new Error('Invalid user key'); + } +} + +function getSpecifiedSkills(agent_id: string) { + return agent_id === 'babydeeragi' ? SPECIFIED_SKILLS : []; +} + +function setupAbortController(addObserver: Function) { + const abortController = new AbortController(); + + addObserver((isActive: boolean) => { + if (!isActive) abortController.abort(); + }); + + return abortController; +} From 273374808ef48d3ecba06e4de458c78050974460 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 15:27:36 +0900 Subject: [PATCH 34/42] Refactor message parsing logic in useAgent.ts --- src/hooks/useAgent.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index b3701070..711cfcf3 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -80,6 +80,7 @@ export function useAgent({ }); const reader = response.body?.getReader(); + let partialMessage; if (reader) { while (true) { @@ -90,11 +91,28 @@ export function useAgent({ } if (value) { - const message = new TextDecoder().decode(value); - const newAgentMessages: AgentMessage[] = message + const decodedValue = new TextDecoder().decode(value); + const splitMessages = decodedValue.split('\n\n'); + + if (partialMessage && !splitMessages[0].startsWith('{"message":')) { + splitMessages[0] = partialMessage + splitMessages[0]; + partialMessage = null; + } + + if (!decodedValue.endsWith('\n\n')) { + partialMessage = splitMessages.pop() || ''; + } + if (splitMessages.length === 0) { + continue; + } + + let combinedMessage = splitMessages.join('\n\n'); + + const newAgentMessages: AgentMessage[] = combinedMessage .trim() - .split('\n') - .map((m) => parseMessage(m)); + .split('\n\n') + .map(parseMessage) + .filter((m): m is AgentMessage => m !== null); newAgentMessages.forEach((newMsg) => { if (newMsg.id) { From 14163aedcd07690f2065dbe907390f27323d461c Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 15:27:55 +0900 Subject: [PATCH 35/42] Set BASE_URL for production --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 27abccbd..99140160 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,9 @@ NEXT_PUBLIC_USE_USER_API_KEY="false" # GOOGLE_SEARCH_API_KEY="Your Google Search API Key" # GOOGLE_CUSTOM_INDEX_ID="Your Custom Index ID" +# 7. Set your BASE_URL, only used for production +BASE_URL="https://your-project.vercel.app/" + # if you want to use the auto-translation feature, set the following variables # and npm run translate TRANSLATOR_SERVICE="google" # possible values: "google", "openai" From ce813798a00c6b1a498b2d544274b5d77e4e4265 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 15:31:42 +0900 Subject: [PATCH 36/42] Decode and process incoming messages from the agent --- src/hooks/useAgent.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hooks/useAgent.ts b/src/hooks/useAgent.ts index 711cfcf3..862c6b2a 100644 --- a/src/hooks/useAgent.ts +++ b/src/hooks/useAgent.ts @@ -91,29 +91,39 @@ export function useAgent({ } if (value) { + // Decode the value using TextDecoder const decodedValue = new TextDecoder().decode(value); + // Split the decoded value into messages const splitMessages = decodedValue.split('\n\n'); + // If there is a partial message and the first split message does not start with '{"message":' + // then prepend the partial message to the first split message if (partialMessage && !splitMessages[0].startsWith('{"message":')) { splitMessages[0] = partialMessage + splitMessages[0]; partialMessage = null; } + // If the decoded value does not end with '\n\n', then the last message is partial + // So, pop the last message and store it as partial message if (!decodedValue.endsWith('\n\n')) { partialMessage = splitMessages.pop() || ''; } + // If there are no split messages, then continue to the next iteration if (splitMessages.length === 0) { continue; } + // Combine the split messages into a single message let combinedMessage = splitMessages.join('\n\n'); + // Parse the combined message const newAgentMessages: AgentMessage[] = combinedMessage .trim() .split('\n\n') .map(parseMessage) .filter((m): m is AgentMessage => m !== null); + // Update the message map newAgentMessages.forEach((newMsg) => { if (newMsg.id) { const existingMsg = messageMap.current.get(newMsg.id); @@ -131,6 +141,7 @@ export function useAgent({ } }); + // Update the agent messages state const updatedNewMessages = Array.from(messageMap.current.values()); setAgentMessages(updatedNewMessages); From 606565dff848c312631ce5b8075531f2e9261dbc Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 15:35:52 +0900 Subject: [PATCH 37/42] Refactor parseMessage function to handle error and return null --- src/utils/message.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/utils/message.ts b/src/utils/message.ts index a31329d3..583e1a6c 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -340,14 +340,22 @@ export const getMessageBlocks = ( return messageBlocks; }; -export const parseMessage = (json: string): AgentMessage => { - const message = JSON.parse(json).message as AgentMessage; - - return { - ...message, - style: message.style ?? 'text', - status: message.status ?? 'incomplete', - }; +export const parseMessage = (json: string): AgentMessage | null => { + try { + const message = JSON.parse(json).message as AgentMessage; + + return { + ...message, + style: message.style ?? 'text', + status: message.status ?? 'incomplete', + }; + } catch (e) { + console.error( + `Failed to parse message: ${json}, length: ${json.length}`, + e, + ); + return null; + } }; export const getEmoji = (type?: string) => { From 6b019bbfb2befc53b64e13e9d3973930733533e6 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 15:54:03 +0900 Subject: [PATCH 38/42] Refactor relevantInfoExtraction, AgentStream, and findMostRelevantObjective --- .../tools/utils/relevantInfoExtraction.ts | 1 + src/agents/base/AgentStream.ts | 2 +- src/utils/objective.ts | 60 ++++++++++++++----- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts b/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts index beedf4d4..339bd4da 100644 --- a/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts +++ b/src/agents/babyelfagi/tools/utils/relevantInfoExtraction.ts @@ -19,6 +19,7 @@ export const relevantInfoExtraction = async ( const llm = new OpenAIChat( { modelName, + openAIApiKey: userApiKey, temperature: 0.7, maxTokens: 800, topP: 1, diff --git a/src/agents/base/AgentStream.ts b/src/agents/base/AgentStream.ts index f6a1c936..331c31a3 100644 --- a/src/agents/base/AgentStream.ts +++ b/src/agents/base/AgentStream.ts @@ -26,7 +26,7 @@ export function AgentStream(callbacks?: AIStreamCallbacks) { await writer.write( `${JSON.stringify({ message, - })}\n`, + })}\n\n`, ); }, handleEnd: async () => { diff --git a/src/utils/objective.ts b/src/utils/objective.ts index 6c9f6994..1deb57e0 100644 --- a/src/utils/objective.ts +++ b/src/utils/objective.ts @@ -17,8 +17,18 @@ async function fetchJsonFiles(targetJsonFiles: string[]) { const response = await fetch( `${BASE_URL}/api/json-provider?file=${jsonFile}`, ); - const data = await response.json(); - loadedObjectives.push(data); + + if (!response.ok) { + console.error(`Error fetching ${jsonFile}: ${response.statusText}`); + continue; + } + + try { + const data = await response.json(); + loadedObjectives.push(data); + } catch (e) { + console.error(`Error parsing JSON for ${jsonFile}: ${e}`); + } } return loadedObjectives; @@ -37,14 +47,22 @@ async function getEmbedding( modelName: string = 'text-embedding-ada-002', userApiKey?: string, ) { - const embedding = new OpenAIEmbeddings({ - modelName, - openAIApiKey: userApiKey, - }); - return await embedding.embedQuery(text); + try { + const embedding = new OpenAIEmbeddings({ + modelName, + openAIApiKey: userApiKey, + }); + return await embedding.embedQuery(text); + } catch (e) { + throw new Error(`error: ${e}`); + } } function calculateSimilarity(embedding1: number[], embedding2: number[]) { + if (!embedding1 || !embedding2) { + throw new Error('Embedding is not defined'); + } + const dotProduct = embedding1.reduce( (sum, a, i) => sum + a * embedding2[i], 0, @@ -69,17 +87,29 @@ export async function findMostRelevantObjective( let mostRelevantObjective = null; for (const example of examples) { - const objectiveEmbedding = await getEmbedding(example.objective); - const similarity = calculateSimilarity( - objectiveEmbedding, - userInputEmbedding, - ); + try { + const objectiveEmbedding = await getEmbedding( + example.objective, + 'text-embedding-ada-002', + userApiKey, + ); + const similarity = calculateSimilarity( + objectiveEmbedding, + userInputEmbedding, + ); - if (similarity > maxSimilarity) { - maxSimilarity = similarity; - mostRelevantObjective = example; + if (similarity > maxSimilarity) { + maxSimilarity = similarity; + mostRelevantObjective = example; + } + } catch (e) { + console.error(`Error in processing example: ${e}`); } } + if (mostRelevantObjective === null) { + throw new Error(`No objective found ${examples.length}`); + } + return mostRelevantObjective; } From 568e926af52b58101fe0910c941670919a9e5aaa Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 16:00:34 +0900 Subject: [PATCH 39/42] Refactor code to include options in task object --- src/agents/babyelfagi/skills/skill.ts | 3 --- src/utils/print.ts | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agents/babyelfagi/skills/skill.ts b/src/agents/babyelfagi/skills/skill.ts index d787871f..9d48cee1 100644 --- a/src/agents/babyelfagi/skills/skill.ts +++ b/src/agents/babyelfagi/skills/skill.ts @@ -121,9 +121,6 @@ export class Skill { type: task.skill, taskId: task.id.toString(), status: 'complete', - options: { - dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', - }, }; const llm = new ChatOpenAI( { diff --git a/src/utils/print.ts b/src/utils/print.ts index 6e68ddf8..3ddf716d 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -36,6 +36,9 @@ export class Printer { type: task.skill, title: task.task, icon: task.icon || '', + options: { + dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', + }, }); if (!this.verbose) return; From 61e541f8857e6d35dc52e98ea137604dd8060329 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 16:29:39 +0900 Subject: [PATCH 40/42] Rename ExamplePage to ExampleHeadlessPage, add agentId in useAgent hook, and create ExampleUIPage component --- src/pages/example/{ => headless}/index.tsx | 5 ++-- src/pages/example/ui/index.tsx | 35 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) rename src/pages/example/{ => headless}/index.tsx (83%) create mode 100644 src/pages/example/ui/index.tsx diff --git a/src/pages/example/index.tsx b/src/pages/example/headless/index.tsx similarity index 83% rename from src/pages/example/index.tsx rename to src/pages/example/headless/index.tsx index 2f2057ce..b55cd790 100644 --- a/src/pages/example/index.tsx +++ b/src/pages/example/headless/index.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { useAgent } from '@/hooks/useAgent'; -const ExamplePage: React.FC = () => { +const ExampleHeadlessPage: React.FC = () => { const { agentMessages, input, handleSubmit, handleInputChange } = useAgent({ api: '/api/agent', + agentId: 'babydeeragi', }); return (
@@ -22,4 +23,4 @@ const ExamplePage: React.FC = () => { ); }; -export default ExamplePage; +export default ExampleHeadlessPage; diff --git a/src/pages/example/ui/index.tsx b/src/pages/example/ui/index.tsx new file mode 100644 index 00000000..cff5db1a --- /dev/null +++ b/src/pages/example/ui/index.tsx @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react'; +import { Block } from '@/types'; +import { useAgent } from '@/hooks/useAgent'; +import { groupMessages } from '@/utils/message'; +import { AgentBlock } from '@/components/Agent/AgentBlock'; +const ExampleUIPage: React.FC = () => { + const [agentBlocks, setAgentBlocks] = useState([]); + const { agentMessages, input, handleSubmit, handleInputChange, isRunning } = + useAgent({ + api: '/api/agent', + agentId: 'babydeeragi', + }); + + useEffect(() => { + const newGroupedMessages = groupMessages(agentMessages, isRunning); + setAgentBlocks(newGroupedMessages); + }, [agentMessages, isRunning]); + + return ( +
+
+ +
+ {agentBlocks.map((block, index) => ( + + ))} +
+ ); +}; + +export default ExampleUIPage; From 8431170e192d7c01e06686560ac2d8571d7f2b61 Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 17:19:44 +0900 Subject: [PATCH 41/42] Extract relevant information and update status to running --- src/agents/babyelfagi/tools/webBrowsing.ts | 10 +++++++++- src/components/Agent/IntroGuide.tsx | 1 - src/utils/print.ts | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/agents/babyelfagi/tools/webBrowsing.ts b/src/agents/babyelfagi/tools/webBrowsing.ts index ab62a9b1..dbd73e82 100644 --- a/src/agents/babyelfagi/tools/webBrowsing.ts +++ b/src/agents/babyelfagi/tools/webBrowsing.ts @@ -47,7 +47,7 @@ export const webBrowsing = async ( let results = ''; let index = 1; let completedCount = 0; - const MaxCompletedCount = 3; + const MaxCompletedCount = 2; // Loop through search results for (const searchResult of simplifiedSearchResults) { if (signal?.aborted) return ''; @@ -81,6 +81,13 @@ export const webBrowsing = async ( message = ` - Extracting relevant information\n`; title = `${index}. Extracting relevant info...`; callbackSearchStatus(id, message, task, messageCallback, verbose); + + // Set an interval to ping callbackSearchStatus every 10 seconds + const intervalId = setInterval(() => { + message = ' - Still extracting relevant information...\n'; + callbackSearchStatus(id, message, task, messageCallback, verbose); + }, 10000); + const info = await largeTextExtract( id, objective, @@ -90,6 +97,7 @@ export const webBrowsing = async ( messageCallback, signal, ); + clearInterval(intervalId); message = ` - Relevant info: ${info .slice(0, 100) diff --git a/src/components/Agent/IntroGuide.tsx b/src/components/Agent/IntroGuide.tsx index 6454189d..aafc9f6f 100644 --- a/src/components/Agent/IntroGuide.tsx +++ b/src/components/Agent/IntroGuide.tsx @@ -10,7 +10,6 @@ export const IntroGuide: FC = ({ onClick, agent }) => { const deerExamples = [ translate('EXAMPLE_OBJECTIVE_1', 'constants'), translate('EXAMPLE_OBJECTIVE_2', 'constants'), - translate('EXAMPLE_OBJECTIVE_3', 'constants'), ]; const elfExample = [ `${translate( diff --git a/src/utils/print.ts b/src/utils/print.ts index 3ddf716d..297ddcf2 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -39,6 +39,7 @@ export class Printer { options: { dependentTaskIds: task.dependentTaskIds?.join(', ') ?? '', }, + status: 'running', }); if (!this.verbose) return; From 2f78916fd6997f14e5ee39d55cf2f19bec22d6ee Mon Sep 17 00:00:00 2001 From: Yoshiki Miura Date: Mon, 21 Aug 2023 17:36:36 +0900 Subject: [PATCH 42/42] Remove unnecessary callback function call in WebLoader skill --- src/agents/babyelfagi/skills/presets/webLoader.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agents/babyelfagi/skills/presets/webLoader.ts b/src/agents/babyelfagi/skills/presets/webLoader.ts index 3e0cdf76..76eed946 100644 --- a/src/agents/babyelfagi/skills/presets/webLoader.ts +++ b/src/agents/babyelfagi/skills/presets/webLoader.ts @@ -32,7 +32,6 @@ export class WebLoader extends Skill { }); }; - callback(message); const urlString = await this.extractUrlsFromTask(task, callback); const urls = urlString.split(',').map((url) => url.trim()); const contents = await this.fetchContentsFromUrls(urls, callback);