refactor: migrate Deposit Agent from Assistants API to direct function calling

- Replace OpenAI Assistants API with direct function calling (AI Gateway)
- Add action-based parameters to manage_deposit tool (like manage_domain)
- Export executeDepositFunction for direct use in openai-service.ts
- Add formatDepositResult function for consistent response formatting
- Remove DEPOSIT_AGENT_ID dependency (no longer needed)
- Update CLAUDE.md documentation

Benefits:
- Bypasses regional restrictions via AI Gateway
- 100% consistent response formatting
- No Assistants API costs
- Faster execution (no thread creation)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-18 11:59:43 +09:00
parent 1baeb0f04c
commit edbd790538
3 changed files with 166 additions and 53 deletions

View File

@@ -1,5 +1,5 @@
import type { Env } from './types';
import { callDepositAgent } from './deposit-agent';
import { executeDepositFunction, type DepositContext } from './deposit-agent';
// Cloudflare AI Gateway를 통해 OpenAI API 호출 (지역 제한 우회)
const OPENAI_API_URL = 'https://gateway.ai.cloudflare.com/v1/d8e5997eb4040f8b489f09095c0f623c/telegram-bot/openai/chat/completions';
@@ -153,16 +153,33 @@ const tools = [
type: 'function',
function: {
name: 'manage_deposit',
description: '예치금을 관리합니다. 잔액 조회, 입금 계좌 안내, 입금 신고(충전 요청), 거래 내역 조회 등을 수행합니다. "입금", "충전", "잔액", "계좌", "계좌번호", "송금" 등의 키워드가 포함되면 반드시 이 도구를 사용하세요.',
description: '예치금을 관리합니다. "입금", "충전", "잔액", "계좌", "계좌번호", "송금", "거래내역" 등의 키워드가 포함되면 반드시 이 도구를 사용하세요.',
parameters: {
type: 'object',
properties: {
query: {
action: {
type: 'string',
description: '예치금 관련 요청 (예: 잔액 확인, 홍길동 10000원 입금, 거래 내역, 입금 취소 #123)',
enum: ['balance', 'account', 'request', 'history', 'cancel', 'pending', 'confirm', 'reject'],
description: 'balance: 잔액 조회, account: 입금 계좌 안내, request: 입금 신고(충전 요청), history: 거래 내역, cancel: 입금 취소, pending: 대기 목록(관리자), confirm: 입금 확인(관리자), reject: 입금 거절(관리자)',
},
depositor_name: {
type: 'string',
description: '입금자명. request action에서 필수',
},
amount: {
type: 'number',
description: '금액. request action에서 필수. 자연어 금액은 숫자로 변환 (만원→10000, 5천원→5000)',
},
transaction_id: {
type: 'number',
description: '거래 ID. cancel, confirm, reject action에서 필수',
},
limit: {
type: 'number',
description: '조회 개수. history action에서 사용 (기본 10)',
},
},
required: ['query'],
required: ['action'],
},
},
},
@@ -804,6 +821,84 @@ async function executeDomainAction(
}
}
// 예치금 결과 포맷팅 (고정 형식)
function formatDepositResult(action: string, result: any): string {
if (result.error) {
return `🚫 ${result.error}`;
}
switch (action) {
case 'balance':
return `💰 현재 잔액: ${result.formatted}`;
case 'account':
return `💳 입금 계좌 안내
• 은행: ${result.bank}
• 계좌번호: ${result.account}
• 예금주: ${result.holder}
📌 ${result.instruction}`;
case 'request':
if (result.auto_matched) {
return `✅ 입금 확인 완료!
• 입금액: ${result.amount.toLocaleString()}
• 입금자: ${result.depositor_name}
• 현재 잔액: ${result.new_balance.toLocaleString()}
${result.message}`;
} else {
return `📋 입금 요청 등록 (#${result.transaction_id})
• 입금액: ${result.amount.toLocaleString()}
• 입금자: ${result.depositor_name}
💳 입금 계좌
${result.account_info.bank} ${result.account_info.account}
(${result.account_info.holder})
📌 ${result.message}`;
}
case 'history': {
if (result.message && !result.transactions?.length) {
return `📋 ${result.message}`;
}
const statusIcon = (s: string) => s === 'confirmed' ? '✓' : s === 'pending' ? '⏳' : '✗';
const txList = result.transactions.map((tx: any) => {
const date = tx.confirmed_at || tx.created_at;
const dateStr = date ? new Date(date).toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' }) : '';
return `#${tx.id}: ${tx.type === 'deposit' ? '입금' : tx.type} ${tx.amount.toLocaleString()}${statusIcon(tx.status)} (${dateStr})`;
}).join('\n');
return `📋 거래 내역\n\n${txList}`;
}
case 'cancel':
return `✅ 거래 #${result.transaction_id} 취소 완료`;
case 'pending': {
if (result.message && !result.pending?.length) {
return `📋 ${result.message}`;
}
const pendingList = result.pending.map((p: any) =>
`#${p.id}: ${p.depositor_name} ${p.amount.toLocaleString()}원 (${p.user})`
).join('\n');
return `📋 대기 중인 입금 요청\n\n${pendingList}`;
}
case 'confirm':
return `✅ 입금 확인 완료 (#${result.transaction_id}, ${result.amount.toLocaleString()}원)`;
case 'reject':
return `❌ 입금 거절 완료 (#${result.transaction_id})`;
default:
return `💰 ${JSON.stringify(result)}`;
}
}
// 도구 실행
async function executeTool(name: string, args: Record<string, string>, env?: Env, telegramUserId?: string, db?: D1Database): Promise<string> {
switch (name) {
@@ -963,8 +1058,8 @@ async function executeTool(name: string, args: Record<string, string>, env?: Env
}
case 'manage_deposit': {
const query = args.query;
console.log('[manage_deposit] 시작:', { query, telegramUserId, hasDb: !!db });
const { action, depositor_name, amount, transaction_id, limit } = args;
console.log('[manage_deposit] 시작:', { action, depositor_name, amount, telegramUserId });
if (!telegramUserId || !db) {
return '🚫 예치금 기능을 사용할 수 없습니다.';
@@ -980,36 +1075,46 @@ async function executeTool(name: string, args: Record<string, string>, env?: Env
}
const isAdmin = telegramUserId === env?.DEPOSIT_ADMIN_ID;
const context: DepositContext = {
userId: user.id,
telegramUserId,
isAdmin,
db,
};
if (!env?.OPENAI_API_KEY || !env?.DEPOSIT_AGENT_ID) {
console.log('[manage_deposit] DEPOSIT_AGENT_ID 미설정, 기본 응답');
return '💰 예치금 에이전트가 설정되지 않았습니다. 관리자에게 문의하세요.';
// action → executeDepositFunction 매핑
const actionMap: Record<string, string> = {
balance: 'get_balance',
account: 'get_account_info',
request: 'request_deposit',
history: 'get_transactions',
cancel: 'cancel_transaction',
pending: 'get_pending_list',
confirm: 'confirm_deposit',
reject: 'reject_deposit',
};
const funcName = actionMap[action];
if (!funcName) {
return `🚫 알 수 없는 작업: ${action}`;
}
try {
console.log('[manage_deposit] callDepositAgent 호출');
const result = await callDepositAgent(
env.OPENAI_API_KEY,
env.DEPOSIT_AGENT_ID,
query,
{
userId: user.id,
telegramUserId,
isAdmin,
db,
}
);
console.log('[manage_deposit] callDepositAgent 완료:', result?.slice(0, 100));
const funcArgs: Record<string, any> = {};
if (depositor_name) funcArgs.depositor_name = depositor_name;
if (amount) funcArgs.amount = Number(amount);
if (transaction_id) funcArgs.transaction_id = Number(transaction_id);
if (limit) funcArgs.limit = Number(limit);
// Markdown → HTML 변환
const htmlResult = result
.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>')
.replace(/\*(.+?)\*/g, '<i>$1</i>')
.replace(/`(.+?)`/g, '<code>$1</code>');
return `💰 ${htmlResult}`;
console.log('[manage_deposit] executeDepositFunction 호출:', funcName, funcArgs);
const result = await executeDepositFunction(funcName, funcArgs, context);
console.log('[manage_deposit] 결과:', JSON.stringify(result).slice(0, 200));
// 결과 포맷팅 (고정 형식)
return formatDepositResult(action, result);
} catch (error) {
console.error('[manage_deposit] 오류:', error);
return `💰 예치금 처리 오류: ${String(error)}`;
return `🚫 예치금 처리 오류: ${String(error)}`;
}
}