diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/ChatBox/Agent.ts | 83 | ||||
-rw-r--r-- | src/client/views/nodes/ChatBox/ChatBot.ts | 16 | ||||
-rw-r--r-- | src/client/views/nodes/ChatBox/ChatBox.tsx | 27 | ||||
-rw-r--r-- | src/client/views/nodes/ChatBox/MessageComponent.tsx | 8 | ||||
-rw-r--r-- | src/client/views/nodes/ChatBox/prompts.ts | 5 |
5 files changed, 77 insertions, 62 deletions
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<string> { + async askAgent(question: string, maxTurns: number = 8): Promise<string> { 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: `<query>${question}</query>` }); + 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 '<error>Invalid response format.</error>'; + } + + 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>' }, ...observation, { type: 'text', text: '</observation>' }]; - this.interMessages.push({ role: 'user', content: nextPrompt }); - } catch (e) { - console.error(`Error processing action: ${e}`); - return `<error>${e}</error>`; + if (currentAction) { + try { + const observation = await this.processAction(currentAction, step[key]); + const nextPrompt = [{ type: 'text', text: '<observation>' }, ...observation, { type: 'text', text: '</observation>' }]; + this.interMessages.push({ role: 'assistant', content: nextPrompt }); + break; + } catch (error) { + console.log(`Error processing action: ${error}`); + return `<error>${error}</error>`; + } + } else { + console.log('Error: Action input without a valid action'); + return '<error>Action input without a valid action</error>'; } - } - 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 '<error>Invalid response format.</error>'; } } @@ -102,8 +122,9 @@ export class Agent { } private async execute(): Promise<string> { + 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<string> { - 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { ); } + @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<FieldViewProps>() { doc['ai_document'] = document_json; } }); + this.isInitializing = false; }; @action @@ -120,7 +122,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 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<FieldViewProps>() { 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<MessageComponentProps> = function ({ message, index, onFollowUpClick, onCitationClick, updateMessageCitations }) { - const LinkRenderer = ({ children }: { children: React.ReactNode }) => { - const text = children as string; + const renderContent = (text: string) => { const citationRegex = /<citation chunk_id="([^"]*)" type="([^"]*)">([^<]*)<\/citation>/g; const parts = []; let lastIndex = 0; @@ -47,7 +46,6 @@ const MessageComponent: React.FC<MessageComponentProps> = 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<MessageComponentProps> = function ({ message, i return ( <div className={`message ${message.role}`}> - <div> - <LinkRenderer>{message.text}</LinkRenderer> - </div> + <div>{renderContent(message.text)}</div> {message.follow_up_questions && message.follow_up_questions.length > 0 && ( <div className="follow-up-questions"> <h4>Follow-up Questions:</h4> 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. <thought>content of the thought</thought>). At the end of the loop, you output an Answer with the answer content contained within an XML element with an <answer> 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 <follow_up_questions> key. Use <thought> to describe your thoughts about the question you have been asked. - Use <action> to specify run one of the actions available to you - then return a </pause> element. + Use <action> to specify run one of the actions available to you. Then, you will be provided with action rules within an <action_rules> 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 </pause> element. Then, provide within an <action_input> 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 <observation> element will be the result of running those actions. @@ -28,7 +28,6 @@ export function getReactPrompt(tools: Tool[], chatHistory: string): string { <step1> <thought>I should look up France on Wikipedia</thought> <action>wikipedia</action> - <pause/> </step1> You will be called again with this: |