feat(agent): add 4 real-time tools for enhanced agent capabilities

Add the following real-time tools to ImmigrationToolsService:
- get_current_datetime: Get current date/time with timezone support
- web_search: Search internet for latest immigration news/policies (Google CSE)
- get_exchange_rate: Query real-time currency exchange rates (for investment immigration)
- fetch_immigration_news: Fetch latest immigration announcements

All tools include graceful degradation with fallback responses when external APIs are unavailable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-23 00:46:55 -08:00
parent 022b1eb608
commit 4c125f3276
2 changed files with 398 additions and 1 deletions

View File

@ -78,6 +78,8 @@ ${config.conversationStyle || '专业但不生硬,用简洁明了的语言解
## 使
使
###
1. **search_knowledge**:
2. **check_off_topic**:
3. **collect_assessment_info**:
@ -85,6 +87,12 @@ ${config.conversationStyle || '专业但不生硬,用简洁明了的语言解
5. **save_user_memory**: 便
6. **get_user_context**:
###
7. **get_current_datetime**:
8. **web_search**:
9. **get_exchange_rate**:
10. **fetch_immigration_news**:
##
${config.accumulatedExperience || '暂无'}

View File

@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ConversationContext } from '../claude-agent.service';
import { KnowledgeClientService } from '../../knowledge/knowledge-client.service';
@ -14,7 +15,10 @@ export interface Tool {
@Injectable()
export class ImmigrationToolsService {
constructor(private knowledgeClient: KnowledgeClientService) {}
constructor(
private knowledgeClient: KnowledgeClientService,
private configService: ConfigService,
) {}
/**
* Get all available tools for the agent
@ -164,6 +168,88 @@ export class ImmigrationToolsService {
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条',
},
},
},
},
];
}
@ -196,6 +282,18 @@ export class ImmigrationToolsService {
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}` };
}
@ -452,4 +550,295 @@ export class ImmigrationToolsService {
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' },
],
};
}
}