const axios = require('axios'); class ToolExecutor { static async execute(toolName, args, context = []) { const startTime = Date.now(); console.log(`[ToolExecutor] Executing: ${toolName}`, args); const handlers = { query_weather: this.queryWeather, query_order: this.queryOrder, search_knowledge: this.searchKnowledge, get_current_time: this.getCurrentTime, calculate: this.calculate, }; const handler = handlers[toolName]; if (!handler) { console.warn(`[ToolExecutor] Unknown tool: ${toolName}`); return { error: `未知的工具: ${toolName}` }; } try { const result = await handler.call(this, args, context); const ms = Date.now() - startTime; console.log(`[ToolExecutor] ${toolName} completed in ${ms}ms:`, JSON.stringify(result).substring(0, 200)); return result; } catch (error) { console.error(`[ToolExecutor] ${toolName} error:`, error); return { error: `工具执行失败: ${error.message}` }; } } static async queryWeather({ city }) { const mockData = { '北京': { temp: '22°C', weather: '晴', humidity: '45%', wind: '北风3级', aqi: 65, tips: '空气质量良好,适合户外活动' }, '上海': { temp: '26°C', weather: '多云', humidity: '72%', wind: '东南风2级', aqi: 78, tips: '注意防晒' }, '广州': { temp: '30°C', weather: '阵雨', humidity: '85%', wind: '南风1级', aqi: 55, tips: '记得带伞' }, '深圳': { temp: '29°C', weather: '多云', humidity: '80%', wind: '东风2级', aqi: 60, tips: '较为闷热,注意防暑' }, '杭州': { temp: '24°C', weather: '晴', humidity: '55%', wind: '西北风2级', aqi: 50, tips: '天气宜人' }, '成都': { temp: '20°C', weather: '阴', humidity: '70%', wind: '微风', aqi: 85, tips: '天气阴沉,适合室内活动' }, '武汉': { temp: '25°C', weather: '晴', humidity: '60%', wind: '东风3级', aqi: 72, tips: '适合出行' }, '南京': { temp: '23°C', weather: '多云', humidity: '58%', wind: '东北风2级', aqi: 68, tips: '温度适宜' }, '西安': { temp: '18°C', weather: '晴', humidity: '35%', wind: '西北风3级', aqi: 90, tips: '天气干燥,注意补水' }, '重庆': { temp: '27°C', weather: '阴转多云', humidity: '75%', wind: '微风', aqi: 80, tips: '注意防潮' }, }; const data = mockData[city]; if (data) { return { city, date: new Date().toLocaleDateString('zh-CN'), ...data }; } // 对未知城市生成随机数据 const weathers = ['晴', '多云', '阴', '小雨', '大风']; return { city, date: new Date().toLocaleDateString('zh-CN'), temp: `${Math.floor(Math.random() * 20 + 10)}°C`, weather: weathers[Math.floor(Math.random() * weathers.length)], humidity: `${Math.floor(Math.random() * 50 + 30)}%`, wind: '微风', aqi: Math.floor(Math.random() * 100 + 30), tips: '数据仅供参考', }; } static async queryOrder({ order_id }) { const statuses = ['待支付', '已支付', '拣货中', '已发货', '运输中', '已签收']; const hash = order_id.split('').reduce((a, c) => a + c.charCodeAt(0), 0); const statusIdx = hash % statuses.length; return { order_id, status: statuses[statusIdx], estimated_delivery: '2026-03-01', tracking_number: 'SF' + order_id.replace(/\D/g, '').padEnd(10, '0').substring(0, 10), items: [ { name: '智能音箱 Pro', quantity: 1, price: '¥299' }, ], create_time: '2026-02-20 14:30:00', }; } static async searchKnowledge({ query } = {}, context = []) { const startTime = Date.now(); query = query || ''; console.log(`[ToolExecutor] searchKnowledge called with query="${query}"`); const kbIds = process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS; if (kbIds && kbIds !== 'your_knowledge_base_dataset_id') { try { console.log('[ToolExecutor] Trying Ark Knowledge Search...'); const result = await this.searchArkKnowledge(query, context); console.log(`[ToolExecutor] Ark KB search succeeded in ${Date.now() - startTime}ms`); return result; } catch (error) { console.warn('[ToolExecutor] Ark Knowledge Search failed:', error.message); console.log('[ToolExecutor] Falling back to local Knowledge Base'); } } console.log('[ToolExecutor] Using local Knowledge Base (voice fast path)'); const result = this.searchLocalKnowledge(query); console.log(`[ToolExecutor] Local KB search completed in ${Date.now() - startTime}ms`); return result; } /** * 通过方舟 Chat Completions API + knowledge_base metadata 进行知识检索 * 使用独立的 LLM 调用,专门用于知识库检索场景(如语音通话的工具回调) */ static async searchArkKnowledge(query, context = []) { const endpointId = process.env.VOLC_ARK_ENDPOINT_ID; const authKey = process.env.VOLC_ARK_API_KEY || process.env.VOLC_ACCESS_KEY_ID; const kbIds = process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS; const datasetIds = kbIds.split(',').map(id => id.trim()).filter(Boolean); const topK = parseInt(process.env.VOLC_ARK_KNOWLEDGE_TOP_K) || 3; const threshold = parseFloat(process.env.VOLC_ARK_KNOWLEDGE_THRESHOLD) || 0.5; // 当 query 为空时(FC 流式 chunks 乱序无法解析),使用简短的默认查询 const effectiveQuery = (query && query.trim()) ? query : '请介绍你们的产品和服务'; if (!query || !query.trim()) { console.log('[ToolExecutor] Empty query, using default: "' + effectiveQuery + '"'); } // 提取最近 3 轮对话作为上下文(最多 6 条 user/assistant 消息) const recentContext = context .filter(m => m.role === 'user' || m.role === 'assistant') .slice(-6); const messages = [ { role: 'system', content: '你是一个知识库检索助手。请根据知识库中的内容回答用户问题。如果知识库中没有相关内容,请如实说明。回答时请引用知识库来源。', }, ...recentContext, { role: 'user', content: effectiveQuery, }, ]; if (recentContext.length > 0) { console.log(`[ToolExecutor] Ark KB search with ${recentContext.length} context messages`); } const body = { model: endpointId, messages, metadata: { knowledge_base: { dataset_ids: datasetIds, top_k: topK, threshold: threshold, }, }, stream: false, }; const response = await axios.post( 'https://ark.cn-beijing.volces.com/api/v3/chat/completions', body, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authKey}`, }, timeout: 15000, // 方舟知识库超时 15s(减少等待,防止 LLM 重试风暴) } ); const choice = response.data.choices?.[0]; const content = choice?.message?.content || '未找到相关信息'; return { query, results: [{ title: '方舟知识库检索结果', content: content, }], total: 1, source: 'ark_knowledge', }; } /** * 通过 Coze v3 Chat API 进行知识库检索 * 需要在 Coze 平台创建 Bot 并挂载知识库插件 */ static async searchCozeKnowledge(query) { const apiToken = process.env.COZE_API_TOKEN; const botId = process.env.COZE_BOT_ID; const baseUrl = 'https://api.coze.cn/v3'; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiToken}`, }; // 1. 创建对话 const chatRes = await axios.post(`${baseUrl}/chat`, { bot_id: botId, user_id: 'kb_search_user', additional_messages: [ { role: 'user', content: query, content_type: 'text', }, ], stream: true, auto_save_history: false, }, { headers, timeout: 15000 }); const chatData = chatRes.data?.data; if (!chatData?.id || !chatData?.conversation_id) { throw new Error('Coze chat creation failed: ' + JSON.stringify(chatRes.data)); } const chatId = chatData.id; const conversationId = chatData.conversation_id; // 2. 轮询等待完成(最多 30 秒) const maxAttempts = 15; for (let i = 0; i < maxAttempts; i++) { await new Promise(r => setTimeout(r, 2000)); const statusRes = await axios.get( `${baseUrl}/chat/retrieve?chat_id=${chatId}&conversation_id=${conversationId}`, { headers, timeout: 10000 } ); const status = statusRes.data?.data?.status; if (status === 'completed') break; if (status === 'failed' || status === 'requires_action') { throw new Error(`Coze chat ended with status: ${status}`); } } // 3. 获取消息列表 const msgRes = await axios.get( `${baseUrl}/chat/message/list?chat_id=${chatId}&conversation_id=${conversationId}`, { headers, timeout: 10000 } ); const messages = msgRes.data?.data || []; const answerMsg = messages.find(m => m.role === 'assistant' && m.type === 'answer'); const content = answerMsg?.content || '未找到相关信息'; return { query, results: [{ title: 'Coze 知识库检索结果', content: content, }], total: 1, source: 'coze', }; } static async searchLocalKnowledge(query) { const knowledgeBase = { '退货': { title: '退货政策', content: '自签收之日起7天内可无理由退货,15天内可换货。请保持商品及包装完好。退货运费由买家承担(质量问题除外)。', }, '退款': { title: '退款流程', content: '退货审核通过后,退款将在3-5个工作日内原路返回。如超过时间未到账,请联系客服。', }, '配送': { title: '配送说明', content: '默认顺丰快递,普通订单1-3天送达,偏远地区3-7天。满99元免运费。', }, '保修': { title: '保修政策', content: '电子产品保修期1年,自购买之日起计算。人为损坏不在保修范围内。', }, '会员': { title: '会员权益', content: '会员享受9折优惠、免运费、专属客服、生日礼券等权益。年费128元。', }, }; const results = []; const q = query || ''; for (const [key, value] of Object.entries(knowledgeBase)) { if (q.includes(key) || key.includes(q)) { results.push(value); } } if (results.length === 0) { results.push({ title: '搜索结果', content: `未找到与"${query}"直接相关的知识库文档。建议联系人工客服获取更详细的帮助。`, }); } return { query, results, total: results.length, source: 'local' }; } static async getCurrentTime() { const now = new Date(); return { datetime: now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }), timestamp: now.getTime(), timezone: 'Asia/Shanghai', weekday: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][now.getDay()], }; } static async calculate({ expression }) { try { // 仅允许数字和基本运算符,防止注入 const sanitized = expression.replace(/[^0-9+\-*/().% ]/g, ''); if (!sanitized || sanitized !== expression.replace(/\s/g, '')) { return { error: '表达式包含不支持的字符', expression }; } const result = Function('"use strict"; return (' + sanitized + ')')(); return { expression, result: Number(result), formatted: String(result) }; } catch (e) { return { error: '计算失败: ' + e.message, expression }; } } } module.exports = ToolExecutor;