249 lines
6.3 KiB
JavaScript
249 lines
6.3 KiB
JavaScript
// src/services/mcpService.js
|
||
// MCP (Model Context Protocol) 服务层
|
||
// 用于与FastAPI后端的MCP工具进行交互
|
||
|
||
import axios from 'axios';
|
||
import { getApiBase } from '../utils/apiConfig';
|
||
import { logger } from '../utils/logger';
|
||
|
||
/**
|
||
* MCP API客户端
|
||
*/
|
||
class MCPService {
|
||
constructor() {
|
||
this.baseURL = `${getApiBase()}/mcp`;
|
||
this.client = axios.create({
|
||
baseURL: this.baseURL,
|
||
timeout: 60000, // 60秒超时(MCP工具可能需要较长时间)
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
});
|
||
|
||
// 请求拦截器
|
||
this.client.interceptors.request.use(
|
||
(config) => {
|
||
logger.debug('MCPService', 'Request', {
|
||
url: config.url,
|
||
method: config.method,
|
||
data: config.data,
|
||
});
|
||
return config;
|
||
},
|
||
(error) => {
|
||
logger.error('MCPService', 'Request Error', error);
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// 响应拦截器
|
||
this.client.interceptors.response.use(
|
||
(response) => {
|
||
logger.debug('MCPService', 'Response', {
|
||
url: response.config.url,
|
||
status: response.status,
|
||
data: response.data,
|
||
});
|
||
return response.data;
|
||
},
|
||
(error) => {
|
||
logger.error('MCPService', 'Response Error', {
|
||
url: error.config?.url,
|
||
status: error.response?.status,
|
||
message: error.message,
|
||
});
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 列出所有可用的MCP工具
|
||
* @returns {Promise<Object>} 工具列表
|
||
*/
|
||
async listTools() {
|
||
try {
|
||
const response = await this.client.get('/tools');
|
||
return {
|
||
success: true,
|
||
data: response.tools || [],
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
success: false,
|
||
error: error.message || '获取工具列表失败',
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取特定工具的定义
|
||
* @param {string} toolName - 工具名称
|
||
* @returns {Promise<Object>} 工具定义
|
||
*/
|
||
async getTool(toolName) {
|
||
try {
|
||
const response = await this.client.get(`/tools/${toolName}`);
|
||
return {
|
||
success: true,
|
||
data: response,
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
success: false,
|
||
error: error.message || '获取工具定义失败',
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 调用MCP工具
|
||
* @param {string} toolName - 工具名称
|
||
* @param {Object} arguments - 工具参数
|
||
* @returns {Promise<Object>} 工具执行结果
|
||
*/
|
||
async callTool(toolName, toolArguments) {
|
||
try {
|
||
const response = await this.client.post('/tools/call', {
|
||
tool: toolName,
|
||
arguments: toolArguments,
|
||
});
|
||
return {
|
||
success: true,
|
||
data: response.data || response,
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
success: false,
|
||
error: error.response?.data?.detail || error.message || '工具调用失败',
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 智能对话 - 根据用户输入自动选择合适的工具
|
||
* @param {string} userMessage - 用户消息
|
||
* @param {Array} conversationHistory - 对话历史(可选)
|
||
* @returns {Promise<Object>} AI响应
|
||
*/
|
||
async chat(userMessage, conversationHistory = []) {
|
||
try {
|
||
// 这里可以实现智能路由逻辑
|
||
// 根据用户输入判断应该调用哪个工具
|
||
|
||
// 示例:关键词匹配
|
||
if (userMessage.includes('新闻') || userMessage.includes('资讯')) {
|
||
return await this.callTool('search_china_news', {
|
||
query: userMessage.replace(/新闻|资讯/g, '').trim(),
|
||
top_k: 5,
|
||
});
|
||
} else if (userMessage.includes('概念') || userMessage.includes('板块')) {
|
||
const query = userMessage.replace(/概念|板块/g, '').trim();
|
||
return await this.callTool('search_concepts', {
|
||
query,
|
||
size: 5,
|
||
sort_by: 'change_pct',
|
||
});
|
||
} else if (userMessage.includes('涨停')) {
|
||
const query = userMessage.replace(/涨停/g, '').trim();
|
||
return await this.callTool('search_limit_up_stocks', {
|
||
query,
|
||
mode: 'hybrid',
|
||
page_size: 5,
|
||
});
|
||
} else if (/^[0-9]{6}$/.test(userMessage.trim())) {
|
||
// 6位数字 = 股票代码
|
||
return await this.callTool('get_stock_basic_info', {
|
||
seccode: userMessage.trim(),
|
||
});
|
||
} else {
|
||
// 默认:搜索新闻
|
||
return await this.callTool('search_china_news', {
|
||
query: userMessage,
|
||
top_k: 5,
|
||
});
|
||
}
|
||
} catch (error) {
|
||
return {
|
||
success: false,
|
||
error: error.message || '对话处理失败',
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 工具类别枚举
|
||
*/
|
||
static TOOL_CATEGORIES = {
|
||
NEWS: 'news', // 新闻搜索
|
||
STOCK: 'stock', // 股票信息
|
||
CONCEPT: 'concept', // 概念板块
|
||
LIMIT_UP: 'limit_up', // 涨停分析
|
||
RESEARCH: 'research', // 研报搜索
|
||
ROADSHOW: 'roadshow', // 路演信息
|
||
FINANCIAL: 'financial', // 财务数据
|
||
TRADE: 'trade', // 交易数据
|
||
};
|
||
|
||
/**
|
||
* 常用工具快捷方式
|
||
*/
|
||
async searchNews(query, topK = 5, exactMatch = false) {
|
||
return await this.callTool('search_china_news', {
|
||
query,
|
||
top_k: topK,
|
||
exact_match: exactMatch,
|
||
});
|
||
}
|
||
|
||
async searchConcepts(query, size = 10, sortBy = 'change_pct') {
|
||
return await this.callTool('search_concepts', {
|
||
query,
|
||
size,
|
||
sort_by: sortBy,
|
||
});
|
||
}
|
||
|
||
async searchLimitUpStocks(query, mode = 'hybrid', pageSize = 10) {
|
||
return await this.callTool('search_limit_up_stocks', {
|
||
query,
|
||
mode,
|
||
page_size: pageSize,
|
||
});
|
||
}
|
||
|
||
async getStockInfo(seccode) {
|
||
return await this.callTool('get_stock_basic_info', {
|
||
seccode,
|
||
});
|
||
}
|
||
|
||
async getStockConcepts(stockCode, size = 10) {
|
||
return await this.callTool('get_stock_concepts', {
|
||
stock_code: stockCode,
|
||
size,
|
||
});
|
||
}
|
||
|
||
async searchResearchReports(query, mode = 'hybrid', size = 5) {
|
||
return await this.callTool('search_research_reports', {
|
||
query,
|
||
mode,
|
||
size,
|
||
});
|
||
}
|
||
|
||
async getConceptStatistics(days = 7, minStockCount = 3) {
|
||
return await this.callTool('get_concept_statistics', {
|
||
days,
|
||
min_stock_count: minStockCount,
|
||
});
|
||
}
|
||
}
|
||
|
||
// 导出单例实例
|
||
export const mcpService = new MCPService();
|
||
|
||
// 导出类(供测试使用)
|
||
export default MCPService;
|