From cd5299807bc52b12e21adb6aa1194c16894021b1 Mon Sep 17 00:00:00 2001 From: Daniel Lew Date: Fri, 19 Dec 2025 09:00:41 -0600 Subject: [PATCH] =?UTF-8?q?=E2=8F=B3=20refactor:=20Exclude=20Temporary=20C?= =?UTF-8?q?onversations=20and=20Messages=20from=20Meilisearch=20Indexing?= =?UTF-8?q?=20(#10872)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Temporary chat data should not show up when searching. Now we check whether a TTL has been set on a conversation/message before indexing it in meilisearch. If there is a TTL, we skip it. --- .../src/models/plugins/mongoMeili.spec.ts | 110 ++++++++++++++++++ .../src/models/plugins/mongoMeili.ts | 9 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 packages/data-schemas/src/models/plugins/mongoMeili.spec.ts diff --git a/packages/data-schemas/src/models/plugins/mongoMeili.spec.ts b/packages/data-schemas/src/models/plugins/mongoMeili.spec.ts new file mode 100644 index 0000000000..6455bba105 --- /dev/null +++ b/packages/data-schemas/src/models/plugins/mongoMeili.spec.ts @@ -0,0 +1,110 @@ +import { MongoMemoryServer } from 'mongodb-memory-server'; +import mongoose from 'mongoose'; +import { EModelEndpoint } from 'librechat-data-provider'; +import { createConversationModel } from '~/models/convo'; +import { createMessageModel } from '~/models/message'; +import { SchemaWithMeiliMethods } from '~/models/plugins/mongoMeili'; + +const mockAddDocuments = jest.fn(); +const mockIndex = jest.fn().mockReturnValue({ + getRawInfo: jest.fn(), + updateSettings: jest.fn(), + addDocuments: mockAddDocuments, + getDocuments: jest.fn().mockReturnValue({ results: [] }), +}); +jest.mock('meilisearch', () => { + return { + MeiliSearch: jest.fn().mockImplementation(() => { + return { + index: mockIndex, + }; + }), + }; +}); + +describe('Meilisearch Mongoose plugin', () => { + const OLD_ENV = process.env; + + let mongoServer: MongoMemoryServer; + + beforeAll(async () => { + process.env = { + ...OLD_ENV, + // Set a fake meilisearch host/key so that we activate the meilisearch plugin + MEILI_HOST: 'foo', + MEILI_MASTER_KEY: 'bar', + }; + + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + await mongoose.connect(mongoUri); + }); + + beforeEach(() => { + mockAddDocuments.mockClear(); + }); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + + process.env = OLD_ENV; + }); + + test('saving conversation indexes w/ meilisearch', async () => { + await createConversationModel(mongoose).create({ + conversationId: new mongoose.Types.ObjectId(), + user: new mongoose.Types.ObjectId(), + title: 'Test Conversation', + endpoint: EModelEndpoint.openAI, + }); + expect(mockAddDocuments).toHaveBeenCalled(); + }); + + test('saving TTL conversation does NOT index w/ meilisearch', async () => { + await createConversationModel(mongoose).create({ + conversationId: new mongoose.Types.ObjectId(), + user: new mongoose.Types.ObjectId(), + title: 'Test Conversation', + endpoint: EModelEndpoint.openAI, + expiredAt: new Date(), + }); + expect(mockAddDocuments).not.toHaveBeenCalled(); + }); + + test('saving messages indexes w/ meilisearch', async () => { + await createMessageModel(mongoose).create({ + messageId: new mongoose.Types.ObjectId(), + conversationId: new mongoose.Types.ObjectId(), + user: new mongoose.Types.ObjectId(), + isCreatedByUser: true, + }); + expect(mockAddDocuments).toHaveBeenCalled(); + }); + + test('saving TTL messages does NOT index w/ meilisearch', async () => { + await createMessageModel(mongoose).create({ + messageId: new mongoose.Types.ObjectId(), + conversationId: new mongoose.Types.ObjectId(), + user: new mongoose.Types.ObjectId(), + isCreatedByUser: true, + expiredAt: new Date(), + }); + expect(mockAddDocuments).not.toHaveBeenCalled(); + }); + + test('sync w/ meili does not include TTL documents', async () => { + const conversationModel = createConversationModel(mongoose) as SchemaWithMeiliMethods; + await conversationModel.create({ + conversationId: new mongoose.Types.ObjectId(), + user: new mongoose.Types.ObjectId(), + title: 'Test Conversation', + endpoint: EModelEndpoint.openAI, + expiredAt: new Date(), + }); + + await conversationModel.syncWithMeili(); + + expect(mockAddDocuments).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/data-schemas/src/models/plugins/mongoMeili.ts b/packages/data-schemas/src/models/plugins/mongoMeili.ts index 7c0086e2d1..ea7689d22d 100644 --- a/packages/data-schemas/src/models/plugins/mongoMeili.ts +++ b/packages/data-schemas/src/models/plugins/mongoMeili.ts @@ -183,7 +183,9 @@ const createMeiliMongooseModel = ({ ); // Build query with resume capability - const query: FilterQuery = {}; + const query: FilterQuery = { + expiredAt: { $exists: false }, // Do not sync TTL documents + }; if (options?.resumeFromId) { query._id = { $gt: options.resumeFromId }; } @@ -430,6 +432,11 @@ const createMeiliMongooseModel = ({ this: DocumentWithMeiliIndex, next: CallbackWithoutResultAndOptionalError, ): Promise { + // If this conversation or message has a TTL, don't index it + if (!_.isNil(this.expiredAt)) { + return next(); + } + const object = this.preprocessObjectForIndex!(); const maxRetries = 3; let retryCount = 0;