From 02ad9ec0d85a47c36acbac5828e58c0fbc25486c Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:58:23 -0400 Subject: [PATCH 01/19] init version --- .../addMessageToConversation.test.ts | 4 ++ .../conversations/addMessageToConversation.ts | 59 ++++++++++++++++--- .../conversations/conversationsRouter.test.ts | 3 + .../conversations/conversationsRouter.ts | 12 ++++ 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index 723dbc518..b8d302feb 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -399,6 +399,10 @@ describe("POST /conversations/:conversationId/messages", () => { }); }); }); + + describe("create conversation with 'null' conversationId", () => { + // TODO + }); }); async function createNewConversation( diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts index d97e9cb80..a9d4e40d0 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts @@ -9,12 +9,8 @@ import { ConversationsService, Conversation, SomeMessage, - UserMessage, - AssistantMessage, - DataStreamer, makeDataStreamer, ChatLlm, - OpenAiChatMessage, } from "mongodb-rag-core"; import { ApiMessage, @@ -32,10 +28,7 @@ import { import { GenerateUserPromptFunc } from "../../processors/GenerateUserPromptFunc"; import { FilterPreviousMessages } from "../../processors/FilterPreviousMessages"; import { filterOnlySystemPrompt } from "../../processors/filterOnlySystemPrompt"; -import { - convertMessageFromLlmToDb, - generateResponse, -} from "../generateResponse"; +import { generateResponse } from "../generateResponse"; export const DEFAULT_MAX_INPUT_LENGTH = 3000; // magic number for max input size for LLM export const DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION = 7; // magic number for max messages in a conversation @@ -70,6 +63,26 @@ export interface AddMessageToConversationRouteParams { maxInputLengthCharacters?: number; maxUserMessagesInConversation?: number; addMessageToConversationCustomData?: AddCustomDataFunc; + /** + If present, the route will create a new conversation + when given the `conversationIdPathParam` in the URL. + */ + createConversation?: { + /** + Create a new conversation when the `conversationId` is the string "null". + */ + createOnNullConversationId: boolean; + /** + The custom data to add to the new conversation + when it is created. + */ + addCustomData?: AddCustomDataFunc; + /** + The initial messages to add to the new conversation + when it is created. + */ + initialMessages?: SomeMessage[]; + }; } export function makeAddMessageToConversationRoute({ @@ -80,6 +93,7 @@ export function makeAddMessageToConversationRoute({ maxUserMessagesInConversation = DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION, filterPreviousMessages = filterOnlySystemPrompt, addMessageToConversationCustomData, + createConversation, }: AddMessageToConversationRouteParams) { return async ( req: ExpressRequest, @@ -122,6 +136,10 @@ export function makeAddMessageToConversationRoute({ const conversation = await loadConversation({ conversationIdString, conversations, + createConversation, + reqId, + req, + res, }); // --- MAX CONVERSATION LENGTH CHECK --- @@ -249,10 +267,35 @@ async function addMessagesToDatabase({ const loadConversation = async ({ conversationIdString, conversations, + createConversation, + reqId, + req, + res, }: { conversationIdString: string; conversations: ConversationsService; + createConversation?: AddMessageToConversationRouteParams["createConversation"]; + reqId: string; + req: ExpressRequest; + res: ExpressResponse; }) => { + // Create a new conversation if the conversationId is "null" + // and the route is configured to do so + if ( + createConversation?.createOnNullConversationId === true && + conversationIdString === "null" + ) { + logRequest({ + reqId, + message: stripIndents`Creating new conversation`, + }); + return await conversations.create({ + initialMessages: createConversation.initialMessages, + customData: createConversation.addCustomData + ? await createConversation.addCustomData(req, res) + : undefined, + }); + } const conversationId = toObjectId(conversationIdString); const conversation = await conversations.findById({ _id: conversationId, diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts index f854aaed9..d8695c8f5 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts @@ -200,6 +200,9 @@ describe("Conversations Router", () => { await createConversationReq({ app, origin }); expect(called).toBe(true); }); + it("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { + // TODO: implement + }); // Helpers /** diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts index 0ef9f9ac8..1f01392ae 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts @@ -190,6 +190,12 @@ export interface ConversationsRouterParams { If not specified, user comments may be of any length. */ maxUserCommentLength?: number; + + /** + Whether to create a new conversation if the message ID is "null" + on the addMessageToConversation route. + */ + createConversationOnNullMessageId: boolean; } export const rateLimitResponse = { @@ -228,6 +234,7 @@ export function makeConversationsRouter({ createConversationCustomData = addOriginAndIpToCustomData, addMessageToConversationCustomData = addOriginToCustomData, maxUserCommentLength, + createConversationOnNullMessageId, }: ConversationsRouterParams) { const conversationsRouter = Router(); // Set the customData and conversations on the response locals @@ -315,6 +322,11 @@ export function makeConversationsRouter({ addMessageToConversationCustomData, generateUserPrompt, filterPreviousMessages, + createConversation: { + createOnNullConversationId: createConversationOnNullMessageId, + addCustomData: createConversationCustomData, + initialMessages: [systemPrompt], + }, }); conversationsRouter.post( "/:conversationId/messages", From 8e86e295c96f1e8a348cd04f19e02eb1ab8309e4 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:23:10 -0400 Subject: [PATCH 02/19] update docs w new behavior --- docs/docs/server/openapi.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/docs/server/openapi.yaml b/docs/docs/server/openapi.yaml index 3dd79f50d..563d652f5 100644 --- a/docs/docs/server/openapi.yaml +++ b/docs/docs/server/openapi.yaml @@ -218,6 +218,7 @@ components: type: object required: - id + - conversationId - role - content - createdAt @@ -225,6 +226,9 @@ components: id: type: string description: The unique identifier for a message. + conversationId: + type: string + description: The unique identifier for a conversation. role: type: string enum: [user, assistant] @@ -284,6 +288,7 @@ components: - $ref: "#/components/schemas/StreamEventProcessing" - $ref: "#/components/schemas/StreamEventReferences" - $ref: "#/components/schemas/StreamEventFinished" + - $ref: "#/components/schemas/StreamEventConversationId" description: A server-sent event data payload for a streaming message. StreamEventDelta: description: Assistant response text. @@ -337,6 +342,16 @@ components: data: description: Message ID. type: string + StreamEventConversationId: + description: Conversation ID for the message. + type: object + properties: + type: + type: string + enum: [conversationId] + data: + description: Conversation ID. + type: string MessageComment: type: object properties: From 621f4143111d0d8d8078ed17ffdc1b01bef6879d Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:23:26 -0400 Subject: [PATCH 03/19] update streamer w/ convo id msg --- packages/mongodb-rag-core/src/DataStreamer.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/mongodb-rag-core/src/DataStreamer.ts b/packages/mongodb-rag-core/src/DataStreamer.ts index a2325dfd6..34c5ee6eb 100644 --- a/packages/mongodb-rag-core/src/DataStreamer.ts +++ b/packages/mongodb-rag-core/src/DataStreamer.ts @@ -90,6 +90,14 @@ export type FinishedStreamEvent = StreamEvent & { data: string; }; +/** + Event when server streams the conversation ID to the client. + */ +export type ConversationIdStreamEvent = StreamEvent & { + type: "conversationId"; + data: string; +}; + /** The event types streamed from the chat server to the client. */ @@ -98,7 +106,8 @@ export type SomeStreamEvent = | MetadataStreamEvent | ProcessingStreamEvent | ReferencesStreamEvent - | FinishedStreamEvent; + | FinishedStreamEvent + | ConversationIdStreamEvent; /** Service that streams data to the client. From c67249246e527d7b5eb3bc5b64676ccfdacde80a Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:24:44 -0400 Subject: [PATCH 04/19] update server conf --- packages/chatbot-server-mongodb-public/src/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/chatbot-server-mongodb-public/src/config.ts b/packages/chatbot-server-mongodb-public/src/config.ts index bc9a16010..dce026d5d 100644 --- a/packages/chatbot-server-mongodb-public/src/config.ts +++ b/packages/chatbot-server-mongodb-public/src/config.ts @@ -203,6 +203,7 @@ export const config: AppConfig = { maxUserCommentLength: 500, conversations, maxInputLengthCharacters: 3000, + createConversationOnNullMessageId: true, }, maxRequestTimeoutMs: 60000, corsOptions: { From 70d0d790c75984163709b47267ff7b51ea304c03 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:24:55 -0400 Subject: [PATCH 05/19] add implementation --- .../addMessageToConversation.test.ts | 16 +++++++++++++++- .../conversations/addMessageToConversation.ts | 9 ++++++++- .../routes/conversations/conversationsRouter.ts | 14 ++++++++------ .../src/routes/conversations/utils.ts | 10 +++++++--- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index b8d302feb..87c349e92 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -17,6 +17,7 @@ import { AddMessageRequestBody, DEFAULT_MAX_INPUT_LENGTH, DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION, + makeAddMessageToConversationRoute, } from "./addMessageToConversation"; import { ApiConversation, ApiMessage } from "./utils"; import { stripIndent } from "common-tags"; @@ -27,6 +28,7 @@ import { AppConfig } from "../../app"; import { AzureKeyCredential, OpenAIClient } from "@azure/openai"; import { strict as assert } from "assert"; import { NO_VECTOR_CONTENT, REJECT_QUERY_CONTENT } from "../../test/testConfig"; +import { add } from "winston"; const { OPENAI_CHAT_COMPLETION_DEPLOYMENT, OPENAI_ENDPOINT } = assertEnvVars(CORE_ENV_VARS); @@ -401,7 +403,19 @@ describe("POST /conversations/:conversationId/messages", () => { }); describe("create conversation with 'null' conversationId", () => { - // TODO + const mockCustomDataFunction = jest.fn(); + + test("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { + const route = await makeAddMessageToConversationRoute({ + ...appConfig.conversationsRouterConfig, + createConversation: { + createOnNullConversationId: true, + initialMessages: [systemPrompt], + addCustomData: mockCustomDataFunction, + }, + }); + // TODO + }); }); }); diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts index a9d4e40d0..de3916d47 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts @@ -188,11 +188,18 @@ export function makeAddMessageToConversationRoute({ const dbAssistantMessage = dbNewMessages[dbNewMessages.length - 1]; assert(dbAssistantMessage !== undefined, "No assistant message found"); - const apiRes = convertMessageFromDbToApi(dbAssistantMessage); + const apiRes = convertMessageFromDbToApi( + dbAssistantMessage, + conversation._id + ); if (!shouldStream) { return res.status(200).json(apiRes); } else { + dataStreamer.streamData({ + type: "conversationId", + data: conversation._id.toString(), + }); dataStreamer.streamData({ type: "finished", data: apiRes.id, diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts index 1f01392ae..84806b911 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts @@ -195,7 +195,7 @@ export interface ConversationsRouterParams { Whether to create a new conversation if the message ID is "null" on the addMessageToConversation route. */ - createConversationOnNullMessageId: boolean; + createConversationOnNullMessageId?: boolean; } export const rateLimitResponse = { @@ -322,11 +322,13 @@ export function makeConversationsRouter({ addMessageToConversationCustomData, generateUserPrompt, filterPreviousMessages, - createConversation: { - createOnNullConversationId: createConversationOnNullMessageId, - addCustomData: createConversationCustomData, - initialMessages: [systemPrompt], - }, + createConversation: createConversationOnNullMessageId + ? { + createOnNullConversationId: createConversationOnNullMessageId, + addCustomData: createConversationCustomData, + initialMessages: [systemPrompt], + } + : undefined, }); conversationsRouter.post( "/:conversationId/messages", diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts b/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts index 3a17bf7db..32e1eb248 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts @@ -1,6 +1,6 @@ import { isIP } from "net"; import { Address6 } from "ip-address"; -import { Conversation, Message, References } from "mongodb-rag-core"; +import { Conversation, Message, ObjectId, References } from "mongodb-rag-core"; import { z } from "zod"; export type ApiMessage = z.infer; @@ -21,10 +21,14 @@ export const ApiConversation = z.object({ createdAt: z.number(), }); -export function convertMessageFromDbToApi(message: Message): ApiMessage { +export function convertMessageFromDbToApi( + message: Message, + conversationId: ObjectId +): ApiMessage { const { id, createdAt, role, content } = message; const apiMessage = { id: id.toString(), + conversationId: conversationId.toString(), role, content, createdAt: createdAt.getTime(), @@ -71,7 +75,7 @@ export function convertConversationFromDbToApi( createdAt: conversation.createdAt.getTime(), messages: conversation.messages .filter(isMessageAllowedInApiResponse) - .map(convertMessageFromDbToApi), + .map((message) => convertMessageFromDbToApi(message, conversation._id)), }; } From f3212e50fc0290d4c9a1c9593ec08243910781d5 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:44:24 -0400 Subject: [PATCH 06/19] make default behavior --- packages/chatbot-server-mongodb-public/src/config.ts | 1 - .../src/routes/conversations/conversationsRouter.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/chatbot-server-mongodb-public/src/config.ts b/packages/chatbot-server-mongodb-public/src/config.ts index dce026d5d..bc9a16010 100644 --- a/packages/chatbot-server-mongodb-public/src/config.ts +++ b/packages/chatbot-server-mongodb-public/src/config.ts @@ -203,7 +203,6 @@ export const config: AppConfig = { maxUserCommentLength: 500, conversations, maxInputLengthCharacters: 3000, - createConversationOnNullMessageId: true, }, maxRequestTimeoutMs: 60000, corsOptions: { diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts index 84806b911..900f6bb4f 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts @@ -194,6 +194,8 @@ export interface ConversationsRouterParams { /** Whether to create a new conversation if the message ID is "null" on the addMessageToConversation route. + + @default true */ createConversationOnNullMessageId?: boolean; } @@ -234,7 +236,7 @@ export function makeConversationsRouter({ createConversationCustomData = addOriginAndIpToCustomData, addMessageToConversationCustomData = addOriginToCustomData, maxUserCommentLength, - createConversationOnNullMessageId, + createConversationOnNullMessageId = true, }: ConversationsRouterParams) { const conversationsRouter = Router(); // Set the customData and conversations on the response locals From 47ecdd2a030feba1c245d5f707a295a5547ec4a8 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:50:31 -0400 Subject: [PATCH 07/19] system prompt refactor --- .../conversations/addMessageToConversation.test.ts | 2 +- .../routes/conversations/addMessageToConversation.ts | 10 ++++++---- .../src/routes/conversations/conversationsRouter.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index 87c349e92..c5c7efbeb 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -410,7 +410,7 @@ describe("POST /conversations/:conversationId/messages", () => { ...appConfig.conversationsRouterConfig, createConversation: { createOnNullConversationId: true, - initialMessages: [systemPrompt], + systemMessage: systemPrompt, addCustomData: mockCustomDataFunction, }, }); diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts index de3916d47..e20c04c09 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts @@ -4,7 +4,7 @@ import { Request as ExpressRequest, Response as ExpressResponse, } from "express"; -import { ObjectId } from "mongodb-rag-core"; +import { ObjectId, SystemMessage } from "mongodb-rag-core"; import { ConversationsService, Conversation, @@ -78,10 +78,10 @@ export interface AddMessageToConversationRouteParams { */ addCustomData?: AddCustomDataFunc; /** - The initial messages to add to the new conversation + The system message to add to the new conversation when it is created. */ - initialMessages?: SomeMessage[]; + systemMessage?: SystemMessage; }; } @@ -297,7 +297,9 @@ const loadConversation = async ({ message: stripIndents`Creating new conversation`, }); return await conversations.create({ - initialMessages: createConversation.initialMessages, + initialMessages: createConversation.systemMessage + ? [createConversation.systemMessage] + : undefined, customData: createConversation.addCustomData ? await createConversation.addCustomData(req, res) : undefined, diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts index 900f6bb4f..2d8796b9f 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.ts @@ -328,7 +328,7 @@ export function makeConversationsRouter({ ? { createOnNullConversationId: createConversationOnNullMessageId, addCustomData: createConversationCustomData, - initialMessages: [systemPrompt], + systemMessage: systemPrompt, } : undefined, }); From 507bc768d19709fc171fe13f9fddab0e12e22929 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:17:47 -0400 Subject: [PATCH 08/19] test mdb in memory --- package-lock.json | 49 ++++++++++--------- packages/mongodb-chatbot-server/package.json | 2 +- .../addMessageToConversation.test.ts | 34 ++++++++++--- .../src/test/testConfig.ts | 22 +++++---- .../src/test/testHelpers.ts | 13 +++-- 5 files changed, 73 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97392d264..49112dd98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18074,6 +18074,8 @@ }, "node_modules/async-mutex": { "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", "dev": true, "license": "MIT", "dependencies": { @@ -19123,6 +19125,8 @@ }, "node_modules/bson": { "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -19134,6 +19138,8 @@ }, "node_modules/bson/node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "funding": [ { @@ -28618,6 +28624,8 @@ }, "node_modules/md5-file": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", + "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", "dev": true, "license": "MIT", "bin": { @@ -30046,6 +30054,8 @@ }, "node_modules/mongodb": { "version": "4.17.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", + "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -30122,12 +30132,14 @@ } }, "node_modules/mongodb-memory-server": { - "version": "8.16.0", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-8.16.1.tgz", + "integrity": "sha512-Zje3i+xKN+nxALkOOraDfIvc9X8mNy979IvJdjUghvf5PbwvX5ZPr5gUtCcmzz2VRj97WsZbdUSkxny+GXZTIA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "mongodb-memory-server-core": "8.16.0", + "mongodb-memory-server-core": "8.16.1", "tslib": "^2.6.1" }, "engines": { @@ -30135,7 +30147,9 @@ } }, "node_modules/mongodb-memory-server-core": { - "version": "8.16.0", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-8.16.1.tgz", + "integrity": "sha512-skRGr7vzVIyefKm/YTn73sWI/7ghIb+gBxYNt42kGO7zeOfy+3S2Xg3kHYLkBz1IrOmTyV2HpFVzbZ1HF8grsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -30161,6 +30175,8 @@ }, "node_modules/mongodb-memory-server-core/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -30170,24 +30186,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mongodb-memory-server-core/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mongodb-memory-server-core/node_modules/semver": { - "version": "7.6.0", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -30195,11 +30199,6 @@ "node": ">=10" } }, - "node_modules/mongodb-memory-server-core/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, "node_modules/mongodb-rag-core": { "resolved": "packages/mongodb-rag-core", "link": true @@ -30347,6 +30346,8 @@ }, "node_modules/new-find-package-json": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", "dev": true, "license": "MIT", "dependencies": { @@ -43173,7 +43174,7 @@ "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^27.2.1", "jest": "^29.6.1", - "mongodb-memory-server": "^8.12.2", + "mongodb-memory-server": "^8.16.1", "node-mocks-http": "^1.12.2", "nodemon": "^3.0.1", "prettier": "^2.8.7", diff --git a/packages/mongodb-chatbot-server/package.json b/packages/mongodb-chatbot-server/package.json index bab817398..e7ca476c3 100644 --- a/packages/mongodb-chatbot-server/package.json +++ b/packages/mongodb-chatbot-server/package.json @@ -80,7 +80,7 @@ "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^27.2.1", "jest": "^29.6.1", - "mongodb-memory-server": "^8.12.2", + "mongodb-memory-server": "^8.16.1", "node-mocks-http": "^1.12.2", "nodemon": "^3.0.1", "prettier": "^2.8.7", diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index c5c7efbeb..358918921 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -403,18 +403,36 @@ describe("POST /conversations/:conversationId/messages", () => { }); describe("create conversation with 'null' conversationId", () => { + // TODO const mockCustomDataFunction = jest.fn(); test("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { - const route = await makeAddMessageToConversationRoute({ - ...appConfig.conversationsRouterConfig, - createConversation: { - createOnNullConversationId: true, - systemMessage: systemPrompt, - addCustomData: mockCustomDataFunction, - }, + const res = await request(app) + .post(DEFAULT_API_PREFIX + `/conversations/null/messages`) + .send({ + message: "hello", + }); + console.log(res.body); + expect(res.statusCode).toEqual(200); + expect(res.body).toMatchObject({ + // content is some string + content: expect.any(String), + }); + }); + }); + describe("create converastion with 'null' conversationId", () => { + test("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { + const res = await request(app) + .post(DEFAULT_API_PREFIX + `/conversations/null/messages`) + .send({ + message: "hello", + }); + console.log(res.body); + expect(res.statusCode).toEqual(200); + expect(res.body).toMatchObject({ + // content is some string + content: expect.any(String), }); - // TODO }); }); }); diff --git a/packages/mongodb-chatbot-server/src/test/testConfig.ts b/packages/mongodb-chatbot-server/src/test/testConfig.ts index 36a86e7db..c683eefb1 100644 --- a/packages/mongodb-chatbot-server/src/test/testConfig.ts +++ b/packages/mongodb-chatbot-server/src/test/testConfig.ts @@ -17,6 +17,7 @@ import { makeOpenAiChatLlm, SystemPrompt, UserMessage, + defaultConversationConstants, } from "mongodb-rag-core"; import { stripIndents } from "common-tags"; import { AppConfig } from "../app"; @@ -82,6 +83,11 @@ export const verifiedAnswerStore = makeMongoDbVerifiedAnswerStore({ collectionName: "verified_answers", }); +afterAll(async () => { + await embeddedContentStore.close(); + await verifiedAnswerStore.close(); +}); + export const embedder = makeOpenAiEmbedder({ openAiClient, deployment: OPENAI_EMBEDDING_DEPLOYMENT, @@ -148,7 +154,7 @@ export const fakeGenerateUserPrompt: GenerateUserPromptFunc = async (args) => { rejectQuery: args.userMessageText === REJECT_QUERY_CONTENT, staticResponse: noVectorContent ? { - content: conversations.conversationConstants.NO_RELEVANT_CONTENT, + content: defaultConversationConstants.NO_RELEVANT_CONTENT, role: "assistant", references: [], } @@ -187,12 +193,6 @@ export const llm = makeOpenAiChatLlm({ }, }); -export const mongodb = new MongoClient(MONGODB_CONNECTION_URI); - -export const conversations = makeMongoDbConversationsService( - mongodb.db(MONGODB_DATABASE_NAME) -); - /** MongoDB Chatbot implementation of {@link MakeReferenceLinksFunc}. Returns references that look like: @@ -217,10 +217,14 @@ export function makeMongoDbReferences(chunks: EmbeddedContent[]) { export const filterPrevious12Messages = makeFilterNPreviousMessages(12); -export const config: AppConfig = { +export const config: Omit & { + conversationsRouterConfig: Omit< + AppConfig["conversationsRouterConfig"], + "conversations" + >; +} = { conversationsRouterConfig: { llm, - conversations, generateUserPrompt: fakeGenerateUserPrompt, filterPreviousMessages: filterPrevious12Messages, systemPrompt, diff --git a/packages/mongodb-chatbot-server/src/test/testHelpers.ts b/packages/mongodb-chatbot-server/src/test/testHelpers.ts index 0fa339ece..940d622d5 100644 --- a/packages/mongodb-chatbot-server/src/test/testHelpers.ts +++ b/packages/mongodb-chatbot-server/src/test/testHelpers.ts @@ -5,21 +5,24 @@ import { makeMongoDbConversationsService, } from "mongodb-rag-core"; import { AppConfig, makeApp } from "../app"; -import { MONGODB_CONNECTION_URI, config, systemPrompt } from "./testConfig"; +import { config, systemPrompt } from "./testConfig"; +import { MongoMemoryServer } from "mongodb-memory-server"; let mongoClient: MongoClient | undefined; let mongodb: Db | undefined; -let testDbName: string | undefined; - +let mongod: MongoMemoryServer | undefined; beforeAll(async () => { - testDbName = `conversations-test-${Date.now()}`; - mongoClient = new MongoClient(MONGODB_CONNECTION_URI); + mongod = await MongoMemoryServer.create(); + const uri = mongod.getUri(); + const testDbName = `conversations-test-${Date.now()}`; + mongoClient = new MongoClient(uri); mongodb = mongoClient.db(testDbName); }); afterAll(async () => { await mongodb?.dropDatabase(); await mongoClient?.close(); + await mongod?.stop(); }); export function makeTestAppConfig(defaultConfigOverrides?: Partial) { From ab7a0d74e2494895a1a1b9410e676c0d922d9498 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:47:20 -0400 Subject: [PATCH 09/19] udpate tests w memdb --- .../mongodb-chatbot-server/src/app.test.ts | 2 +- .../addMessageToConversation.test.ts | 68 ++++++++++++------- .../conversations/commentMessage.test.ts | 3 +- .../conversations/conversationsRouter.test.ts | 4 +- .../conversations/createConversation.test.ts | 4 +- .../conversations/getConversation.test.ts | 5 +- .../src/routes/conversations/utils.test.ts | 42 ++++++++---- .../src/routes/conversations/utils.ts | 6 +- .../src/test/testConfig.ts | 58 ++++++++++------ .../src/test/testHelpers.ts | 60 ++++++---------- 10 files changed, 142 insertions(+), 110 deletions(-) diff --git a/packages/mongodb-chatbot-server/src/app.test.ts b/packages/mongodb-chatbot-server/src/app.test.ts index eab4a8a9c..73e397e5e 100644 --- a/packages/mongodb-chatbot-server/src/app.test.ts +++ b/packages/mongodb-chatbot-server/src/app.test.ts @@ -7,7 +7,7 @@ import { makeTestAppConfig } from "./test/testHelpers"; describe("App", () => { let app: Express; beforeAll(async () => { - const { appConfig } = makeTestAppConfig(); + const { appConfig } = await makeTestAppConfig(); app = await makeApp({ ...appConfig, corsOptions: { diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index 358918921..ef88d37f5 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -11,6 +11,7 @@ import { defaultConversationConstants, Message, makeOpenAiChatLlm, + SomeMessage, } from "mongodb-rag-core"; import { Express } from "express"; import { @@ -28,7 +29,6 @@ import { AppConfig } from "../../app"; import { AzureKeyCredential, OpenAIClient } from "@azure/openai"; import { strict as assert } from "assert"; import { NO_VECTOR_CONTENT, REJECT_QUERY_CONTENT } from "../../test/testConfig"; -import { add } from "winston"; const { OPENAI_CHAT_COMPLETION_DEPLOYMENT, OPENAI_ENDPOINT } = assertEnvVars(CORE_ENV_VARS); @@ -40,9 +40,14 @@ describe("POST /conversations/:conversationId/messages", () => { let conversations: ConversationsService; let app: Express; let appConfig: AppConfig; + const mockCustomDataFunction = jest.fn(); beforeAll(async () => { - ({ ipAddress, origin, mongodb, app, appConfig } = await makeTestApp()); + ({ ipAddress, origin, app, appConfig, mongodb } = await makeTestApp({ + conversationsRouterConfig: { + createConversationCustomData: mockCustomDataFunction, + }, + })); ({ conversationsRouterConfig: { conversations }, } = appConfig); @@ -252,16 +257,21 @@ describe("POST /conversations/:conversationId/messages", () => { test("Should respond 400 if number of messages in conversation exceeds limit", async () => { const { _id } = await conversations.create(); // Init conversation with max length - for await (const i of Array(DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION)) { - await conversations.addConversationMessage({ - conversationId: _id, - message: { + const messages = Array.from( + { + length: DEFAULT_MAX_USER_MESSAGES_IN_CONVERSATION, + }, + (_, i) => { + return { content: `message ${i}`, role: "user", - embedding: [1, 2, 3], - }, - }); - } + } satisfies SomeMessage; + } + ); + await conversations.addManyConversationMessages({ + conversationId: _id, + messages, + }); const res = await request(app) .post(endpointUrl.replace(":conversationId", _id.toString())) @@ -341,7 +351,6 @@ describe("POST /conversations/:conversationId/messages", () => { .set("X-FORWARDED-FOR", ipAddress) .set("Origin", origin) .send({ message: NO_VECTOR_CONTENT }); - console.log(response.body); expect(response.statusCode).toBe(200); expect(response.body.references).toStrictEqual([]); expect(response.body.content).toEqual( @@ -368,7 +377,7 @@ describe("POST /conversations/:conversationId/messages", () => { app: Express; let testMongo: Db; beforeEach(async () => { - const { mongodb, appConfig } = makeTestAppConfig(); + const { mongodb, appConfig } = await makeTestAppConfig(); testMongo = mongodb; ({ app } = await makeTestApp({ ...appConfig, @@ -403,35 +412,44 @@ describe("POST /conversations/:conversationId/messages", () => { }); describe("create conversation with 'null' conversationId", () => { - // TODO - const mockCustomDataFunction = jest.fn(); - test("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { const res = await request(app) .post(DEFAULT_API_PREFIX + `/conversations/null/messages`) + .set("X-FORWARDED-FOR", ipAddress) + .set("Origin", origin) .send({ message: "hello", }); - console.log(res.body); expect(res.statusCode).toEqual(200); expect(res.body).toMatchObject({ - // content is some string content: expect.any(String), + conversationId: expect.any(String), + role: "assistant", }); + expect(mockCustomDataFunction).toHaveBeenCalled(); + const conversation = await conversations.findById({ + _id: ObjectId.createFromHexString(res.body.conversationId), + }); + expect(conversation?._id.toString()).toEqual(res.body.conversationId); + expect(conversation?.messages).toHaveLength(3); + expect(conversation?.messages[0]).toMatchObject(systemPrompt); }); - }); - describe("create converastion with 'null' conversationId", () => { - test("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { - const res = await request(app) + test("should not create a new conversation with 'null' value for addMessageToConversation if NOT configured", async () => { + const { app: appWithoutCustomData } = await makeTestApp({ + conversationsRouterConfig: { + createConversationOnNullMessageId: false, + }, + }); + const res = await request(appWithoutCustomData) .post(DEFAULT_API_PREFIX + `/conversations/null/messages`) + .set("X-FORWARDED-FOR", ipAddress) + .set("Origin", origin) .send({ message: "hello", }); - console.log(res.body); - expect(res.statusCode).toEqual(200); + expect(res.statusCode).toEqual(400); expect(res.body).toMatchObject({ - // content is some string - content: expect.any(String), + error: expect.any(String), }); }); }); diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/commentMessage.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/commentMessage.test.ts index f61545fc1..43c8f4673 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/commentMessage.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/commentMessage.test.ts @@ -12,7 +12,7 @@ import { Express } from "express"; import { DEFAULT_API_PREFIX } from "../../app"; import { makeTestApp } from "../../test/testHelpers"; import { AppConfig } from "../../app"; -import { systemPrompt, config as testConfig } from "../../test/testConfig"; +import { systemPrompt, makeDefaultConfig } from "../../test/testConfig"; jest.setTimeout(100000); @@ -273,6 +273,7 @@ describe("POST /conversations/:conversationId/messages/:messageId/comment", () = }); it("Should enforce maximum comment length (if configured)", async () => { + const testConfig = await makeDefaultConfig(); const { app, ipAddress, appConfig, origin } = await makeTestApp({ ...testConfig, conversationsRouterConfig: { diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts index d8695c8f5..7c86a8dc2 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts @@ -17,8 +17,8 @@ describe("Conversations Router", () => { DEFAULT_API_PREFIX + "/conversations/:conversationId/messages"; let appConfig: AppConfig; - beforeAll(() => { - ({ appConfig } = makeTestAppConfig()); + beforeAll(async () => { + ({ appConfig } = await makeTestAppConfig()); }); test("Should apply conversation router rate limit", async () => { const { app, origin } = await makeTestApp({ diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/createConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/createConversation.test.ts index 2c0b3a77e..2ce3ec797 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/createConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/createConversation.test.ts @@ -10,8 +10,8 @@ const CONVERSATIONS_API_V1_PREFIX = DEFAULT_API_PREFIX + "/conversations"; describe("POST /conversations", () => { let appConfig: AppConfig; let mongodb: Db; - beforeAll(() => { - ({ appConfig, mongodb } = makeTestAppConfig()); + beforeAll(async () => { + ({ appConfig, mongodb } = await makeTestAppConfig()); }); it("should respond 200 and create a conversation", async () => { diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/getConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/getConversation.test.ts index 078bd5d2e..cfd2899d7 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/getConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/getConversation.test.ts @@ -8,8 +8,9 @@ const CONVERSATIONS_API_V1_PREFIX = DEFAULT_API_PREFIX + "/conversations"; describe("GET /conversations/:conversationId", () => { let appConfig: AppConfig; let conversations: ConversationsService; - beforeAll(() => { - ({ appConfig, conversations } = makeTestAppConfig()); + beforeAll(async () => { + ({ appConfig } = await makeTestAppConfig()); + conversations = appConfig.conversationsRouterConfig.conversations; }); it("should return 400 when the conversation ID is invalid", async () => { diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts index 44875e788..3a907360d 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts @@ -97,43 +97,51 @@ describe("Data Conversion Functions", () => { functionResultMessage, assistantMessage, ] = exampleConversationInDatabase.messages; + const convoId = new ObjectId(); - expect(convertMessageFromDbToApi(systemMessage)).toEqual({ + expect(convertMessageFromDbToApi(systemMessage, convoId)).toEqual({ id: "65ca766ab564b694eba8c330", + conversationId: convoId.toString(), role: "system", content: "You are an expert conversationalist! You can chat about anything with anyone.", createdAt: 1704067201000, }); - expect(convertMessageFromDbToApi(userMessage)).toEqual({ + expect(convertMessageFromDbToApi(userMessage, convoId)).toEqual({ id: "65ca76775a57e51c3b4c286d", + conversationId: convoId.toString(), role: "user", content: "Hello! I'm looking for a new book to read. I like fantasy and science fiction novels. Can you recommend one?", createdAt: 1704067242000, }); - expect(convertMessageFromDbToApi(functionCallMessage)).toEqual({ + expect(convertMessageFromDbToApi(functionCallMessage, convoId)).toEqual({ id: "65ca767e30116ce068e17bb5", + conversationId: convoId.toString(), role: "assistant", content: "", createdAt: 1704067245000, }); - expect(convertMessageFromDbToApi(functionResultMessage)).toEqual({ - id: "65ca768341f9ea61d048aaa8", - role: "function", - content: JSON.stringify([ - { title: "The Way of Kings", author: "Brandon Sanderson" }, - { title: "Neuromancer", author: "William Gibson" }, - { title: "Snow Crash", author: "Neal Stephenson" }, - ]), - createdAt: 1704067247000, - }); + expect(convertMessageFromDbToApi(functionResultMessage, convoId)).toEqual( + { + id: "65ca768341f9ea61d048aaa8", + conversationId: convoId.toString(), + role: "function", + content: JSON.stringify([ + { title: "The Way of Kings", author: "Brandon Sanderson" }, + { title: "Neuromancer", author: "William Gibson" }, + { title: "Snow Crash", author: "Neal Stephenson" }, + ]), + createdAt: 1704067247000, + } + ); - expect(convertMessageFromDbToApi(assistantMessage)).toEqual({ + expect(convertMessageFromDbToApi(assistantMessage, convoId)).toEqual({ id: "65ca76874e1df9cf2742bf86", + conversationId: convoId.toString(), role: "assistant", content: `I recommend "The Way of Kings" by Brandon Sanderson. You may also enjoy "Neuromancer" by William Gibson or "Snow Crash" by Neal Stephenson.`, createdAt: 1704067252000, @@ -141,6 +149,12 @@ describe("Data Conversion Functions", () => { references: undefined, }); }); + test("do not include conversationId if not provided", () => { + const message = exampleConversationInDatabase.messages[0]; + expect(convertMessageFromDbToApi(message)).not.toHaveProperty( + "conversationId" + ); + }); }); describe("convertConversationFromDbToApi", () => { diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts b/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts index 32e1eb248..b96d53ca8 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts @@ -23,12 +23,12 @@ export const ApiConversation = z.object({ export function convertMessageFromDbToApi( message: Message, - conversationId: ObjectId + conversationId?: ObjectId ): ApiMessage { const { id, createdAt, role, content } = message; const apiMessage = { id: id.toString(), - conversationId: conversationId.toString(), + ...(conversationId ? { conversationId: conversationId.toString() } : {}), role, content, createdAt: createdAt.getTime(), @@ -75,7 +75,7 @@ export function convertConversationFromDbToApi( createdAt: conversation.createdAt.getTime(), messages: conversation.messages .filter(isMessageAllowedInApiResponse) - .map((message) => convertMessageFromDbToApi(message, conversation._id)), + .map((message) => convertMessageFromDbToApi(message)), }; } diff --git a/packages/mongodb-chatbot-server/src/test/testConfig.ts b/packages/mongodb-chatbot-server/src/test/testConfig.ts index c683eefb1..28917a0fb 100644 --- a/packages/mongodb-chatbot-server/src/test/testConfig.ts +++ b/packages/mongodb-chatbot-server/src/test/testConfig.ts @@ -18,6 +18,7 @@ import { SystemPrompt, UserMessage, defaultConversationConstants, + Db, } from "mongodb-rag-core"; import { stripIndents } from "common-tags"; import { AppConfig } from "../app"; @@ -29,7 +30,26 @@ import { makeFilterNPreviousMessages, } from "../processors"; import { makeDefaultReferenceLinks } from "../processors/makeDefaultReferenceLinks"; +import { MongoMemoryServer } from "mongodb-memory-server"; + +let mongoClient: MongoClient | undefined; +export let memoryDb: Db; +let mongod: MongoMemoryServer | undefined; +beforeAll(async () => { + mongod = await MongoMemoryServer.create(); + const uri = mongod.getUri(); + const testDbName = `conversations-test-${Date.now()}`; + mongoClient = new MongoClient(uri); + memoryDb = mongoClient.db(testDbName); +}); +afterAll(async () => { + await memoryDb?.dropDatabase(); + await mongoClient?.close(); + await mongod?.stop(); + await embeddedContentStore.close(); + await verifiedAnswerStore.close(); +}); export const { MONGODB_CONNECTION_URI, MONGODB_DATABASE_NAME, @@ -83,11 +103,6 @@ export const verifiedAnswerStore = makeMongoDbVerifiedAnswerStore({ collectionName: "verified_answers", }); -afterAll(async () => { - await embeddedContentStore.close(); - await verifiedAnswerStore.close(); -}); - export const embedder = makeOpenAiEmbedder({ openAiClient, deployment: OPENAI_EMBEDDING_DEPLOYMENT, @@ -217,20 +232,19 @@ export function makeMongoDbReferences(chunks: EmbeddedContent[]) { export const filterPrevious12Messages = makeFilterNPreviousMessages(12); -export const config: Omit & { - conversationsRouterConfig: Omit< - AppConfig["conversationsRouterConfig"], - "conversations" - >; -} = { - conversationsRouterConfig: { - llm, - generateUserPrompt: fakeGenerateUserPrompt, - filterPreviousMessages: filterPrevious12Messages, - systemPrompt, - }, - maxRequestTimeoutMs: 30000, - corsOptions: { - origin: allowedOrigins, - }, -}; +export async function makeDefaultConfig(): Promise { + const conversations = makeMongoDbConversationsService(memoryDb); + return { + conversationsRouterConfig: { + llm, + generateUserPrompt: fakeGenerateUserPrompt, + filterPreviousMessages: filterPrevious12Messages, + systemPrompt, + conversations, + }, + maxRequestTimeoutMs: 30000, + corsOptions: { + origin: allowedOrigins, + }, + }; +} diff --git a/packages/mongodb-chatbot-server/src/test/testHelpers.ts b/packages/mongodb-chatbot-server/src/test/testHelpers.ts index 940d622d5..0455abcd9 100644 --- a/packages/mongodb-chatbot-server/src/test/testHelpers.ts +++ b/packages/mongodb-chatbot-server/src/test/testHelpers.ts @@ -1,51 +1,35 @@ import { strict as assert } from "assert"; -import { - MongoClient, - Db, - makeMongoDbConversationsService, -} from "mongodb-rag-core"; import { AppConfig, makeApp } from "../app"; -import { config, systemPrompt } from "./testConfig"; -import { MongoMemoryServer } from "mongodb-memory-server"; +import { makeDefaultConfig, memoryDb, systemPrompt } from "./testConfig"; -let mongoClient: MongoClient | undefined; -let mongodb: Db | undefined; -let mongod: MongoMemoryServer | undefined; -beforeAll(async () => { - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - const testDbName = `conversations-test-${Date.now()}`; - mongoClient = new MongoClient(uri); - mongodb = mongoClient.db(testDbName); -}); - -afterAll(async () => { - await mongodb?.dropDatabase(); - await mongoClient?.close(); - await mongod?.stop(); -}); - -export function makeTestAppConfig(defaultConfigOverrides?: Partial) { - assert(mongodb !== undefined); - assert(mongoClient !== undefined); - - const conversations = makeMongoDbConversationsService(mongodb); +export async function makeTestAppConfig( + defaultConfigOverrides?: PartialAppConfig +) { + const config = await makeDefaultConfig(); const appConfig: AppConfig = { ...config, - conversationsRouterConfig: { - ...config.conversationsRouterConfig, - conversations, - }, + ...(defaultConfigOverrides ?? {}), async expressAppConfig(app) { app.get("/hello", (_req, res) => { res.send({ foo: "bar" }); }); }, - ...(defaultConfigOverrides ?? {}), + conversationsRouterConfig: { + ...config.conversationsRouterConfig, + ...(defaultConfigOverrides?.conversationsRouterConfig ?? {}), + }, }; - return { appConfig, mongodb, conversations, systemPrompt, mongoClient }; + assert(memoryDb, "memoryDb must be defined"); + return { appConfig, systemPrompt, mongodb: memoryDb }; } +export type PartialAppConfig = Omit< + Partial, + "conversationsRouterConfig" +> & { + conversationsRouterConfig?: Partial; +}; + export const TEST_ORIGIN = "http://localhost:5173"; /** @@ -53,12 +37,12 @@ export const TEST_ORIGIN = "http://localhost:5173"; before `beforeAll()`. @param defaultConfigOverrides - optional overrides for default app config */ -export async function makeTestApp(defaultConfigOverrides?: Partial) { +export async function makeTestApp(defaultConfigOverrides?: PartialAppConfig) { // ip address for local host const ipAddress = "127.0.0.1"; const origin = TEST_ORIGIN; - const { appConfig, systemPrompt, mongodb, conversations } = makeTestAppConfig( + const { appConfig, systemPrompt, mongodb } = await makeTestAppConfig( defaultConfigOverrides ); const app = await makeApp(appConfig); @@ -68,7 +52,7 @@ export async function makeTestApp(defaultConfigOverrides?: Partial) { origin, appConfig, app, - conversations, + conversations: appConfig.conversationsRouterConfig.conversations, mongodb, systemPrompt, }; From 5f03573d004e586ca30cf3db914041913e1be50e Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:57:23 -0400 Subject: [PATCH 10/19] update node env for fix --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 0bb3742a3..d405f9464 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ trigger: steps: - name: test - image: node:18 + image: node:20 commands: - npm ci - npm run build From 89f71de8bf7e9183d718a7011b5aee722005c7ab Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:08:34 -0400 Subject: [PATCH 11/19] use alpine build --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index d405f9464..6e68ad287 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ trigger: steps: - name: test - image: node:20 + image: node:20-alpine commands: - npm ci - npm run build From 3e83ec212641778d8afde591311bde924fa27c9e Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:18:16 -0400 Subject: [PATCH 12/19] Add libcrpyto --- .drone.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 6e68ad287..fa1547697 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,8 +15,9 @@ trigger: steps: - name: test - image: node:20-alpine + image: node:20 commands: + - sudo apt-get install -y libssl-dev - npm ci - npm run build - npm run lint From 4ce6abf7756e60340cdaa456633a4dc2eb96442b Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:21:52 -0400 Subject: [PATCH 13/19] remove sudo --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index fa1547697..d35e3e493 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,7 +17,7 @@ steps: - name: test image: node:20 commands: - - sudo apt-get install -y libssl-dev + - apt-get install -y libssl-dev - npm ci - npm run build - npm run lint From ddd2e88409781d4ef98d8645de8a201efe8cc47a Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:43:06 -0400 Subject: [PATCH 14/19] try nother fix --- .drone.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index d35e3e493..d7aa7ad55 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,7 +17,8 @@ steps: - name: test image: node:20 commands: - - apt-get install -y libssl-dev + - wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb + - dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb - npm ci - npm run build - npm run lint From 1e149598037afe25914886fbe5b37aa3d882d151 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:53:45 -0400 Subject: [PATCH 15/19] try again w/ node 18 --- .drone.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index d7aa7ad55..ccd40c313 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,8 +15,9 @@ trigger: steps: - name: test - image: node:20 + image: node:18 commands: + # Getting libssl to use mongodb-memory-server per https://github.com/typegoose/mongodb-memory-server/issues/480#issuecomment-1488548395 - wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb - dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb - npm ci From bd37d9874e3b5677218ce21f60de90433e6f9842 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:14:31 -0400 Subject: [PATCH 16/19] test streamed response --- .../src/routes/conversations/addMessageToConversation.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index ef88d37f5..f1ea24b26 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -174,6 +174,7 @@ describe("POST /conversations/:conversationId/messages", () => { expect(res.text).toContain(`data: {"type":"delta","data":"`); expect(res.text).toContain(`data: {"type":"references","data":[{`); expect(res.text).toContain(`data: {"type":"finished","data":"`); + expect(res.text).toContain(`data: {"type":"conversationId","data":"`); }); it("should stream two requests concurrently", async () => { const newConvoId1 = await createNewConversation(app, ipAddress); From f189b0f3a48c1fe412169376927bd0ff7749cf2c Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:29:22 -0400 Subject: [PATCH 17/19] implement NL feedback --- .../addMessageToConversation.test.ts | 2 +- .../conversations/addMessageToConversation.ts | 23 +++++++------- .../conversations/conversationsRouter.test.ts | 30 ++++++++++++++++++- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index f1ea24b26..c6ea4cd16 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -217,7 +217,7 @@ describe("POST /conversations/:conversationId/messages", () => { }); expect(res.statusCode).toEqual(400); expect(res.body).toStrictEqual({ - error: `Invalid ObjectId string: ${notAValidId}`, + error: `Invalid conversationId: ${notAValidId}`, }); }); diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts index e20c04c09..f1fa62692 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts @@ -305,7 +305,17 @@ const loadConversation = async ({ : undefined, }); } - const conversationId = toObjectId(conversationIdString); + + // Throw if the conversationId is not a valid ObjectId + const conversationId = ObjectId.isValid(conversationIdString) + ? ObjectId.createFromHexString(conversationIdString) + : (() => { + throw makeRequestError({ + httpStatus: 400, + message: `Invalid conversationId: ${conversationIdString}`, + }); + })(); + const conversation = await conversations.findById({ _id: conversationId, }); @@ -317,14 +327,3 @@ const loadConversation = async ({ } return conversation; }; - -const toObjectId = (id: string) => { - try { - return new ObjectId(id); - } catch (error) { - throw makeRequestError({ - httpStatus: 400, - message: `Invalid ObjectId string: ${id}`, - }); - } -}; diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts index 7c86a8dc2..25d502d5a 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/conversationsRouter.test.ts @@ -9,6 +9,7 @@ import { DEFAULT_API_PREFIX } from "../../app"; import { makeTestApp } from "../../test/testHelpers"; import { makeTestAppConfig } from "../../test/testHelpers"; import { ObjectId } from "mongodb-rag-core"; +import exp from "constants"; jest.setTimeout(60000); describe("Conversations Router", () => { @@ -201,7 +202,34 @@ describe("Conversations Router", () => { expect(called).toBe(true); }); it("should create a new conversation with 'null' value for addMessageToConversation if configured", async () => { - // TODO: implement + const { app, origin } = await makeTestApp({ + conversationsRouterConfig: { + ...appConfig.conversationsRouterConfig, + createConversationOnNullMessageId: true, + }, + }); + const res = await createConversationMessageReq({ + app, + origin, + conversationId: "null", + message: "what is the current version of mongodb server?", + }); + expect(res.status).toBe(200); + }); + it("should not create a new conversation with 'null' value for addMessageToConversation if not configured", async () => { + const { app, origin } = await makeTestApp({ + conversationsRouterConfig: { + ...appConfig.conversationsRouterConfig, + createConversationOnNullMessageId: false, + }, + }); + const res = await createConversationMessageReq({ + app, + origin, + conversationId: "null", + message: "what is the current version of mongodb server?", + }); + expect(res.status).toBe(400); }); // Helpers From 73146ec8f69ad0a3f4c12eb06e00b5a6c828c6bf Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:20:50 -0400 Subject: [PATCH 18/19] update to include conversationId as metadata --- docs/docs/server/openapi.yaml | 19 +++--- .../addMessageToConversation.test.ts | 14 +++-- .../conversations/addMessageToConversation.ts | 4 +- .../src/routes/conversations/utils.test.ts | 60 ++++++++++++------- .../src/routes/conversations/utils.ts | 13 ++-- .../src/services/conversations.ts | 8 ++- 6 files changed, 71 insertions(+), 47 deletions(-) diff --git a/docs/docs/server/openapi.yaml b/docs/docs/server/openapi.yaml index 563d652f5..4431f72fd 100644 --- a/docs/docs/server/openapi.yaml +++ b/docs/docs/server/openapi.yaml @@ -39,7 +39,13 @@ paths: post: operationId: addMessage summary: Add message to the conversation - description: Add a message to the conversation and get a response back from chatbot. + description: | + Add a message to the conversation and get a response back from chatbot. + + You can configure your server to create new conversations + when you set the conversation ID to `null`. + If you do this, the server creates a new conversation + and returns the conversation ID in the response's `metadata.conversationId` field. parameters: - $ref: "#/components/parameters/conversationId" - name: stream @@ -288,7 +294,6 @@ components: - $ref: "#/components/schemas/StreamEventProcessing" - $ref: "#/components/schemas/StreamEventReferences" - $ref: "#/components/schemas/StreamEventFinished" - - $ref: "#/components/schemas/StreamEventConversationId" description: A server-sent event data payload for a streaming message. StreamEventDelta: description: Assistant response text. @@ -342,16 +347,6 @@ components: data: description: Message ID. type: string - StreamEventConversationId: - description: Conversation ID for the message. - type: object - properties: - type: - type: string - enum: [conversationId] - data: - description: Conversation ID. - type: string MessageComment: type: object properties: diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts index c6ea4cd16..41d004a4a 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.test.ts @@ -174,7 +174,9 @@ describe("POST /conversations/:conversationId/messages", () => { expect(res.text).toContain(`data: {"type":"delta","data":"`); expect(res.text).toContain(`data: {"type":"references","data":[{`); expect(res.text).toContain(`data: {"type":"finished","data":"`); - expect(res.text).toContain(`data: {"type":"conversationId","data":"`); + expect(res.text).toContain( + `data: {"type":"metadata","data":{"conversationId":"${conversationId}"}}` + ); }); it("should stream two requests concurrently", async () => { const newConvoId1 = await createNewConversation(app, ipAddress); @@ -424,14 +426,18 @@ describe("POST /conversations/:conversationId/messages", () => { expect(res.statusCode).toEqual(200); expect(res.body).toMatchObject({ content: expect.any(String), - conversationId: expect.any(String), + metadata: { + conversationId: expect.any(String), + }, role: "assistant", }); expect(mockCustomDataFunction).toHaveBeenCalled(); const conversation = await conversations.findById({ - _id: ObjectId.createFromHexString(res.body.conversationId), + _id: ObjectId.createFromHexString(res.body.metadata.conversationId), }); - expect(conversation?._id.toString()).toEqual(res.body.conversationId); + expect(conversation?._id.toString()).toEqual( + res.body.metadata.conversationId + ); expect(conversation?.messages).toHaveLength(3); expect(conversation?.messages[0]).toMatchObject(systemPrompt); }); diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts index f1fa62692..3abb77b00 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/addMessageToConversation.ts @@ -197,8 +197,8 @@ export function makeAddMessageToConversationRoute({ return res.status(200).json(apiRes); } else { dataStreamer.streamData({ - type: "conversationId", - data: conversation._id.toString(), + type: "metadata", + data: { conversationId: conversation._id.toString() }, }); dataStreamer.streamData({ type: "finished", diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts b/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts index 3a907360d..82327a0cd 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/utils.test.ts @@ -1,3 +1,4 @@ +import { strict as assert } from "assert"; import { areEquivalentIpAddresses, convertConversationFromDbToApi, @@ -99,49 +100,42 @@ describe("Data Conversion Functions", () => { ] = exampleConversationInDatabase.messages; const convoId = new ObjectId(); - expect(convertMessageFromDbToApi(systemMessage, convoId)).toEqual({ + expect(convertMessageFromDbToApi(systemMessage)).toEqual({ id: "65ca766ab564b694eba8c330", - conversationId: convoId.toString(), role: "system", content: "You are an expert conversationalist! You can chat about anything with anyone.", createdAt: 1704067201000, }); - expect(convertMessageFromDbToApi(userMessage, convoId)).toEqual({ + expect(convertMessageFromDbToApi(userMessage)).toEqual({ id: "65ca76775a57e51c3b4c286d", - conversationId: convoId.toString(), role: "user", content: "Hello! I'm looking for a new book to read. I like fantasy and science fiction novels. Can you recommend one?", createdAt: 1704067242000, }); - expect(convertMessageFromDbToApi(functionCallMessage, convoId)).toEqual({ + expect(convertMessageFromDbToApi(functionCallMessage)).toEqual({ id: "65ca767e30116ce068e17bb5", - conversationId: convoId.toString(), role: "assistant", content: "", createdAt: 1704067245000, }); - expect(convertMessageFromDbToApi(functionResultMessage, convoId)).toEqual( - { - id: "65ca768341f9ea61d048aaa8", - conversationId: convoId.toString(), - role: "function", - content: JSON.stringify([ - { title: "The Way of Kings", author: "Brandon Sanderson" }, - { title: "Neuromancer", author: "William Gibson" }, - { title: "Snow Crash", author: "Neal Stephenson" }, - ]), - createdAt: 1704067247000, - } - ); + expect(convertMessageFromDbToApi(functionResultMessage)).toEqual({ + id: "65ca768341f9ea61d048aaa8", + role: "function", + content: JSON.stringify([ + { title: "The Way of Kings", author: "Brandon Sanderson" }, + { title: "Neuromancer", author: "William Gibson" }, + { title: "Snow Crash", author: "Neal Stephenson" }, + ]), + createdAt: 1704067247000, + }); - expect(convertMessageFromDbToApi(assistantMessage, convoId)).toEqual({ + expect(convertMessageFromDbToApi(assistantMessage)).toEqual({ id: "65ca76874e1df9cf2742bf86", - conversationId: convoId.toString(), role: "assistant", content: `I recommend "The Way of Kings" by Brandon Sanderson. You may also enjoy "Neuromancer" by William Gibson or "Snow Crash" by Neal Stephenson.`, createdAt: 1704067252000, @@ -151,9 +145,29 @@ describe("Data Conversion Functions", () => { }); test("do not include conversationId if not provided", () => { const message = exampleConversationInDatabase.messages[0]; - expect(convertMessageFromDbToApi(message)).not.toHaveProperty( - "conversationId" + expect(convertMessageFromDbToApi(message)).not.toMatchObject({ + metadata: { conversationId: expect.any(String) }, + }); + }); + test("include conversationId in assistant message if provided", () => { + const message = exampleConversationInDatabase.messages.find( + (m) => m.role === "assistant" + ); + assert(message); + const convoId = new ObjectId(); + expect(convertMessageFromDbToApi(message, convoId)).toMatchObject({ + metadata: { conversationId: convoId.toString() }, + }); + }); + test("don't include conversationId in non-assistant messages", () => { + const message = exampleConversationInDatabase.messages.find( + (m) => m.role !== "assistant" ); + assert(message); + const convoId = new ObjectId(); + expect(convertMessageFromDbToApi(message, convoId)).not.toMatchObject({ + metadata: { conversationId: expect.any(String) }, + }); }); }); diff --git a/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts b/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts index b96d53ca8..9fc9f02a9 100644 --- a/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts +++ b/packages/mongodb-chatbot-server/src/routes/conversations/utils.ts @@ -28,19 +28,24 @@ export function convertMessageFromDbToApi( const { id, createdAt, role, content } = message; const apiMessage = { id: id.toString(), - ...(conversationId ? { conversationId: conversationId.toString() } : {}), role, content, createdAt: createdAt.getTime(), }; if (role === "assistant") { - const { rating, references, metadata } = message; - return { + const { rating, references, metadata = {} } = message; + if (conversationId) { + metadata.conversationId = conversationId?.toString(); + } + const augmentedApiMessage: ApiMessage = { ...apiMessage, rating, references, - metadata, }; + if (Object.keys(metadata).length > 0) { + augmentedApiMessage.metadata = metadata; + } + return augmentedApiMessage; } return apiMessage; } diff --git a/packages/mongodb-chatbot-ui/src/services/conversations.ts b/packages/mongodb-chatbot-ui/src/services/conversations.ts index 644207684..a252b4dce 100644 --- a/packages/mongodb-chatbot-ui/src/services/conversations.ts +++ b/packages/mongodb-chatbot-ui/src/services/conversations.ts @@ -1,5 +1,9 @@ import { fetchEventSource } from "@microsoft/fetch-event-source"; -import { References, VerifiedAnswer } from "mongodb-rag-core"; +import { + ConversationIdStreamEvent, + References, + VerifiedAnswer, +} from "mongodb-rag-core"; import { ConversationState } from "../useConversation"; import { strict as assert } from "node:assert"; @@ -265,7 +269,7 @@ export class ConversationService { (e.type === "delta" && typeof e.data === "string") || (e.type === "references" && typeof e.data === "object") || (e.type === "metadata" && typeof e.data === "object") || - (e.type === "finished" && typeof e.data === "string") + (e.type === "finished" && typeof e.data === "string") || ); }; From e2354996a4968b7d0e54bbc7336a06797bb56682 Mon Sep 17 00:00:00 2001 From: mongodben <90647379+mongodben@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:31:40 -0400 Subject: [PATCH 19/19] fix ui --- packages/mongodb-chatbot-ui/src/services/conversations.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/mongodb-chatbot-ui/src/services/conversations.ts b/packages/mongodb-chatbot-ui/src/services/conversations.ts index a252b4dce..644207684 100644 --- a/packages/mongodb-chatbot-ui/src/services/conversations.ts +++ b/packages/mongodb-chatbot-ui/src/services/conversations.ts @@ -1,9 +1,5 @@ import { fetchEventSource } from "@microsoft/fetch-event-source"; -import { - ConversationIdStreamEvent, - References, - VerifiedAnswer, -} from "mongodb-rag-core"; +import { References, VerifiedAnswer } from "mongodb-rag-core"; import { ConversationState } from "../useConversation"; import { strict as assert } from "node:assert"; @@ -269,7 +265,7 @@ export class ConversationService { (e.type === "delta" && typeof e.data === "string") || (e.type === "references" && typeof e.data === "object") || (e.type === "metadata" && typeof e.data === "object") || - (e.type === "finished" && typeof e.data === "string") || + (e.type === "finished" && typeof e.data === "string") ); };