// src/services/llmService.js // LLM服务层 - 集成AI模型进行对话和工具调用 import axios from 'axios'; import { mcpService } from './mcpService'; import { logger } from '../utils/logger'; /** * LLM服务配置 */ const LLM_CONFIG = { // 可以使用 OpenAI、Claude、通义千问等 provider: 'openai', // 或 'claude', 'qwen' apiKey: process.env.REACT_APP_OPENAI_API_KEY || '', apiUrl: 'https://api.openai.com/v1/chat/completions', model: 'gpt-4o-mini', // 更便宜的模型 }; /** * LLM服务类 */ class LLMService { constructor() { this.conversationHistory = []; } /** * 构建系统提示词 */ getSystemPrompt(availableTools) { return `你是一个专业的金融投资助手。你可以使用以下工具来帮助用户查询信息: ${availableTools.map(tool => ` **${tool.name}** 描述:${tool.description} 参数:${JSON.stringify(tool.parameters, null, 2)} `).join('\n')} 用户提问时,请按照以下步骤: 1. 理解用户的意图 2. 选择合适的工具(可以多个) 3. 提取工具需要的参数 4. 调用工具后,用自然语言总结结果 回复格式: - 如果需要调用工具,返回JSON格式:{"tool": "工具名", "arguments": {...}} - 如果不需要工具,直接回复自然语言 注意: - 贵州茅台的股票代码是 600519 - 涨停是指股票当日涨幅达到10% - 概念板块是指相同题材的股票分类`; } /** * 智能对话 - 使用LLM理解意图并调用工具 */ async chat(userMessage, conversationHistory = []) { try { // 1. 获取可用工具列表 const toolsResult = await mcpService.listTools(); if (!toolsResult.success) { throw new Error('获取工具列表失败'); } const availableTools = toolsResult.data; // 2. 构建对话历史 const messages = [ { role: 'system', content: this.getSystemPrompt(availableTools), }, ...conversationHistory.map(msg => ({ role: msg.isUser ? 'user' : 'assistant', content: msg.content, })), { role: 'user', content: userMessage, }, ]; // 3. 调用LLM logger.info('LLMService', '调用LLM', { messageCount: messages.length }); // 注意:这里需要配置API密钥 if (!LLM_CONFIG.apiKey) { // 如果没有配置LLM,使用简单的关键词匹配 logger.warn('LLMService', '未配置LLM API密钥,使用简单匹配'); return await this.fallbackChat(userMessage); } const response = await axios.post( LLM_CONFIG.apiUrl, { model: LLM_CONFIG.model, messages: messages, temperature: 0.7, max_tokens: 1000, }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${LLM_CONFIG.apiKey}`, }, timeout: 30000, } ); const aiResponse = response.data.choices[0].message.content; logger.info('LLMService', 'LLM响应', { response: aiResponse }); // 4. 解析LLM响应 // 如果LLM返回工具调用指令 try { const toolCall = JSON.parse(aiResponse); if (toolCall.tool && toolCall.arguments) { // 调用MCP工具 const toolResult = await mcpService.callTool(toolCall.tool, toolCall.arguments); if (!toolResult.success) { return { success: false, error: toolResult.error, }; } // 5. 让LLM总结工具结果 const summaryMessages = [ ...messages, { role: 'assistant', content: aiResponse, }, { role: 'system', content: `工具 ${toolCall.tool} 返回的数据:\n${JSON.stringify(toolResult.data, null, 2)}\n\n请用自然语言总结这些数据,给用户一个简洁清晰的回复。`, }, ]; const summaryResponse = await axios.post( LLM_CONFIG.apiUrl, { model: LLM_CONFIG.model, messages: summaryMessages, temperature: 0.7, max_tokens: 500, }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${LLM_CONFIG.apiKey}`, }, timeout: 30000, } ); const summary = summaryResponse.data.choices[0].message.content; return { success: true, data: { message: summary, rawData: toolResult.data, toolUsed: toolCall.tool, }, }; } } catch (parseError) { // 不是JSON格式,说明是直接回复 return { success: true, data: { message: aiResponse, }, }; } // 默认返回LLM的直接回复 return { success: true, data: { message: aiResponse, }, }; } catch (error) { logger.error('LLMService', 'chat error', error); return { success: false, error: error.message || '对话处理失败', }; } } /** * 降级方案:简单的关键词匹配(当没有配置LLM时) */ async fallbackChat(userMessage) { logger.info('LLMService', '使用降级方案', { message: userMessage }); // 使用原有的简单匹配逻辑 if (userMessage.includes('新闻') || userMessage.includes('资讯')) { const result = await mcpService.callTool('search_china_news', { query: userMessage.replace(/新闻|资讯/g, '').trim(), top_k: 5, }); return this.formatFallbackResponse(result, '新闻搜索'); } else if (userMessage.includes('概念') || userMessage.includes('板块')) { const query = userMessage.replace(/概念|板块/g, '').trim(); const result = await mcpService.callTool('search_concepts', { query, size: 5, sort_by: 'change_pct', }); return this.formatFallbackResponse(result, '概念搜索'); } else if (userMessage.includes('涨停')) { const query = userMessage.replace(/涨停/g, '').trim(); const result = await mcpService.callTool('search_limit_up_stocks', { query, mode: 'hybrid', page_size: 5, }); return this.formatFallbackResponse(result, '涨停分析'); } else if (/^[0-9]{6}$/.test(userMessage.trim())) { // 6位数字 = 股票代码 const result = await mcpService.callTool('get_stock_basic_info', { seccode: userMessage.trim(), }); return this.formatFallbackResponse(result, '股票信息'); } else if (userMessage.includes('茅台') || userMessage.includes('贵州茅台')) { // 特殊处理茅台 const result = await mcpService.callTool('get_stock_basic_info', { seccode: '600519', }); return this.formatFallbackResponse(result, '贵州茅台股票信息'); } else { // 默认:搜索新闻 const result = await mcpService.callTool('search_china_news', { query: userMessage, top_k: 5, }); return this.formatFallbackResponse(result, '新闻搜索'); } } /** * 格式化降级响应 */ formatFallbackResponse(result, action) { if (!result.success) { return { success: false, error: result.error, }; } return { success: true, data: { message: `已为您完成${action},找到以下结果:`, rawData: result.data, }, }; } /** * 清除对话历史 */ clearHistory() { this.conversationHistory = []; } } // 导出单例 export const llmService = new LLMService(); export default LLMService;