chat.controller.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import {
  2. Body,
  3. Controller,
  4. Post,
  5. Request,
  6. Res,
  7. UseGuards,
  8. } from '@nestjs/common';
  9. import { Response } from 'express';
  10. import { ChatMessage, ChatService } from './chat.service';
  11. import { CombinedAuthGuard } from '../auth/combined-auth.guard';
  12. import { ModelConfigService } from '../model-config/model-config.service';
  13. import { TenantService } from '../tenant/tenant.service';
  14. import { ModelType } from '../types';
  15. class StreamChatDto {
  16. message: string;
  17. history: ChatMessage[];
  18. userLanguage?: string;
  19. selectedEmbeddingId?: string;
  20. selectedLLMId?: string;
  21. selectedGroups?: string[];
  22. selectedFiles?: string[];
  23. historyId?: string;
  24. enableRerank?: boolean;
  25. selectedRerankId?: string;
  26. temperature?: number;
  27. maxTokens?: number;
  28. topK?: number;
  29. similarityThreshold?: number;
  30. rerankSimilarityThreshold?: number;
  31. enableQueryExpansion?: boolean;
  32. enableHyDE?: boolean;
  33. }
  34. @Controller('chat')
  35. @UseGuards(CombinedAuthGuard)
  36. export class ChatController {
  37. constructor(
  38. private chatService: ChatService,
  39. private modelConfigService: ModelConfigService,
  40. private tenantService: TenantService,
  41. ) { }
  42. @Post('stream')
  43. async streamChat(
  44. @Request() req,
  45. @Body() body: StreamChatDto,
  46. @Res() res: Response,
  47. ) {
  48. try {
  49. console.log('Full Request Body:', JSON.stringify(body, null, 2));
  50. const { message, history = [], userLanguage = 'zh', selectedEmbeddingId, selectedLLMId, selectedGroups, selectedFiles, historyId, enableRerank, selectedRerankId, temperature, maxTokens, topK, similarityThreshold, rerankSimilarityThreshold, enableQueryExpansion, enableHyDE } = body;
  51. const userId = req.user.id;
  52. console.log('=== Chat Debug Info ===');
  53. console.log('User ID:', userId);
  54. console.log('Message:', message);
  55. console.log('User Language:', userLanguage);
  56. console.log('Selected Embedding ID:', selectedEmbeddingId);
  57. console.log('Selected LLM ID:', selectedLLMId);
  58. console.log('Selected Groups:', selectedGroups);
  59. console.log('Selected Files:', selectedFiles);
  60. console.log('History ID:', historyId);
  61. console.log('Temperature:', temperature);
  62. console.log('Max Tokens:', maxTokens);
  63. console.log('Top K:', topK);
  64. console.log('Similarity Threshold:', similarityThreshold);
  65. console.log('Rerank Similarity Threshold:', rerankSimilarityThreshold);
  66. console.log('Query Expansion:', enableQueryExpansion);
  67. console.log('HyDE:', enableHyDE);
  68. const role = req.user.role;
  69. const tenantId = req.user.tenantId;
  70. let models = await this.modelConfigService.findAll(userId, tenantId);
  71. if (role !== 'SUPER_ADMIN') {
  72. const tenantSettings = await this.tenantService.getSettings(tenantId);
  73. const enabledIds = tenantSettings.enabledModelIds || [];
  74. // Only allow models that are enabled by the tenant admin
  75. models = models.filter(m => enabledIds.includes(m.id));
  76. }
  77. let llmModel;
  78. if (selectedLLMId) {
  79. // Find specifically selected model
  80. llmModel = await this.modelConfigService.findOne(selectedLLMId, userId, tenantId);
  81. console.log('Using selected LLM model:', llmModel.name);
  82. } else {
  83. // Use organization's default LLM from Index Chat Config (strict)
  84. llmModel = await this.modelConfigService.findDefaultByType(tenantId, ModelType.LLM);
  85. console.log('Final LLM model used (default):', llmModel ? llmModel.name : 'None');
  86. }
  87. res.setHeader('Content-Type', 'text/event-stream');
  88. res.setHeader('Cache-Control', 'no-cache');
  89. res.setHeader('Connection', 'keep-alive');
  90. res.setHeader('Access-Control-Allow-Origin', '*');
  91. if (!llmModel) {
  92. res.write(
  93. `data: ${JSON.stringify({ type: 'error', data: 'Please add LLM model and configure API key in model management' })}\n\n`,
  94. );
  95. res.write('data: [DONE]\n\n');
  96. res.end();
  97. return;
  98. }
  99. const stream = this.chatService.streamChat(
  100. message,
  101. history,
  102. userId,
  103. llmModel as any,
  104. userLanguage,
  105. selectedEmbeddingId,
  106. selectedGroups,
  107. selectedFiles,
  108. historyId,
  109. enableRerank,
  110. selectedRerankId,
  111. temperature,
  112. maxTokens,
  113. topK,
  114. similarityThreshold,
  115. rerankSimilarityThreshold,
  116. enableQueryExpansion,
  117. enableHyDE,
  118. req.user.tenantId // Pass tenant ID
  119. );
  120. for await (const chunk of stream) {
  121. res.write(`data: ${JSON.stringify(chunk)}\n\n`);
  122. }
  123. res.write('data: [DONE]\n\n');
  124. res.end();
  125. } catch (error) {
  126. console.error('Stream chat error:', error);
  127. try {
  128. res.write(
  129. `data: ${JSON.stringify({ type: 'error', data: error.message || 'Server Error' })}\n\n`,
  130. );
  131. res.write('data: [DONE]\n\n');
  132. res.end();
  133. } catch (writeError) {
  134. console.error('Failed to write error response:', writeError);
  135. }
  136. }
  137. }
  138. @Post('assist')
  139. async streamAssist(
  140. @Request() req,
  141. @Body() body: { instruction: string; context: string },
  142. @Res() res: Response,
  143. ) {
  144. try {
  145. const { instruction, context } = body;
  146. const userId = req.user.id; // Corrected to use req.user.id
  147. const tenantId = req.user.tenantId;
  148. const role = req.user.role;
  149. // Use organization's default LLM from Index Chat Config (strict)
  150. const llmModel = await this.modelConfigService.findDefaultByType(tenantId, ModelType.LLM);
  151. res.setHeader('Content-Type', 'text/event-stream');
  152. res.setHeader('Cache-Control', 'no-cache');
  153. res.setHeader('Connection', 'keep-alive');
  154. res.setHeader('Access-Control-Allow-Origin', '*');
  155. if (!llmModel) {
  156. res.write(
  157. `data: ${JSON.stringify({ type: 'error', data: 'LLM model configuration not found' })}\n\n`,
  158. );
  159. res.write('data: [DONE]\n\n');
  160. res.end();
  161. return;
  162. }
  163. const stream = this.chatService.streamAssist(
  164. instruction,
  165. context,
  166. llmModel as any,
  167. );
  168. for await (const chunk of stream) {
  169. res.write(`data: ${JSON.stringify(chunk)}\n\n`);
  170. }
  171. res.write('data: [DONE]\n\n');
  172. res.end();
  173. } catch (error) {
  174. console.error('Stream assist error:', error);
  175. res.write(
  176. `data: ${JSON.stringify({ type: 'error', data: error.message || 'Server Error' })}\n\n`,
  177. );
  178. res.write('data: [DONE]\n\n');
  179. res.end();
  180. }
  181. }
  182. }