feat: DuckDuckGo → Brave Search 교체 + 입금 알림 개선

## 검색 API 교체
- DuckDuckGo Instant Answer API → Brave Search API
- 실제 웹 검색 결과 반환 (제목, 설명, URL)
- Vault에 API 키 저장 (secret/brave-search)
- Free AI 플랜: 2,000 queries/월

## 시스템 프롬프트 개선
- 검색 도구 사용 조건 명시 (최신 정보, 실시간 가격 등)
- 도구 description에 트리거 키워드 추가

## 입금 알림 개선
- 자동 매칭 성공 시 사용자에게 Telegram 알림 전송
- tryAutoMatch() 반환값에 userId, amount 추가

## 문서 업데이트
- Function Calling Tools 테이블에 트리거 키워드 컬럼 추가
- AI 시스템 프롬프트 섹션 추가
- Deposit Agent 프롬프트 수정 방법 문서화
- 자동 알림 시스템 섹션 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-17 10:25:27 +09:00
parent 9822b28028
commit 363a0a504f
6 changed files with 198 additions and 38 deletions

View File

@@ -223,6 +223,47 @@ export default {
// 자동 매칭 시도
const matched = await tryAutoMatch(env.DB, notificationId as number, notification);
// 매칭 성공 시 사용자에게 알림
if (matched && env.BOT_TOKEN) {
const user = await env.DB.prepare(
'SELECT telegram_id FROM users WHERE id = ?'
).bind(matched.userId).first<{ telegram_id: string }>();
if (user) {
// 업데이트된 잔액 조회
const deposit = await env.DB.prepare(
'SELECT balance FROM user_deposits WHERE user_id = ?'
).bind(matched.userId).first<{ balance: number }>();
await sendMessage(
env.BOT_TOKEN,
parseInt(user.telegram_id),
`✅ <b>입금 확인 완료!</b>\n\n` +
`입금액: ${matched.amount.toLocaleString()}\n` +
`현재 잔액: ${(deposit?.balance || 0).toLocaleString()}\n\n` +
`감사합니다! 🎉`
);
}
}
// 관리자에게 알림
if (env.BOT_TOKEN && env.DEPOSIT_ADMIN_ID) {
const statusMsg = matched
? `✅ 자동 매칭 완료! (거래 #${matched.transactionId})`
: '⏳ 매칭 대기 중 (사용자 입금 신고 필요)';
await sendMessage(
env.BOT_TOKEN,
parseInt(env.DEPOSIT_ADMIN_ID),
`🏦 <b>입금 알림</b>\n\n` +
`은행: ${notification.bankName}\n` +
`입금자: ${notification.depositorName}\n` +
`금액: ${notification.amount.toLocaleString()}\n` +
`${notification.balanceAfter ? `잔액: ${notification.balanceAfter.toLocaleString()}\n` : ''}` +
`\n${statusMsg}`
);
}
return Response.json({
success: true,
notification,
@@ -308,6 +349,29 @@ Documentation: https://github.com/your-repo
// 자동 매칭 시도
const matched = await tryAutoMatch(env.DB, notificationId, notification);
// 매칭 성공 시 사용자에게 알림
if (matched && env.BOT_TOKEN) {
const user = await env.DB.prepare(
'SELECT telegram_id FROM users WHERE id = ?'
).bind(matched.userId).first<{ telegram_id: string }>();
if (user) {
// 업데이트된 잔액 조회
const deposit = await env.DB.prepare(
'SELECT balance FROM user_deposits WHERE user_id = ?'
).bind(matched.userId).first<{ balance: number }>();
await sendMessage(
env.BOT_TOKEN,
parseInt(user.telegram_id),
`✅ <b>입금 확인 완료!</b>\n\n` +
`입금액: ${matched.amount.toLocaleString()}\n` +
`현재 잔액: ${(deposit?.balance || 0).toLocaleString()}\n\n` +
`감사합니다! 🎉`
);
}
}
// 관리자에게 알림
if (env.BOT_TOKEN && env.DEPOSIT_ADMIN_ID) {
const statusMsg = matched
@@ -444,7 +508,7 @@ async function tryAutoMatch(
db: D1Database,
notificationId: number,
notification: BankNotification
): Promise<{ transactionId: number } | null> {
): Promise<{ transactionId: number; userId: number; amount: number } | null> {
// 매칭 조건: 입금자명 + 금액이 일치하는 pending 거래
const pendingTx = await db.prepare(
`SELECT dt.id, dt.user_id, dt.amount
@@ -481,5 +545,5 @@ async function tryAutoMatch(
).bind(pendingTx.id, notificationId),
]);
return { transactionId: pendingTx.id };
return { transactionId: pendingTx.id, userId: pendingTx.user_id, amount: pendingTx.amount };
}

View File

@@ -47,7 +47,7 @@ const tools = [
type: 'function',
function: {
name: 'search_web',
description: '웹에서 정보를 검색합니다',
description: '웹에서 최신 정보를 검색합니다. 실시간 가격, 뉴스, 현재 날짜 이후 정보, 특정 사실 확인이 필요할 때 반드시 사용하세요. "비트코인 가격", "오늘 뉴스", "~란", "~뭐야" 등의 질문에 사용합니다.',
parameters: {
type: 'object',
properties: {
@@ -136,7 +136,7 @@ const tools = [
type: 'function',
function: {
name: 'manage_deposit',
description: '예치금을 관리합니다. 잔액 조회, 입금 신고(충전 요청), 거래 내역 조회, 입금 확인(관리자) 등을 수행할 수 있습니다. 사용자가 "입금했어", "송금했어", "충전하고 싶어", "잔액", "10000원 입금" 등의 말을 하면 이 도구를 사용합니다.',
description: '예치금을 관리합니다. 잔액 조회, 입금 계좌 안내, 입금 신고(충전 요청), 거래 내역 조회 등을 수행합니다. "입금", "충전", "잔액", "계좌", "계좌번호", "송금" 등의 키워드가 포함되면 반드시 이 도구를 사용하세요.',
parameters: {
type: 'object',
properties: {
@@ -471,25 +471,39 @@ async function executeTool(name: string, args: Record<string, string>, env?: Env
}
case 'search_web': {
// 간단한 DuckDuckGo Instant Answer API
// Brave Search API
const query = args.query;
try {
const response = await fetch(
`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1`
);
const data = await response.json() as any;
if (data.Abstract) {
return `🔍 검색 결과: ${query}\n\n${data.Abstract}\n\n출처: ${data.AbstractSource}`;
} else if (data.RelatedTopics?.length > 0) {
const topics = data.RelatedTopics.slice(0, 3)
.filter((t: any) => t.Text)
.map((t: any) => `${t.Text}`)
.join('\n');
return `🔍 관련 정보: ${query}\n\n${topics}`;
if (!env?.BRAVE_API_KEY) {
return `🔍 검색 기능이 설정되지 않았습니다.`;
}
return `"${query}"에 대한 즉시 답변을 찾을 수 없습니다. 더 구체적인 질문을 해주세요.`;
const response = await fetch(
`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5`,
{
headers: {
'Accept': 'application/json',
'X-Subscription-Token': env.BRAVE_API_KEY,
},
}
);
if (!response.ok) {
return `🔍 검색 오류: ${response.status}`;
}
const data = await response.json() as any;
// Web 검색 결과 파싱
const webResults = data.web?.results || [];
if (webResults.length === 0) {
return `🔍 "${query}"에 대한 검색 결과가 없습니다.`;
}
const results = webResults.slice(0, 3).map((r: any, i: number) =>
`${i + 1}. <b>${r.title}</b>\n ${r.description}\n ${r.url}`
).join('\n\n');
return `🔍 검색 결과: ${query}\n\n${results}`;
} catch (error) {
return `검색 중 오류가 발생했습니다.`;
return `검색 중 오류가 발생했습니다: ${String(error)}`;
}
}

View File

@@ -249,7 +249,10 @@ ${context.previousSummary.summary}
위 프로필을 바탕으로 사용자의 관심사와 맥락을 이해하고 개인화된 응답을 제공하세요.
` : ''}
- 날씨, 시간, 계산, 검색 등의 요청은 제공된 도구를 사용하세요.
- 날씨, 시간, 계산 요청은 제공된 도구를 사용하세요.
- 최신 정보, 실시간 데이터, 현재 가격, 뉴스, 특정 사실 확인이 필요한 질문은 반드시 search_web 도구로 검색하세요. 자체 지식으로 답변하지 마세요.
- 예치금, 입금, 충전, 잔액, 계좌 관련 요청은 반드시 manage_deposit 도구를 사용하세요. 금액 제한이나 규칙을 직접 판단하지 마세요.
- 도메인 관련 요청은 반드시 manage_domain 도구를 사용하세요.
- 응답은 간결하고 도움이 되도록 한국어로 작성하세요.`;
const recentContext = context.recentMessages.slice(-10).map((m) => ({

View File

@@ -12,6 +12,7 @@ export interface Env {
DOMAIN_OWNER_ID?: string;
DEPOSIT_AGENT_ID?: string;
DEPOSIT_ADMIN_ID?: string;
BRAVE_API_KEY?: string;
}
export interface IntentAnalysis {