const express = require('express'); const router = express.Router(); const { titleConvo, validateTools, PluginsClient } = require('../../../app'); const { abortMessage, getAzureCredentials } = require('../../../utils'); const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); const { handleError, sendMessage, createOnProgress, formatSteps, formatAction, } = require('./handlers'); const requireJwtAuth = require('../../../middleware/requireJwtAuth'); const abortControllers = new Map(); router.post('/abort', requireJwtAuth, async (req, res) => { return await abortMessage(req, res, abortControllers); }); router.post('/', requireJwtAuth, async (req, res) => { const { endpoint, text, parentMessageId, conversationId } = req.body; if (text.length === 0) { return handleError(res, { text: 'Prompt empty or too short' }); } if (endpoint !== 'gptPlugins') { return handleError(res, { text: 'Illegal request' }); } const agentOptions = req.body?.agentOptions ?? { agent: 'functions', skipCompletion: true, model: 'gpt-3.5-turbo', temperature: 0, // top_p: 1, // presence_penalty: 0, // frequency_penalty: 0 }; const tools = req.body?.tools.map((tool) => tool.pluginKey) ?? []; // build endpoint option const endpointOption = { chatGptLabel: tools.length === 0 ? req.body?.chatGptLabel ?? null : null, promptPrefix: tools.length === 0 ? req.body?.promptPrefix ?? null : null, tools, modelOptions: { model: req.body?.model ?? 'gpt-4', temperature: req.body?.temperature ?? 0, top_p: req.body?.top_p ?? 1, presence_penalty: req.body?.presence_penalty ?? 0, frequency_penalty: req.body?.frequency_penalty ?? 0, }, agentOptions: { ...agentOptions, // agent: 'functions' }, }; console.log('ask log'); console.dir({ text, conversationId, endpointOption }, { depth: null }); // eslint-disable-next-line no-use-before-define return await ask({ text, endpoint, endpointOption, conversationId, parentMessageId, req, res, }); }); const ask = async ({ text, endpoint, endpointOption, parentMessageId = null, conversationId, req, res, }) => { res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache, no-transform', 'Access-Control-Allow-Origin': '*', 'X-Accel-Buffering': 'no', }); let userMessage; let userMessageId; let responseMessageId; let lastSavedTimestamp = 0; const newConvo = !conversationId; const { overrideParentMessageId = null } = req.body; const user = req.user.id; const plugin = { loading: true, inputs: [], latest: null, outputs: null, }; try { const getIds = (data) => { userMessage = data.userMessage; userMessageId = userMessage.messageId; responseMessageId = data.responseMessageId; if (!conversationId) { conversationId = data.conversationId; } }; const { onProgress: progressCallback, sendIntermediateMessage, getPartialText, } = createOnProgress({ onProgress: ({ text: partialText }) => { const currentTimestamp = Date.now(); if (plugin.loading === true) { plugin.loading = false; } if (currentTimestamp - lastSavedTimestamp > 500) { lastSavedTimestamp = currentTimestamp; saveMessage({ messageId: responseMessageId, sender: 'ChatGPT', conversationId, parentMessageId: overrideParentMessageId || userMessageId, text: partialText, model: endpointOption.modelOptions.model, unfinished: true, cancelled: false, error: false, }); } }, }); const abortController = new AbortController(); abortController.abortAsk = async function () { this.abort(); const responseMessage = { messageId: responseMessageId, sender: endpointOption?.chatGptLabel || 'ChatGPT', conversationId, parentMessageId: overrideParentMessageId || userMessageId, text: getPartialText(), plugin: { ...plugin, loading: false }, model: endpointOption.modelOptions.model, unfinished: false, cancelled: true, error: false, }; saveMessage(responseMessage); return { title: await getConvoTitle(req.user.id, conversationId), final: true, conversation: await getConvo(req.user.id, conversationId), requestMessage: userMessage, responseMessage: responseMessage, }; }; const onStart = (userMessage) => { sendMessage(res, { message: userMessage, created: true }); abortControllers.set(userMessage.conversationId, { abortController, ...endpointOption }); }; endpointOption.tools = await validateTools(user, endpointOption.tools); const clientOptions = { debug: true, endpoint, reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null, proxy: process.env.PROXY || null, ...endpointOption, }; let openAIApiKey = req.body?.token ?? process.env.OPENAI_API_KEY; if (process.env.PLUGINS_USE_AZURE) { clientOptions.azure = getAzureCredentials(); openAIApiKey = clientOptions.azure.azureOpenAIApiKey; } if (openAIApiKey && openAIApiKey.includes('azure') && !clientOptions.azure) { clientOptions.azure = JSON.parse(req.body?.token) ?? getAzureCredentials(); openAIApiKey = clientOptions.azure.azureOpenAIApiKey; } const chatAgent = new PluginsClient(openAIApiKey, clientOptions); const onAgentAction = (action, start = false) => { const formattedAction = formatAction(action); plugin.inputs.push(formattedAction); plugin.latest = formattedAction.plugin; if (!start) { saveMessage(userMessage); } sendIntermediateMessage(res, { plugin }); // console.log('PLUGIN ACTION', formattedAction); }; const onChainEnd = (data) => { let { intermediateSteps: steps } = data; plugin.outputs = steps && steps[0].action ? formatSteps(steps) : 'An error occurred.'; plugin.loading = false; saveMessage(userMessage); sendIntermediateMessage(res, { plugin }); // console.log('CHAIN END', plugin.outputs); }; let response = await chatAgent.sendMessage(text, { getIds, user, parentMessageId, conversationId, overrideParentMessageId, onAgentAction, onChainEnd, onStart, ...endpointOption, onProgress: progressCallback.call(null, { res, text, plugin, parentMessageId: overrideParentMessageId || userMessageId, }), abortController, }); if (overrideParentMessageId) { response.parentMessageId = overrideParentMessageId; } console.log('CLIENT RESPONSE'); console.dir(response, { depth: null }); response.plugin = { ...plugin, loading: false }; await saveMessage(response); sendMessage(res, { title: await getConvoTitle(req.user.id, conversationId), final: true, conversation: await getConvo(req.user.id, conversationId), requestMessage: userMessage, responseMessage: response, }); res.end(); if (parentMessageId == '00000000-0000-0000-0000-000000000000' && newConvo) { const title = await titleConvo({ text, response, openAIApiKey, azure: !!clientOptions.azure, }); await saveConvo(req.user.id, { conversationId: conversationId, title, }); } } catch (error) { console.error(error); const errorMessage = { messageId: responseMessageId, sender: 'ChatGPT', conversationId, parentMessageId: userMessageId, unfinished: false, cancelled: false, error: true, text: error.message, }; await saveMessage(errorMessage); handleError(res, errorMessage); } }; module.exports = router;