import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { ConversationContext } from '../claude-agent.service'; import { KnowledgeClientService } from '../../knowledge/knowledge-client.service'; export interface Tool { name: string; description: string; input_schema: { type: string; properties: Record; required?: string[]; }; } @Injectable() export class ImmigrationToolsService { constructor( private knowledgeClient: KnowledgeClientService, private configService: ConfigService, ) {} /** * Get all available tools for the agent */ getTools(): Tool[] { return [ { name: 'search_knowledge', description: '搜索香港移民相关知识库,获取最新的政策信息和常见问题解答', input_schema: { type: 'object', properties: { query: { type: 'string', description: '搜索查询内容', }, category: { type: 'string', enum: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS'], description: '移民类别代码(可选)', }, }, required: ['query'], }, }, { name: 'check_off_topic', description: '检查用户的问题是否与香港移民相关,用于判断是否需要拒绝回答', input_schema: { type: 'object', properties: { question: { type: 'string', description: '用户的问题', }, }, required: ['question'], }, }, { name: 'collect_assessment_info', description: '收集用户的个人信息用于移民资格评估', input_schema: { type: 'object', properties: { category: { type: 'string', enum: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS'], description: '用户感兴趣的移民类别', }, age: { type: 'number', description: '用户年龄', }, education: { type: 'string', description: '最高学历', }, university: { type: 'string', description: '毕业院校', }, yearsOfExperience: { type: 'number', description: '工作年限', }, currentJobTitle: { type: 'string', description: '当前职位', }, industry: { type: 'string', description: '所属行业', }, annualIncome: { type: 'number', description: '年收入(人民币)', }, hasHKEmployer: { type: 'boolean', description: '是否有香港雇主', }, }, required: ['category'], }, }, { name: 'generate_payment', description: '为付费评估服务生成支付二维码', input_schema: { type: 'object', properties: { serviceType: { type: 'string', enum: ['ASSESSMENT'], description: '服务类型', }, category: { type: 'string', enum: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS'], description: '移民类别', }, paymentMethod: { type: 'string', enum: ['ALIPAY', 'WECHAT', 'CREDIT_CARD'], description: '支付方式', }, }, required: ['serviceType', 'category', 'paymentMethod'], }, }, { name: 'save_user_memory', description: '保存用户的重要信息到长期记忆,以便后续对话中记住用户情况', input_schema: { type: 'object', properties: { memoryType: { type: 'string', enum: ['FACT', 'PREFERENCE', 'INTENT'], description: '记忆类型:FACT-用户陈述的事实,PREFERENCE-用户偏好,INTENT-用户意图', }, content: { type: 'string', description: '要记住的内容', }, category: { type: 'string', enum: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS'], description: '相关的移民类别(可选)', }, }, required: ['memoryType', 'content'], }, }, { name: 'get_user_context', description: '获取用户的历史背景信息和之前的对话记忆,帮助提供更个性化的建议', input_schema: { type: 'object', properties: { query: { type: 'string', description: '当前问题,用于检索相关记忆', }, }, required: ['query'], }, }, // ========== 实时工具 ========== { name: 'get_current_datetime', description: '获取当前的日期和时间,用于回答与时间相关的问题', input_schema: { type: 'object', properties: { timezone: { type: 'string', description: '时区,默认为 Asia/Hong_Kong', }, format: { type: 'string', enum: ['full', 'date', 'time'], description: '返回格式:full-完整日期时间,date-仅日期,time-仅时间', }, }, }, }, { name: 'web_search', description: '搜索互联网获取最新信息,用于查询移民政策变更、新闻动态等实时内容', input_schema: { type: 'object', properties: { query: { type: 'string', description: '搜索关键词', }, site: { type: 'string', description: '限定搜索网站,如 immd.gov.hk(香港入境处)', }, language: { type: 'string', enum: ['zh-CN', 'zh-TW', 'en'], description: '搜索语言,默认 zh-CN', }, }, required: ['query'], }, }, { name: 'get_exchange_rate', description: '查询实时货币汇率,用于投资移民金额换算等场景', input_schema: { type: 'object', properties: { from: { type: 'string', description: '源货币代码,如 CNY(人民币)、USD(美元)', }, to: { type: 'string', description: '目标货币代码,如 HKD(港币)', }, amount: { type: 'number', description: '要转换的金额(可选)', }, }, required: ['from', 'to'], }, }, { name: 'fetch_immigration_news', description: '获取香港移民相关的最新新闻和政策公告', input_schema: { type: 'object', properties: { category: { type: 'string', enum: ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TECHTAS', 'ALL'], description: '移民类别,ALL表示所有类别', }, limit: { type: 'number', description: '返回条数,默认5条', }, }, }, }, ]; } /** * Execute a tool and return the result */ async executeTool( toolName: string, input: Record, context: ConversationContext, ): Promise { console.log(`[Tool] Executing ${toolName} with input:`, JSON.stringify(input)); switch (toolName) { case 'search_knowledge': return this.searchKnowledge(input, context); case 'check_off_topic': return this.checkOffTopic(input); case 'collect_assessment_info': return this.collectAssessmentInfo(input, context); case 'generate_payment': return this.generatePayment(input, context); case 'save_user_memory': return this.saveUserMemory(input, context); case 'get_user_context': return this.getUserContext(input, context); case 'get_current_datetime': return this.getCurrentDatetime(input); case 'web_search': return this.webSearch(input); case 'get_exchange_rate': return this.getExchangeRate(input); case 'fetch_immigration_news': return this.fetchImmigrationNews(input); default: return { error: `Unknown tool: ${toolName}` }; } } /** * Search knowledge base - 调用 knowledge-service RAG API */ private async searchKnowledge( input: Record, context: ConversationContext, ): Promise { const { query, category } = input as { query: string; category?: string }; console.log(`[Tool:search_knowledge] Query: "${query}", Category: ${category || 'all'}`); // 调用 knowledge-service RAG API const result = await this.knowledgeClient.retrieveKnowledge({ query, userId: context.userId, category, includeMemories: true, includeExperiences: true, }); if (result && result.content) { return { success: true, content: result.content, sources: result.sources, userContext: result.userMemories, relatedExperiences: result.systemExperiences, message: `找到 ${result.sources?.length || 0} 条相关知识`, }; } // 降级:返回基础响应 return { success: false, content: null, message: '知识库暂无相关内容,请基于内置知识回答', }; } /** * Check if question is off-topic - 调用 knowledge-service API */ private async checkOffTopic(input: Record): Promise { const { question } = input as { question: string }; console.log(`[Tool:check_off_topic] Checking: "${question}"`); // 调用 knowledge-service 离题检测 API const result = await this.knowledgeClient.checkOffTopic(question); if (result.isOffTopic) { return { isOffTopic: true, confidence: result.confidence, suggestion: result.reason || '这个问题似乎与香港移民无关。作为移民咨询顾问,我专注于香港各类移民政策的咨询。请问您有香港移民相关的问题吗?', }; } return { isOffTopic: false, confidence: result.confidence, suggestion: null, }; } /** * Collect assessment info - 收集评估信息并保存到记忆 */ private async collectAssessmentInfo( input: Record, context: ConversationContext, ): Promise { const info = input as { category: string; age?: number; education?: string; university?: string; yearsOfExperience?: number; currentJobTitle?: string; industry?: string; annualIncome?: number; hasHKEmployer?: boolean; }; console.log(`[Tool:collect_assessment_info] User ${context.userId} - Category: ${info.category}`); // 保存收集到的信息到用户记忆 const memoryContent = Object.entries(info) .filter(([, v]) => v !== undefined) .map(([k, v]) => `${k}: ${v}`) .join(', '); if (memoryContent) { await this.knowledgeClient.saveUserMemory({ userId: context.userId, memoryType: 'FACT', content: `用户评估信息 - ${memoryContent}`, sourceConversationId: context.conversationId, relatedCategory: info.category, importance: 80, }); } return { success: true, collectedInfo: info, message: '已记录您的信息。如需完整评估,请选择付费评估服务。', nextStep: 'payment', }; } /** * Generate payment QR code */ private async generatePayment( input: Record, context: ConversationContext, ): Promise { const { serviceType, category, paymentMethod } = input as { serviceType: string; category: string; paymentMethod: string; }; console.log( `[Tool:generate_payment] User ${context.userId} - ${serviceType} for ${category} via ${paymentMethod}`, ); // 记录用户付费意向 await this.knowledgeClient.saveUserMemory({ userId: context.userId, memoryType: 'INTENT', content: `用户希望购买${category}类别的${serviceType}服务,选择${paymentMethod}支付`, sourceConversationId: context.conversationId, relatedCategory: category, importance: 90, }); // TODO: 调用 payment-service 生成实际支付 const priceMap: Record = { QMAS: 99, GEP: 99, IANG: 79, TTPS: 99, CIES: 199, TECHTAS: 99, }; const price = priceMap[category] || 99; return { success: true, orderId: `ORD_${Date.now()}`, amount: price, currency: 'CNY', paymentMethod, qrCodeUrl: `https://placeholder-payment-qr.com/${paymentMethod.toLowerCase()}/${Date.now()}`, expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), message: `请扫描二维码支付 ¥${price} 完成${category}类别的移民资格评估服务`, }; } /** * Save user memory - 调用 knowledge-service Memory API */ private async saveUserMemory( input: Record, context: ConversationContext, ): Promise { const { memoryType, content, category } = input as { memoryType: 'FACT' | 'PREFERENCE' | 'INTENT'; content: string; category?: string; }; console.log( `[Tool:save_user_memory] User ${context.userId} - Type: ${memoryType}, Content: "${content}"`, ); // 调用 knowledge-service Memory API const memory = await this.knowledgeClient.saveUserMemory({ userId: context.userId, memoryType, content, sourceConversationId: context.conversationId, relatedCategory: category, importance: memoryType === 'FACT' ? 70 : memoryType === 'INTENT' ? 80 : 60, }); if (memory) { return { success: true, memoryId: memory.id, message: '已记住您的信息,下次对话时我会记得', }; } return { success: false, message: '记忆保存失败,但不影响当前对话', }; } /** * Get user context - 获取用户历史背景 */ private async getUserContext( input: Record, context: ConversationContext, ): Promise { const { query } = input as { query: string }; console.log(`[Tool:get_user_context] User ${context.userId} - Query: "${query}"`); // 并行获取相关记忆和重要记忆 const [relevantMemories, topMemories] = await Promise.all([ this.knowledgeClient.searchUserMemories({ userId: context.userId, query, limit: 3, }), this.knowledgeClient.getUserTopMemories(context.userId, 5), ]); const allMemories = [...relevantMemories]; // 添加不重复的重要记忆 for (const mem of topMemories) { if (!allMemories.find(m => m.id === mem.id)) { allMemories.push(mem); } } if (allMemories.length > 0) { return { success: true, memories: allMemories.map(m => ({ type: m.memoryType, content: m.content, importance: m.importance, })), message: `找到 ${allMemories.length} 条用户背景信息`, }; } return { success: true, memories: [], message: '这是新用户,暂无历史信息', }; } // ========== 实时工具实现 ========== /** * Get current datetime - 获取当前日期时间 */ private getCurrentDatetime(input: Record): unknown { const { timezone = 'Asia/Hong_Kong', format = 'full' } = input as { timezone?: string; format?: 'full' | 'date' | 'time'; }; console.log(`[Tool:get_current_datetime] Timezone: ${timezone}, Format: ${format}`); const now = new Date(); const options: Intl.DateTimeFormatOptions = { timeZone: timezone, }; let result: string; switch (format) { case 'date': options.year = 'numeric'; options.month = 'long'; options.day = 'numeric'; options.weekday = 'long'; result = now.toLocaleDateString('zh-CN', options); break; case 'time': options.hour = '2-digit'; options.minute = '2-digit'; options.second = '2-digit'; options.hour12 = false; result = now.toLocaleTimeString('zh-CN', options); break; case 'full': default: options.year = 'numeric'; options.month = 'long'; options.day = 'numeric'; options.weekday = 'long'; options.hour = '2-digit'; options.minute = '2-digit'; options.second = '2-digit'; options.hour12 = false; result = now.toLocaleString('zh-CN', options); break; } return { success: true, datetime: result, timezone, timestamp: now.toISOString(), message: `当前${timezone}时间: ${result}`, }; } /** * Web search - 搜索互联网获取最新信息 * 使用 Google Custom Search API 或其他搜索服务 */ private async webSearch(input: Record): Promise { const { query, site, language = 'zh-CN' } = input as { query: string; site?: string; language?: string; }; console.log(`[Tool:web_search] Query: "${query}", Site: ${site || 'any'}, Language: ${language}`); // 构建搜索查询 let searchQuery = query; if (site) { searchQuery = `site:${site} ${query}`; } // 尝试使用 Google Custom Search API const googleApiKey = this.configService.get('GOOGLE_SEARCH_API_KEY'); const googleCseId = this.configService.get('GOOGLE_CSE_ID'); if (googleApiKey && googleCseId) { try { const url = new URL('https://www.googleapis.com/customsearch/v1'); url.searchParams.set('key', googleApiKey); url.searchParams.set('cx', googleCseId); url.searchParams.set('q', searchQuery); url.searchParams.set('lr', `lang_${language.split('-')[0]}`); url.searchParams.set('num', '5'); const response = await fetch(url.toString()); if (response.ok) { const data = (await response.json()) as { items?: Array<{ title: string; link: string; snippet: string }> }; const results = (data.items || []).map((item: { title: string; link: string; snippet: string }) => ({ title: item.title, url: item.link, snippet: item.snippet, })); return { success: true, query: searchQuery, results, resultCount: results.length, message: `找到 ${results.length} 条相关结果`, }; } } catch (error) { console.error('[Tool:web_search] Google API error:', error); } } // 降级:返回建议用户自行搜索的信息 return { success: false, query: searchQuery, results: [], message: '搜索服务暂时不可用。建议您访问以下官方网站获取最新信息:', suggestedSites: [ { name: '香港入境事务处', url: 'https://www.immd.gov.hk' }, { name: '香港人才服务窗口', url: 'https://www.talentservicewindow.gov.hk' }, { name: '投资推广署', url: 'https://www.investhk.gov.hk' }, ], }; } /** * Get exchange rate - 获取实时汇率 * 使用免费的汇率 API */ private async getExchangeRate(input: Record): Promise { const { from, to, amount } = input as { from: string; to: string; amount?: number; }; console.log(`[Tool:get_exchange_rate] From: ${from}, To: ${to}, Amount: ${amount || 'N/A'}`); // 使用 Exchange Rate API (免费层) const apiKey = this.configService.get('EXCHANGE_RATE_API_KEY'); const baseUrl = apiKey ? `https://v6.exchangerate-api.com/v6/${apiKey}/pair/${from}/${to}` : `https://api.exchangerate-api.com/v4/latest/${from}`; try { const response = await fetch(baseUrl); if (response.ok) { const data = (await response.json()) as { conversion_rate?: number; rates?: Record; time_last_update_utc?: string; }; let rate: number | undefined; if (apiKey) { // v6 API response rate = data.conversion_rate; } else { // v4 API response (free, no key needed) rate = data.rates?.[to]; } if (rate) { const convertedAmount = amount ? amount * rate : null; return { success: true, from, to, rate, amount: amount || null, convertedAmount, lastUpdate: data.time_last_update_utc || new Date().toISOString(), message: amount ? `${amount} ${from} = ${convertedAmount?.toFixed(2)} ${to} (汇率: ${rate.toFixed(4)})` : `1 ${from} = ${rate.toFixed(4)} ${to}`, }; } } } catch (error) { console.error('[Tool:get_exchange_rate] API error:', error); } // 降级:使用固定参考汇率 const fallbackRates: Record> = { CNY: { HKD: 1.08, USD: 0.14 }, USD: { HKD: 7.78, CNY: 7.24 }, HKD: { CNY: 0.93, USD: 0.13 }, }; const fallbackRate = fallbackRates[from]?.[to]; if (fallbackRate) { const convertedAmount = amount ? amount * fallbackRate : null; return { success: true, from, to, rate: fallbackRate, amount: amount || null, convertedAmount, isEstimate: true, message: `参考汇率 (可能有偏差): 1 ${from} ≈ ${fallbackRate} ${to}${amount ? `,${amount} ${from} ≈ ${convertedAmount?.toFixed(2)} ${to}` : ''}`, }; } return { success: false, message: `暂不支持 ${from} 到 ${to} 的汇率查询`, }; } /** * Fetch immigration news - 获取移民相关新闻 * 从官方渠道或新闻源获取 */ private async fetchImmigrationNews(input: Record): Promise { const { category = 'ALL', limit = 5 } = input as { category?: string; limit?: number; }; console.log(`[Tool:fetch_immigration_news] Category: ${category}, Limit: ${limit}`); // 尝试从 knowledge-service 获取最新新闻(如果有此功能) try { const response = await fetch( `${this.configService.get('KNOWLEDGE_SERVICE_URL') || 'http://knowledge-service:3003'}/api/v1/news/immigration?category=${category}&limit=${limit}`, ); if (response.ok) { const data = (await response.json()) as { success?: boolean; data?: Array }; if (data.success && data.data) { return { success: true, category, news: data.data, message: `获取到 ${data.data.length} 条${category === 'ALL' ? '' : category + '相关'}移民新闻`, }; } } } catch { console.log('[Tool:fetch_immigration_news] Knowledge service news not available'); } // 降级:返回静态参考信息和官方渠道 const categoryNews: Record> = { QMAS: [ { title: '优才计划持续接受申请', summary: '香港优才计划无配额限制,全年接受申请。申请人需符合基本资格要求,并通过计分制评核。', date: '2024-01', source: '香港入境事务处', }, ], TTPS: [ { title: '高才通计划最新动态', summary: '高端人才通行证计划持续运作,A/B/C三类申请通道开放。', date: '2024-01', source: '香港入境事务处', }, ], CIES: [ { title: '新资本投资者入境计划', summary: '投资门槛为3000万港币,需投资于许可资产类别。', date: '2024-03', source: '香港入境事务处', }, ], }; const newsItems = category === 'ALL' ? Object.values(categoryNews).flat() : categoryNews[category] || []; return { success: true, category, news: newsItems.slice(0, limit), isStatic: true, message: newsItems.length > 0 ? `以下是${category === 'ALL' ? '' : category + '类别'}的参考信息。如需最新资讯,请访问香港入境事务处官网。` : '暂无相关新闻,建议访问香港入境事务处官网获取最新信息。', officialSources: [ { name: '香港入境事务处', url: 'https://www.immd.gov.hk/hks/services/visas/admission_talents.html' }, { name: '香港人才服务窗口', url: 'https://www.talentservicewindow.gov.hk' }, ], }; } }