1
This commit is contained in:
317
api/controller_admin/ai_call_records.js
Normal file
317
api/controller_admin/ai_call_records.js
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* AI调用记录管理API - 后台管理
|
||||
* 提供AI调用记录的查询和统计功能
|
||||
*/
|
||||
|
||||
const Framework = require("../../framework/node-core-framework.js");
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/ai_call_records/list:
|
||||
* post:
|
||||
* summary: 获取AI调用记录列表
|
||||
* description: 分页获取所有AI调用记录
|
||||
* tags: [后台-AI调用记录管理]
|
||||
*/
|
||||
'POST /ai_call_records/list': async (ctx) => {
|
||||
try {
|
||||
const models = Framework.getModels();
|
||||
const { ai_call_records, op } = models;
|
||||
const body = ctx.getBody();
|
||||
|
||||
// 获取分页参数
|
||||
const { limit, offset } = ctx.getPageSize();
|
||||
|
||||
// 构建查询条件
|
||||
const where = { is_delete: 0 };
|
||||
|
||||
// 搜索条件
|
||||
if (body.seachOption) {
|
||||
const { key, value, status, service_type, model_name, api_provider, business_type, user_id, sn_code } = body.seachOption;
|
||||
|
||||
// 关键字搜索
|
||||
if (value && key) {
|
||||
if (key === 'user_id' || key === 'reference_id') {
|
||||
where[key] = value;
|
||||
} else if (key === 'sn_code') {
|
||||
where.sn_code = { [op.like]: `%${value}%` };
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
// 服务类型筛选
|
||||
if (service_type) {
|
||||
where.service_type = service_type;
|
||||
}
|
||||
|
||||
// 模型名称筛选
|
||||
if (model_name) {
|
||||
where.model_name = model_name;
|
||||
}
|
||||
|
||||
// API提供商筛选
|
||||
if (api_provider) {
|
||||
where.api_provider = api_provider;
|
||||
}
|
||||
|
||||
// 业务类型筛选
|
||||
if (business_type) {
|
||||
where.business_type = business_type;
|
||||
}
|
||||
|
||||
// 用户ID筛选
|
||||
if (user_id) {
|
||||
where.user_id = user_id;
|
||||
}
|
||||
|
||||
// 设备序列号筛选
|
||||
if (sn_code) {
|
||||
where.sn_code = sn_code;
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
if (body.seachOption.start_date && body.seachOption.end_date) {
|
||||
where.create_time = {
|
||||
[op.between]: [new Date(body.seachOption.start_date), new Date(body.seachOption.end_date)]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const result = await ai_call_records.findAndCountAll({
|
||||
where,
|
||||
limit,
|
||||
offset,
|
||||
order: [['create_time', 'DESC'], ['id', 'DESC']],
|
||||
attributes: [
|
||||
'id', 'user_id', 'sn_code', 'service_type', 'model_name',
|
||||
'prompt_tokens', 'completion_tokens', 'total_tokens',
|
||||
'cost_amount', 'status', 'response_time', 'api_provider',
|
||||
'business_type', 'reference_id', 'create_time'
|
||||
]
|
||||
});
|
||||
|
||||
return ctx.success(result);
|
||||
} catch (error) {
|
||||
console.error('获取AI调用记录列表失败:', error);
|
||||
return ctx.fail('获取AI调用记录列表失败: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/ai_call_records/detail:
|
||||
* get:
|
||||
* summary: 获取AI调用记录详情
|
||||
* description: 根据ID获取AI调用记录详细信息(包含请求和响应内容)
|
||||
* tags: [后台-AI调用记录管理]
|
||||
*/
|
||||
'GET /ai_call_records/detail': async (ctx) => {
|
||||
try {
|
||||
const models = Framework.getModels();
|
||||
const { ai_call_records } = models;
|
||||
const { id } = ctx.getQuery();
|
||||
|
||||
if (!id) {
|
||||
return ctx.fail('记录ID不能为空');
|
||||
}
|
||||
|
||||
const record = await ai_call_records.findOne({
|
||||
where: { id, is_delete: 0 }
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
return ctx.fail('记录不存在');
|
||||
}
|
||||
|
||||
return ctx.success(record);
|
||||
} catch (error) {
|
||||
console.error('获取AI调用记录详情失败:', error);
|
||||
return ctx.fail('获取AI调用记录详情失败: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/ai_call_records/stats:
|
||||
* post:
|
||||
* summary: 获取AI调用统计数据
|
||||
* description: 统计Token使用量、调用次数、费用等
|
||||
* tags: [后台-AI调用记录管理]
|
||||
*/
|
||||
'POST /ai_call_records/stats': async (ctx) => {
|
||||
try {
|
||||
const models = Framework.getModels();
|
||||
const { ai_call_records, op } = models;
|
||||
const body = ctx.getBody();
|
||||
|
||||
const where = { is_delete: 0, status: 'success' };
|
||||
|
||||
// 构建查询条件
|
||||
if (body.user_id) {
|
||||
where.user_id = body.user_id;
|
||||
}
|
||||
if (body.sn_code) {
|
||||
where.sn_code = body.sn_code;
|
||||
}
|
||||
if (body.business_type) {
|
||||
where.business_type = body.business_type;
|
||||
}
|
||||
if (body.start_date && body.end_date) {
|
||||
where.create_time = {
|
||||
[op.between]: [new Date(body.start_date), new Date(body.end_date)]
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
avg_response_time: 0,
|
||||
by_model: {},
|
||||
by_service_type: {},
|
||||
by_status: { success: 0, failed: 0, timeout: 0 }
|
||||
};
|
||||
|
||||
let totalResponseTime = 0;
|
||||
let responseTimeCount = 0;
|
||||
|
||||
records.forEach(record => {
|
||||
// Token统计
|
||||
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);
|
||||
|
||||
// 响应时间统计
|
||||
if (record.response_time) {
|
||||
totalResponseTime += record.response_time;
|
||||
responseTimeCount++;
|
||||
}
|
||||
|
||||
// 按模型统计
|
||||
if (!stats.by_model[record.model_name]) {
|
||||
stats.by_model[record.model_name] = {
|
||||
count: 0,
|
||||
total_tokens: 0,
|
||||
total_cost: 0
|
||||
};
|
||||
}
|
||||
stats.by_model[record.model_name].count++;
|
||||
stats.by_model[record.model_name].total_tokens += record.total_tokens || 0;
|
||||
stats.by_model[record.model_name].total_cost += parseFloat(record.cost_amount || 0);
|
||||
|
||||
// 按服务类型统计
|
||||
if (!stats.by_service_type[record.service_type]) {
|
||||
stats.by_service_type[record.service_type] = {
|
||||
count: 0,
|
||||
total_tokens: 0
|
||||
};
|
||||
}
|
||||
stats.by_service_type[record.service_type].count++;
|
||||
stats.by_service_type[record.service_type].total_tokens += record.total_tokens || 0;
|
||||
});
|
||||
|
||||
// 计算平均响应时间
|
||||
if (responseTimeCount > 0) {
|
||||
stats.avg_response_time = Math.round(totalResponseTime / responseTimeCount);
|
||||
}
|
||||
|
||||
// 查询失败和超时的记录
|
||||
const failedCount = await ai_call_records.count({
|
||||
where: { ...where, status: 'failed', is_delete: 0 }
|
||||
});
|
||||
const timeoutCount = await ai_call_records.count({
|
||||
where: { ...where, status: 'timeout', is_delete: 0 }
|
||||
});
|
||||
|
||||
stats.by_status.success = records.length;
|
||||
stats.by_status.failed = failedCount;
|
||||
stats.by_status.timeout = timeoutCount;
|
||||
|
||||
return ctx.success(stats);
|
||||
} catch (error) {
|
||||
console.error('获取AI调用统计失败:', error);
|
||||
return ctx.fail('获取AI调用统计失败: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/ai_call_records/delete:
|
||||
* post:
|
||||
* summary: 删除AI调用记录
|
||||
* description: 软删除指定的AI调用记录
|
||||
* tags: [后台-AI调用记录管理]
|
||||
*/
|
||||
'POST /ai_call_records/delete': async (ctx) => {
|
||||
try {
|
||||
const models = Framework.getModels();
|
||||
const { ai_call_records } = models;
|
||||
const { id } = ctx.getBody();
|
||||
|
||||
if (!id) {
|
||||
return ctx.fail('记录ID不能为空');
|
||||
}
|
||||
|
||||
const record = await ai_call_records.findOne({
|
||||
where: { id, is_delete: 0 }
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
return ctx.fail('记录不存在');
|
||||
}
|
||||
|
||||
// 软删除
|
||||
await ai_call_records.update(
|
||||
{ is_delete: 1 },
|
||||
{ where: { id } }
|
||||
);
|
||||
|
||||
return ctx.success({ message: 'AI调用记录删除成功' });
|
||||
} catch (error) {
|
||||
console.error('删除AI调用记录失败:', error);
|
||||
return ctx.fail('删除AI调用记录失败: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /admin_api/ai_call_records/batch_delete:
|
||||
* post:
|
||||
* summary: 批量删除AI调用记录
|
||||
* description: 批量软删除AI调用记录
|
||||
* tags: [后台-AI调用记录管理]
|
||||
*/
|
||||
'POST /ai_call_records/batch_delete': async (ctx) => {
|
||||
try {
|
||||
const models = Framework.getModels();
|
||||
const { ai_call_records, op } = models;
|
||||
const { ids } = ctx.getBody();
|
||||
|
||||
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
||||
return ctx.fail('记录ID列表不能为空');
|
||||
}
|
||||
|
||||
// 批量软删除
|
||||
await ai_call_records.update(
|
||||
{ is_delete: 1 },
|
||||
{ where: { id: { [op.in]: ids } } }
|
||||
);
|
||||
|
||||
return ctx.success({ message: `成功删除 ${ids.length} 条记录` });
|
||||
} catch (error) {
|
||||
console.error('批量删除AI调用记录失败:', error);
|
||||
return ctx.fail('批量删除AI调用记录失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
142
api/model/ai_call_records.js
Normal file
142
api/model/ai_call_records.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const Sequelize = require('sequelize');
|
||||
|
||||
/**
|
||||
* AI调用记录表模型
|
||||
* 记录所有AI API调用的详细信息和Token使用情况
|
||||
*/
|
||||
module.exports = (db) => {
|
||||
const ai_call_records = db.define("ai_call_records", {
|
||||
user_id: {
|
||||
comment: '用户ID(如果是用户触发的调用)',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
sn_code: {
|
||||
comment: '设备序列号',
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
service_type: {
|
||||
comment: '服务类型(如:chat, completion, embedding等)',
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: ''
|
||||
},
|
||||
model_name: {
|
||||
comment: 'AI模型名称(如:gpt-4, gpt-3.5-turbo等)',
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
defaultValue: ''
|
||||
},
|
||||
prompt_tokens: {
|
||||
comment: '输入Token数量',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
completion_tokens: {
|
||||
comment: '输出Token数量',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
total_tokens: {
|
||||
comment: '总Token数量',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
request_content: {
|
||||
comment: '请求内容(用户输入的prompt)',
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
response_content: {
|
||||
comment: '响应内容(AI返回的结果)',
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
cost_amount: {
|
||||
comment: '本次调用费用(元)',
|
||||
type: Sequelize.DECIMAL(10, 4),
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
status: {
|
||||
comment: '调用状态(success=成功, failed=失败, timeout=超时)',
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
defaultValue: 'success'
|
||||
},
|
||||
error_message: {
|
||||
comment: '错误信息(如果调用失败)',
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
response_time: {
|
||||
comment: '响应时间(毫秒)',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
api_provider: {
|
||||
comment: 'API提供商(openai, azure, anthropic等)',
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
defaultValue: 'openai'
|
||||
},
|
||||
business_type: {
|
||||
comment: '业务类型(job_filter, chat, resume_optimization等)',
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
reference_id: {
|
||||
comment: '关联业务ID(如job_posting_id, chat_record_id等)',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
}, {
|
||||
timestamps: false,
|
||||
indexes: [
|
||||
{
|
||||
unique: false,
|
||||
fields: ['user_id']
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['sn_code']
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['service_type']
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['status']
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['create_time']
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['business_type']
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
fields: ['reference_id']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// ai_call_records.sync({ force: true });
|
||||
|
||||
return ai_call_records;
|
||||
}
|
||||
174
api/services/ai_call_recorder.js
Normal file
174
api/services/ai_call_recorder.js
Normal 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;
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user