Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(EAI-566): Support creating new conversation when add message when conversationId = "null" #531

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ steps:
- name: test
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
- npm run build
- npm run lint
Expand Down
12 changes: 11 additions & 1 deletion docs/docs/server/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -218,13 +224,17 @@ components:
type: object
required:
- id
- conversationId
- role
- content
- createdAt
properties:
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]
Expand Down
49 changes: 25 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/mongodb-chatbot-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/mongodb-chatbot-server/src/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
defaultConversationConstants,
Message,
makeOpenAiChatLlm,
SomeMessage,
} from "mongodb-rag-core";
import { Express } from "express";
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";
Expand All @@ -38,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);
Expand Down Expand Up @@ -167,6 +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":"metadata","data":{"conversationId":"${conversationId}"}}`
);
});
it("should stream two requests concurrently", async () => {
const newConvoId1 = await createNewConversation(app, ipAddress);
Expand Down Expand Up @@ -209,7 +219,7 @@ describe("POST /conversations/:conversationId/messages", () => {
});
expect(res.statusCode).toEqual(400);
expect(res.body).toStrictEqual({
error: `Invalid ObjectId string: ${notAValidId}`,
error: `Invalid conversationId: ${notAValidId}`,
});
});

Expand Down Expand Up @@ -250,16 +260,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()))
Expand Down Expand Up @@ -339,7 +354,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(
Expand All @@ -366,7 +380,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,
Expand Down Expand Up @@ -399,6 +413,53 @@ describe("POST /conversations/:conversationId/messages", () => {
});
});
});

describe("create conversation 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`)
.set("X-FORWARDED-FOR", ipAddress)
.set("Origin", origin)
.send({
message: "hello",
});
expect(res.statusCode).toEqual(200);
expect(res.body).toMatchObject({
content: expect.any(String),
metadata: {
conversationId: expect.any(String),
},
role: "assistant",
});
expect(mockCustomDataFunction).toHaveBeenCalled();
const conversation = await conversations.findById({
_id: ObjectId.createFromHexString(res.body.metadata.conversationId),
});
expect(conversation?._id.toString()).toEqual(
res.body.metadata.conversationId
);
expect(conversation?.messages).toHaveLength(3);
expect(conversation?.messages[0]).toMatchObject(systemPrompt);
});
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",
});
expect(res.statusCode).toEqual(400);
expect(res.body).toMatchObject({
error: expect.any(String),
});
});
});
});

async function createNewConversation(
Expand Down
Loading