feat: add AI review of server recommendations

- Server Expert AI now reviews recommendation results before showing to user
- Changed flow: get recommendations first → AI reviews → show with comments
- AI provides specific advice based on actual recommended specs
- Reviews include: spec adequacy, bandwidth warnings, CDN suggestions

Before: AI gave generic advice without seeing recommendations
After: AI reviews actual results and gives contextual feedback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-27 11:08:36 +09:00
parent 1591202d2f
commit 20b4139dd4

View File

@@ -191,11 +191,38 @@ interface OpenAIAPIResponse {
}>; }>;
} }
// RecommendResponse 타입 (server-tool.ts와 동일)
interface RecommendResponse {
recommendations: Array<{
server: {
instance_name: string;
vcpu: number;
memory_gb: number;
storage_gb: number;
transfer_tb: number;
monthly_price: number;
provider_name: string;
region_code: string;
region_name: string;
};
score: number;
estimated_capacity?: {
max_concurrent_users?: number;
};
bandwidth_analysis?: {
estimated_monthly_tb?: number;
overage_tb?: number;
overage_cost_krw?: number;
};
}>;
}
// OpenAI 호출 (서버 전문가 AI with Function Calling) // OpenAI 호출 (서버 전문가 AI with Function Calling)
async function callServerExpertAI( async function callServerExpertAI(
env: Env, env: Env,
session: ServerSession, session: ServerSession,
userMessage: string userMessage: string,
recommendationData?: RecommendResponse
): Promise<{ action: 'question' | 'recommend'; message: string; collectedInfo: ServerSession['collectedInfo'] }> { ): Promise<{ action: 'question' | 'recommend'; message: string; collectedInfo: ServerSession['collectedInfo'] }> {
if (!env.OPENAI_API_KEY) { if (!env.OPENAI_API_KEY) {
throw new Error('OPENAI_API_KEY not configured'); throw new Error('OPENAI_API_KEY not configured');
@@ -209,7 +236,42 @@ async function callServerExpertAI(
content: m.content, content: m.content,
})); }));
const systemPrompt = `당신은 30년 경력의 시니어 클라우드 아키텍트입니다. // 검토 모드: 추천 결과가 있을 때
const isReviewMode = !!recommendationData;
const systemPrompt = isReviewMode
? `당신은 Cloud Orchestrator가 추천한 서버를 검토하는 30년 경력의 시니어 클라우드 아키텍트입니다.
## 전문성 (30년 경력)
- 서버 엔지니어: Linux, Windows Server, 가상화, 컨테이너 마스터
- 네트워크 엔지니어: 로드밸런싱, CDN, DNS, 보안 설계 전문
- 클라우드 아키텍트: 모든 클라우드 플랫폼 경험
- 수천 개의 서버 구축 경험
## 검토 대상 추천 결과
${JSON.stringify(recommendationData?.recommendations, null, 2)}
## 사용자 요구사항
- 용도: ${session.collectedInfo.useCase || '웹 서비스'}
- 규모: ${session.collectedInfo.scale === 'business' ? '사업용' : '개인용'}
${session.collectedInfo.budgetLimit ? `- 예산: ${session.collectedInfo.budgetLimit}` : ''}
## 검토 작업
다음을 검토하고 간결하게 2-3문장으로 코멘트해주세요:
1. 추천된 서버가 용도와 규모에 적합한지
2. 스펙이 충분한지 (RAM, CPU, 스토리지)
3. 대역폭 경고(overage)가 있다면 언급
4. 더 적합한 스펙이 필요하다면 제안
## 응답 형식 (반드시 JSON만 반환)
{
"action": "recommend",
"message": "검토 코멘트 (자연스럽고 친근한 어조, 2-3문장)",
"collectedInfo": ${JSON.stringify(session.collectedInfo)}
}
중요: 검토 코멘트만 작성하세요. 추천 결과 나열은 하지 마세요.`
: `당신은 30년 경력의 시니어 클라우드 아키텍트입니다.
## 전문성 (30년 경력) ## 전문성 (30년 경력)
- 서버 엔지니어: Linux, Windows Server, 가상화, 컨테이너 마스터 - 서버 엔지니어: Linux, Windows Server, 가상화, 컨테이너 마스터
@@ -466,7 +528,7 @@ export async function processServerConsultation(
session.status = 'recommending'; session.status = 'recommending';
await saveServerSession(env.SESSION_KV, session.telegramUserId, session); await saveServerSession(env.SESSION_KV, session.telegramUserId, session);
// Call recommendation API // 1. Call recommendation API (추천 먼저 받기)
logger.info('추천 API 호출', { collectedInfo: session.collectedInfo }); logger.info('추천 API 호출', { collectedInfo: session.collectedInfo });
const { executeServerAction, getRecommendationData } = await import('./tools/server-tool'); const { executeServerAction, getRecommendationData } = await import('./tools/server-tool');
@@ -516,10 +578,11 @@ export async function processServerConsultation(
createdAt: Date.now() createdAt: Date.now()
}; };
// Mark session as selecting (사용자 선택 대기) // 2. AI에게 추천 결과 전달하여 검토 요청
session.status = 'selecting'; logger.info('AI 검토 요청', { recommendationCount: recommendationData.recommendations.length });
await saveServerSession(env.SESSION_KV, session.telegramUserId, session); const reviewResult = await callServerExpertAI(env, session, userMessage, recommendationData);
// 3. 포맷팅된 추천 결과 생성
const formattedRecommendation = await executeServerAction( const formattedRecommendation = await executeServerAction(
'recommend', 'recommend',
{ {
@@ -534,7 +597,12 @@ export async function processServerConsultation(
session.telegramUserId session.telegramUserId
); );
return `${aiResult.message}\n\n${formattedRecommendation}\n\n💡 원하는 서버 번호를 선택해주세요 (예: 1번)`; // Mark session as selecting (사용자 선택 대기)
session.status = 'selecting';
await saveServerSession(env.SESSION_KV, session.telegramUserId, session);
// 4. AI 검토 코멘트 + 추천 결과 함께 반환
return `${reviewResult.message}\n\n${formattedRecommendation}\n\n💡 원하는 서버 번호를 선택해주세요 (예: 1번)`;
} else { } else {
// 추천 결과 없음 - 세션 삭제 // 추천 결과 없음 - 세션 삭제
session.status = 'completed'; session.status = 'completed';