import { prisma } from '../config/database.js'; import { LLMFactory } from '../adapters/LLMFactory.js'; import { Message, ModelType, StreamChunk } from '../adapters/types.js'; import { agentService } from './agentService.js'; import * as knowledgeBaseService from './knowledgeBaseService.js'; interface CreateConversationData { userId: string; projectId: string; agentId: string; title?: string; } interface SendMessageData { conversationId: string; content: string; modelType: ModelType; knowledgeBaseIds?: string[]; } export class ConversationService { /** * 创建新对话 */ async createConversation(data: CreateConversationData) { const { userId, projectId, agentId, title } = data; // 验证智能体是否存在 const agent = agentService.getAgentById(agentId); if (!agent) { throw new Error('智能体不存在'); } // 验证项目是否存在 const project = await prisma.project.findFirst({ where: { id: projectId, userId: userId, deletedAt: null, }, }); if (!project) { throw new Error('项目不存在或无权访问'); } // 创建对话 const conversation = await prisma.conversation.create({ data: { userId, projectId, agentId, title: title || `与${agent.name}的对话`, metadata: { agentName: agent.name, agentCategory: agent.category, }, }, }); return conversation; } /** * 获取对话列表 */ async getConversations(userId: string, projectId?: string) { const where: any = { userId, deletedAt: null, }; if (projectId) { where.projectId = projectId; } const conversations = await prisma.conversation.findMany({ where, include: { project: { select: { id: true, name: true, }, }, _count: { select: { messages: true, }, }, }, orderBy: { updatedAt: 'desc', }, }); return conversations; } /** * 获取对话详情(包含消息) */ async getConversationById(conversationId: string, userId: string) { const conversation = await prisma.conversation.findFirst({ where: { id: conversationId, userId, deletedAt: null, }, include: { project: { select: { id: true, name: true, background: true, researchType: true, }, }, messages: { orderBy: { createdAt: 'asc', }, }, }, }); if (!conversation) { throw new Error('对话不存在或无权访问'); } return conversation; } /** * 组装上下文消息 */ private async assembleContext( conversationId: string, agentId: string, projectBackground: string, userInput: string, knowledgeBaseContext?: string ): Promise { console.log('🔧 [assembleContext] 开始组装上下文', { conversationId, agentId, hasKnowledgeBaseContext: !!knowledgeBaseContext, knowledgeBaseContextLength: knowledgeBaseContext?.length || 0 }); // 获取系统Prompt const systemPrompt = agentService.getSystemPrompt(agentId); // 获取历史消息(最近100条,约50轮对话) // DeepSeek-V3支持64K tokens,实际可容纳100-200轮对话 const historyMessages = await prisma.message.findMany({ where: { conversationId, }, orderBy: { createdAt: 'desc', }, take: 100, }); // 反转顺序(最早的在前) historyMessages.reverse(); // 判断是否是第一条消息 const isFirstMessage = historyMessages.length === 0; console.log(`📜 [assembleContext] 历史消息数: ${historyMessages.length}, 是否首次: ${isFirstMessage}`); // 渲染用户Prompt let userPromptContent: string; if (isFirstMessage) { // 第一条消息:使用完整模板(包含项目背景) userPromptContent = agentService.renderUserPrompt(agentId, { projectBackground, userInput, knowledgeBaseContext, }); console.log(`📝 [assembleContext] 首次消息,使用完整模板,长度: ${userPromptContent.length}`); console.log(`📋 [assembleContext] userPromptContent完整内容:\n${userPromptContent}`); console.log(`🔍 [assembleContext] 是否包含"参考文献": ${userPromptContent.includes('参考文献')}`); console.log(`🔍 [assembleContext] 是否包含知识库内容: ${userPromptContent.includes('阿尔兹海默症')}`); } else { // 后续消息:只发送用户输入和知识库上下文(如果有) if (knowledgeBaseContext) { userPromptContent = `${userInput}\n\n## 参考文献(来自知识库)\n${knowledgeBaseContext}`; console.log(`📝 [assembleContext] 后续消息+知识库,总长度: ${userPromptContent.length}`); console.log(`📋 [assembleContext] userPromptContent预览:\n${userPromptContent.substring(0, 300)}...`); } else { userPromptContent = userInput; console.log(`📝 [assembleContext] 后续消息,仅用户输入: ${userPromptContent}`); } } // 组装消息数组 const messages: Message[] = [ { role: 'system', content: systemPrompt, }, ]; // 添加历史消息 for (const msg of historyMessages) { messages.push({ role: msg.role as 'user' | 'assistant', content: msg.content, }); } // 添加当前用户输入 messages.push({ role: 'user', content: userPromptContent, }); console.log(`✅ [assembleContext] 组装完成,消息总数: ${messages.length}`); return messages; } /** * 发送消息(非流式) */ async sendMessage(data: SendMessageData, userId: string) { const { conversationId, content, modelType, knowledgeBaseIds } = data; // 获取对话信息 const conversation = await this.getConversationById(conversationId, userId); // 获取知识库上下文(如果有@知识库) let knowledgeBaseContext = ''; if (knowledgeBaseIds && knowledgeBaseIds.length > 0) { const knowledgeResults: string[] = []; // 对每个知识库进行检索 for (const kbId of knowledgeBaseIds) { try { const searchResult = await knowledgeBaseService.searchKnowledgeBase( userId, kbId, content, 3 // 每个知识库返回3个最相关的段落 ); // 格式化检索结果 if (searchResult.records && searchResult.records.length > 0) { const kbInfo = await prisma.knowledgeBase.findUnique({ where: { id: kbId }, select: { name: true }, }); knowledgeResults.push( `【知识库:${kbInfo?.name || '未命名'}】\n` + searchResult.records .map((record: any, index: number) => { const score = (record.score * 100).toFixed(1); return `${index + 1}. [相关度${score}%] ${record.segment.content}`; }) .join('\n\n') ); } } catch (error) { console.error(`Failed to search knowledge base ${kbId}:`, error); // 检索失败不阻止对话,继续处理 } } if (knowledgeResults.length > 0) { knowledgeBaseContext = knowledgeResults.join('\n\n---\n\n'); } } // 组装上下文 const messages = await this.assembleContext( conversationId, conversation.agentId, conversation.project?.background || '', content, knowledgeBaseContext ); // 获取LLM适配器 const adapter = LLMFactory.getAdapter(modelType); // 获取智能体配置的模型参数 const agent = agentService.getAgentById(conversation.agentId); const modelConfig = agent?.models?.[modelType]; // 调用LLM const response = await adapter.chat(messages, { temperature: modelConfig?.temperature, maxTokens: modelConfig?.maxTokens, topP: modelConfig?.topP, }); // 保存用户消息 const userMessage = await prisma.message.create({ data: { conversationId, role: 'user', content, metadata: { knowledgeBaseIds, }, }, }); // 保存助手回复 const assistantMessage = await prisma.message.create({ data: { conversationId, role: 'assistant', content: response.content, model: response.model, tokens: response.usage?.totalTokens, metadata: { usage: response.usage, finishReason: response.finishReason, }, }, }); // 更新对话的最后更新时间 await prisma.conversation.update({ where: { id: conversationId }, data: { updatedAt: new Date() }, }); return { userMessage, assistantMessage, usage: response.usage, }; } /** * 发送消息(流式) */ async *sendMessageStream( data: SendMessageData, userId: string ): AsyncGenerator { const { conversationId, content, modelType, knowledgeBaseIds } = data; // 获取对话信息 const conversation = await this.getConversationById(conversationId, userId); // 获取知识库上下文(如果有@知识库) console.log('📚 [sendMessageStream] 开始处理知识库', { knowledgeBaseIds }); let knowledgeBaseContext = ''; if (knowledgeBaseIds && knowledgeBaseIds.length > 0) { const knowledgeResults: string[] = []; // 对每个知识库进行检索 for (const kbId of knowledgeBaseIds) { try { console.log(`🔎 [sendMessageStream] 检索知识库 ${kbId}`); const searchResult = await knowledgeBaseService.searchKnowledgeBase( userId, kbId, content, 3 // 每个知识库返回3个最相关的段落 ); console.log(`✅ [sendMessageStream] 检索结果`, { kbId, recordCount: searchResult.records?.length || 0 }); // 格式化检索结果 if (searchResult.records && searchResult.records.length > 0) { const kbInfo = await prisma.knowledgeBase.findUnique({ where: { id: kbId }, select: { name: true }, }); const formattedResult = `【知识库:${kbInfo?.name || '未命名'}】\n` + searchResult.records .map((record: any, index: number) => { const score = (record.score * 100).toFixed(1); return `${index + 1}. [相关度${score}%] ${record.segment.content}`; }) .join('\n\n'); console.log(`📄 [sendMessageStream] 格式化结果长度: ${formattedResult.length} 字符`); knowledgeResults.push(formattedResult); } else { console.warn(`⚠️ [sendMessageStream] 知识库 ${kbId} 没有检索到记录`); } } catch (error) { console.error(`❌ [sendMessageStream] 检索知识库失败 ${kbId}:`, error); // 检索失败不阻止对话,继续处理 } } if (knowledgeResults.length > 0) { knowledgeBaseContext = knowledgeResults.join('\n\n---\n\n'); console.log(`💾 [sendMessageStream] 知识库上下文总长度: ${knowledgeBaseContext.length} 字符`); console.log(`📋 [sendMessageStream] 知识库上下文预览:\n${knowledgeBaseContext.substring(0, 500)}...`); } else { console.warn('⚠️ [sendMessageStream] 没有构建任何知识库上下文'); } } else { console.log('ℹ️ [sendMessageStream] 未选择知识库'); } // 组装上下文 const messages = await this.assembleContext( conversationId, conversation.agentId, conversation.project?.background || '', content, knowledgeBaseContext ); // 获取LLM适配器 const adapter = LLMFactory.getAdapter(modelType); // 获取智能体配置的模型参数 const agent = agentService.getAgentById(conversation.agentId); const modelConfig = agent?.models?.[modelType]; // 保存用户消息 await prisma.message.create({ data: { conversationId, role: 'user', content, metadata: { knowledgeBaseIds, }, }, }); // 用于累积完整的回复内容 let fullContent = ''; let usage: any = null; // 流式调用LLM for await (const chunk of adapter.chatStream(messages, { temperature: modelConfig?.temperature, maxTokens: modelConfig?.maxTokens, topP: modelConfig?.topP, })) { fullContent += chunk.content; if (chunk.usage) { usage = chunk.usage; } yield chunk; } // 流式输出完成后,保存助手回复 await prisma.message.create({ data: { conversationId, role: 'assistant', content: fullContent, model: modelType, tokens: usage?.totalTokens, metadata: { usage, }, }, }); // 更新对话的最后更新时间 await prisma.conversation.update({ where: { id: conversationId }, data: { updatedAt: new Date() }, }); } /** * 删除对话(软删除) */ async deleteConversation(conversationId: string, userId: string) { const conversation = await prisma.conversation.findFirst({ where: { id: conversationId, userId, deletedAt: null, }, }); if (!conversation) { throw new Error('对话不存在或无权访问'); } await prisma.conversation.update({ where: { id: conversationId }, data: { deletedAt: new Date() }, }); return { success: true }; } } export const conversationService = new ConversationService();