Files
autoAiWorkSys/api/services/ai_service.js
张成 956cfe88f8 1
2025-12-30 16:23:45 +08:00

416 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AI智能服务
* 提供岗位筛选、简历分析、聊天生成等AI功能
*/
const axios = require('axios');
const config = require('../../config/config');
class AIService {
constructor() {
this.apiKey = config.ai.apiKey;
this.baseURL = config.ai.baseUrl;
this.model = config.ai.model;
this.timeout = 30000;
// 创建axios实例
this.client = axios.create({
baseURL: this.baseURL,
timeout: this.timeout,
headers: {
'Content-Type': 'application/json',
'Authorization': `${this.apiKey}`
}
});
}
/**
* 调用AI接口
* @param {Array} messages - 消息数组
* @param {Object} options - 额外选项
* @returns {Promise<String>} 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<Object>} 分析结果
*/
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<Object>} 匹配结果
*/
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<Array>} 评估结果列表
*/
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<String>} 生成的聊天内容
*/
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<Object>} 判断结果
*/
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<Object>} 情感分析结果
*/
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服务实例
* @returns {AIService}
*/
getInstance() {
if (!instance) {
instance = new AIService();
}
return instance;
},
/**
* 创建新的AI服务实例
* @returns {AIService}
*/
createInstance() {
return new AIService();
}
};