agent功能开发增加MCP后端

This commit is contained in:
2025-11-07 19:11:58 +08:00
parent 446d8f0870
commit 315d606945
15 changed files with 3639 additions and 4 deletions

278
src/services/llmService.js Normal file
View File

@@ -0,0 +1,278 @@
// 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;