mongodb-chatbot-server
Version:
A chatbot server for retrieval augmented generation (RAG).
271 lines • 11.5 kB
JavaScript
;
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