845 lines
25 KiB
TypeScript
845 lines
25 KiB
TypeScript
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<string, unknown>;
|
||
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<string, unknown>,
|
||
context: ConversationContext,
|
||
): Promise<unknown> {
|
||
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<string, unknown>,
|
||
context: ConversationContext,
|
||
): Promise<unknown> {
|
||
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<string, unknown>): Promise<unknown> {
|
||
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<string, unknown>,
|
||
context: ConversationContext,
|
||
): Promise<unknown> {
|
||
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<string, unknown>,
|
||
context: ConversationContext,
|
||
): Promise<unknown> {
|
||
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<string, number> = {
|
||
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<string, unknown>,
|
||
context: ConversationContext,
|
||
): Promise<unknown> {
|
||
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<string, unknown>,
|
||
context: ConversationContext,
|
||
): Promise<unknown> {
|
||
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<string, unknown>): 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<string, unknown>): Promise<unknown> {
|
||
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<string>('GOOGLE_SEARCH_API_KEY');
|
||
const googleCseId = this.configService.get<string>('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<string, unknown>): Promise<unknown> {
|
||
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<string>('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<string, number>;
|
||
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<string, Record<string, number>> = {
|
||
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<string, unknown>): Promise<unknown> {
|
||
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<string>('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<unknown> };
|
||
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<string, Array<{ title: string; summary: string; date: string; source: string }>> = {
|
||
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' },
|
||
],
|
||
};
|
||
}
|
||
}
|