This commit is contained in:
张成
2025-12-27 20:14:40 +08:00
parent 43382668a3
commit 43f7884e52
14 changed files with 1818 additions and 21 deletions

View File

@@ -0,0 +1,174 @@
/**
* AI调用记录工具类
* 用于记录每次AI API调用的详细信息
*/
const Framework = require("../../framework/node-core-framework.js");
class AiCallRecorder {
/**
* 记录AI调用
* @param {Object} params - 调用参数
* @param {Number} params.user_id - 用户ID
* @param {String} params.sn_code - 设备序列号
* @param {String} params.service_type - 服务类型
* @param {String} params.model_name - 模型名称
* @param {Number} params.prompt_tokens - 输入token数
* @param {Number} params.completion_tokens - 输出token数
* @param {Number} params.total_tokens - 总token数
* @param {String} params.request_content - 请求内容
* @param {String} params.response_content - 响应内容
* @param {Number} params.cost_amount - 费用
* @param {String} params.status - 状态
* @param {String} params.error_message - 错误信息
* @param {Number} params.response_time - 响应时间(毫秒)
* @param {String} params.api_provider - API提供商
* @param {String} params.business_type - 业务类型
* @param {Number} params.reference_id - 关联业务ID
* @returns {Promise<Object>} 记录结果
*/
static async record(params) {
try {
const models = Framework.getModels();
const { ai_call_records } = models;
if (!ai_call_records) {
console.error('AI调用记录模型未找到');
return null;
}
const record = await ai_call_records.create({
user_id: params.user_id || null,
sn_code: params.sn_code || null,
service_type: params.service_type || '',
model_name: params.model_name || '',
prompt_tokens: params.prompt_tokens || 0,
completion_tokens: params.completion_tokens || 0,
total_tokens: params.total_tokens || 0,
request_content: params.request_content || null,
response_content: params.response_content || null,
cost_amount: params.cost_amount || null,
status: params.status || 'success',
error_message: params.error_message || null,
response_time: params.response_time || null,
api_provider: params.api_provider || 'openai',
business_type: params.business_type || null,
reference_id: params.reference_id || null,
is_delete: 0,
create_time: new Date()
});
console.log(`AI调用已记录 - ID: ${record.id}, Model: ${params.model_name}, Tokens: ${params.total_tokens}`);
return record;
} catch (error) {
console.error('记录AI调用失败:', error);
return null;
}
}
/**
* 统计用户Token使用量
* @param {Number} user_id - 用户ID
* @param {String} startDate - 开始日期 (可选)
* @param {String} endDate - 结束日期 (可选)
* @returns {Promise<Object>} 统计结果
*/
static async getUserTokenStats(user_id, startDate = null, endDate = null) {
try {
const models = Framework.getModels();
const { ai_call_records, op } = models;
const where = {
user_id,
is_delete: 0,
status: 'success'
};
if (startDate && endDate) {
where.create_time = {
[op.between]: [new Date(startDate), new Date(endDate)]
};
} else if (startDate) {
where.create_time = {
[op.gte]: new Date(startDate)
};
} else if (endDate) {
where.create_time = {
[op.lte]: new Date(endDate)
};
}
const records = await ai_call_records.findAll({ where });
const stats = {
total_calls: records.length,
total_prompt_tokens: 0,
total_completion_tokens: 0,
total_tokens: 0,
total_cost: 0
};
records.forEach(record => {
stats.total_prompt_tokens += record.prompt_tokens || 0;
stats.total_completion_tokens += record.completion_tokens || 0;
stats.total_tokens += record.total_tokens || 0;
stats.total_cost += parseFloat(record.cost_amount || 0);
});
return stats;
} catch (error) {
console.error('统计Token使用量失败:', error);
return null;
}
}
/**
* 统计设备Token使用量
* @param {String} sn_code - 设备序列号
* @param {String} startDate - 开始日期 (可选)
* @param {String} endDate - 结束日期 (可选)
* @returns {Promise<Object>} 统计结果
*/
static async getDeviceTokenStats(sn_code, startDate = null, endDate = null) {
try {
const models = Framework.getModels();
const { ai_call_records, op } = models;
const where = {
sn_code,
is_delete: 0,
status: 'success'
};
if (startDate && endDate) {
where.create_time = {
[op.between]: [new Date(startDate), new Date(endDate)]
};
}
const records = await ai_call_records.findAll({ where });
const stats = {
total_calls: records.length,
total_prompt_tokens: 0,
total_completion_tokens: 0,
total_tokens: 0,
total_cost: 0
};
records.forEach(record => {
stats.total_prompt_tokens += record.prompt_tokens || 0;
stats.total_completion_tokens += record.completion_tokens || 0;
stats.total_tokens += record.total_tokens || 0;
stats.total_cost += parseFloat(record.cost_amount || 0);
});
return stats;
} catch (error) {
console.error('统计设备Token使用量失败:', error);
return null;
}
}
}
module.exports = AiCallRecorder;

View File

@@ -4,6 +4,7 @@
*/
const axios = require('axios');
const AiCallRecorder = require('./ai_call_recorder.js');
class AIService {
constructor(config = {}) {
@@ -30,6 +31,9 @@ class AIService {
* @returns {Promise<String>} AI响应内容
*/
async chat(messages, options = {}) {
const startTime = Date.now();
const requestContent = JSON.stringify(messages);
try {
const response = await this.client.post('/v1/chat/completions', {
model: this.model,
@@ -39,19 +43,90 @@ class AIService {
...options
});
return response.data.choices[0].message.content;
const responseTime = Date.now() - startTime;
const responseContent = response.data.choices[0].message.content;
const usage = response.data.usage || {};
// 记录AI调用异步不阻塞主流程
this.recordAiCall({
user_id: options.user_id,
sn_code: options.sn_code,
service_type: options.service_type || 'chat',
model_name: this.model,
prompt_tokens: usage.prompt_tokens || 0,
completion_tokens: usage.completion_tokens || 0,
total_tokens: usage.total_tokens || 0,
request_content: requestContent,
response_content: responseContent,
cost_amount: this.calculateCost(usage.total_tokens || 0),
status: 'success',
response_time: responseTime,
api_provider: 'deepseek',
business_type: options.business_type,
reference_id: options.reference_id
}).catch(err => {
console.warn('记录AI调用失败不影响主流程:', err.message);
});
return responseContent;
} catch (error) {
const responseTime = Date.now() - startTime;
// 记录失败的调用
this.recordAiCall({
user_id: options.user_id,
sn_code: options.sn_code,
service_type: options.service_type || 'chat',
model_name: this.model,
request_content: requestContent,
status: 'failed',
error_message: error.message,
response_time: responseTime,
api_provider: 'deepseek',
business_type: options.business_type,
reference_id: options.reference_id
}).catch(err => {
console.warn('记录失败调用失败:', err.message);
});
console.warn('AI服务调用失败:', error.message);
throw new Error(`AI服务调用失败: ${error.message}`);
}
}
/**
* 记录AI调用
* @param {Object} params - 调用参数
* @returns {Promise}
*/
async recordAiCall(params) {
try {
await AiCallRecorder.record(params);
} catch (error) {
// 记录失败不应影响主流程
console.warn('AI调用记录失败:', error.message);
}
}
/**
* 计算调用费用
* @param {Number} totalTokens - 总Token数
* @returns {Number} 费用(元)
*/
calculateCost(totalTokens) {
// DeepSeek 价格(元/1000 tokens
// 可以根据实际API定价调整
const pricePerThousand = 0.001; // 示例价格
return (totalTokens / 1000) * pricePerThousand;
}
/**
* 分析简历竞争力
* @param {Object} resumeData - 简历数据
* @param {Object} context - 上下文信息user_id, sn_code等
* @returns {Promise<Object>} 分析结果
*/
async analyzeResume(resumeData) {
async analyzeResume(resumeData, context = {}) {
const prompt = `请分析以下简历的竞争力,并提供详细评估:
简历信息:
@@ -91,7 +166,14 @@ class AIService {
];
try {
const response = await this.chat(messages, { temperature: 0.3 });
const response = await this.chat(messages, {
temperature: 0.3,
user_id: context.user_id,
sn_code: context.sn_code,
service_type: 'completion',
business_type: 'resume_analysis',
reference_id: resumeData.id || resumeData.resumeId
});
// 提取JSON部分
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
@@ -120,9 +202,10 @@ class AIService {
* 岗位匹配度评估
* @param {Object} jobData - 岗位数据
* @param {Object} resumeData - 简历数据
* @param {Object} context - 上下文信息user_id, sn_code等
* @returns {Promise<Object>} 匹配结果
*/
async matchJobWithResume(jobData, resumeData) {
async matchJobWithResume(jobData, resumeData, context = {}) {
const prompt = `请评估以下岗位与简历的匹配度:
【岗位信息】
@@ -169,7 +252,14 @@ class AIService {
];
try {
const response = await this.chat(messages, { temperature: 0.3 });
const response = await this.chat(messages, {
temperature: 0.3,
user_id: context.user_id,
sn_code: context.sn_code,
service_type: 'completion',
business_type: 'job_matching',
reference_id: jobData.id || jobData.jobId
});
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
@@ -197,9 +287,10 @@ class AIService {
* 批量评估岗位(用于智能筛选)
* @param {Array} jobs - 岗位列表
* @param {Object} resumeData - 简历数据
* @param {Object} context - 上下文信息user_id, sn_code等
* @returns {Promise<Array>} 评估结果列表
*/
async batchMatchJobs(jobs, resumeData) {
async batchMatchJobs(jobs, resumeData, context = {}) {
const results = [];
// 限制并发数量避免API限流
@@ -207,7 +298,7 @@ class AIService {
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 => {
this.matchJobWithResume(job, resumeData, context).catch(err => {
console.warn(`岗位${job.jobId}匹配失败:`, err.message);
return {
jobId: job.jobId,
@@ -231,11 +322,11 @@ class AIService {
/**
* 生成聊天内容
* @param {Object} context - 聊天上下文
* @param {Object} context - 聊天上下文包含jobInfo, resumeInfo, chatType, user_id, sn_code等
* @returns {Promise<String>} 生成的聊天内容
*/
async generateChatContent(context) {
const { jobInfo, resumeInfo, chatType = 'greeting', previousMessages = [] } = context;
const { jobInfo, resumeInfo, chatType = 'greeting', previousMessages = [], user_id, sn_code } = context;
let prompt = '';
@@ -279,7 +370,15 @@ class AIService {
];
try {
const response = await this.chat(messages, { temperature: 0.8, max_tokens: 200 });
const response = await this.chat(messages, {
temperature: 0.8,
max_tokens: 200,
user_id,
sn_code,
service_type: 'chat',
business_type: 'chat_generation',
reference_id: jobInfo?.jobId || jobInfo?.id
});
return response.trim();
} catch (error) {
console.warn('生成聊天内容失败:', error);
@@ -300,9 +399,10 @@ class AIService {
/**
* 判断是否为面试邀约
* @param {String} message - HR消息内容
* @param {Object} context - 上下文信息user_id, sn_code等
* @returns {Promise<Object>} 判断结果
*/
async detectInterviewInvitation(message) {
async detectInterviewInvitation(message, context = {}) {
const prompt = `判断以下HR消息是否为面试邀约并提取关键信息
消息内容: "${message}"
@@ -323,7 +423,14 @@ class AIService {
];
try {
const response = await this.chat(messages, { temperature: 0.1 });
const response = await this.chat(messages, {
temperature: 0.1,
user_id: context.user_id,
sn_code: context.sn_code,
service_type: 'completion',
business_type: 'interview_detection',
reference_id: context.conversation_id || context.job_id
});
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
@@ -349,9 +456,10 @@ class AIService {
/**
* 分析HR反馈情感
* @param {String} message - HR消息内容
* @param {Object} context - 上下文信息user_id, sn_code等
* @returns {Promise<Object>} 情感分析结果
*/
async analyzeSentiment(message) {
async analyzeSentiment(message, context = {}) {
const prompt = `分析以下HR消息的情感倾向
消息: "${message}"
@@ -370,7 +478,14 @@ class AIService {
];
try {
const response = await this.chat(messages, { temperature: 0.1 });
const response = await this.chat(messages, {
temperature: 0.1,
user_id: context.user_id,
sn_code: context.sn_code,
service_type: 'completion',
business_type: 'sentiment_analysis',
reference_id: context.conversation_id || context.job_id
});
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);