/** * Cloudflare Pages Functions: Gemini API Proxy * 이 파일은 Cloudflare Pages 프로젝트의 /functions/api 디렉토리에 위치해야 합니다. */ export async function onRequestPost(context) { try { // 1. 클라우드플레어 대시보드에서 설정한 환경 변수 가져오기 // Cloudflare Pages에서는 context.env[변수명]으로 접근합니다. const apiKey = context.env.GEMINI_API_KEY; if (!apiKey) { return new Response(JSON.stringify({ error: "설정 오류", message: "Cloudflare 대시보드 (Settings > Functions)에서 GEMINI_API_KEY 환경 변수를 등록해 주세요." }), { status: 500, headers: { "Content-Type": "application/json" } }); } // 2. 클라이언트로부터 요청 데이터 받기 const requestData = await context.request.json(); const { jd, resume, level, customCriteria } = requestData; if (!jd || !resume) { return new Response(JSON.stringify({ error: "데이터 부족", message: "JD와 이력서 데이터가 필요합니다." }), { status: 400, headers: { "Content-Type": "application/json" } }); } // 3. AI에게 전달할 시스템 프롬프트 구성 const systemPrompt = `당신은 최고 수준의 글로벌 IT 기업 CHRO이자 매우 엄격하고 객관적인 데이터 기반 HR 분석 전문가입니다. 동일한 입력에 대해 항상 100% 동일한 결과(JSON)를 출력하십시오. [평가 원칙] 1. 팩트 기반: 이력서에 명시된 수치와 구체적 성과만 인정합니다. 2. 할루시네이션 배제: 없는 내용을 지어내지 마십시오. 3. 신뢰도 평가: 경력 기간은 신뢰도 점수에 영향을 주지 않으며 구체성과 논리성만 봅니다. [맞춤 기준] ${customCriteria || "기본 직무 적합도 중심 평가"} 반드시 다음 구조의 순수 JSON으로만 응답하십시오 (마크다운 기호 금지): { "candidate_name": "string", "overall_score": number, "factors": { "hard_skills": number, "experience": number, "soft_skills": number, "problem_solving": number, "culture_fit": number }, "reliability": { "overall_score": number, "summary": "string", "green_flags": ["string"], "red_flags": ["string"], "verification_questions": ["string"] }, "strengths": ["string"], "weaknesses": ["string"], "interview_questions": ["string"], "factor_details": { "hard_skills": "string", "experience": "string", "soft_skills": "string", "problem_solving": "string", "culture_fit": "string" } }`; // 4. 구글 Gemini API 호출 const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`; const geminiResponse = await fetch(geminiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: `[JD]\n${jd}\n\n[Level]\n${level}\n\n[Resume]\n${resume}` }] }], systemInstruction: { parts: [{ text: systemPrompt }] }, generationConfig: { responseMimeType: "application/json", temperature: 0.0, topK: 1, topP: 0.0 } }) }); if (!geminiResponse.ok) { const errorText = await geminiResponse.text(); return new Response(JSON.stringify({ error: "Gemini API 오류", message: errorText }), { status: geminiResponse.status, headers: { "Content-Type": "application/json" } }); } const resultData = await geminiResponse.json(); let aiText = resultData.candidates?.[0]?.content?.parts?.[0]?.text; if (!aiText) { throw new Error("AI 응답 데이터가 비어 있습니다."); } // 5. JSON 정제 로직 (마크다운 백틱 제거) let cleanedText = aiText.trim(); if (cleanedText.startsWith("```")) { cleanedText = cleanedText.replace(/^```[a-z]*\n/i, "").replace(/\n```$/i, "").trim(); } // 6. 최종 결과 반환 return new Response(JSON.stringify({ data: JSON.parse(cleanedText), usage: resultData.usageMetadata }), { headers: { "Content-Type": "application/json" } }); } catch (err) { console.error("Cloudflare Function Error:", err); return new Response(JSON.stringify({ error: "분석 실패", message: err.message, stack: err.stack }), { status: 500, headers: { "Content-Type": "application/json" } }); } }