/** * AI智能服务 * 提供岗位筛选、简历分析、聊天生成等AI功能 */ const axios = require('axios'); class AIService { constructor(config = {}) { this.apiKey = config.apiKey || process.env.AI_API_KEY || ''; this.baseURL = config.baseURL || process.env.AI_BASE_URL || 'https://api.deepseek.com'; this.model = config.model || 'deepseek-chat'; this.timeout = config.timeout || 30000; // 创建axios实例 this.client = axios.create({ baseURL: this.baseURL, timeout: this.timeout, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` } }); } /** * 调用AI接口 * @param {Array} messages - 消息数组 * @param {Object} options - 额外选项 * @returns {Promise} AI响应内容 */ async chat(messages, options = {}) { try { const response = await this.client.post('/v1/chat/completions', { model: this.model, messages, temperature: options.temperature || 0.7, max_tokens: options.max_tokens || 2000, ...options }); return response.data.choices[0].message.content; } catch (error) { console.warn('AI服务调用失败:', error.message); throw new Error(`AI服务调用失败: ${error.message}`); } } /** * 分析简历竞争力 * @param {Object} resumeData - 简历数据 * @returns {Promise} 分析结果 */ async analyzeResume(resumeData) { const prompt = `请分析以下简历的竞争力,并提供详细评估: 简历信息: - 姓名: ${resumeData.fullName || '未知'} - 工作年限: ${resumeData.workYears || '未知'} - 教育背景: ${resumeData.education || '未知'} - 期望职位: ${resumeData.expectedPosition || '未知'} - 期望薪资: ${resumeData.expectedSalary || '未知'} - 技能标签: ${Array.isArray(resumeData.skills) ? resumeData.skills.join('、') : '未知'} - 工作经历: ${resumeData.workExperience || '未提供'} - 项目经历: ${resumeData.projectExperience || '未提供'} 请从以下维度进行评估(1-100分): 1. 技术能力 2. 项目经验 3. 教育背景 4. 工作年限匹配度 5. 综合竞争力 返回JSON格式: { "overallScore": 总分(1-100), "technicalScore": 技术能力分(1-100), "projectScore": 项目经验分(1-100), "educationScore": 教育背景分(1-100), "experienceScore": 工作年限分(1-100), "strengths": ["优势1", "优势2", "优势3"], "weaknesses": ["不足1", "不足2"], "suggestions": ["建议1", "建议2", "建议3"], "keySkills": ["核心技能1", "核心技能2"], "marketCompetitiveness": "市场竞争力描述" }`; const messages = [ { role: 'system', content: '你是一个专业的HR和招聘顾问,擅长分析简历和评估候选人竞争力。' }, { role: 'user', content: prompt } ]; try { const response = await this.chat(messages, { temperature: 0.3 }); // 提取JSON部分 const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } throw new Error('AI返回格式不正确'); } catch (error) { console.warn('简历分析失败:', error); // 返回默认值 return { overallScore: 60, technicalScore: 60, projectScore: 60, educationScore: 60, experienceScore: 60, strengths: ['待AI分析'], weaknesses: ['待AI分析'], suggestions: ['请稍后重试'], keySkills: Array.isArray(resumeData.skills) ? resumeData.skills.slice(0, 5) : [], marketCompetitiveness: '待AI分析' }; } } /** * 岗位匹配度评估 * @param {Object} jobData - 岗位数据 * @param {Object} resumeData - 简历数据 * @returns {Promise} 匹配结果 */ async matchJobWithResume(jobData, resumeData) { const prompt = `请评估以下岗位与简历的匹配度: 【岗位信息】 - 职位名称: ${jobData.jobTitle || '未知'} - 公司名称: ${jobData.companyName || '未知'} - 薪资范围: ${jobData.salary || '未知'} - 工作地点: ${jobData.location || '未知'} - 工作经验要求: ${jobData.experienceRequired || '未知'} - 学历要求: ${jobData.educationRequired || '未知'} - 岗位描述: ${jobData.jobDescription || '未提供'} - 技能要求: ${jobData.skillsRequired || '未提供'} 【简历信息】 - 工作年限: ${resumeData.workYears || '未知'} - 教育背景: ${resumeData.education || '未知'} - 技能标签: ${Array.isArray(resumeData.skills) ? resumeData.skills.join('、') : '未知'} - 期望职位: ${resumeData.expectedPosition || '未知'} - 期望薪资: ${resumeData.expectedSalary || '未知'} 请分析: 1. 技能匹配度 2. 经验匹配度 3. 薪资匹配度 4. 是否为外包岗位(根据公司名称、岗位描述判断) 5. 综合推荐度 返回JSON格式: { "matchScore": 匹配度分数(1-100), "skillMatch": 技能匹配度(1-100), "experienceMatch": 经验匹配度(1-100), "salaryMatch": 薪资匹配度(1-100), "isOutsourcing": 是否外包(true/false), "outsourcingConfidence": 外包判断置信度(0-1), "recommendLevel": "推荐等级(excellent/good/medium/low)", "matchReasons": ["匹配原因1", "匹配原因2"], "concerns": ["顾虑点1", "顾虑点2"], "applyAdvice": "投递建议" }`; const messages = [ { role: 'system', content: '你是一个专业的职业规划师,擅长评估岗位与求职者的匹配度。' }, { role: 'user', content: prompt } ]; try { const response = await this.chat(messages, { temperature: 0.3 }); const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } throw new Error('AI返回格式不正确'); } catch (error) { console.warn('岗位匹配分析失败:', error); // 返回默认值 return { matchScore: 50, skillMatch: 50, experienceMatch: 50, salaryMatch: 50, isOutsourcing: false, outsourcingConfidence: 0, recommendLevel: 'medium', matchReasons: ['待AI分析'], concerns: ['待AI分析'], applyAdvice: '建议人工审核' }; } } /** * 批量评估岗位(用于智能筛选) * @param {Array} jobs - 岗位列表 * @param {Object} resumeData - 简历数据 * @returns {Promise} 评估结果列表 */ async batchMatchJobs(jobs, resumeData) { const results = []; // 限制并发数量,避免API限流 const concurrency = 3; for (let i = 0; i < jobs.length; i += concurrency) { const batch = jobs.slice(i, i + concurrency); const batchPromises = batch.map(job => this.matchJobWithResume(job, resumeData).catch(err => { console.warn(`岗位${job.jobId}匹配失败:`, err.message); return { jobId: job.jobId, matchScore: 0, error: err.message }; }) ); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); // 避免请求过快,休眠一下 if (i + concurrency < jobs.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } } return results; } /** * 生成聊天内容 * @param {Object} context - 聊天上下文 * @returns {Promise} 生成的聊天内容 */ async generateChatContent(context) { const { jobInfo, resumeInfo, chatType = 'greeting', previousMessages = [] } = context; let prompt = ''; switch (chatType) { case 'greeting': prompt = `作为求职者,向HR发送第一条消息表达对以下岗位的兴趣: 岗位: ${jobInfo.jobTitle} 公司: ${jobInfo.companyName} 要求: 简洁、专业、突出自己的优势,不超过100字`; break; case 'follow_up': prompt = `HR已查看简历但未回复,需要发送一条礼貌的跟进消息: 岗位: ${jobInfo.jobTitle} 要求: 礼貌、不唐突、展现持续兴趣,不超过80字`; break; case 'interview_confirm': prompt = `HR发出面试邀约,需要确认并表达感谢: 岗位: ${jobInfo.jobTitle} 面试时间: ${context.interviewTime || '待定'} 要求: 专业、感谢、确认参加,不超过60字`; break; case 'reply': prompt = `HR说: "${context.hrMessage}" 请作为求职者回复,要求: 自然、专业、回答问题,不超过100字`; break; default: prompt = `生成一条针对${jobInfo.jobTitle}岗位的沟通消息`; } const messages = [ { role: 'system', content: '你是一个求职者,需要与HR进行专业、礼貌的沟通。回复要简洁、真诚、突出优势。' }, ...previousMessages.map(msg => ({ role: msg.role, content: msg.content })), { role: 'user', content: prompt } ]; try { const response = await this.chat(messages, { temperature: 0.8, max_tokens: 200 }); return response.trim(); } catch (error) { console.warn('生成聊天内容失败:', error); // 返回默认模板 switch (chatType) { case 'greeting': return `您好,我对贵司的${jobInfo.jobTitle}岗位非常感兴趣,我有相关工作经验,期待能有机会详细沟通。`; case 'follow_up': return `您好,不知道您对我的简历是否感兴趣?期待您的回复。`; case 'interview_confirm': return `好的,感谢您的面试邀约,我会准时参加。`; default: return `您好,期待与您沟通。`; } } } /** * 判断是否为面试邀约 * @param {String} message - HR消息内容 * @returns {Promise} 判断结果 */ async detectInterviewInvitation(message) { const prompt = `判断以下HR消息是否为面试邀约,并提取关键信息: 消息内容: "${message}" 返回JSON格式: { "isInterview": 是否为面试邀约(true/false), "confidence": 置信度(0-1), "interviewType": "面试类型(phone/video/onsite/unknown)", "interviewTime": "面试时间(如果提到)", "interviewLocation": "面试地点(如果提到)", "needReply": 是否需要回复确认(true/false) }`; const messages = [ { role: 'system', content: '你是一个文本分析专家,擅长识别面试邀约信息。' }, { role: 'user', content: prompt } ]; try { const response = await this.chat(messages, { temperature: 0.1 }); const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } throw new Error('AI返回格式不正确'); } catch (error) { console.warn('面试邀约判断失败:', error); // 简单的关键词判断作为降级方案 const keywords = ['面试', '邀请', '来面', '约您', '见面', '电话沟通', '视频面试']; const isInterview = keywords.some(kw => message.includes(kw)); return { isInterview, confidence: isInterview ? 0.7 : 0.3, interviewType: 'unknown', interviewTime: null, interviewLocation: null, needReply: isInterview }; } } /** * 分析HR反馈情感 * @param {String} message - HR消息内容 * @returns {Promise} 情感分析结果 */ async analyzeSentiment(message) { const prompt = `分析以下HR消息的情感倾向: 消息: "${message}" 返回JSON格式: { "sentiment": "情感倾向(positive/neutral/negative)", "interest": "兴趣程度(high/medium/low)", "urgency": "紧急程度(high/medium/low)", "keywords": ["关键词1", "关键词2"] }`; const messages = [ { role: 'system', content: '你是一个情感分析专家。' }, { role: 'user', content: prompt } ]; try { const response = await this.chat(messages, { temperature: 0.1 }); const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } throw new Error('AI返回格式不正确'); } catch (error) { console.warn('情感分析失败:', error); return { sentiment: 'neutral', interest: 'medium', urgency: 'low', keywords: [] }; } } } // 导出单例 let instance = null; module.exports = { /** * 获取AI服务实例 * @param {Object} config - 配置选项 * @returns {AIService} */ getInstance(config) { if (!instance) { instance = new AIService(config); } return instance; }, /** * 创建新的AI服务实例 * @param {Object} config - 配置选项 * @returns {AIService} */ createInstance(config) { return new AIService(config); } };