UNPKG

mongodb-chatbot-server

Version:

A chatbot server for retrieval augmented generation (RAG).

271 lines 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeAddMessageToConversationRoute = exports.AddMessageRequest = exports.AddMessageRequestBody = exports.DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION = exports.DEFAULT_MAX_INPUT_LENGTH = void 0; const common_tags_1 = require("common-tags"); const assert_1 = require("assert"); const mongodb_1 = require("mongodb-rag-core/mongodb"); const mongodb_rag_core_1 = require("mongodb-rag-core"); const utils_1 = require("./utils"); const utils_2 = require("../../utils"); const zod_1 = require("zod"); const validateRequestSchema_1 = require("../../middleware/validateRequestSchema"); const filterOnlySystemPrompt_1 = require("../../processors/filterOnlySystemPrompt"); const generateResponse_1 = require("../generateResponse"); const braintrust_1 = require("mongodb-rag-core/braintrust"); const UpdateTraceFunc_1 = require("./UpdateTraceFunc"); exports.DEFAULT_MAX_INPUT_LENGTH = 3000; // magic number for max input size for LLM exports.DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION = 7; // magic number for max messages in a conversation exports.AddMessageRequestBody = zod_1.z.object({ message: zod_1.z.string(), clientContext: zod_1.z.object({}).passthrough().optional(), }); exports.AddMessageRequest = validateRequestSchema_1.SomeExpressRequest.merge(zod_1.z.object({ headers: zod_1.z.object({ "req-id": zod_1.z.string(), }), params: zod_1.z.object({ conversationId: zod_1.z.string(), }), query: zod_1.z.object({ stream: zod_1.z.string().optional(), }), body: exports.AddMessageRequestBody, })); function makeAddMessageToConversationRoute({ conversations, llm, generateUserPrompt, maxInputLengthCharacters = exports.DEFAULT_MAX_INPUT_LENGTH, maxUserMessagesInConversation = exports.DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION, filterPreviousMessages = filterOnlySystemPrompt_1.filterOnlySystemPrompt, addMessageToConversationCustomData, createConversation, updateTrace, }) { const generateResponseTraced = function ({ latestMessageText, clientContext, customData, dataStreamer, shouldStream, reqId, conversation, traceId, }) { const tracedFunc = (0, braintrust_1.wrapTraced)(({ latestMessageText, clientContext, customData, dataStreamer, shouldStream, reqId, conversation, }) => { return (0, generateResponse_1.generateResponse)({ latestMessageText, clientContext, customData, dataStreamer, shouldStream, reqId, llm, conversation, generateUserPrompt, filterPreviousMessages, llmNotWorkingMessage: conversations.conversationConstants.LLM_NOT_WORKING, noRelevantContentMessage: conversations.conversationConstants.NO_RELEVANT_CONTENT, }); }, { name: "generateResponse", event: { id: traceId, metadata: { conversationId: conversation._id.toHexString(), }, }, }); return tracedFunc({ latestMessageText, clientContext, customData, dataStreamer, shouldStream, reqId, conversation, }); }; return async (req, res) => { const dataStreamer = (0, mongodb_rag_core_1.makeDataStreamer)(); const reqId = (0, utils_2.getRequestId)(req); try { const { params: { conversationId: conversationIdString }, body: { message, clientContext }, query: { stream }, ip, } = req; (0, utils_2.logRequest)({ reqId, message: (0, common_tags_1.stripIndents) `Request info: User message: ${message} Stream: ${stream} IP: ${ip} ConversationId: ${conversationIdString}`, }); const latestMessageText = message; if (latestMessageText.length > maxInputLengthCharacters) { throw (0, utils_1.makeRequestError)({ httpStatus: 400, message: "Message too long", }); } const customData = await getCustomData({ req, res, addMessageToConversationCustomData, }); // --- LOAD CONVERSATION --- const conversation = await loadConversation({ conversationIdString, conversations, createConversation, reqId, req, res, }); // --- MAX CONVERSATION LENGTH CHECK --- const numUserMessages = conversation.messages.reduce((acc, message) => (message.role === "user" ? acc + 1 : acc), 0); if (numUserMessages >= maxUserMessagesInConversation) { // Omit the system prompt and assume the user always received one response per message throw (0, utils_1.makeRequestError)({ httpStatus: 400, message: `Too many messages. You cannot send more than ${maxUserMessagesInConversation} messages in this conversation.`, }); } // --- DETERMINE IF SHOULD STREAM --- const shouldStream = Boolean(stream); if (shouldStream) { dataStreamer.connect(res); } const assistantResponseMessageId = new mongodb_1.ObjectId(); // Only include the necessary message info for the conversastion. // This sends less data to Braintrust speeding up tracing // and also being more readable in the Braintrust UI. const traceConversation = { ...conversation, messages: conversation.messages.map((message) => { const baseFields = { content: message.content, id: message.id, createdAt: message.createdAt, metadata: message.metadata, }; if (message.role === "function") { return { role: "function", name: message.name, ...baseFields, }; } else { return { ...baseFields, role: message.role }; } }), }; const { messages } = await generateResponseTraced({ conversation: traceConversation, latestMessageText, clientContext, customData, dataStreamer, shouldStream, reqId, traceId: assistantResponseMessageId.toHexString(), }); // --- SAVE QUESTION & RESPONSE --- const dbNewMessages = await addMessagesToDatabase({ conversations, conversation, messages, assistantResponseMessageId, }); const dbAssistantMessage = dbNewMessages[dbNewMessages.length - 1]; (0, assert_1.strict)(dbAssistantMessage !== undefined, "No assistant message found"); const apiRes = (0, utils_1.convertMessageFromDbToApi)(dbAssistantMessage, conversation._id); if (!shouldStream) { return res.status(200).json(apiRes); } else { dataStreamer.streamData({ type: "metadata", data: { conversationId: conversation._id.toString() }, }); dataStreamer.streamData({ type: "finished", data: apiRes.id, }); if (dataStreamer.connected) { dataStreamer.disconnect(); } await (0, UpdateTraceFunc_1.updateTraceIfExists)({ updateTrace, reqId, conversations, conversationId: conversation._id, assistantResponseMessageId: dbAssistantMessage.id, }); } } catch (error) { const { httpStatus, message } = error.name === "RequestError" ? error : (0, utils_1.makeRequestError)({ message: error.message, stack: error.stack, httpStatus: 500, }); (0, utils_2.sendErrorResponse)({ res, reqId, httpStatus, errorMessage: message, }); } finally { if (dataStreamer.connected) { dataStreamer.disconnect(); } } }; } exports.makeAddMessageToConversationRoute = makeAddMessageToConversationRoute; // --- HELPERS --- async function getCustomData({ req, res, addMessageToConversationCustomData, }) { try { return addMessageToConversationCustomData ? await addMessageToConversationCustomData(req, res) : undefined; } catch (_err) { throw (0, utils_1.makeRequestError)({ httpStatus: 500, message: "Unable to process custom data", }); } } async function addMessagesToDatabase({ conversation, conversations, messages, assistantResponseMessageId, }) { messages[messages.length - 1].id = assistantResponseMessageId; const conversationId = conversation._id; const dbMessages = await conversations.addManyConversationMessages({ conversationId, messages, }); return dbMessages; } const loadConversation = async ({ conversationIdString, conversations, createConversation, reqId, req, res, }) => { // Create a new conversation if the conversationId is "null" // and the route is configured to do so if (createConversation?.createOnNullConversationId === true && conversationIdString === "null") { (0, utils_2.logRequest)({ reqId, message: (0, common_tags_1.stripIndents) `Creating new conversation`, }); return await conversations.create({ initialMessages: createConversation.systemMessage ? [createConversation.systemMessage] : undefined, customData: createConversation.addCustomData ? await createConversation.addCustomData(req, res) : undefined, }); } // Throw if the conversationId is not a valid ObjectId const conversationId = mongodb_1.ObjectId.isValid(conversationIdString) ? mongodb_1.ObjectId.createFromHexString(conversationIdString) : (() => { throw (0, utils_1.makeRequestError)({ httpStatus: 400, message: `Invalid conversationId: ${conversationIdString}`, }); })(); const conversation = await conversations.findById({ _id: conversationId, }); if (!conversation) { throw (0, utils_1.makeRequestError)({ httpStatus: 404, message: `Conversation ${conversationId} not found`, }); } return conversation; }; //# sourceMappingURL=addMessageToConversation.js.map