Skip to content

Commit c07d715

Browse files
phase2
1 parent 542313c commit c07d715

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+7833
-402
lines changed

app/api/rag/chat/route.ts

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { ragMiddleware } from '@/lib/rag/middleware';
3+
import { ragOrchestratorService } from '@/lib/rag/services/rag-orchestrator.service';
4+
import { UnifiedQueryAnalysisResult, unifiedQueryAnalysisService } from '@/lib/rag/services/unified-query-analysis.service';
5+
import { selectOptimalSearchStrategy } from '@/lib/rag/utils/similarity-utils';
6+
import { textGenerationService } from '@/lib/rag/text-generation';
7+
import { streamText } from 'ai';
8+
import { openai } from '@ai-sdk/openai';
9+
import { chatStorageService } from '@/lib/rag/services/chat-storage.service';
10+
import { ChatMetadataTracker } from '@/lib/rag/services/chat-metadata-tracker.service';
11+
12+
const API_CONFIG = {
13+
REQUEST_TIMEOUT_MS: 60000, // 60 seconds for full request
14+
ANALYSIS_TIMEOUT_MS: 10000 // 10 seconds for analysis only
15+
} as const;
16+
17+
export async function POST(req: NextRequest) {
18+
let analysisController: AbortController | undefined;
19+
let chatSessionId: string | undefined;
20+
let metadataTracker: ChatMetadataTracker | undefined;
21+
22+
try {
23+
const abortSignal = req.signal;
24+
25+
const { messages, dataroomId, viewerId, query, linkId, selectedFolderIds, selectedDocIds, folderDocIds } = await Promise.race([
26+
ragMiddleware.validateRequest(req),
27+
new Promise<never>((_, reject) =>
28+
setTimeout(() => reject(new Error(`Request validation timeout after ${API_CONFIG.REQUEST_TIMEOUT_MS}ms`)),
29+
API_CONFIG.REQUEST_TIMEOUT_MS)
30+
)
31+
]);
32+
33+
try {
34+
chatSessionId = await chatStorageService.createSession({
35+
dataroomId,
36+
linkId,
37+
viewerId,
38+
});
39+
40+
await chatStorageService.addMessage({
41+
sessionId: chatSessionId,
42+
role: 'user',
43+
content: query,
44+
});
45+
46+
metadataTracker = ChatMetadataTracker.create();
47+
48+
} catch (error) {
49+
console.error('Failed to create chat session:', error);
50+
}
51+
52+
if (abortSignal.aborted) {
53+
console.log('🛑 Request aborted before analysis');
54+
return new Response('Request aborted', { status: 499 });
55+
}
56+
57+
58+
analysisController = new AbortController();
59+
60+
const combinedSignal = abortSignal || analysisController.signal;
61+
62+
let analysisResult: UnifiedQueryAnalysisResult;
63+
try {
64+
analysisResult = await Promise.race([
65+
unifiedQueryAnalysisService.analyzeQuery(query, viewerId, dataroomId, combinedSignal),
66+
new Promise<never>((_, reject) =>
67+
setTimeout(() => reject(new Error(`Query analysis timeout after ${API_CONFIG.ANALYSIS_TIMEOUT_MS}ms`)),
68+
API_CONFIG.ANALYSIS_TIMEOUT_MS)
69+
)
70+
]);
71+
} catch (error) {
72+
if (abortSignal.aborted || (error instanceof Error && (error.name === 'AbortError' || error.message.includes('aborted')))) {
73+
console.log('🛑 Query analysis aborted gracefully');
74+
return new Response('Request aborted by user', { status: 499 });
75+
}
76+
77+
console.error('Query analysis failed:', error);
78+
79+
if (chatSessionId && metadataTracker) {
80+
textGenerationService.setChatContext(chatSessionId, metadataTracker);
81+
}
82+
83+
const fallbackResponse = await textGenerationService.generateSimpleResponse(
84+
"I'm having trouble understanding your query. Please try rephrasing it.",
85+
[{ id: 'fallback-user', role: 'user', parts: [{ type: 'text', text: query }] }],
86+
undefined,
87+
chatSessionId,
88+
metadataTracker
89+
);
90+
return fallbackResponse;
91+
}
92+
93+
94+
if (abortSignal.aborted) {
95+
console.log('🛑 Request aborted before processing');
96+
return new Response('Request aborted', { status: 499 });
97+
}
98+
99+
// 6. Handle chitchat/abusive queries
100+
if (['abusive', 'chitchat'].includes(analysisResult.queryClassification.type)) {
101+
const responseText = analysisResult.queryClassification.response;
102+
const stream = streamText({
103+
model: openai('gpt-4o-mini'),
104+
prompt: `You are a helpful assistant. Respond with exactly this text, no additional content: ${responseText}`,
105+
temperature: 0,
106+
abortSignal: abortSignal,
107+
onError: (error) => {
108+
console.error('streamText error in chitchat/abusive handling:', error);
109+
},
110+
});
111+
return stream.toUIMessageStreamResponse();
112+
}
113+
114+
// 4. Extract query parameters
115+
const sanitizedQuery = analysisResult.sanitization?.sanitizedQuery || query;
116+
const complexityScore = analysisResult.complexityAnalysis?.complexityScore || 0.5;
117+
const queryLength = analysisResult.complexityAnalysis?.wordCount || query.split(' ').length;
118+
const mentionedPageNumbers = analysisResult.queryExtraction?.pageNumbers || [];
119+
const keywords = analysisResult.queryExtraction?.keywords || [];
120+
121+
// 4.5. Chat Session Management (already done above)
122+
123+
// 5. Document Access Control & Permission Validation
124+
const { indexedDocuments, accessError } = await ragMiddleware.getAccessibleIndexedDocuments(
125+
dataroomId,
126+
viewerId,
127+
{ selectedDocIds, selectedFolderIds, folderDocIds }
128+
);
129+
130+
131+
if (accessError || !indexedDocuments || indexedDocuments.length === 0) {
132+
// Set chat context for fallback response storage
133+
if (chatSessionId && metadataTracker) {
134+
textGenerationService.setChatContext(chatSessionId, metadataTracker);
135+
}
136+
137+
const fallbackResponse = await textGenerationService.generateFallbackResponse(
138+
accessError || "No documents are available for search. Please ensure documents are properly indexed.",
139+
chatSessionId,
140+
metadataTracker
141+
);
142+
return fallbackResponse;
143+
}
144+
145+
// 6. Enhanced Strategy Selection
146+
const queryContext = {
147+
wordCount: analysisResult.complexityAnalysis.wordCount,
148+
keywords: analysisResult.queryExtraction.keywords,
149+
mentionedPageNumbers
150+
};
151+
152+
const analysisData = {
153+
intent: analysisResult.queryClassification.intent,
154+
requiresExpansion: analysisResult.queryClassification.requiresExpansion,
155+
optimalContextSize: analysisResult.queryClassification.optimalContextSize,
156+
processingStrategy: analysisResult.queryClassification.processingStrategy,
157+
complexityLevel: analysisResult.complexityAnalysis.complexityLevel,
158+
expansionStrategy: analysisResult.queryRewriting.expansionStrategy,
159+
requiresHyde: analysisResult.queryRewriting.requiresHyde,
160+
contextWindowHint: analysisResult.queryRewriting.contextWindowHint,
161+
generatedQueryCount: analysisResult.queryRewriting.generatedQueryCount || 0
162+
};
163+
164+
const optimalStrategy = selectOptimalSearchStrategy(
165+
queryLength,
166+
complexityScore,
167+
indexedDocuments.length,
168+
queryContext,
169+
analysisData
170+
);
171+
172+
console.log('🎯 Strategy Selection:', {
173+
strategy: optimalStrategy.search_strategy,
174+
confidence: optimalStrategy.confidence,
175+
});
176+
177+
// 7. Check if request was aborted before RAG processing
178+
if (abortSignal.aborted) {
179+
console.log('🛑 Request aborted before RAG processing');
180+
return new Response('Request aborted', { status: 499 });
181+
}
182+
183+
// 8. Process Query with RAG Orchestrator (pass abort signal)
184+
try {
185+
const result = await ragOrchestratorService.processQuery(
186+
sanitizedQuery,
187+
dataroomId,
188+
indexedDocuments,
189+
messages,
190+
optimalStrategy.search_strategy,
191+
analysisResult.queryClassification.intent,
192+
analysisResult.complexityAnalysis,
193+
{
194+
pageNumbers: mentionedPageNumbers,
195+
keywords,
196+
queryRewriting: analysisResult.queryRewriting
197+
},
198+
API_CONFIG.REQUEST_TIMEOUT_MS, // timeout
199+
abortSignal, // Pass the abort signal
200+
chatSessionId, // Chat session ID
201+
metadataTracker // Metadata tracker
202+
);
203+
204+
// 8. Return streaming response
205+
if (!result) {
206+
// Set chat context for fallback response storage
207+
if (chatSessionId && metadataTracker) {
208+
textGenerationService.setChatContext(chatSessionId, metadataTracker);
209+
}
210+
211+
const fallbackResponse = await textGenerationService.generateFallbackResponse(
212+
"Unable to process your query. Please try again.",
213+
chatSessionId,
214+
metadataTracker
215+
);
216+
return fallbackResponse;
217+
}
218+
219+
return result;
220+
} catch (error) {
221+
if (abortSignal.aborted || (error instanceof Error && (error.name === 'AbortError' || error.message.includes('aborted')))) {
222+
console.log('🛑 RAG processing aborted gracefully');
223+
return new Response('Request aborted by user', { status: 499 });
224+
}
225+
226+
// Set chat context for fallback response storage
227+
if (chatSessionId && metadataTracker) {
228+
textGenerationService.setChatContext(chatSessionId, metadataTracker);
229+
}
230+
231+
const fallbackResponse = await textGenerationService.generateFallbackResponse(
232+
"I encountered an issue processing your query. Please try again.",
233+
chatSessionId,
234+
metadataTracker
235+
);
236+
return fallbackResponse;
237+
}
238+
239+
} catch (error: unknown) {
240+
console.log('error', error)
241+
const errorMessage = error instanceof Error ? error.message : 'Internal server error';
242+
243+
console.error('RAG chat route error:', {
244+
error: errorMessage,
245+
timestamp: new Date().toISOString()
246+
});
247+
248+
// Set chat context for fallback response storage if available
249+
if (chatSessionId && metadataTracker) {
250+
textGenerationService.setChatContext(chatSessionId, metadataTracker);
251+
}
252+
253+
const fallbackResponse = await textGenerationService.generateFallbackResponse(
254+
`An error occurred: ${errorMessage}. Please try again or contact support if the problem persists.`,
255+
chatSessionId,
256+
metadataTracker
257+
);
258+
return fallbackResponse;
259+
} finally {
260+
if (analysisController) {
261+
analysisController.abort();
262+
}
263+
}
264+
}
265+
266+

0 commit comments

Comments
 (0)