feat: add Server Expert AI with search/docs tools for trend-aware recommendations

- Add server-agent.ts with 30-year senior architect persona
- Implement KV-based session management for multi-turn conversations
- Add search_trends (Brave Search) and lookup_framework_docs (Context7) tools
- Function Calling support with max 3 tool calls per request
- Auto-infer tech stack and expected users from use case/scale
- Prohibit competitor provider mentions (AWS, GCP, Azure, etc.)
- Simplify main AI system prompt, delegate complex logic to expert AI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-26 14:15:15 +09:00
parent 87c92e1ed1
commit a2d05c82c3
6 changed files with 587 additions and 11 deletions

View File

@@ -136,32 +136,33 @@ export const manageServerTool = {
type: 'function',
function: {
name: 'manage_server',
description: '클라우드 서버 관리 및 추천. "서버", "VPS", "클라우드", "호스팅" 등의 키워드가 포함되면 사용하세요.',
description: '클라우드 서버 관리 및 추천. 서버/VPS/클라우드/호스팅 관련 요청 시 사용. 상담 시작: start_consultation',
parameters: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['recommend', 'order', 'start', 'stop', 'delete', 'list'],
description: 'recommend: 서버 추천, order: 서버 신청 (준비 중), start: 서버 켜기 (준비 중), stop: 서버 끄기 (준비 중), delete: 서버 해지 (준비 중), list: 내 서버 목록 (준비 중)',
enum: ['recommend', 'order', 'start', 'stop', 'delete', 'list',
'start_consultation', 'continue_consultation', 'cancel_consultation'],
description: 'start_consultation: 서버 추천 상담 시작, continue_consultation: 상담 계속, recommend: 직접 추천',
},
tech_stack: {
type: 'array',
items: { type: 'string' },
description: '사용할 기술 스택 (예: ["nodejs", "nginx", "postgresql"]). recommend action에서 필수',
description: '기술 스택. 용도에서 추론 (블로그→wordpress, 쇼핑몰→ecommerce, 커뮤니티→php,mysql). 모르면 ["web"]',
},
expected_users: {
type: 'number',
description: '예상 동시 접속자 수. recommend action에서 필수',
description: '예상 사용자 수. 모르면 개인용=100, 사업용=500 사용',
},
use_case: {
type: 'string',
description: '사용 목적 설명 (예: "웹사이트 호스팅", "API 서버"). recommend action에서 필수',
description: '용도 (예: "블로그", "쇼핑몰", "커뮤니티")',
},
traffic_pattern: {
type: 'string',
enum: ['steady', 'spiky', 'growing'],
description: '트래픽 패턴. steady: 일정한 트래픽, spiky: 순간 급증, growing: 점진적 성장. recommend action에서 선택',
description: '생략 가능. 기본값: steady',
},
region_preference: {
type: 'array',
@@ -189,6 +190,10 @@ export const manageServerTool = {
type: 'string',
description: '서버 라벨 (예: "myapp-prod"). order action에서 필수',
},
message: {
type: 'string',
description: '사용자 메시지. continue_consultation action에서 필수',
},
},
required: ['action'],
},
@@ -314,7 +319,7 @@ function formatRecommendations(data: RecommendResponse): string {
}
// 서버 작업 직접 실행
async function executeServerAction(
export async function executeServerAction(
action: string,
args: {
tech_stack?: string[];
@@ -327,6 +332,7 @@ async function executeServerAction(
server_id?: string;
region_code?: string;
label?: string;
message?: string;
},
env?: Env,
telegramUserId?: string
@@ -338,6 +344,76 @@ async function executeServerAction(
});
switch (action) {
case 'start_consultation': {
// Import session functions
const { saveServerSession } = await import('../server-agent');
if (!telegramUserId) {
return '🚫 사용자 인증이 필요합니다.';
}
if (!env?.SESSION_KV) {
return '🚫 세션 저장소가 설정되지 않았습니다.';
}
const session: import('../types').ServerSession = {
telegramUserId,
status: 'gathering',
collectedInfo: {},
messages: [],
createdAt: Date.now(),
updatedAt: Date.now(),
};
await saveServerSession(env.SESSION_KV, telegramUserId, session);
logger.info('상담 세션 생성', { userId: maskUserId(telegramUserId) });
return '안녕하세요! 서버 추천을 도와드리겠습니다. 😊\n\n어떤 서비스를 운영하실 건가요?\n예: 블로그, 쇼핑몰, 커뮤니티, API 서버 등';
}
case 'continue_consultation': {
const { getServerSession, processServerConsultation } = await import('../server-agent');
if (!telegramUserId) {
return '🚫 사용자 인증이 필요합니다.';
}
if (!env?.SESSION_KV) {
return '🚫 세션 저장소가 설정되지 않았습니다.';
}
if (!args.message) {
return '🚫 메시지가 필요합니다.';
}
const session = await getServerSession(env.SESSION_KV, telegramUserId);
if (!session) {
return '세션이 만료되었습니다. 다시 시작하려면 "서버 추천"이라고 말씀해주세요.';
}
const result = await processServerConsultation(args.message, session, env);
return result;
}
case 'cancel_consultation': {
const { deleteServerSession } = await import('../server-agent');
if (!telegramUserId) {
return '🚫 사용자 인증이 필요합니다.';
}
if (!env?.SESSION_KV) {
return '🚫 세션 저장소가 설정되지 않았습니다.';
}
await deleteServerSession(env.SESSION_KV, telegramUserId);
logger.info('상담 세션 취소', { userId: maskUserId(telegramUserId) });
return '상담이 취소되었습니다. 다시 시작하려면 "서버 추천"이라고 말씀해주세요.';
}
case 'recommend': {
const { tech_stack, expected_users, use_case, traffic_pattern, region_preference, budget_limit, lang } = args;
@@ -438,6 +514,7 @@ export async function executeManageServer(
server_id?: string;
region_code?: string;
label?: string;
message?: string;
},
env?: Env,
telegramUserId?: string