From 9dcbe735e1d399070a7cfcb648f7f07d8e6c98cc Mon Sep 17 00:00:00 2001 From: ajoysr Date: Fri, 21 Mar 2025 20:31:03 +0600 Subject: [PATCH 1/2] feat: add similar questions generation and improve bot service logging --- src/modules/bot/bot.service.ts | 1 + src/modules/gemini/gemini.controller.ts | 7 +++ src/modules/gemini/gemini.service.ts | 61 ++++++++++++++++--------- src/modules/qna/qna.repository.ts | 3 +- src/modules/qna/qna.service.ts | 34 ++++++++++++++ src/prompts/index.ts | 28 ++++++++++++ src/utils/index.ts | 45 ++++++++++++++++++ 7 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 src/prompts/index.ts diff --git a/src/modules/bot/bot.service.ts b/src/modules/bot/bot.service.ts index f5b7e5a..9f94b85 100644 --- a/src/modules/bot/bot.service.ts +++ b/src/modules/bot/bot.service.ts @@ -42,6 +42,7 @@ export class BotService { } async getBotById(botId: string): Promise { + console.log('🚀 ~ BotService ~ getBotById ~ botId:', botId); const bot = await this.botRepository.findBotById(botId); if (!bot) throw new HttpException( diff --git a/src/modules/gemini/gemini.controller.ts b/src/modules/gemini/gemini.controller.ts index 630a252..94e5aa0 100644 --- a/src/modules/gemini/gemini.controller.ts +++ b/src/modules/gemini/gemini.controller.ts @@ -21,4 +21,11 @@ export class GeminiController { const ans = await this.geminiService.enhanceAnswer(question, answer); return ans; } + @Get('/similar') + async similarQuestions( + @Query('question') question: string, + ): Promise<{ questions: string[] }> { + const ans = await this.geminiService.generateSimilarQuestions(question); + return ans; + } } diff --git a/src/modules/gemini/gemini.service.ts b/src/modules/gemini/gemini.service.ts index 4e0e74e..bc999fb 100644 --- a/src/modules/gemini/gemini.service.ts +++ b/src/modules/gemini/gemini.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; import { GoogleGenerativeAI } from '@google/generative-ai'; import * as dotenv from 'dotenv'; +import { questionGeneratorPrompt, textGeneratorPrompt } from 'src/prompts'; +import { extractJsonFromText } from 'src/utils'; dotenv.config(); @@ -10,6 +12,7 @@ export class GeminiService { private genAI: GoogleGenerativeAI; private embeddingModel: any; private textModel: any; + private questionGeneratorModel: any; constructor() { // Initialize Google Generative AI with your API key @@ -22,31 +25,16 @@ export class GeminiService { model: 'text-embedding-004', }); + // Initialize the question generation model + this.questionGeneratorModel = this.genAI.getGenerativeModel({ + model: 'gemini-2.0-flash', + systemInstruction: questionGeneratorPrompt, + }); + // Initialize the text generation model this.textModel = this.genAI.getGenerativeModel({ model: 'gemini-2.0-flash', - systemInstruction: ` - You are an intelligent assistant for a website FAQ. Your task is to rewrite formal or technical answers into clear, friendly, and helpful responses that sound like they're from a knowledgeable human. - - Rules to follow: - 1. Stick to the facts — avoid adding guesses or unnecessary details. - 2. Use a natural, friendly, and helpful tone that feels human, not robotic. - 3. Keep the response concise yet informative. - 4. Use simple, everyday language — avoid jargon unless it's essential for understanding. - 5. Ensure all key information from the original answer is preserved. - 6. Structure the response logically, making it easy to follow. - 7. If the question is unclear, provide the most relevant and practical answer without over-explaining. - 8. IMPORTANT: Format your entire response using proper markdown. Use markdown formatting for: - - Headings (## for main headings, ### for subheadings) - - **Bold text** for emphasis - - *Italic text* for secondary emphasis - - Bullet points and numbered lists - - Code blocks with backticks when showing technical content - - Tables when presenting structured information - - > Blockquotes for highlighting important information - - **Output only the improved answer with proper markdown formatting — no introductions, explanations, or formatting comments.** - `, + systemInstruction: textGeneratorPrompt, }); } @@ -113,6 +101,35 @@ Format your response using proper markdown including headings, bold/italic text, throw new Error('Failed to generate embeddings'); } } + /** + * Generates similar questions from a given question + * @param question - The question to generate similar questions from + * @returns Array of similar questions + * + * Note: This is a simple implementation for generating similar questions. In a real-world scenario, you might want to use a more sophisticated method like semantic search or transformer models. + */ + + async generateSimilarQuestions( + question: string, + ): Promise<{ questions: string[] }> { + try { + const result = await this.questionGeneratorModel.generateContent({ + contents: [{ role: 'user', parts: [{ text: question }] }], + generationConfig: { + temperature: 0.2, + topK: 40, + topP: 0.95, + maxOutputTokens: 150, + }, + }); + + const response = result.response; + return extractJsonFromText(response.text()); + } catch (error) { + console.error('Error generating similar questions:', error); + throw new Error('Failed to generate similar questions'); + } + } /** * Comprehensive method to process a FAQ item - enhances the answer and generates embeddings diff --git a/src/modules/qna/qna.repository.ts b/src/modules/qna/qna.repository.ts index 809c73e..84516fc 100644 --- a/src/modules/qna/qna.repository.ts +++ b/src/modules/qna/qna.repository.ts @@ -32,7 +32,6 @@ export class QnARepository { try { const { skip, limit } = pagination; const { botId } = query; - console.log('🚀 ~ QnARepository ~ botId:', botId); let sqlQuery = 'SELECT id, question, answer, "botId", "createdAt", "updatedAt" FROM question_n_answers'; @@ -67,7 +66,7 @@ export class QnARepository { if (botId) { sqlQuery += ' WHERE "botId" = $1'; - queryParams.push(botId); + queryParams.push(botId?.toString()); } const result = await client.query(sqlQuery, queryParams); diff --git a/src/modules/qna/qna.service.ts b/src/modules/qna/qna.service.ts index 5573182..374f141 100644 --- a/src/modules/qna/qna.service.ts +++ b/src/modules/qna/qna.service.ts @@ -51,6 +51,40 @@ export class QnAService { embedding, ); + if (!createdQna) { + throw new Error(QnaErrorMessages.COULD_NOT_CREATE_QNA); + } + const { questions: similarQuestions = [] } = + (await this.geminiService.generateSimilarQuestions(question)) ?? {}; + console.log( + '🚀 ~ QnAService ~ create ~ similarQuestions:', + similarQuestions, + ); + + if (similarQuestions?.length > 0) { + const insertPromises = similarQuestions?.map( + async (similarQuestion) => { + try { + const similarEmbedding = + await this.geminiService.generateEmbeddings(similarQuestion); + return this.qnaRepo.insertVector( + similarQuestion, + answer, + botId, + similarEmbedding, + ); + } catch (error) { + console.error( + `Error processing similar question "${similarQuestion}":`, + error, + ); + } + }, + ); + + await Promise.all(insertPromises); + } + return this.apiResponse.success(createdQna); } catch (error) { throw new HttpException( diff --git a/src/prompts/index.ts b/src/prompts/index.ts new file mode 100644 index 0000000..a6b089e --- /dev/null +++ b/src/prompts/index.ts @@ -0,0 +1,28 @@ +export const textGeneratorPrompt = `You are an intelligent assistant for a website FAQ. Your task is to rewrite formal or technical answers into clear, friendly, and helpful responses that sound like they're from a knowledgeable human. + + Rules to follow: + 1. Stick to the facts — avoid adding guesses or unnecessary details. + 2. Use a natural, friendly, and helpful tone that feels human, not robotic. + 3. Keep the response concise yet informative. + 4. Use simple, everyday language — avoid jargon unless it's essential for understanding. + 5. Ensure all key information from the original answer is preserved. + 6. Structure the response logically, making it easy to follow. + 7. If the question is unclear, provide the most relevant and practical answer without over-explaining. + 8. IMPORTANT: Format your entire response using proper markdown. Use markdown formatting for: + - Headings (## for main headings, ### for subheadings) + - **Bold text** for emphasis + - *Italic text* for secondary emphasis + - Bullet points and numbered lists + - Code blocks with backticks when showing technical content + - Tables when presenting structured information + - > Blockquotes for highlighting important information + + **Output only the improved answer with proper markdown formatting — no introductions, explanations, or formatting comments.**`; + +export const questionGeneratorPrompt = `You are an intelligent assistant for a generating similar question from given a question. and make sure you return this format +questions:[ + "put similar question of the given questions here?", +] + make sure, you are strictly bound to return the output in the above format. I mean maintain this json format. + only and only in json format but don't add code format '''json like that only return the json. + `; diff --git a/src/utils/index.ts b/src/utils/index.ts index 98e26bb..c1f8e01 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,3 +15,48 @@ export function toObjectId(id: string): mongoose.Types.ObjectId { } return new mongoose.Types.ObjectId(id); } +/** + * Extracts a JSON object from a string containing JSON. + * @param {string} text - The text containing JSON. + * @returns {object|null} - The extracted JSON object or null if extraction fails. + */ +export function extractJsonFromText(text: string) { + try { + // Look for patterns that might be JSON objects + const jsonRegex = /{[\s\S]*?}/g; + const matches = text.match(jsonRegex); + + if (!matches || matches.length === 0) { + return null; + } + + // Try to parse each match until we find valid JSON + for (const match of matches) { + try { + const parsedJson = JSON.parse(match); + // Check if the parsed object has the expected structure + if ( + parsedJson && + parsedJson.questions && + Array.isArray(parsedJson.questions) + ) { + return parsedJson; + } + } catch (innerError) { + // Skip invalid JSON matches + continue; + } + } + + // If we haven't returned yet, try to parse the largest match + // (which is likely to be the complete JSON object) + const largestMatch = matches.reduce( + (a, b) => (a.length > b.length ? a : b), + '', + ); + return JSON.parse(largestMatch); + } catch (error) { + console.error('Error extracting JSON:', error); + return null; + } +} From 6c616e7d312671a551c4ca2de3edff5850b1be09 Mon Sep 17 00:00:00 2001 From: ajoysr Date: Fri, 21 Mar 2025 20:33:00 +0600 Subject: [PATCH 2/2] refactor: remove unnecessary console.log statements from service modules --- src/helper/utils/index.ts | 1 - src/modules/auth/auth.service.ts | 1 - src/modules/bot/bot.service.ts | 1 - src/modules/conversation/conversation.service.ts | 2 -- src/modules/qna/qna.repository.ts | 1 - src/modules/qna/qna.service.ts | 1 - src/modules/user/user.service.ts | 1 - 7 files changed, 8 deletions(-) diff --git a/src/helper/utils/index.ts b/src/helper/utils/index.ts index d469077..18edbc8 100644 --- a/src/helper/utils/index.ts +++ b/src/helper/utils/index.ts @@ -60,7 +60,6 @@ export function generateSearchQuery(condition: { q?: string; }): object { const { botId, q } = condition; - console.log('🚀 ~ condition:', condition); const query: Record = {}; if (q !== undefined && q !== '') { diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 11d3195..eaa696f 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -184,7 +184,6 @@ export class AuthService { */ async forgotPassword(email: string): Promise<{ message: string }> { const user = await this.userRepo.findUser({ email }); - console.log('🚀 ~ AuthService ~ forgotPassword ~ user:', user); if (!user) throw new HttpException( diff --git a/src/modules/bot/bot.service.ts b/src/modules/bot/bot.service.ts index 9f94b85..f5b7e5a 100644 --- a/src/modules/bot/bot.service.ts +++ b/src/modules/bot/bot.service.ts @@ -42,7 +42,6 @@ export class BotService { } async getBotById(botId: string): Promise { - console.log('🚀 ~ BotService ~ getBotById ~ botId:', botId); const bot = await this.botRepository.findBotById(botId); if (!bot) throw new HttpException( diff --git a/src/modules/conversation/conversation.service.ts b/src/modules/conversation/conversation.service.ts index 82d9826..4c1eb13 100644 --- a/src/modules/conversation/conversation.service.ts +++ b/src/modules/conversation/conversation.service.ts @@ -141,7 +141,6 @@ export class ConversationService { conversation.botId?._id.toString(), conversation._id ?? id, ); - console.log('🚀 ~ ConversationService ~ botMessage:', botMessage); const botMessageUpdate = await this.conversationRepository.addMessageToConversation( @@ -228,7 +227,6 @@ export class ConversationService { botId: botId, limit: 3, }); - console.log('🚀 231~ ConversationService ~ botId:', botId); if (searchResults.data.length === 0) { const botDetails = await this.botRepo.findBotById(botId); diff --git a/src/modules/qna/qna.repository.ts b/src/modules/qna/qna.repository.ts index 84516fc..2b4ce8b 100644 --- a/src/modules/qna/qna.repository.ts +++ b/src/modules/qna/qna.repository.ts @@ -77,7 +77,6 @@ export class QnARepository { } async findVectorById(id: string) { - console.log('🚀 ~ QnARepository ~ findVectorById ~ id:', id); const client = await this.dbService.getClient(); try { const result = await client.query( diff --git a/src/modules/qna/qna.service.ts b/src/modules/qna/qna.service.ts index 374f141..ab0d487 100644 --- a/src/modules/qna/qna.service.ts +++ b/src/modules/qna/qna.service.ts @@ -98,7 +98,6 @@ export class QnAService { async findAll(condition: { q: string }, pagination: PaginationQueryDto) { const query = generateSearchQuery(condition); - console.log('🚀 ~ QnAService ~ findAll ~ query:', query); // Paginate the list of users based on the generated query, role IDs query, and pagination settings const { data, page, limit, total } = await this.paginationService.paginate( diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index d761c18..724acb2 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -126,7 +126,6 @@ export class UserService { }, pagination, ); - console.log('🚀 ~ UserService ~ total:', total); const users: UserInterface[] = []; for (const user of data as UserInterface[]) {