import { z } from 'zod'; import { Env, IntentAnalysis, N8nResponse, WorkersAITextGenerationOutput, WorkersAITextGenerationInput } from './types'; import { createLogger } from './utils/logger'; const logger = createLogger('n8n-service'); // Zod schema for N8n webhook response validation const N8nResponseSchema = z.object({ reply: z.string().optional(), error: z.string().optional(), }); // n8n으로 처리할 기능 목록 (참고용) // - weather: 날씨 // - search: 검색 // - image: 이미지 생성 // - translate: 번역 // - schedule: 일정 // - reminder: 알림 // - news: 뉴스 // - calculate: 계산 // - summarize_url: URL 요약 // AI가 의도를 분석하여 n8n 호출 여부 결정 export async function analyzeIntent( ai: Ai, userMessage: string ): Promise { const prompt = `사용자 메시지를 분석하여 어떤 처리가 필요한지 JSON으로 응답하세요. ## 외부 기능이 필요한 경우 (action: "n8n") - 날씨 정보: type = "weather" - 웹 검색: type = "search" - 이미지 생성: type = "image" - 번역: type = "translate" - 일정/캘린더: type = "schedule" - 알림 설정: type = "reminder" - 뉴스: type = "news" - 복잡한 계산: type = "calculate" - URL/링크 요약: type = "summarize_url" ## 일반 대화인 경우 (action: "chat") - 인사, 잡담, 질문, 조언 요청 등 ## 응답 형식 (JSON만 출력) {"action": "n8n", "type": "weather", "confidence": 0.9} 또는 {"action": "chat", "confidence": 0.95} ## 사용자 메시지 ${userMessage} JSON:`; try { const input: WorkersAITextGenerationInput = { messages: [{ role: 'user', content: prompt }], max_tokens: 100, }; const response = await ai.run( '@cf/meta/llama-3.1-8b-instruct' as '@cf/meta/llama-3.1-8b-instruct-fp8', input ) as WorkersAITextGenerationOutput; const text = response.response || ''; // JSON 추출 const jsonMatch = text.match(/\{[^}]+\}/); if (jsonMatch) { const parsed = JSON.parse(jsonMatch[0]); return { action: parsed.action || 'chat', type: parsed.type, confidence: parsed.confidence || 0.5, }; } } catch (error) { logger.error('Intent analysis error', error as Error); } // 기본값: 일반 대화 return { action: 'chat', confidence: 0.5 }; } // n8n Webhook 호출 export async function callN8n( env: Env, type: string, userMessage: string, userId: number, chatId: string, userProfile?: string | null ): Promise { if (!env.N8N_WEBHOOK_URL) { return { error: 'n8n이 설정되지 않았습니다.' }; } const webhookUrl = `${env.N8N_WEBHOOK_URL}/webhook/telegram-bot`; try { const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ type, message: userMessage, user_id: userId, chat_id: chatId, profile: userProfile, timestamp: new Date().toISOString(), }), }); if (!response.ok) { const errorText = await response.text(); logger.error('n8n API error', new Error(`Status ${response.status}: ${errorText}`), { status: response.status, userId, type }); return { error: `n8n 호출 실패 (${response.status})` }; } const jsonData = await response.json(); const parseResult = N8nResponseSchema.safeParse(jsonData); if (!parseResult.success) { logger.error('N8n response schema validation failed', parseResult.error); return { error: 'n8n 응답 형식 오류' }; } return parseResult.data; } catch (error) { logger.error('n8n fetch error', error as Error, { userId, type }); return { error: 'n8n 연결 실패' }; } } // 스마트 라우팅: AI 판단 → n8n 또는 로컬 AI export async function smartRoute( env: Env, userMessage: string, userId: number, chatId: string, userProfile?: string | null ): Promise<{ useN8n: boolean; n8nType?: string; n8nResponse?: N8nResponse }> { // n8n URL이 없으면 항상 로컬 AI 사용 if (!env.N8N_WEBHOOK_URL) { return { useN8n: false }; } // 의도 분석 const intent = await analyzeIntent(env.AI, userMessage); // n8n 호출이 필요하고 신뢰도가 높은 경우 if (intent.action === 'n8n' && intent.confidence >= 0.7 && intent.type) { const n8nResponse = await callN8n( env, intent.type, userMessage, userId, chatId, userProfile ); // n8n 응답이 성공적인 경우 if (n8nResponse.reply && !n8nResponse.error) { return { useN8n: true, n8nType: intent.type, n8nResponse, }; } } return { useN8n: false }; }