| import { GeminiEmbedding } from '../agent/gemini-embedding.js'; |
| import { SupabaseService } from './supabase.service.js'; |
| import { logger } from '../utils/logger.js'; |
| import type { GuidelineQuery } from '../types/index.js'; |
|
|
| export class RAGService { |
| private supabaseService: SupabaseService; |
| private embedding: GeminiEmbedding; |
|
|
| constructor(supabaseService: SupabaseService) { |
| this.supabaseService = supabaseService; |
| this.embedding = new GeminiEmbedding(); |
| } |
|
|
| async initialize(): Promise<void> { |
| |
| logger.info('RAG service ready'); |
| } |
|
|
| async searchGuidelines(query: GuidelineQuery): Promise<string[]> { |
| try { |
| |
| logger.info('='.repeat(80)); |
| logger.info('[MCP RAG] INPUT:'); |
| logger.info(JSON.stringify(query, null, 2)); |
| |
| |
| const queryText = this.buildQueryText(query); |
| logger.info(`[MCP RAG] Search query text: ${queryText}`); |
|
|
| |
| const queryEmbedding = await this.embedding.embedQuery(queryText); |
|
|
| |
| logger.info('[MCP RAG] Calling match_guideline_chunks RPC...'); |
| logger.info(`[MCP RAG] RPC params: { match_threshold: 0.3, match_count: 3 }`); |
| |
| const { data: docs, error } = await this.supabaseService.getClient().rpc( |
| 'match_guideline_chunks', |
| { |
| query_embedding: queryEmbedding, |
| match_threshold: 0.3, |
| match_count: 3 |
| } |
| ); |
|
|
| if (error) { |
| logger.error('[MCP RAG] ERROR calling match_guideline_chunks RPC:'); |
| logger.error(JSON.stringify(error, null, 2)); |
| throw error; |
| } |
|
|
| if (!docs || docs.length === 0) { |
| logger.info('[MCP RAG] OUTPUT: No relevant guideline chunks found'); |
| logger.info('='.repeat(80)); |
| return []; |
| } |
|
|
| |
| const guidelines = docs.map((doc: any) => doc.content); |
| |
| |
| logger.info(`[MCP RAG] OUTPUT: Found ${guidelines.length} relevant guideline snippets`); |
| docs.forEach((doc: any, index: number) => { |
| logger.info(`[MCP RAG] Result ${index + 1}:`); |
| logger.info(` - Similarity: ${doc.similarity?.toFixed(4) || 'N/A'}`); |
| logger.info(` - Content preview: ${doc.content?.substring(0, 150) || 'N/A'}...`); |
| }); |
| logger.info('='.repeat(80)); |
| |
| return guidelines; |
| } catch (error) { |
| logger.error('[MCP RAG] ERROR in searchGuidelines:'); |
| logger.error(JSON.stringify(error, null, 2)); |
| logger.info('='.repeat(80)); |
| return []; |
| } |
| } |
|
|
| private buildQueryText(query: GuidelineQuery): string { |
| const parts: string[] = []; |
| |
| if (query.symptoms) { |
| parts.push(`Triệu chứng: ${query.symptoms}`); |
| } |
| |
| if (query.suspected_conditions && query.suspected_conditions.length > 0) { |
| parts.push(`Tình trạng nghi ngờ: ${query.suspected_conditions.join(', ')}`); |
| } |
| |
| if (query.triage_level) { |
| parts.push(`Mức độ: ${query.triage_level}`); |
| } |
| |
| return parts.join('. '); |
| } |
| |
| |
| async addGuideline( |
| condition: string, |
| source: string, |
| chunks: string[] |
| ): Promise<void> { |
| try { |
| logger.info(`Adding guideline for ${condition}...`); |
|
|
| |
| const { data: guideline, error: guidelineError } = await this.supabaseService |
| .getClient() |
| .from('guidelines') |
| .insert({ |
| condition, |
| source, |
| updated_at: new Date().toISOString() |
| }) |
| .select() |
| .single(); |
|
|
| if (guidelineError) throw guidelineError; |
| if (!guideline) throw new Error('Failed to create guideline record'); |
|
|
| |
| const embeddings = await Promise.all( |
| chunks.map(chunk => this.embedding.embedQuery(chunk)) |
| ); |
|
|
| |
| const chunksData = chunks.map((chunk, index) => ({ |
| guideline_id: guideline.id, |
| content: chunk, |
| embedding: embeddings[index], |
| metadata: { condition, source } |
| })); |
|
|
| const { error: chunksError } = await this.supabaseService |
| .getClient() |
| .from('guideline_chunks') |
| .insert(chunksData); |
|
|
| if (chunksError) throw chunksError; |
|
|
| logger.info(`Successfully added ${chunks.length} chunks for ${condition}`); |
| } catch (error) { |
| logger.error({ error }, 'Error adding guideline'); |
| throw error; |
| } |
| } |
| } |
|
|
|
|