Skip to content
This repository was archived by the owner on Sep 29, 2025. It is now read-only.

Commit 639f332

Browse files
authored
handle cleaning customData and metadata based on store setting (#848)
* handle cleaning customData and metadata based on `store` setting * cleanup response router config types * allow config of alwaysAllowedMetadataKeys * add new config to tests * clearer and improved testing for alwaysAllowedMetadataKeys * update comment
1 parent f726c51 commit 639f332

File tree

5 files changed

+168
-34
lines changed

5 files changed

+168
-34
lines changed

packages/chatbot-server-mongodb-public/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ export const config: AppConfig = {
443443
supportedModels: ["mongodb-chat-latest"],
444444
maxOutputTokens: 4000,
445445
maxUserMessagesInConversation: 6,
446+
alwaysAllowedMetadataKeys: ["ip", "origin", "userAgent"],
446447
},
447448
},
448449
maxRequestTimeoutMs: 60000,

packages/mongodb-chatbot-server/src/routes/responses/createResponse.test.ts

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import {
1414
makeCreateResponseRequestStream,
1515
type Stream,
1616
} from "../../test/testHelpers";
17-
import { makeDefaultConfig } from "../../test/testConfig";
17+
import {
18+
TEST_ALWAYS_ALLOWED_METADATA_KEYS,
19+
makeDefaultConfig,
20+
} from "../../test/testConfig";
1821
import { ERR_MSG, type CreateResponseRequest } from "./createResponse";
1922
import { ERROR_CODE, ERROR_TYPE } from "./errors";
2023

@@ -46,9 +49,14 @@ describe("POST /responses", () => {
4649
});
4750

4851
const makeClientAndRequest = (
49-
body?: Partial<CreateResponseRequest["body"]>
52+
body?: Partial<CreateResponseRequest["body"]>,
53+
overrideOrigin?: string,
54+
overrideIpAddress?: string
5055
) => {
51-
const openAiClient = makeOpenAiClient(origin, ipAddress);
56+
const openAiClient = makeOpenAiClient(
57+
overrideOrigin ?? origin,
58+
overrideIpAddress ?? ipAddress
59+
);
5260
return makeCreateResponseRequestStream(openAiClient, body);
5361
};
5462

@@ -321,12 +329,13 @@ describe("POST /responses", () => {
321329
});
322330
});
323331

324-
it("Should store conversation messages when `store: true`", async () => {
332+
it("Should store conversation messages with all metadata keys when `store: true`", async () => {
325333
const store = true;
326334
const userId = "customUserId";
327335
const metadata = {
328-
customMessage1: "customMessage1",
329-
customMessage2: "customMessage2",
336+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[0]]: "127.0.0.1",
337+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[1]]: "http://localhost:3000",
338+
notAllowedMetadataKey: "please STORE me in this scenario",
330339
};
331340
const requestBody: Partial<CreateResponseRequest["body"]> = {
332341
store,
@@ -353,12 +362,18 @@ describe("POST /responses", () => {
353362
});
354363
});
355364

356-
it("Should not store conversation messages when `store: false`", async () => {
365+
it("Should not store message content or metadata fields (except alwaysAllowedMetadataKeys) when `store: false`", async () => {
357366
const store = false;
358367
const userId = "customUserId";
359368
const metadata = {
360-
customMessage1: "customMessage1",
361-
customMessage2: "customMessage2",
369+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[0]]: "127.0.0.1",
370+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[1]]: "http://localhost:3000",
371+
notAllowedMetadataKey: "please REMOVE me in this scenario",
372+
};
373+
const expectedMetadata = {
374+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[0]]: "127.0.0.1",
375+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[1]]: "http://localhost:3000",
376+
notAllowedMetadataKey: "",
362377
};
363378
const requestBody: Partial<CreateResponseRequest["body"]> = {
364379
store,
@@ -380,9 +395,63 @@ describe("POST /responses", () => {
380395
expectDefaultMessageContent({
381396
updatedConversation,
382397
userId,
398+
store,
399+
metadata: expectedMetadata,
400+
});
401+
});
402+
403+
it("Should not store message content or ANY metadata fields when `store: false` and `alwaysAllowedMetadataKeys` is empty", async () => {
404+
const customConfig = await makeDefaultConfig();
405+
const testPort = 5201; // Use a different port
406+
customConfig.responsesRouterConfig.createResponse.alwaysAllowedMetadataKeys =
407+
[];
408+
const {
409+
server: customServer,
410+
ipAddress: customIpAddress,
411+
origin: customOrigin,
412+
} = await makeTestLocalServer(customConfig, testPort);
413+
414+
const store = false;
415+
const userId = "customUserId";
416+
const metadata = {
417+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[0]]: "127.0.0.1",
418+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[1]]: "http://localhost:3000",
419+
notAllowedMetadataKey: "please REMOVE all fields in this scenario",
420+
};
421+
const expectedMetadata = {
422+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[0]]: "",
423+
[TEST_ALWAYS_ALLOWED_METADATA_KEYS[1]]: "",
424+
notAllowedMetadataKey: "",
425+
};
426+
const requestBody: Partial<CreateResponseRequest["body"]> = {
383427
store,
384428
metadata,
429+
user: userId,
430+
};
431+
const stream = await makeClientAndRequest(
432+
requestBody,
433+
customOrigin,
434+
customIpAddress
435+
);
436+
437+
const results = await expectValidResponses({ requestBody, stream });
438+
439+
const updatedConversation = await conversations.findByMessageId({
440+
messageId: getMessageIdFromResults(results),
441+
});
442+
if (!updatedConversation) {
443+
return expect(updatedConversation).not.toBeNull();
444+
}
445+
446+
expect(updatedConversation.storeMessageContent).toEqual(store);
447+
expectDefaultMessageContent({
448+
updatedConversation,
449+
userId,
450+
store,
451+
metadata: expectedMetadata,
385452
});
453+
454+
customServer.close();
386455
});
387456

388457
it("Should store function_call messages when `store: true`", async () => {

packages/mongodb-chatbot-server/src/routes/responses/createResponse.ts

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ export interface CreateResponseRouteParams {
189189
supportedModels: string[];
190190
maxOutputTokens: number;
191191
maxUserMessagesInConversation: number;
192+
/** These metadata keys will persist in conversations and messages even if `Conversation.store: false`.
193+
* Otherwise, keys will have their values set to an empty string `""` if `Conversation.store: false`. */
194+
alwaysAllowedMetadataKeys: string[];
192195
}
193196

194197
export function makeCreateResponseRoute({
@@ -197,6 +200,7 @@ export function makeCreateResponseRoute({
197200
supportedModels,
198201
maxOutputTokens,
199202
maxUserMessagesInConversation,
203+
alwaysAllowedMetadataKeys,
200204
}: CreateResponseRouteParams) {
201205
return async (req: ExpressRequest, res: ExpressResponse) => {
202206
const reqId = getRequestId(req);
@@ -263,6 +267,7 @@ export function makeCreateResponseRoute({
263267
metadata,
264268
userId: user,
265269
storeMessageContent: store,
270+
alwaysAllowedMetadataKeys,
266271
});
267272

268273
// --- CONVERSATION USER ID CHECK ---
@@ -335,6 +340,7 @@ export function makeCreateResponseRoute({
335340
input,
336341
messages,
337342
responseId,
343+
alwaysAllowedMetadataKeys,
338344
});
339345

340346
dataStreamer.streamResponses({
@@ -379,6 +385,7 @@ interface LoadConversationByMessageIdParams {
379385
metadata?: Record<string, string>;
380386
userId?: string;
381387
storeMessageContent: boolean;
388+
alwaysAllowedMetadataKeys: string[];
382389
}
383390

384391
const loadConversationByMessageId = async ({
@@ -388,12 +395,19 @@ const loadConversationByMessageId = async ({
388395
metadata,
389396
userId,
390397
storeMessageContent,
398+
alwaysAllowedMetadataKeys,
391399
}: LoadConversationByMessageIdParams): Promise<Conversation> => {
392400
if (!messageId) {
401+
const formattedMetadata = formatMetadata({
402+
shouldStore: storeMessageContent,
403+
alwaysAllowedMetadataKeys,
404+
metadata,
405+
});
406+
393407
return await conversations.create({
394408
userId,
395409
storeMessageContent,
396-
customData: { metadata },
410+
customData: { metadata: formattedMetadata },
397411
});
398412
}
399413

@@ -474,6 +488,7 @@ interface AddMessagesToConversationParams {
474488
input: CreateResponseRequest["body"]["input"];
475489
messages: MessagesParam;
476490
responseId: ObjectId;
491+
alwaysAllowedMetadataKeys: string[];
477492
}
478493

479494
const saveMessagesToConversation = async ({
@@ -484,10 +499,18 @@ const saveMessagesToConversation = async ({
484499
input,
485500
messages,
486501
responseId,
502+
alwaysAllowedMetadataKeys,
487503
}: AddMessagesToConversationParams) => {
488504
const messagesToAdd = [
489-
...convertInputToDBMessages(input, store, metadata),
490-
...messages.map((message) => formatMessage(message, store, metadata)),
505+
...convertInputToDBMessages(
506+
input,
507+
store,
508+
alwaysAllowedMetadataKeys,
509+
metadata
510+
),
511+
...messages.map((message) =>
512+
formatMessage(message, store, alwaysAllowedMetadataKeys, metadata)
513+
),
491514
];
492515
// handle setting the response id for the last message
493516
// this corresponds to the response id in the response stream
@@ -504,49 +527,94 @@ const saveMessagesToConversation = async ({
504527
const convertInputToDBMessages = (
505528
input: CreateResponseRequest["body"]["input"],
506529
store: boolean,
530+
alwaysAllowedMetadataKeys: string[],
507531
metadata?: Record<string, string>
508532
): MessagesParam => {
509533
if (typeof input === "string") {
510-
return [formatMessage({ role: "user", content: input }, store, metadata)];
534+
return [
535+
formatMessage(
536+
{ role: "user", content: input },
537+
store,
538+
alwaysAllowedMetadataKeys,
539+
metadata
540+
),
541+
];
511542
}
512543

513544
return input.map((message) => {
514545
if (isInputMessage(message)) {
515546
const role = message.role;
516547
const content = formatUserMessageContent(message.content);
517-
return formatMessage({ role, content }, store, metadata);
548+
return formatMessage(
549+
{ role, content },
550+
store,
551+
alwaysAllowedMetadataKeys,
552+
metadata
553+
);
518554
}
519555
// handle function tool calls and outputs
520556
const role = "tool";
521557
const name = message.type === "function_call" ? message.name : message.type;
522558
const content =
523559
message.type === "function_call" ? message.arguments : message.output;
524-
return formatMessage({ role, name, content }, store, metadata);
560+
return formatMessage(
561+
{ role, name, content },
562+
store,
563+
alwaysAllowedMetadataKeys,
564+
metadata
565+
);
525566
});
526567
};
527568

528569
const formatMessage = (
529570
message: MessagesParam[number],
530571
store: boolean,
572+
alwaysAllowedMetadataKeys: string[],
531573
metadata?: Record<string, string>
532574
): MessagesParam[number] => {
533575
// store a placeholder string if we're not storing message data
534-
const content = store ? message.content : "";
535-
// handle cleaning custom data if we're not storing message data
536-
const customData = {
537-
...message.customData,
538-
query: store ? message.customData?.query : "",
539-
reason: store ? message.customData?.reason : "",
540-
};
576+
const formattedContent = store ? message.content : "";
577+
// handle cleaning metadata fields if we're not storing message data
578+
const formattedMetadata = formatMetadata({
579+
shouldStore: store,
580+
alwaysAllowedMetadataKeys,
581+
metadata,
582+
});
583+
const formattedCustomData = formatMetadata({
584+
shouldStore: store,
585+
alwaysAllowedMetadataKeys,
586+
metadata: message.customData,
587+
});
541588

542589
return {
543590
...message,
544-
content,
545-
metadata,
546-
customData,
591+
content: formattedContent,
592+
metadata: formattedMetadata,
593+
customData: formattedCustomData,
547594
};
548595
};
549596

597+
interface FormatMetadataParams {
598+
shouldStore: boolean;
599+
alwaysAllowedMetadataKeys: string[];
600+
metadata?: Record<string, unknown>;
601+
}
602+
603+
const formatMetadata = ({
604+
shouldStore,
605+
alwaysAllowedMetadataKeys,
606+
metadata,
607+
}: FormatMetadataParams) => {
608+
if (shouldStore || !metadata) return metadata;
609+
610+
return Object.fromEntries(
611+
Object.entries(metadata).map(([key, value]) => [
612+
key,
613+
alwaysAllowedMetadataKeys.includes(key) ? value : "",
614+
])
615+
);
616+
};
617+
550618
interface BaseResponseData {
551619
responseId: ObjectId;
552620
data: CreateResponseRequest["body"];

packages/mongodb-chatbot-server/src/routes/responses/responsesRouter.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import Router from "express-promise-router";
2-
import type { ConversationsService } from "mongodb-rag-core";
32
import { makeCreateResponseRoute } from "./createResponse";
4-
import type { GenerateResponse } from "../../processors";
53
import { getRequestId } from "../../utils";
64
import {
75
makeRateLimit,
@@ -10,19 +8,14 @@ import {
108
type SlowDownOptions,
119
} from "../../middleware";
1210
import { makeRateLimitError, sendErrorResponse } from "./errors";
11+
import type { CreateResponseRouteParams } from "./createResponse";
1312

1413
export interface ResponsesRouterParams {
1514
rateLimitConfig?: {
1615
routerRateLimitConfig?: RateLimitOptions;
1716
routerSlowDownConfig?: SlowDownOptions;
1817
};
19-
createResponse: {
20-
conversations: ConversationsService;
21-
generateResponse: GenerateResponse;
22-
supportedModels: string[];
23-
maxOutputTokens: number;
24-
maxUserMessagesInConversation: number;
25-
};
18+
createResponse: CreateResponseRouteParams;
2619
}
2720

2821
/**

packages/mongodb-chatbot-server/src/test/testConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ export const mockGenerateResponsesStream: GenerateResponse = async ({
203203

204204
export const MONGO_CHAT_MODEL = "mongodb-chat-latest";
205205

206+
export const TEST_ALWAYS_ALLOWED_METADATA_KEYS = ["ip", "origin"];
207+
206208
export const basicResponsesRequestBody = {
207209
model: MONGO_CHAT_MODEL,
208210
input: "What is MongoDB?",
@@ -222,6 +224,7 @@ export async function makeDefaultConfig(): Promise<AppConfig> {
222224
supportedModels: [MONGO_CHAT_MODEL],
223225
maxOutputTokens: 4000,
224226
maxUserMessagesInConversation: 6,
227+
alwaysAllowedMetadataKeys: TEST_ALWAYS_ALLOWED_METADATA_KEYS,
225228
},
226229
},
227230
maxRequestTimeoutMs: 30000,

0 commit comments

Comments
 (0)