import { Env } from './types'; interface OpenAIMessage { role: 'system' | 'user' | 'assistant' | 'tool'; content: string | null; tool_calls?: ToolCall[]; tool_call_id?: string; } interface ToolCall { id: string; type: 'function'; function: { name: string; arguments: string; }; } interface OpenAIResponse { choices: { message: OpenAIMessage; finish_reason: string; }[]; } // 사용 가능한 도구 정의 const tools = [ { type: 'function', function: { name: 'get_weather', description: '특정 도시의 현재 날씨 정보를 가져옵니다', parameters: { type: 'object', properties: { city: { type: 'string', description: '도시 이름 (예: Seoul, Tokyo, New York)', }, }, required: ['city'], }, }, }, { type: 'function', function: { name: 'search_web', description: '웹에서 정보를 검색합니다', parameters: { type: 'object', properties: { query: { type: 'string', description: '검색 쿼리', }, }, required: ['query'], }, }, }, { type: 'function', function: { name: 'get_current_time', description: '현재 시간을 가져옵니다', parameters: { type: 'object', properties: { timezone: { type: 'string', description: '타임존 (예: Asia/Seoul, UTC)', }, }, required: [], }, }, }, { type: 'function', function: { name: 'calculate', description: '수학 계산을 수행합니다', parameters: { type: 'object', properties: { expression: { type: 'string', description: '계산할 수식 (예: 2+2, 100*5)', }, }, required: ['expression'], }, }, }, { type: 'function', function: { name: 'lookup_docs', description: '프로그래밍 라이브러리의 공식 문서를 조회합니다. React, OpenAI, Cloudflare Workers 등의 최신 문서와 코드 예제를 검색할 수 있습니다.', parameters: { type: 'object', properties: { library: { type: 'string', description: '라이브러리 이름 (예: react, openai, cloudflare-workers, next.js)', }, query: { type: 'string', description: '찾고 싶은 내용 (예: hooks 사용법, API 호출 방법)', }, }, required: ['library', 'query'], }, }, }, ]; // 도구 실행 async function executeTool(name: string, args: Record): Promise { switch (name) { case 'get_weather': { const city = args.city || 'Seoul'; try { const response = await fetch( `https://wttr.in/${encodeURIComponent(city)}?format=j1` ); const data = await response.json() as any; const current = data.current_condition[0]; return `🌤 ${city} 날씨 온도: ${current.temp_C}°C (체감 ${current.FeelsLikeC}°C) 상태: ${current.weatherDesc[0].value} 습도: ${current.humidity}% 풍속: ${current.windspeedKmph} km/h`; } catch (error) { return `날씨 정보를 가져올 수 없습니다: ${city}`; } } case 'search_web': { // 간단한 DuckDuckGo Instant Answer 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}`; } return `"${query}"에 대한 즉시 답변을 찾을 수 없습니다. 더 구체적인 질문을 해주세요.`; } catch (error) { return `검색 중 오류가 발생했습니다.`; } } case 'get_current_time': { const timezone = args.timezone || 'Asia/Seoul'; try { const now = new Date(); const formatted = now.toLocaleString('ko-KR', { timeZone: timezone }); return `🕐 현재 시간 (${timezone}): ${formatted}`; } catch (error) { return `시간 정보를 가져올 수 없습니다.`; } } case 'calculate': { const expression = args.expression; try { // 안전한 수식 계산 (기본 연산만) const sanitized = expression.replace(/[^0-9+\-*/().% ]/g, ''); const result = Function('"use strict"; return (' + sanitized + ')')(); return `🔢 계산 결과: ${expression} = ${result}`; } catch (error) { return `계산할 수 없는 수식입니다: ${expression}`; } } case 'lookup_docs': { const library = args.library; const query = args.query; try { // Context7 REST API 직접 호출 // 1. 라이브러리 검색 const searchUrl = `https://context7.com/api/v2/libs/search?libraryName=${encodeURIComponent(library)}&query=${encodeURIComponent(query)}`; const searchResponse = await fetch(searchUrl); const searchData = await searchResponse.json() as any; if (!searchData.libraries?.length) { return `📚 "${library}" 라이브러리를 찾을 수 없습니다.`; } const libraryId = searchData.libraries[0].id; // 2. 문서 조회 const docsUrl = `https://context7.com/api/v2/context?libraryId=${encodeURIComponent(libraryId)}&query=${encodeURIComponent(query)}`; const docsResponse = await fetch(docsUrl); const docsData = await docsResponse.json() as any; if (docsData.error) { return `📚 문서 조회 실패: ${docsData.message || docsData.error}`; } const content = docsData.context || docsData.content || JSON.stringify(docsData, null, 2); return `📚 ${library} 문서 (${query}):\n\n${content.slice(0, 1500)}`; } catch (error) { return `📚 문서 조회 중 오류: ${String(error)}`; } } default: return `알 수 없는 도구: ${name}`; } } // OpenAI API 호출 async function callOpenAI( apiKey: string, messages: OpenAIMessage[], useTools: boolean = true ): Promise { const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, body: JSON.stringify({ model: 'gpt-4o-mini', messages, tools: useTools ? tools : undefined, tool_choice: useTools ? 'auto' : undefined, max_tokens: 1000, }), }); if (!response.ok) { const error = await response.text(); throw new Error(`OpenAI API error: ${response.status} - ${error}`); } return response.json(); } // 메인 응답 생성 함수 export async function generateOpenAIResponse( env: Env, userMessage: string, systemPrompt: string, recentContext: { role: 'user' | 'assistant'; content: string }[] ): Promise { if (!env.OPENAI_API_KEY) { throw new Error('OPENAI_API_KEY not configured'); } const messages: OpenAIMessage[] = [ { role: 'system', content: systemPrompt }, ...recentContext.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content, })), { role: 'user', content: userMessage }, ]; // 첫 번째 호출 let response = await callOpenAI(env.OPENAI_API_KEY, messages); let assistantMessage = response.choices[0].message; // Function Calling 처리 (최대 3회 반복) let iterations = 0; while (assistantMessage.tool_calls && iterations < 3) { iterations++; // 도구 호출 결과 수집 const toolResults: OpenAIMessage[] = []; for (const toolCall of assistantMessage.tool_calls) { const args = JSON.parse(toolCall.function.arguments); const result = await executeTool(toolCall.function.name, args); toolResults.push({ role: 'tool', tool_call_id: toolCall.id, content: result, }); } // 대화에 추가 messages.push({ role: 'assistant', content: assistantMessage.content, tool_calls: assistantMessage.tool_calls, }); messages.push(...toolResults); // 다시 호출 response = await callOpenAI(env.OPENAI_API_KEY, messages, false); assistantMessage = response.choices[0].message; } return assistantMessage.content || '응답을 생성할 수 없습니다.'; } // 프로필 생성용 (도구 없이) export async function generateProfileWithOpenAI( env: Env, prompt: string ): Promise { if (!env.OPENAI_API_KEY) { throw new Error('OPENAI_API_KEY not configured'); } const response = await callOpenAI( env.OPENAI_API_KEY, [{ role: 'user', content: prompt }], false ); return response.choices[0].message.content || '프로필 생성 실패'; }