From d2c968cb3705b314396c0503b089f8a233a26502 Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Wed, 10 Jul 2024 17:54:59 -0400 Subject: Working now somewhat --- src/client/views/nodes/ChatBox/Agent.ts | 83 ++++++++++++++-------- src/client/views/nodes/ChatBox/ChatBot.ts | 16 ----- src/client/views/nodes/ChatBox/ChatBox.tsx | 27 +++++-- .../views/nodes/ChatBox/MessageComponent.tsx | 8 +-- src/client/views/nodes/ChatBox/prompts.ts | 5 +- 5 files changed, 77 insertions(+), 62 deletions(-) delete mode 100644 src/client/views/nodes/ChatBox/ChatBot.ts diff --git a/src/client/views/nodes/ChatBox/Agent.ts b/src/client/views/nodes/ChatBox/Agent.ts index 4c2838540..355acb19f 100644 --- a/src/client/views/nodes/ChatBox/Agent.ts +++ b/src/client/views/nodes/ChatBox/Agent.ts @@ -18,7 +18,7 @@ export class Agent { private summaries: string; constructor(private vectorstore: Vectorstore) { - this.client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + this.client = new OpenAI({ apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true }); this.summaries = this.vectorstore ? this.vectorstore.getSummaries() : 'No documents available.'; this.tools = { wikipedia: new WikipediaTool(), @@ -40,7 +40,7 @@ export class Agent { return history; } - async askAgent(question: string, maxTurns: number = 5): Promise { + async askAgent(question: string, maxTurns: number = 8): Promise { console.log(`Starting query: ${question}`); this.messages.push({ role: 'user', content: question }); const chatHistory = this.formatChatHistory(); @@ -51,6 +51,10 @@ export class Agent { this.interMessages.push({ role: 'assistant', content: `${question}` }); + const parser = new XMLParser(); + const builder = new XMLBuilder(); + let currentAction: string | undefined; + for (let i = 0; i < maxTurns; i++) { console.log(`Turn ${i + 1}/${maxTurns}`); @@ -58,42 +62,58 @@ export class Agent { console.log(`Bot response: ${result}`); this.interMessages.push({ role: 'assistant', content: result }); + let parsedResult; try { - const parser = new XMLParser(); - const parsedResult = parser.parse(result); - const step = parsedResult[`step${i + 1}`]; - - if (step.thought) console.log(`Thought: ${step.thought}`); - if (step.action) { - console.log(`Action: ${step.action}`); - const action = step.action; - const actionRules = new XMLBuilder().build({ - action_rules: this.tools[action].getActionRule(), - }); - this.interMessages.push({ role: 'user', content: actionRules }); - } - if (step.action_input) { - const actionInput = new XMLBuilder().build({ action_input: step.action_input }); + parsedResult = parser.parse(result); + } catch (error) { + console.log('Error: Invalid XML response from bot'); + return 'Invalid response format.'; + } + + const step = parsedResult[Object.keys(parsedResult)[0]]; + + for (const key in step) { + if (key === 'thought') { + console.log(`Thought: ${step[key]}`); + } else if (key === 'action') { + currentAction = step[key] as string; + console.log(`Action: ${currentAction}`); + if (this.tools[currentAction]) { + const nextPrompt = [ + { + type: 'text', + text: builder.build({ action_rules: this.tools[currentAction].getActionRule() }), + }, + ]; + this.interMessages.push({ role: 'assistant', content: nextPrompt }); + break; + } else { + console.log('Error: No valid action'); + } + } else if (key === 'action_input') { + const actionInput = builder.build({ action_input: step[key] }); console.log(`Action input: ${actionInput}`); - try { - const observation = await this.processAction(step.action, step.action_input); - const nextPrompt = [{ type: 'text', text: '' }, ...observation, { type: 'text', text: '' }]; - this.interMessages.push({ role: 'user', content: nextPrompt }); - } catch (e) { - console.error(`Error processing action: ${e}`); - return `${e}`; + if (currentAction) { + try { + const observation = await this.processAction(currentAction, step[key]); + const nextPrompt = [{ type: 'text', text: '' }, ...observation, { type: 'text', text: '' }]; + this.interMessages.push({ role: 'assistant', content: nextPrompt }); + break; + } catch (error) { + console.log(`Error processing action: ${error}`); + return `${error}`; + } + } else { + console.log('Error: Action input without a valid action'); + return 'Action input without a valid action'; } - } - if (step.answer) { + } else if (key === 'answer') { console.log('Answer found. Ending query.'); - const answerContent = new XMLBuilder().build({ answer: step.answer }); + const answerContent = builder.build({ answer: step[key] }); this.messages.push({ role: 'assistant', content: answerContent }); this.interMessages = []; return answerContent; } - } catch (e) { - console.error('Error: Invalid XML response from bot'); - return 'Invalid response format.'; } } @@ -102,8 +122,9 @@ export class Agent { } private async execute(): Promise { + console.log('Messages: ' + this.interMessages); const completion = await this.client.chat.completions.create({ - model: 'gpt-4', + model: 'gpt-4o', messages: this.interMessages as ChatCompletionMessageParam[], temperature: 0, }); diff --git a/src/client/views/nodes/ChatBox/ChatBot.ts b/src/client/views/nodes/ChatBox/ChatBot.ts deleted file mode 100644 index 8b5e0982c..000000000 --- a/src/client/views/nodes/ChatBox/ChatBot.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Agent } from './Agent'; -import { Vectorstore } from './vectorstore/VectorstoreUpload'; -import dotenv from 'dotenv'; -dotenv.config(); - -export class ChatBot { - private agent: Agent; - - constructor(vectorstore: Vectorstore) { - this.agent = new Agent(vectorstore); - } - - async ask(question: string): Promise { - return await this.agent.askAgent(question); - } -} diff --git a/src/client/views/nodes/ChatBox/ChatBox.tsx b/src/client/views/nodes/ChatBox/ChatBox.tsx index 3ecb2d340..2ce1ebdd2 100644 --- a/src/client/views/nodes/ChatBox/ChatBox.tsx +++ b/src/client/views/nodes/ChatBox/ChatBox.tsx @@ -16,7 +16,7 @@ import { ASSISTANT_ROLE, AssistantMessage, AI_Document, convertToAIDocument, Cit import { Vectorstore } from './vectorstore/VectorstoreUpload'; import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; -import { ChatBot } from './ChatBot'; +import { Agent } from './Agent'; import dotenv from 'dotenv'; dotenv.config(); @@ -34,7 +34,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { private documents: AI_Document[] = []; private _oldWheel: any; private vectorstore: Vectorstore; - private chatbot: ChatBot; // Add the ChatBot instance + private agent: Agent; // Add the ChatBot instance public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ChatBox, fieldKey); @@ -48,7 +48,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { this.openai = this.initializeOpenAI(); this.getOtherDocs(); this.vectorstore = new Vectorstore(); - this.chatbot = new ChatBot(this.vectorstore); // Initialize the ChatBot + this.agent = new Agent(this.vectorstore); // Initialize the Agent reaction( () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, follow_up_questions: msg.follow_up_questions, citations: msg.citations })), @@ -58,6 +58,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { ); } + @action getOtherDocs = async () => { const visible_docs = (CollectionFreeFormDocumentView.from(this._props.DocumentView?.())?._props.parent as CollectionFreeFormView)?.childDocs .filter(doc => doc != this.Document) @@ -76,6 +77,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { doc['ai_document'] = document_json; } }); + this.isInitializing = false; }; @action @@ -120,7 +122,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { this.history.push({ role: ASSISTANT_ROLE.USER, text: trimmedText }); }); this.isLoading = true; - const response = await this.chatbot.ask(trimmedText); // Use the chatbot to get the response + const response = await this.agent.askAgent(trimmedText); // Use the chatbot to get the response runInAction(() => { this.history.push(this.parseAssistantResponse(response)); }); @@ -142,8 +144,21 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { const answerElement = xmlDoc.querySelector('answer'); const followUpQuestionsElement = xmlDoc.querySelector('follow_up_questions'); - const text = answerElement ? answerElement.innerHTML || '' : ''; // Use innerHTML to preserve citation tags - const followUpQuestions = followUpQuestionsElement ? Array.from(followUpQuestionsElement.querySelectorAll('question')).map(q => q.textContent || '') : []; + let text = ''; + let followUpQuestions: string[] = []; + + if (answerElement) { + // Remove the follow_up_questions element from the answer + const followUpElement = answerElement.querySelector('follow_up_questions'); + if (followUpElement) { + followUpElement.remove(); + } + text = answerElement.innerHTML.trim(); + } + + if (followUpQuestionsElement) { + followUpQuestions = Array.from(followUpQuestionsElement.querySelectorAll('question')).map(q => q.textContent || ''); + } return { role: ASSISTANT_ROLE.ASSISTANT, diff --git a/src/client/views/nodes/ChatBox/MessageComponent.tsx b/src/client/views/nodes/ChatBox/MessageComponent.tsx index 1baf6d7d5..91671a24a 100644 --- a/src/client/views/nodes/ChatBox/MessageComponent.tsx +++ b/src/client/views/nodes/ChatBox/MessageComponent.tsx @@ -12,8 +12,7 @@ interface MessageComponentProps { } const MessageComponent: React.FC = function ({ message, index, onFollowUpClick, onCitationClick, updateMessageCitations }) { - const LinkRenderer = ({ children }: { children: React.ReactNode }) => { - const text = children as string; + const renderContent = (text: string) => { const citationRegex = /([^<]*)<\/citation>/g; const parts = []; let lastIndex = 0; @@ -47,7 +46,6 @@ const MessageComponent: React.FC = function ({ message, i parts.push(text.slice(lastIndex)); - // Update the message's citations in the ChatBox's history updateMessageCitations(index, citations); return <>{parts}; @@ -55,9 +53,7 @@ const MessageComponent: React.FC = function ({ message, i return (
-
- {message.text} -
+
{renderContent(message.text)}
{message.follow_up_questions && message.follow_up_questions.length > 0 && (

Follow-up Questions:

diff --git a/src/client/views/nodes/ChatBox/prompts.ts b/src/client/views/nodes/ChatBox/prompts.ts index 8835265e4..ffea13788 100644 --- a/src/client/views/nodes/ChatBox/prompts.ts +++ b/src/client/views/nodes/ChatBox/prompts.ts @@ -6,12 +6,12 @@ export function getReactPrompt(tools: Tool[], chatHistory: string): string { const toolDescriptions = tools.map(tool => `${tool.name}:\n${tool.briefSummary}`).join('\n*****\n'); return ` - You run in a loop of Thought, Action, PAUSE, Action Input, Pause, Observation. + You run in a loop of Thought, Action, (PAUSE), Action Input, (PAUSE), Observation. (this Thought/Action/PAUSE/Action Input/PAUSE/Observation can repeat N times) Contain each stage of the loop within an XML element that specifies the stage type (e.g. content of the thought). At the end of the loop, you output an Answer with the answer content contained within an XML element with an tag. At the end of the answer should be an array of 3 potential follow-up questions for the user to ask you next, contained within a key. Use to describe your thoughts about the question you have been asked. - Use to specify run one of the actions available to you - then return a element. + Use to specify run one of the actions available to you. Then, you will be provided with action rules within an element that specifies how you should structure the input to the action and what the output of that action will look like - then return another element. Then, provide within an element each parameter, with parameter names as element tags themselves with their values inside, following the structure defined in the action rules. Observation, in an element will be the result of running those actions. @@ -28,7 +28,6 @@ export function getReactPrompt(tools: Tool[], chatHistory: string): string { I should look up France on Wikipedia wikipedia - You will be called again with this: -- cgit v1.2.3-70-g09d2