From 4335e852b92d3b356841c0c8b50cea3c0183904b Mon Sep 17 00:00:00 2001 From: Biniyam Date: Fri, 28 Mar 2025 18:13:12 +0300 Subject: [PATCH 01/67] boiler-plate for the moderator microservice --- src/apps/moderator/main.ts | 27 +++++++++++++++++++ .../moderator/moderator.controller.spec.ts | 20 ++++++++++++++ src/apps/moderator/moderator.controller.ts | 13 +++++++++ src/apps/moderator/moderator.module.ts | 9 +++++++ src/apps/moderator/moderator.service.spec.ts | 18 +++++++++++++ src/apps/moderator/moderator.service.ts | 9 +++++++ 6 files changed, 96 insertions(+) create mode 100644 src/apps/moderator/main.ts create mode 100644 src/apps/moderator/moderator.controller.spec.ts create mode 100644 src/apps/moderator/moderator.controller.ts create mode 100644 src/apps/moderator/moderator.module.ts create mode 100644 src/apps/moderator/moderator.service.spec.ts create mode 100644 src/apps/moderator/moderator.service.ts diff --git a/src/apps/moderator/main.ts b/src/apps/moderator/main.ts new file mode 100644 index 00000000..e124e4e0 --- /dev/null +++ b/src/apps/moderator/main.ts @@ -0,0 +1,27 @@ +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { ModeratorModule } from './moderator.module'; +import { NestFactory } from '@nestjs/core'; + +async function bootstrap() { + const app = await NestFactory.createMicroservice( + ModeratorModule, + { + transport: Transport.RMQ, + options: { + urls: [process.env.RABBITMQ_URL], + queue: 'moderator', + queueOptions: { + durable: true, + messageTtl: 60000, + deadLetterExchange: 'moderator', + deadLetterRoutingKey: 'dead', + }, + noAck: false, + prefetchCount: 1, + }, + }, + ); + await app.listen(); +} + +bootstrap(); diff --git a/src/apps/moderator/moderator.controller.spec.ts b/src/apps/moderator/moderator.controller.spec.ts new file mode 100644 index 00000000..81472b03 --- /dev/null +++ b/src/apps/moderator/moderator.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ModeratorController } from './moderator.controller'; +import { ModeratorService } from './moderator.service'; + +describe('ModeratorController', () => { + let controller: ModeratorController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ModeratorController], + providers: [ModeratorService], + }).compile(); + + controller = module.get(ModeratorController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/apps/moderator/moderator.controller.ts b/src/apps/moderator/moderator.controller.ts new file mode 100644 index 00000000..4ec4546b --- /dev/null +++ b/src/apps/moderator/moderator.controller.ts @@ -0,0 +1,13 @@ +import { Controller } from '@nestjs/common'; +import { ModeratorService } from './moderator.service'; +import { Ctx, RmqContext, Payload, EventPattern } from '@nestjs/microservices'; + +@Controller() +export class ModeratorController { + constructor(private readonly moderatorService: ModeratorService) {} + + @EventPattern() + async handleMessage(@Payload() data: any, @Ctx() context: RmqContext) { + return await this.moderatorService.handleMessage(data, context); + } +} diff --git a/src/apps/moderator/moderator.module.ts b/src/apps/moderator/moderator.module.ts new file mode 100644 index 00000000..7bbd5c44 --- /dev/null +++ b/src/apps/moderator/moderator.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ModeratorService } from './moderator.service'; +import { ModeratorController } from './moderator.controller'; + +@Module({ + controllers: [ModeratorController], + providers: [ModeratorService], +}) +export class ModeratorModule {} diff --git a/src/apps/moderator/moderator.service.spec.ts b/src/apps/moderator/moderator.service.spec.ts new file mode 100644 index 00000000..f2fc54de --- /dev/null +++ b/src/apps/moderator/moderator.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ModeratorService } from './moderator.service'; + +describe('ModeratorService', () => { + let service: ModeratorService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ModeratorService], + }).compile(); + + service = module.get(ModeratorService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts new file mode 100644 index 00000000..b2cf6582 --- /dev/null +++ b/src/apps/moderator/moderator.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; +import { RmqContext } from '@nestjs/microservices'; + +@Injectable() +export class ModeratorService { + async handleMessage(message: any, context: RmqContext) { + console.log({ message: message, context: context }); + } +} From 34f3825a4c5e27c7b5e4c5e5b3d60c7c34f37c65 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Fri, 28 Mar 2025 18:21:56 +0300 Subject: [PATCH 02/67] send message to the moderator queue if no conversation has been created --- src/modules/message/platform/telegram-message.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/message/platform/telegram-message.ts b/src/modules/message/platform/telegram-message.ts index 34b3c4f6..a49b0f88 100644 --- a/src/modules/message/platform/telegram-message.ts +++ b/src/modules/message/platform/telegram-message.ts @@ -17,7 +17,11 @@ export class TelegramMessageStrategy implements MessageStrategy { const data = JSON.stringify( await this.rabbitmqService.getMessageEchangeData(formattedMessage), ); - this.messageExchangeService.send('message', data); + if (!formattedMessage.conversationId) { + await this.messageExchangeService.send('moderator', data); + } else if (formattedMessage.conversationId) { + await this.messageExchangeService.send('message', data); + } return 'ok'; } From 02e021c51ce0ed55a8d0a1940b47e1512d1b47c5 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Fri, 28 Mar 2025 18:24:46 +0300 Subject: [PATCH 03/67] added an image for the moderator microservice into docker --- docker-compose.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a69fd2dd..ac6dabec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,5 +100,21 @@ services: DATABASE_URL: postgres://postgres:password@postgres:5432/mydb?schema=public command: ['node', 'dist/src/apps/consumers/message-consumers/main.js'] + moderator: + build: + context: . + volumes: + - .:/app + platform: linux/amd64 + depends_on: + postgres: + condition: service_healthy + rabbitmq: + condition: service_started + environment: + RABBITMQ_URL: amqp://user:password@rabbitmq:5672 + DATABASE_URL: postgres://postgres:password@postgres:5432/mydb?schema=public + command: ['node', 'dist/src/apps/moderator/main.js'] + volumes: postgres_data: From 1ed2e463dc0ccddf79267cbc9480fdf904d39653 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Fri, 28 Mar 2025 18:43:26 +0300 Subject: [PATCH 04/67] moderator queue creation logic as well as binding the queues --- .../message-exchange-queues.service.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts b/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts index 110b27a4..04ebe767 100644 --- a/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts +++ b/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts @@ -4,15 +4,25 @@ import * as amqp from 'amqplib'; @Injectable() export class MessageExchangeQueuesService { private readonly QUEUE_NAMES = ['message', 'database']; - private readonly ROUTING_KEY = 'message'; + private readonly PRE_CONV_QUEUE_NAMES = ['moderator', 'database']; private channel: amqp.Channel; async init(channel: amqp.Channel, exchangeName: string) { this.channel = channel; - await this.createQueue(exchangeName); + await this.createQueue(exchangeName, this.QUEUE_NAMES, 'message'); + await this.createQueue( + exchangeName, + this.PRE_CONV_QUEUE_NAMES, + 'moderator', + ); } - async createQueue(exchangeName: string) { - for (const queueName of this.QUEUE_NAMES) { + + async createQueue( + exchangeName: string, + queueNames: string[], + routingKey: string, + ) { + for (const queueName of queueNames) { try { await this.channel.assertQueue(queueName, { durable: true, @@ -20,7 +30,7 @@ export class MessageExchangeQueuesService { deadLetterExchange: 'message', deadLetterRoutingKey: 'dead', }); - await this.channel.bindQueue(queueName, exchangeName, this.ROUTING_KEY); + await this.channel.bindQueue(queueName, exchangeName, routingKey); } catch (error) { console.error(error); } From 8a1571aa7786a1e3ce0f26c24cd17f2ec2e969b9 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sat, 29 Mar 2025 11:34:28 +0300 Subject: [PATCH 05/67] debugged inequivalent arg 'x-dead-letter-exchange' for queue 'moderator' --- src/apps/moderator/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/moderator/main.ts b/src/apps/moderator/main.ts index e124e4e0..e3ac3f7e 100644 --- a/src/apps/moderator/main.ts +++ b/src/apps/moderator/main.ts @@ -13,7 +13,7 @@ async function bootstrap() { queueOptions: { durable: true, messageTtl: 60000, - deadLetterExchange: 'moderator', + deadLetterExchange: 'message', deadLetterRoutingKey: 'dead', }, noAck: false, From 43b31066cd8f1a8f0b93299124679c650dabda8d Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sat, 29 Mar 2025 11:41:43 +0300 Subject: [PATCH 06/67] returned making the routing key moderator so that to bind moderator queue with the right routing routing key --- src/apps/moderator/main.ts | 2 +- .../message-exchange/message-exchange-queues.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/moderator/main.ts b/src/apps/moderator/main.ts index e3ac3f7e..e124e4e0 100644 --- a/src/apps/moderator/main.ts +++ b/src/apps/moderator/main.ts @@ -13,7 +13,7 @@ async function bootstrap() { queueOptions: { durable: true, messageTtl: 60000, - deadLetterExchange: 'message', + deadLetterExchange: 'moderator', deadLetterRoutingKey: 'dead', }, noAck: false, diff --git a/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts b/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts index 04ebe767..9d654174 100644 --- a/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts +++ b/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts @@ -27,7 +27,7 @@ export class MessageExchangeQueuesService { await this.channel.assertQueue(queueName, { durable: true, messageTtl: 60000, - deadLetterExchange: 'message', + deadLetterExchange: routingKey, deadLetterRoutingKey: 'dead', }); await this.channel.bindQueue(queueName, exchangeName, routingKey); From 5f7fe963c36cf6bed6e6eef2d6118865b4bd9e35 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sat, 29 Mar 2025 11:51:40 +0300 Subject: [PATCH 07/67] rolled back the routing key to debug error inequivalent arg 'x-dead-letter-exchange' for queue 'database' in vhost '/': received 'moderator' but current is 'message' --- src/apps/moderator/main.ts | 2 +- .../message-exchange/message-exchange-queues.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/moderator/main.ts b/src/apps/moderator/main.ts index e124e4e0..e3ac3f7e 100644 --- a/src/apps/moderator/main.ts +++ b/src/apps/moderator/main.ts @@ -13,7 +13,7 @@ async function bootstrap() { queueOptions: { durable: true, messageTtl: 60000, - deadLetterExchange: 'moderator', + deadLetterExchange: 'message', deadLetterRoutingKey: 'dead', }, noAck: false, diff --git a/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts b/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts index 9d654174..04ebe767 100644 --- a/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts +++ b/src/common/rabbitmq/message-exchange/message-exchange-queues.service.ts @@ -27,7 +27,7 @@ export class MessageExchangeQueuesService { await this.channel.assertQueue(queueName, { durable: true, messageTtl: 60000, - deadLetterExchange: routingKey, + deadLetterExchange: 'message', deadLetterRoutingKey: 'dead', }); await this.channel.bindQueue(queueName, exchangeName, routingKey); From b0ae9696a1b19e80856351604bf26a889c59e0f5 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sat, 29 Mar 2025 18:32:53 +0300 Subject: [PATCH 08/67] configuration to push conversation with the bot to the moderator queue --- src/modules/message/platform/telegram-message.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/modules/message/platform/telegram-message.ts b/src/modules/message/platform/telegram-message.ts index a49b0f88..1d90bd4c 100644 --- a/src/modules/message/platform/telegram-message.ts +++ b/src/modules/message/platform/telegram-message.ts @@ -19,8 +19,19 @@ export class TelegramMessageStrategy implements MessageStrategy { ); if (!formattedMessage.conversationId) { await this.messageExchangeService.send('moderator', data); - } else if (formattedMessage.conversationId) { - await this.messageExchangeService.send('message', data); + } else { + const mentor = await this.prisma.mentor.findFirst({ + where: { + Conversation: { + some: { + id: formattedMessage.conversationId, + }, + }, + }, + }); + mentor.isBot + ? await this.messageExchangeService.send('moderator', data) + : await this.messageExchangeService.send('message', data); } return 'ok'; From 6947fdfbfe83defe5ac6032625ca4983b53a1cd5 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sat, 29 Mar 2025 18:51:48 +0300 Subject: [PATCH 09/67] updated the database consumer so that it delegates new messages to a bot mentor --- .../consumers/database-consumer/database-consumer.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apps/consumers/database-consumer/database-consumer.service.ts b/src/apps/consumers/database-consumer/database-consumer.service.ts index cd41d9f5..11ffc4ff 100644 --- a/src/apps/consumers/database-consumer/database-consumer.service.ts +++ b/src/apps/consumers/database-consumer/database-consumer.service.ts @@ -60,6 +60,7 @@ export class DatabaseConsumerService { if (!message.conversationId) { const mentor = await tx.mentor.findFirst({ where: { + isBot: true, Account: { Channel: { some: { From 1f323036139fbd928a1668730f4314b22a8ab483 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 14:49:23 +0300 Subject: [PATCH 10/67] debugged (only one message was consumed from the moderator queue cause it was not getting and response from the consumer) --- src/apps/moderator/moderator.service.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index b2cf6582..1c1b9109 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -4,6 +4,14 @@ import { RmqContext } from '@nestjs/microservices'; @Injectable() export class ModeratorService { async handleMessage(message: any, context: RmqContext) { - console.log({ message: message, context: context }); + const channel = context.getChannelRef(); + const orgMsg = context.getMessage(); + try { + console.log({ message: message, context: context }); + channel.ack(orgMsg); + } catch (error) { + console.log(error.message); + channel.nack(orgMsg); + } } } From 45958189055d1eb0316bea7db863746789d06eb6 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 15:57:15 +0300 Subject: [PATCH 11/67] configured the moderator service to reply to conversations --- package-lock.json | 237 +++++++++++++++++++++++- package.json | 1 + src/apps/moderator/moderator.service.ts | 58 ++++++ 3 files changed, 294 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dea1c069..aa651f52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@faker-js/faker": "^9.3.0", + "@langchain/google-genai": "^0.2.1", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.4.8", "@nestjs/config": "^3.3.0", @@ -822,6 +823,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT", + "peer": true + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1165,6 +1173,15 @@ "npm": ">=9.0.0" } }, + "node_modules/@google/generative-ai": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.24.0.tgz", + "integrity": "sha512-fnEITCGEB7NdX0BhoYZ/cq/7WPZ1QS5IzJJfC3Tg/OwkvBetMiVJciyaan297OvE4B9Jg1xvo0zIazX/9sGu1Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@graphql-tools/merge": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.0.8.tgz", @@ -1909,6 +1926,72 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@langchain/core": { + "version": "0.3.43", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.43.tgz", + "integrity": "sha512-DwiSUwmZqcuOn7j8SFdeOH1nvaUqG7q8qn3LhobdQYEg5PmjLgd2yLr2KzuT/YWMBfjkOR+Di5K6HEdFmouTxg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": ">=0.2.8 <0.4.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@langchain/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@langchain/google-genai": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@langchain/google-genai/-/google-genai-0.2.1.tgz", + "integrity": "sha512-30utucZZmL605SSIPY3EQeXYZJS9b5Hlrn+gUuW6TS6bvrONFhl/CqpdvmJ6ho8rXJfHDjETGqr8XcolcRGDGQ==", + "license": "MIT", + "dependencies": { + "@google/generative-ai": "^0.24.0", + "zod-to-json-schema": "^3.22.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.17 <0.4.0" + } + }, "node_modules/@ljharb/through": { "version": "2.3.13", "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", @@ -3133,6 +3216,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -3193,6 +3283,13 @@ "@types/superagent": "*" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT", + "peer": true + }, "node_modules/@types/validator": { "version": "13.12.2", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", @@ -5035,6 +5132,16 @@ "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", "license": "MIT" }, + "node_modules/console-table-printer": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz", + "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -5251,6 +5358,16 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -8792,6 +8909,16 @@ "license": "MIT", "optional": true }, + "node_modules/js-tiktoken": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.19.tgz", + "integrity": "sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==", + "license": "MIT", + "peer": true, + "dependencies": { + "base64-js": "^1.5.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8999,6 +9126,30 @@ "node": ">=6" } }, + "node_modules/langsmith": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.15.tgz", + "integrity": "sha512-cv3ebg0Hh0gRbl72cv/uzaZ+KOdfa2mGF1s74vmB2vlNVO/Ap/O9RYaHV+tpR8nwhGZ50R3ILnTOwSwGP+XQxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "openai": "*" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + } + } + }, "node_modules/leac": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", @@ -10031,6 +10182,16 @@ "node": ">= 6.0.0" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "peer": true, + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -10375,7 +10536,6 @@ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "license": "MIT", - "optional": true, "engines": { "node": ">=4" } @@ -10412,12 +10572,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT", + "peer": true + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "license": "MIT", - "optional": true, "dependencies": { "p-finally": "^1.0.0" }, @@ -11698,6 +11895,16 @@ "dev": true, "license": "ISC" }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -12223,6 +12430,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "license": "MIT", + "peer": true + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -14116,6 +14330,25 @@ "dependencies": { "zen-observable": "0.8.15" } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index 0872a862..9f28d1e3 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@faker-js/faker": "^9.3.0", + "@langchain/google-genai": "^0.2.1", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.4.8", "@nestjs/config": "^3.3.0", diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index 1c1b9109..e7152880 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -1,13 +1,71 @@ import { Injectable } from '@nestjs/common'; import { RmqContext } from '@nestjs/microservices'; +import { ChatGoogleGenerativeAI } from '@langchain/google-genai'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; +import { ChatExchangeService } from '../../common/rabbitmq/chat-exchange/chat-exchange.service'; +import { RabbitmqService } from '../../common/rabbitmq/rabbitmq.service'; +import { Chat } from 'src/types/chat'; @Injectable() export class ModeratorService { + private llm: ChatGoogleGenerativeAI; + public constructor( + private prisma: PrismaService, + private chatExchangeService: ChatExchangeService, + private rabbitmqService: RabbitmqService, + ) { + this.llm = new ChatGoogleGenerativeAI({ + apiKey: process.env.GOOGLE_GENAI_API_KEY, + model: 'gemini-2.0-flash', + }); + } + async handleMessage(message: any, context: RmqContext) { const channel = context.getChannelRef(); const orgMsg = context.getMessage(); try { console.log({ message: message, context: context }); + + const conversationId = + message.metadata.conversationId ?? + ( + await this.prisma.conversation.findFirst({ + where: { address: message.metadata.address }, + }) + )?.id; + + const messageHistory = await this.prisma.message.findMany({ + where: { + Threads: { + some: { + conversationId, + }, + }, + }, + orderBy: { + createdAt: 'asc', + }, + }); + + const messages = messageHistory.map(({ type, body }) => { + return `${type === 'RECEIVED' ? 'mentor' : 'mentee'}: ${body}`; + }); + messages.push( + 'system prompt: You are a human mentor, warm and approachable, guiding mentees with care and insight. Your goal is to understand their struggles as deeply as possible by asking thoughtful questions and making them feel heard. Keep the conversation light and engaging with a touch of humor, helping them relax and open up. Avoid robotic or dry responses—make your words feel natural, like a genuine human conversation. While being concise, ensure your responses hold meaning and value, never cutting out what truly matters. Keep it short, sweet, and impactful and your answers should be in plane text do not format it.', + ); + + const response = await this.llm.invoke(messages); + const chat: Chat = { + type: 'CHAT', + metadata: { + conversationId, + }, + payload: response, + }; + + const formattedData = await this.rabbitmqService.getChatEchangeData(chat); + + this.chatExchangeService.send('chat', formattedData); channel.ack(orgMsg); } catch (error) { console.log(error.message); From 5044db376d37afdba7d6103ad357e52859671055 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 16:00:42 +0300 Subject: [PATCH 12/67] included all dependencies in the moderator module --- src/apps/moderator/moderator.module.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/apps/moderator/moderator.module.ts b/src/apps/moderator/moderator.module.ts index 7bbd5c44..9173c04d 100644 --- a/src/apps/moderator/moderator.module.ts +++ b/src/apps/moderator/moderator.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { ModeratorService } from './moderator.service'; import { ModeratorController } from './moderator.controller'; +import { RabbitmqModule } from 'src/common/rabbitmq/rabbitmq.module'; +import { PrismaModule } from 'src/modules/prisma/prisma.module'; @Module({ + imports: [PrismaModule, RabbitmqModule], controllers: [ModeratorController], providers: [ModeratorService], }) From 3338b78ed4c0ceff5948a4b09963b46f86278636 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 16:09:45 +0300 Subject: [PATCH 13/67] debugging (I was trying to access the consumed message as object but was throwing error cause it is stringfied, so placed a parser based on type) --- src/apps/moderator/moderator.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index e7152880..f149b7fc 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -20,11 +20,11 @@ export class ModeratorService { }); } - async handleMessage(message: any, context: RmqContext) { + async handleMessage(data: any, context: RmqContext) { const channel = context.getChannelRef(); const orgMsg = context.getMessage(); try { - console.log({ message: message, context: context }); + const message = typeof data == 'string' ? JSON.parse(data) : data; const conversationId = message.metadata.conversationId ?? @@ -33,6 +33,7 @@ export class ModeratorService { where: { address: message.metadata.address }, }) )?.id; + console.log(conversationId); const messageHistory = await this.prisma.message.findMany({ where: { From 7a89b61f626c05e292833773ae085c5b69d27f2b Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 16:13:25 +0300 Subject: [PATCH 14/67] debugging (picked only the text part from gemini response since the whole response is non-sense when it is sent) --- src/apps/moderator/moderator.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index f149b7fc..e0775ed3 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -61,7 +61,7 @@ export class ModeratorService { metadata: { conversationId, }, - payload: response, + payload: response.text, }; const formattedData = await this.rabbitmqService.getChatEchangeData(chat); From 7f052cc6bb54a5e21eed5d8b1dc1b3106f01688d Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 16:14:40 +0300 Subject: [PATCH 15/67] removed a log placed for debugging --- src/apps/moderator/moderator.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index e0775ed3..caf5a120 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -33,7 +33,6 @@ export class ModeratorService { where: { address: message.metadata.address }, }) )?.id; - console.log(conversationId); const messageHistory = await this.prisma.message.findMany({ where: { From 6f3e827adcd8a80f8d8ef76e639feaddf0299caf Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 16:17:27 +0300 Subject: [PATCH 16/67] updated env.example with new variables I've used but not included --- .env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.example b/.env.example index 05b73d93..fe1ad9ae 100644 --- a/.env.example +++ b/.env.example @@ -14,3 +14,5 @@ WEB_CALLBACK_URL="" RABBITMQ_URL="" CHAT_PORT="" HOST_URL="" +WEBSOCKET_URL="" +GOOGLE_GENAI_API_KEY="" From d564cd9936b29683b8160ddfbf0358bbfb4acf97 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Sun, 30 Mar 2025 16:48:29 +0300 Subject: [PATCH 17/67] a mentor is created everytime account is created --- src/modules/auth/auth.service.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 4c8f208c..6317ccb2 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -74,7 +74,22 @@ export class AuthService { throw new Error('Failed to create account'); } - const role = await tx.role.findFirst({ + let role = await tx.role.findFirst({ + where: { + type: RoleType.MENTOR, + }, + }); + + await tx.mentor.create({ + data: { + name, + accountId: account.id, + email: `${account.id}@leyuchatbot.com`, + isBot: true, + }, + }); + + role = await tx.role.findFirst({ where: { type: RoleType.OWNER, isDefault: true, From 56daedb36474eb2ee0417cd1ae49cd905565b979 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 10:07:38 +0300 Subject: [PATCH 18/67] corrected how the role of the message was differentiated with langchain standardization --- src/apps/moderator/moderator.service.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index caf5a120..9ca41d57 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -46,13 +46,20 @@ export class ModeratorService { createdAt: 'asc', }, }); + let messages: any[] = []; - const messages = messageHistory.map(({ type, body }) => { - return `${type === 'RECEIVED' ? 'mentor' : 'mentee'}: ${body}`; + messages.push({ + role: 'system', + content: + 'You are a human mentor, warm and approachable, guiding mentees with care and insight. Your goal is to understand their struggles as deeply as possible by asking thoughtful questions and making them feel heard. Keep the conversation light and engaging with a touch of humor, helping them relax and open up. Avoid robotic or dry responses—make your words feel natural, like a genuine human conversation. While being concise, ensure your responses hold meaning and value, never cutting out what truly matters. Keep it short, sweet, and impactful and your answers should be in plane text do not format it.', + }); + + messages = messageHistory.map(({ type, body }) => { + return { + role: `${type === 'RECEIVED' ? 'assistant' : 'user'}`, + content: body, + }; }); - messages.push( - 'system prompt: You are a human mentor, warm and approachable, guiding mentees with care and insight. Your goal is to understand their struggles as deeply as possible by asking thoughtful questions and making them feel heard. Keep the conversation light and engaging with a touch of humor, helping them relax and open up. Avoid robotic or dry responses—make your words feel natural, like a genuine human conversation. While being concise, ensure your responses hold meaning and value, never cutting out what truly matters. Keep it short, sweet, and impactful and your answers should be in plane text do not format it.', - ); const response = await this.llm.invoke(messages); const chat: Chat = { From 0d3ac89721aed780d90a766cb9428bd81e9b5a78 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 18:19:45 +0300 Subject: [PATCH 19/67] included the new message on the context that is sent to the ai --- src/apps/moderator/moderator.service.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index 9ca41d57..27f74acd 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -46,6 +46,7 @@ export class ModeratorService { createdAt: 'asc', }, }); + let messages: any[] = []; messages.push({ @@ -56,11 +57,18 @@ export class ModeratorService { messages = messageHistory.map(({ type, body }) => { return { - role: `${type === 'RECEIVED' ? 'assistant' : 'user'}`, + role: `${type === 'RECEIVED' ? 'user' : 'assistant'}`, content: body, }; }); + messages.push({ + role: 'user', + content: message.payload, + }); + + console.log(messages); + const response = await this.llm.invoke(messages); const chat: Chat = { type: 'CHAT', From 0b0d42c080b17862818221bf842fb205fc19985f Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 18:20:31 +0300 Subject: [PATCH 20/67] removed un-necessary logs --- src/apps/moderator/moderator.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index 27f74acd..e6e02776 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -67,8 +67,6 @@ export class ModeratorService { content: message.payload, }); - console.log(messages); - const response = await this.llm.invoke(messages); const chat: Chat = { type: 'CHAT', From 1b0b57414f13f93ef98a71419cc3f21bafdfef98 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 18:31:06 +0300 Subject: [PATCH 21/67] updated the schema to support vector embeddings --- prisma/schema.prisma | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 475c0409..9c190412 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,12 +6,14 @@ generator client { provider = "prisma-client-js" + previewFeatures = ["postgresqlExtensions"] binaryTargets = ["native", "linux-musl-openssl-3.0.x", "debian-openssl-3.0.x"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") + extensions = [vector] } enum RoleType { @@ -76,6 +78,7 @@ model Mentor { name String email String expertise Json? + embedding Unsupported("vector(768)") capacity Int? availability Json? age Int? @@ -98,6 +101,7 @@ model Conversation { address String channelId String isActive Boolean + embedding Unsupported("vector(768)") Mentor Mentor @relation(fields: [mentorId], references: [id]) Channel Channel @relation(fields: [channelId], references: [id]) From 5dcb9430bd1bacfb1e3d29daef114da83fad5c1a Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 18:43:45 +0300 Subject: [PATCH 22/67] added vector extenstion to the postgres docker image --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ac6dabec..94bec0e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: RABBITMQ_URL: amqp://user:password@rabbitmq:5672 postgres: - image: postgres + image: pgvector/pgvector:pg17 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password From 3c3dea5d6fdba653b489044442d45a749c8f27f4 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 18:44:44 +0300 Subject: [PATCH 23/67] made the vector fields optional to avoid errors errors on creation with out embeddings --- prisma/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9c190412..b04dc5ab 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -78,7 +78,7 @@ model Mentor { name String email String expertise Json? - embedding Unsupported("vector(768)") + embedding Unsupported("vector(768)")? capacity Int? availability Json? age Int? @@ -101,7 +101,7 @@ model Conversation { address String channelId String isActive Boolean - embedding Unsupported("vector(768)") + embedding Unsupported("vector(768)")? Mentor Mentor @relation(fields: [mentorId], references: [id]) Channel Channel @relation(fields: [channelId], references: [id]) From 6c907ed96f0fee0d770f1f64c205b6582250a6a9 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 18:45:05 +0300 Subject: [PATCH 24/67] migrated the new schema --- prisma/migrations/20250331153224_pg_vector/migration.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 prisma/migrations/20250331153224_pg_vector/migration.sql diff --git a/prisma/migrations/20250331153224_pg_vector/migration.sql b/prisma/migrations/20250331153224_pg_vector/migration.sql new file mode 100644 index 00000000..51b653fa --- /dev/null +++ b/prisma/migrations/20250331153224_pg_vector/migration.sql @@ -0,0 +1,8 @@ +-- CreateExtension +CREATE EXTENSION IF NOT EXISTS "vector"; + +-- AlterTable +ALTER TABLE "Conversation" ADD COLUMN "embedding" vector(768); + +-- AlterTable +ALTER TABLE "Mentor" ADD COLUMN "embedding" vector(768); From 74268320a57386bdfa7ab37413e24adff80328fa Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 22:05:05 +0300 Subject: [PATCH 25/67] installed GoogleGenerativeAIEmbeddings packages from langchain for embeding data --- package-lock.json | 29 +++++++---------------------- package.json | 2 ++ 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa651f52..b58aecda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "license": "UNLICENSED", "dependencies": { "@faker-js/faker": "^9.3.0", + "@google/generative-ai": "^0.24.0", + "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.1", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.4.8", @@ -827,8 +829,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -1931,7 +1932,6 @@ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.43.tgz", "integrity": "sha512-DwiSUwmZqcuOn7j8SFdeOH1nvaUqG7q8qn3LhobdQYEg5PmjLgd2yLr2KzuT/YWMBfjkOR+Di5K6HEdFmouTxg==", "license": "MIT", - "peer": true, "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -1955,7 +1955,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -1968,7 +1967,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -3220,8 +3218,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/semver": { "version": "7.5.8", @@ -3287,8 +3284,7 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/validator": { "version": "13.12.2", @@ -5137,7 +5133,6 @@ "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz", "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", "license": "MIT", - "peer": true, "dependencies": { "simple-wcswidth": "^1.0.1" } @@ -5363,7 +5358,6 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8914,7 +8908,6 @@ "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.19.tgz", "integrity": "sha512-XC63YQeEcS47Y53gg950xiZ4IWmkfMe4p2V9OSaBt26q+p47WHn18izuXzSclCI73B7yGqtfRsT6jcZQI0y08g==", "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.5.1" } @@ -9131,7 +9124,6 @@ "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.15.tgz", "integrity": "sha512-cv3ebg0Hh0gRbl72cv/uzaZ+KOdfa2mGF1s74vmB2vlNVO/Ap/O9RYaHV+tpR8nwhGZ50R3ILnTOwSwGP+XQxw==", "license": "MIT", - "peer": true, "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", @@ -10187,7 +10179,6 @@ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "license": "MIT", - "peer": true, "bin": { "mustache": "bin/mustache" } @@ -10577,7 +10568,6 @@ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "license": "MIT", - "peer": true, "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" @@ -10593,15 +10583,13 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" @@ -11900,7 +11888,6 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 4" } @@ -12434,8 +12421,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sisteransi": { "version": "1.0.5", @@ -14336,7 +14322,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 9f28d1e3..a0da04c7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ }, "dependencies": { "@faker-js/faker": "^9.3.0", + "@google/generative-ai": "^0.24.0", + "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.1", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.4.8", From 043843a7ab9d1269c6679d788e84a569cab4e2c5 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 22:50:29 +0300 Subject: [PATCH 26/67] installed langchain for MemoryVectorStore --- package-lock.json | 307 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 302 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b58aecda..a9b6dfac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "graphql": "^16.9.0", "graphql-tools": "^9.0.2", "ioredis": "^5.4.2", + "langchain": "^0.3.19", "nodemailer": "^6.9.16", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", @@ -1990,6 +1991,39 @@ "@langchain/core": ">=0.3.17 <0.4.0" } }, + "node_modules/@langchain/openai": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.4.9.tgz", + "integrity": "sha512-NAsaionRHNdqaMjVLPkFCyjUDze+OqRHghA1Cn4fPoAafz+FXcl9c7LlEl9Xo0FH6/8yiCl7Rw2t780C/SBVxQ==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.87.3", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.39 <0.4.0" + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, "node_modules/@ljharb/through": { "version": "2.3.13", "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", @@ -3151,6 +3185,16 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/nodemailer": { "version": "6.4.17", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", @@ -3741,6 +3785,18 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3802,6 +3858,18 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -4027,7 +4095,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-flatten": { @@ -4078,7 +4145,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/babel-jest": { @@ -5043,7 +5109,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -5438,7 +5503,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -6305,6 +6369,15 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", @@ -6796,7 +6869,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -6807,6 +6879,25 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/formidable": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", @@ -7580,6 +7671,15 @@ "node": ">=10.17.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8923,7 +9023,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9015,6 +9114,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -9119,6 +9227,102 @@ "node": ">=6" } }, + "node_modules/langchain": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.19.tgz", + "integrity": "sha512-aGhoTvTBS5ulatA67RHbJ4bcV5zcYRYdm5IH+hpX99RYSFXG24XF3ghSjhYi6sxW+SUnEQ99fJhA5kroVpKNhw==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.5.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": ">=0.2.8 <0.4.0", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cerebras": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.2.21 <0.4.0", + "@langchain/deepseek": "*", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/google-vertexai-web": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "@langchain/xai": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cerebras": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/deepseek": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/google-vertexai-web": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "@langchain/xai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, "node_modules/langsmith": { "version": "0.3.15", "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.15.tgz", @@ -10249,6 +10453,25 @@ "dev": true, "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -10428,6 +10651,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.91.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.91.0.tgz", + "integrity": "sha512-zdDg6eyvUmCP58QAW7/aPb+XdeavJ51pK6AcwZOWG5QNSLIovVz0XonRL9vARGJRmw8iImmvf2A31Q7hoh544w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.84", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.84.tgz", + "integrity": "sha512-ACYy2HGcZPHxEeWTqowTF7dhXN+JU1o7Gr4b41klnn6pj2LD6rsiGqSZojMdk1Jh2ys3m76ap+ae1vvE4+5+vg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, "node_modules/optimism": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.0.tgz", @@ -13980,6 +14254,15 @@ "node": ">=4.0.0" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -14250,6 +14533,18 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index a0da04c7..699ddd5b 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "graphql": "^16.9.0", "graphql-tools": "^9.0.2", "ioredis": "^5.4.2", + "langchain": "^0.3.19", "nodemailer": "^6.9.16", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", From 5afecaee589bbae6bf3a48b16bcc97f8aca17280 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 22:52:09 +0300 Subject: [PATCH 27/67] service for moderator mentor matching --- src/apps/moderator/embedding.service.ts | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/apps/moderator/embedding.service.ts diff --git a/src/apps/moderator/embedding.service.ts b/src/apps/moderator/embedding.service.ts new file mode 100644 index 00000000..0ed87de3 --- /dev/null +++ b/src/apps/moderator/embedding.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from 'src/modules/prisma/prisma.service'; +import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai'; +import { MemoryVectorStore } from 'langchain/vectorstores/memory'; + +@Injectable() +export default class EmbeddingService { + private embedding: GoogleGenerativeAIEmbeddings; + + public constructor(private readonly prisma: PrismaService) { + this.embedding = new GoogleGenerativeAIEmbeddings({ + model: 'text-embedding-004', + }); + } + + async handleEmbedding(channelId: string, summary: string) { + try { + const mentors = await this.prisma.mentor.findMany({ + where: { + Conversation: { + some: { + Channel: { + id: channelId, + }, + }, + }, + }, + }); + + const mentorDocuments: any[] = mentors.map((mentor) => ({ + pageContent: mentor.expertise, + metadata: { mentorId: mentor.id }, + })); + + const mentorVectorStore = await MemoryVectorStore.fromDocuments( + mentorDocuments, + this.embedding, + ); + + const retriever = mentorVectorStore.asRetriever(1); + const matchedMentor = await retriever.invoke(summary); + return matchedMentor; + } catch (error) { + console.log(error.message); + } + } +} From 08fc3d55336d739719fe8a332f0a649c2a99aa66 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 23:02:54 +0300 Subject: [PATCH 28/67] changed the search to a bit stricter manner so that it returns with certain thershold --- src/apps/moderator/embedding.service.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/apps/moderator/embedding.service.ts b/src/apps/moderator/embedding.service.ts index 0ed87de3..27c22ed7 100644 --- a/src/apps/moderator/embedding.service.ts +++ b/src/apps/moderator/embedding.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from 'src/modules/prisma/prisma.service'; import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai'; import { MemoryVectorStore } from 'langchain/vectorstores/memory'; +import { ScoreThresholdRetriever } from 'langchain/retrievers/score_threshold'; @Injectable() export default class EmbeddingService { @@ -37,7 +38,19 @@ export default class EmbeddingService { this.embedding, ); - const retriever = mentorVectorStore.asRetriever(1); + const retriever = ScoreThresholdRetriever.fromVectorStore( + mentorVectorStore, + { + minSimilarityScore: 0.7, + maxK: 3, + }, + ); + + const matchedMentors = await retriever.invoke(summary); + + if (matchedMentors.length === 0) { + return null; + } const matchedMentor = await retriever.invoke(summary); return matchedMentor; } catch (error) { From 1b82255cb2b7b10af7570d4b262c6f85ac4115d4 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 23:06:58 +0300 Subject: [PATCH 29/67] changed default export --- src/apps/moderator/embedding.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/moderator/embedding.service.ts b/src/apps/moderator/embedding.service.ts index 27c22ed7..ecfa58d6 100644 --- a/src/apps/moderator/embedding.service.ts +++ b/src/apps/moderator/embedding.service.ts @@ -5,7 +5,7 @@ import { MemoryVectorStore } from 'langchain/vectorstores/memory'; import { ScoreThresholdRetriever } from 'langchain/retrievers/score_threshold'; @Injectable() -export default class EmbeddingService { +export class EmbeddingService { private embedding: GoogleGenerativeAIEmbeddings; public constructor(private readonly prisma: PrismaService) { From 7461e3521acf775b51da21ab5f5d404d36e26940 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 23:47:58 +0300 Subject: [PATCH 30/67] removed un necessary duplicate invocation --- src/apps/moderator/embedding.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/apps/moderator/embedding.service.ts b/src/apps/moderator/embedding.service.ts index ecfa58d6..091779fc 100644 --- a/src/apps/moderator/embedding.service.ts +++ b/src/apps/moderator/embedding.service.ts @@ -51,8 +51,7 @@ export class EmbeddingService { if (matchedMentors.length === 0) { return null; } - const matchedMentor = await retriever.invoke(summary); - return matchedMentor; + return matchedMentors; } catch (error) { console.log(error.message); } From 29a77a961984a106c95663ce6d61899a7f0d92b5 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 31 Mar 2025 23:50:17 +0300 Subject: [PATCH 31/67] conversation summarization, mentor selection and new conversation instantiation --- src/apps/moderator/moderator.service.ts | 54 +++++++++++++++++++------ 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index e6e02776..48192375 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -5,6 +5,7 @@ import { PrismaService } from 'src/modules/prisma/prisma.service'; import { ChatExchangeService } from '../../common/rabbitmq/chat-exchange/chat-exchange.service'; import { RabbitmqService } from '../../common/rabbitmq/rabbitmq.service'; import { Chat } from 'src/types/chat'; +import { EmbeddingService } from './embedding.service'; @Injectable() export class ModeratorService { @@ -13,6 +14,7 @@ export class ModeratorService { private prisma: PrismaService, private chatExchangeService: ChatExchangeService, private rabbitmqService: RabbitmqService, + private readonly embeddingService: EmbeddingService, ) { this.llm = new ChatGoogleGenerativeAI({ apiKey: process.env.GOOGLE_GENAI_API_KEY, @@ -30,7 +32,7 @@ export class ModeratorService { message.metadata.conversationId ?? ( await this.prisma.conversation.findFirst({ - where: { address: message.metadata.address }, + where: { address: message.metadata.address, isActive: true }, }) )?.id; @@ -52,20 +54,19 @@ export class ModeratorService { messages.push({ role: 'system', content: - 'You are a human mentor, warm and approachable, guiding mentees with care and insight. Your goal is to understand their struggles as deeply as possible by asking thoughtful questions and making them feel heard. Keep the conversation light and engaging with a touch of humor, helping them relax and open up. Avoid robotic or dry responses—make your words feel natural, like a genuine human conversation. While being concise, ensure your responses hold meaning and value, never cutting out what truly matters. Keep it short, sweet, and impactful and your answers should be in plane text do not format it.', + "You are an AI assistant designed to conduct a comprehensive interview with a mentee seeking mentorship. Your objective is to extract detailed information about the mentee's goals, interests, challenges, and preferences to facilitate an optimal mentor-mentee match. Please ask open-ended questions and encourage elaboration where necessary. Ensure that the conversation covers the following areas:\n\n1. **Career Aspirations:** Understand the mentee's professional objectives and desired career trajectory.\n2. **Skills and Expertise:** Identify the mentee's current skill set and areas where they seek development.\n3. **Challenges and Obstacles:** Explore any professional or personal challenges hindering their progress.\n4. **Learning Preferences:** Determine the mentee's preferred learning styles and formats (e.g., one-on-one, group sessions, online courses).\n5. **Availability and Commitment:** Assess the mentee's availability for mentorship sessions and their commitment level.\n6. **Expectations from Mentorship:** Clarify what the mentee hopes to achieve through the mentorship relationship.\n7. **Cultural and Personal Considerations:** Gather any cultural, personal, or contextual information that might influence the mentorship dynamics.\n\nAfter collecting this information, summarize the key points to create a comprehensive profile of the mentee's needs and preferences.", }); - messages = messageHistory.map(({ type, body }) => { - return { - role: `${type === 'RECEIVED' ? 'user' : 'assistant'}`, + messages = [ + ...messageHistory.map(({ type, body }) => ({ + role: type === 'RECEIVED' ? 'user' : 'assistant', content: body, - }; - }); - - messages.push({ - role: 'user', - content: message.payload, - }); + })), + { + role: 'user', + content: message.payload, + }, + ]; const response = await this.llm.invoke(messages); const chat: Chat = { @@ -77,8 +78,35 @@ export class ModeratorService { }; const formattedData = await this.rabbitmqService.getChatEchangeData(chat); - this.chatExchangeService.send('chat', formattedData); + + messages.push({ + role: 'assistant', + content: response.text, + }); + + messages[0] = { + role: 'system', + content: + "You are an AI assistant tasked with summarizing a series of conversation messages between a mentee and a bot. Your goal is to extract and structure key information to facilitate matching the mentee with a suitable mentor. Please analyze the provided conversation history and generate a summary that includes the following fields:\n\n1. **Mentee's Goals and Objectives:** Clearly outline the primary goals and aspirations expressed by the mentee.\n2. **Areas of Interest or Focus:** Identify specific subjects or fields the mentee is interested in.\n3. **Challenges and Obstacles:** Highlight any challenges the mentee is facing.\n4. **Preferred Learning Style:** Note the mentee's preferred learning methods.\n5. **Experience Level:** Assess the mentee's current level of experience in their areas of interest.\n6. **Short-Term and Long-Term Plans:** Summarize any plans or timelines discussed by the mentee.\n7. **Specific Questions or Concerns:** List any particular questions or concerns raised by the mentee.\n8. **Additional Relevant Information:** Include any other pertinent details that could aid in matching the mentee with an appropriate mentor.\n\nEnsure that the summary is concise yet comprehensive, capturing the essence of the mentee's needs and preferences. Structure the information in a way that allows for effective comparison with mentors' expertise profiles stored in JSON format within the EmbeddingService.handleEmbedding function.", + }; + const summary = await this.llm.invoke(messages); + const mentor = await this.embeddingService.handleEmbedding( + message.metadata.channelId, + summary.text, + ); + if (mentor) { + await this.prisma.conversation.update({ + where: { + id: message.metadata.conversationId, + }, + data: { isActive: false }, + }); + await this.prisma.conversation.create({ + mentorId: mentor[0].metadata.mentorId, + ...message.metadata, + }); + } channel.ack(orgMsg); } catch (error) { console.log(error.message); From 8976e9ba783c80f089472fbe5a4a361ae309c99f Mon Sep 17 00:00:00 2001 From: Biniyam Date: Tue, 1 Apr 2025 00:00:42 +0300 Subject: [PATCH 32/67] registered all dependencies --- src/apps/moderator/moderator.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/moderator/moderator.module.ts b/src/apps/moderator/moderator.module.ts index 9173c04d..5e88e446 100644 --- a/src/apps/moderator/moderator.module.ts +++ b/src/apps/moderator/moderator.module.ts @@ -3,10 +3,11 @@ import { ModeratorService } from './moderator.service'; import { ModeratorController } from './moderator.controller'; import { RabbitmqModule } from 'src/common/rabbitmq/rabbitmq.module'; import { PrismaModule } from 'src/modules/prisma/prisma.module'; +import { EmbeddingService } from './embedding.service'; @Module({ imports: [PrismaModule, RabbitmqModule], controllers: [ModeratorController], - providers: [ModeratorService], + providers: [ModeratorService, EmbeddingService], }) export class ModeratorModule {} From 9e602a975545e70f7a7bc7e676a22735d02a0f08 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Tue, 1 Apr 2025 00:02:43 +0300 Subject: [PATCH 33/67] included apiKey for the embedding model --- src/apps/moderator/embedding.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/apps/moderator/embedding.service.ts b/src/apps/moderator/embedding.service.ts index 091779fc..907e64ac 100644 --- a/src/apps/moderator/embedding.service.ts +++ b/src/apps/moderator/embedding.service.ts @@ -10,6 +10,7 @@ export class EmbeddingService { public constructor(private readonly prisma: PrismaService) { this.embedding = new GoogleGenerativeAIEmbeddings({ + apiKey: process.env.GOOGLE_GENAI_API_KEY, model: 'text-embedding-004', }); } From 8943610ad9d7301c5671f5584213e85f577fbdf7 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 7 Apr 2025 15:10:48 +0300 Subject: [PATCH 34/67] needs expertise when mentor creation --- src/modules/admin/mentor/dto/create-mentor.dto.ts | 3 +++ src/modules/admin/mentor/mentor.service.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/src/modules/admin/mentor/dto/create-mentor.dto.ts b/src/modules/admin/mentor/dto/create-mentor.dto.ts index f2f7da8d..e5dcee30 100644 --- a/src/modules/admin/mentor/dto/create-mentor.dto.ts +++ b/src/modules/admin/mentor/dto/create-mentor.dto.ts @@ -11,4 +11,7 @@ export class CreateMentorDto { @IsString() @IsOptional() name: string; + + @IsOptional() + expertise: any; } diff --git a/src/modules/admin/mentor/mentor.service.ts b/src/modules/admin/mentor/mentor.service.ts index cdb41365..0ea57c9f 100644 --- a/src/modules/admin/mentor/mentor.service.ts +++ b/src/modules/admin/mentor/mentor.service.ts @@ -65,6 +65,7 @@ export class MentorService { name: createMentorDto.name, email: createMentorDto.email, accountId: createMentorDto.accountId, + expertise: createMentorDto.expertise, }, }); From 41dcf3b0b11bdaa219efc248f0da02f2d2fb8c57 Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 7 Apr 2025 15:11:47 +0300 Subject: [PATCH 35/67] debugged how the prompt was being sent made the bot functional as intended --- src/apps/moderator/moderator.service.ts | 67 +++++++++++++------------ 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index 48192375..562c8ea3 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -19,6 +19,7 @@ export class ModeratorService { this.llm = new ChatGoogleGenerativeAI({ apiKey: process.env.GOOGLE_GENAI_API_KEY, model: 'gemini-2.0-flash', + maxOutputTokens: 50, }); } @@ -51,13 +52,40 @@ export class ModeratorService { let messages: any[] = []; - messages.push({ + messages = [ + { role: 'system', - content: - "You are an AI assistant designed to conduct a comprehensive interview with a mentee seeking mentorship. Your objective is to extract detailed information about the mentee's goals, interests, challenges, and preferences to facilitate an optimal mentor-mentee match. Please ask open-ended questions and encourage elaboration where necessary. Ensure that the conversation covers the following areas:\n\n1. **Career Aspirations:** Understand the mentee's professional objectives and desired career trajectory.\n2. **Skills and Expertise:** Identify the mentee's current skill set and areas where they seek development.\n3. **Challenges and Obstacles:** Explore any professional or personal challenges hindering their progress.\n4. **Learning Preferences:** Determine the mentee's preferred learning styles and formats (e.g., one-on-one, group sessions, online courses).\n5. **Availability and Commitment:** Assess the mentee's availability for mentorship sessions and their commitment level.\n6. **Expectations from Mentorship:** Clarify what the mentee hopes to achieve through the mentorship relationship.\n7. **Cultural and Personal Considerations:** Gather any cultural, personal, or contextual information that might influence the mentorship dynamics.\n\nAfter collecting this information, summarize the key points to create a comprehensive profile of the mentee's needs and preferences.", - }); + content: `prompt: You are a data collection agent** for the LeyuChat platform. + DO NOT act like a chatbot. + You are to EXTRACT only two pieces of information + 1. The TOPIC the mentee seeks mentorship on. + 2. The TIME they are available for mentorship. - messages = [ + Instructions: + 1. Do NOT GREET!, ask for personal details, or provide opinions. + 2. Ask only what is needed to collect the TOPIC! and TIME!. + 3. Your responses must be no more than 10 WORDS! each. + 4. After collecting both TOPIC and TIME, respond with ONLY: "done" + 5. Do not add extra text, explanations, or encouragement. + 6. If the user says "hello" or similar, immediately move to: "What topic do you want mentorship on?" + 7. Stop the conversation as soon as both answers are collected. + 8. Do not REASON or REFLECT on responses. + 9. Your TASK is PURELY DATA COLLECTION. This is NOT a conversation. + 10. You cannot respond to anything outside of collecting topic and time + 11. Do not narrow down a response. + 12. Do not ask for more elaboration. + 12. Do not add any form of encouragement or admiration on your response. + + Example of how to proceed: + - Mentee: "hello" + - You: "hello there I am leyuchat moderatore I am here to match you with a mentor that best suits your needs, What topic do you want mentorship on?" + - Mentee: "I want a mentor for weight loss" + - You: "When are you available for mentorship sessions?" + - Mentee: "Weekends, in the mornings" + - You: "done" + + Max output is capped at 40 tokens. Do not exceed this limit. Follow these instructions STRICTLY!!. DO NOT IMPROVISE.`, + }, ...messageHistory.map(({ type, body }) => ({ role: type === 'RECEIVED' ? 'user' : 'assistant', content: body, @@ -69,6 +97,7 @@ export class ModeratorService { ]; const response = await this.llm.invoke(messages); + const chat: Chat = { type: 'CHAT', metadata: { @@ -79,34 +108,6 @@ export class ModeratorService { const formattedData = await this.rabbitmqService.getChatEchangeData(chat); this.chatExchangeService.send('chat', formattedData); - - messages.push({ - role: 'assistant', - content: response.text, - }); - - messages[0] = { - role: 'system', - content: - "You are an AI assistant tasked with summarizing a series of conversation messages between a mentee and a bot. Your goal is to extract and structure key information to facilitate matching the mentee with a suitable mentor. Please analyze the provided conversation history and generate a summary that includes the following fields:\n\n1. **Mentee's Goals and Objectives:** Clearly outline the primary goals and aspirations expressed by the mentee.\n2. **Areas of Interest or Focus:** Identify specific subjects or fields the mentee is interested in.\n3. **Challenges and Obstacles:** Highlight any challenges the mentee is facing.\n4. **Preferred Learning Style:** Note the mentee's preferred learning methods.\n5. **Experience Level:** Assess the mentee's current level of experience in their areas of interest.\n6. **Short-Term and Long-Term Plans:** Summarize any plans or timelines discussed by the mentee.\n7. **Specific Questions or Concerns:** List any particular questions or concerns raised by the mentee.\n8. **Additional Relevant Information:** Include any other pertinent details that could aid in matching the mentee with an appropriate mentor.\n\nEnsure that the summary is concise yet comprehensive, capturing the essence of the mentee's needs and preferences. Structure the information in a way that allows for effective comparison with mentors' expertise profiles stored in JSON format within the EmbeddingService.handleEmbedding function.", - }; - const summary = await this.llm.invoke(messages); - const mentor = await this.embeddingService.handleEmbedding( - message.metadata.channelId, - summary.text, - ); - if (mentor) { - await this.prisma.conversation.update({ - where: { - id: message.metadata.conversationId, - }, - data: { isActive: false }, - }); - await this.prisma.conversation.create({ - mentorId: mentor[0].metadata.mentorId, - ...message.metadata, - }); - } channel.ack(orgMsg); } catch (error) { console.log(error.message); From e6192795091dab9d8caf7c0d9941e92a8e77ccfa Mon Sep 17 00:00:00 2001 From: Biniyam Date: Mon, 7 Apr 2025 16:33:44 +0300 Subject: [PATCH 36/67] configured trigurring condition for function calling --- src/apps/moderator/moderator.service.ts | 74 +++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/src/apps/moderator/moderator.service.ts b/src/apps/moderator/moderator.service.ts index 562c8ea3..34f2ac1b 100644 --- a/src/apps/moderator/moderator.service.ts +++ b/src/apps/moderator/moderator.service.ts @@ -54,8 +54,8 @@ export class ModeratorService { messages = [ { - role: 'system', - content: `prompt: You are a data collection agent** for the LeyuChat platform. + role: 'system', + content: `prompt: You are a data collection agent for the LeyuChat platform. DO NOT act like a chatbot. You are to EXTRACT only two pieces of information 1. The TOPIC the mentee seeks mentorship on. @@ -96,18 +96,82 @@ export class ModeratorService { }, ]; - const response = await this.llm.invoke(messages); + const aiMessage = await this.llm.invoke(messages); + let response = aiMessage.text; + console.log(response); + console.log(response == 'done'); + if (response.trim().toLowerCase() === 'done') { + console.log('response == done'); + messages[0] = { + role: 'system', + content: `You are a data extractor bot on a mentorship platform called LeyuChat. + Your role is NOT to chat. You are not a chatbot or assistant. + You are a function that **reads message history** and extracts exactly two fields: + + 1. "topic" what the mentee wants mentorship on + 2. "time" when the mentee is available for mentorship + + Your job is to output a JSON object with the following format ONLY: + + { + "topic": "", + "time": "