xiaoai/php_server/app/api/logic/InterviewLogic.php

1083 lines
40 KiB
PHP
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.

<?php
namespace app\api\logic;
use app\api\logic\service\TokenLogService;
use app\common\enum\user\AccountLogEnum;
use app\common\logic\AccountLogLogic;
use app\common\logic\BaseLogic;
use app\common\model\interview\Interview;
use app\common\model\interview\InterviewCv;
use app\common\model\interview\InterviewDialog;
use app\common\model\interview\InterviewFeedback;
use app\common\model\interview\InterviewJob;
use app\common\model\interview\InterviewRecord;
use app\common\model\ModelConfig;
use app\common\model\user\User;
use app\common\service\ConfigService;
use PhpOffice\PhpWord\IOFactory;
use think\Exception;
use think\facade\Db;
use think\facade\Log;
use think\facade\Queue;
use think\Log as ThinkLog;
class InterviewLogic extends BaseLogic
{
/**
* @desc 我的岗位
* @param array $params
* @return array
* @date 2025/2/13 17:18
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author dagouzi
*/
public static function jobs(array $params)
{
if (!empty($params['job_id'])){
$result = InterviewJob::where(['user_id' => $params['user_id'], 'status' => 1])->where('id', $params['job_id'])->select()->toArray();
} else {
$result = InterviewJob::where(['user_id' => $params['user_id'], 'status' => 1])->select()->toArray();
}
if(!$result){
throw new Exception('岗位不存在!');
}
return $result;
}
/**
* @desc 岗位详情
* @param array $params
* @return array
* @date 2025/2/13 17:29
* @throws Exception
* @author dagouzi
*/
public static function jobDetail(array $params)
{
$result = InterviewJob::where(['id' => $params['id'], 'status' => 1])->findOrEmpty()->toArray();
if (empty($result)){
throw new Exception('面试不存在或已取消!');
}
return $result;
}
/**
* @desc 简历识别
* @return true
* @date 2025/2/13 17:29
* @author dagouzi
*/
public static function extractCv($params)
{
if(!isset($params['interview_job_id'])){
throw new Exception('参数缺少!');
}
$user_id = InterviewJob::where(['id' => $params['interview_job_id']])->value('user_id');
if(!$user_id){
throw new Exception('岗位不存在!');
}
// Db::startTrans();
try {
//计费
//$unit = TokenLogService::checkToken($user_id, 'interview_cv');
$unit = 0;
$url = $params['word'];
$urlData = parse_url($url);
$path = public_path() . $urlData['path'];
$file = new \CURLFile($path);
$response = \app\common\service\ToolsService::Interview()->cv([
'file' => $file,
'action' => 'upload'
]);
$file_id = $response['data']['file_id'] ?? '';
if(empty($file_id)){
throw new Exception('简历上传失败!');
}
$file_id = 'fileid://' . $file_id;
$cv_content = [
"role"=>"简历信息解析助手",
"description"=>"从用户上传的简历文本中精准提取结构化信息并以标准化JSON格式返回。",
"instruction"=>
'请严格按照以下规则解析简历内容:
1. **提取字段**
- 姓名(需全称,忽略昵称/英文名)
- 性别(若未明确标注则留空)
- 手机号(若未明确标注则留空)
- 年龄(优先取数字格式,若为出生日期则自动计算)
- 工作年限(优先取数字格式,若为工作年限则自动计算)
- 学历(最高学历,如:博士/硕士/本科)
- 毕业院校(最高学历对应院校,合并分校信息)
- 工作经历(按倒序排列,格式:["公司名 | 职位 | 时间段(起止年月) | 核心职责摘要"]
- 项目经历(按倒序排列,格式:["项目名称 | 角色 | 技术栈/工具 | 成果量化描述"]
2. **处理规则**
- 合并分散段落:若同一经历分多段描述,需合并为单一条目
- 清洗冗余词:去除「负责」、「参与」等非必要前缀
- 时间标准化时间段统一为「YYYY.MM-YYYY.MM」格式
- 量化成果项目成果需包含可量化的指标如提升30%/节省100小时
3. **输出要求**
- 严格使用JSON格式
- 空值字段保留为null
- 特殊符号转义处理
,
"response_example": {
"name": "王小明",
"sex": "男",
"age": 28,
"work_years": 3,
"mobile": "13800138000",
"degree": "硕士",
"school": "清华大学计算机科学与技术系",
"work_ex": [
"阿里巴巴集团 | 高级后端开发工程师 | 2020.07-2023.05 | 主导支付系统重构QPS从5k提升至12k",
"字节跳动 | Java开发工程师 | 2018.03-2020.06 | 搭建实时推荐系统DAU提升15%"
],
"project_ex": [
"分布式消息队列优化 | 技术负责人 | Kafka/Go/Prometheus | 降低端到端延迟从200ms至80ms",
"智能风控系统 | 核心开发者 | Spring Cloud/Redis/Elasticsearch | 拦截欺诈交易准确率达99.2%"
]'
];
$messages = [
[
"role"=>"system",
"content"=> json_encode($cv_content, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
],
[
"role"=>"system",
"content"=> $file_id
],
[
"role"=>"user",
"content"=>"请把这份简历文件给我JSON格式数据"
]
];
$response = \app\common\service\ToolsService::Interview()->jx([
'messages' => $messages,
'action' => 'qwen'
]);
if (empty($response['data']['message']))
{
throw new Exception('简历分析失败!');
}
$message = $response['data']['message'];
$json = format_json($message);
$sex = [
'男' => 1,
'女' => 2
];
foreach($json as $key => &$value){
if(is_array($value)){
$value = json_encode($value,JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
//性别替换
if($key == 'sex'){
$value= $sex[$value] ?? 0;
}
if(empty($value)){
$value = '';
}
}
$json['word_url'] = $url ;
$json['company_id'] = $user_id;
$json['type'] = 2;
$json['interview_job_id'] = $params['interview_job_id'];
$json['user_id'] = $params['user_id'];
// $cvres = InterviewCv::create($json);
// if($unit > 0){
// //扣除算力
// User::userTokensChange($user_id, $unit);
// $extra = [
// '解析简历数' => 1,
// "算力单价" => $unit,
// "实际消耗算力" => $unit
// ];
// //记录日志
// AccountLogLogic::recordUserTokensLog(true, $user_id, AccountLogEnum::TOKENS_DEC_AI_RESUME, $unit, $cvres->id, $extra);
// }
// Db::commit();
self::$returnData = $json;
return true;
} catch (\Exception $e) {
Log::error('简历上传失败'.$e->getMessage().'参数'.json_encode($json));
// Db::rollback();
throw new Exception($e->getMessage());
}
}
public static function formatJson($text)
{
$text = <<<EOT
{$text}
EOT;
// 使用正则表达式匹配JSON部分
$jsonPattern = '/\{(?:[^{}]|(?R))*\}/';
preg_match($jsonPattern,$text, $matches);
// 输出匹配到的JSON字符串
$jsonString = $matches[0] ?? 'No JSON found';
$result = json_decode($jsonString, true);
return $result;
}
public static function readWord($url = '')
{
// $url = 'http://ms.cc/1.docx';
$array = parse_url($url);
$path = public_path() . $array['path'];
$res = self::getWord($path);
return $res;
}
public static function getWord($filePath)
{
try {
// 获取所有段落
$phpWord = IOFactory::load($filePath);
$sections =$phpWord->getSections();
$textContent = '';
foreach ($sections as$section) {
$elements =$section->getElements();
foreach ($elements as$element) {
if ($element instanceof \PhpOffice\PhpWord\Element\TextRun) {
$textRunElements =$element->getElements();
foreach ($textRunElements as$textElement) {
if ($textElement instanceof \PhpOffice\PhpWord\Element\Text) {
$textContent .=$textElement->getText();
}
}
}
}
}
return $textContent;
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
// 捕获异常并输出错误信息
die('Error loading DOCX file: ' . $e->getMessage());
}
}
/**
* @desc 保存简历
* @return true
* @date 2025/2/14 11:40
* @author dagouzi
*/
public static function saveCv(array $params)
{
if(!isset($params['interview_job_id'])){
throw new Exception('参数缺少!');
}
$user_id = InterviewJob::where(['id' => $params['interview_job_id']])->value('user_id');
if(!$user_id){
throw new Exception('岗位不存在!');
}
$params['company_id'] = $user_id;
$params['interview_job_id'] = $params['interview_job_id'];
$cv = InterviewCv::where(['user_id' => $params['user_id'],'interview_job_id'=>$params['interview_job_id']])->findOrEmpty();
foreach($params as $key => &$value){
$value = trim($value);
if($key == 'word_url'){
continue;
}
if($key == 'work_url'){
continue;
}
if(empty($value)){
throw new Exception('参数不能为空');
}
}
if(isset($params['work_url'])){
unset($params['work_url']);
}
if ($cv->isEmpty())
{
InterviewCv::create($params);
} else {
InterviewCv::where(['id' => $cv->id])->save($params);
}
return true;
}
/**
* @desc 开始面试
* @param array $params
* @return true
* @date 2025/2/17 10:03
* @author dagouzi
*/
public static function start(array $params)
{
Db::startTrans(); // 开始事务
try {
$job = InterviewJob::where('id', $params['job_id'])->findOrEmpty()->toArray();
if (empty($job)) {
throw new Exception('岗位不存在!');
}
if ($job['status'] == 0) {
throw new Exception('岗位已关闭!');
}
$interviewRecord = InterviewRecord::where(['user_id' => $params['user_id'], 'job_id' => $params['job_id']])->findOrEmpty()->toArray();
if (empty($interviewRecord)) {
$interviewCv = InterviewCv::where(['user_id' => $params['user_id'], 'interview_job_id' => $params['job_id']])->findOrEmpty()->toArray();
if (empty($interviewCv)) {
throw new Exception('没有简历信息!');
}
$params['job_name'] = $job['name'];
$params['interview_name'] = $interviewCv['name'];
$params['degree'] = $interviewCv['degree'];
$params['work_years'] = $interviewCv['work_years'];
$params['start_time'] = time();
$params['first_start_time'] = time();
$params['last_end_time'] = $params['first_start_time'];
$params['duration'] = 0;
$interviewRecord = InterviewRecord::create($params)->toArray();
$unit = TokenLogService::checkToken($job['user_id'], 'interview_chat');
if ($unit > 0) {
User::userTokensChange($job['user_id'], $unit);
$extra = [
"AI面试次数" => 1,
"算力单价" => $unit,
"实际消耗算力" => $unit
];
AccountLogLogic::recordUserTokensLog(true, $job['user_id'], AccountLogEnum::TOKENS_DEC_AI_INTERVIEW_CHAT, $unit, $interviewRecord['id'], $extra);
}
}
$interview = Interview::where(['user_id' => $params['user_id'], 'job_id' => $params['job_id']])->order('id', 'desc')->findOrEmpty()->toArray();
//没有面试邀约,生成一个新的
if (empty($interview)) {
self::$returnData = self::createInterviewAndDialog($params, $interviewRecord);
Db::commit(); // 提交事务
return true;
}
if ($interview['status'] == Interview::STATUS_RESTART) {
self::$returnData = self::createInterviewAndDialog($params, $interviewRecord);
InterviewRecord::update(['status' => Interview::STATUS_ONGOING], ['id' => $interviewRecord['id']]);
Db::commit(); // 提交事务
return true;
}
if (empty($interviewRecord)) {
throw new \Exception('没有面试记录');
}
if ($interview['status'] == Interview::STATUS_COMPLETED) {
$interview['msg'] = '面试已结束';
self::$returnData = $interview;
return true;
}
if ($interview['status'] == Interview::STATUS_ONGOING) {
$interview['msg'] = '有个面试正在进行中';
self::$returnData = $interview;
return true;
}
//面试退出或者中断
if (in_array($interview['status'], [Interview::STATUS_EXITED, Interview::STATUS_INTERRUPTED])) {
$params['start_time'] = time();
$params['interview_record_id'] = $interviewRecord['id'];
$interviewnew = Interview::create($params)->toArray();
$interviewnew['last_interview_id'] = $interview['id'];
$interviewnew['prologue'] = '好很高兴您能来参加本轮面试我是你的AI面试官先请您先做一个简单的自我介绍吧。!!';
self::$returnData = $interviewnew;
InterviewRecord::update(['status' => Interview::STATUS_ONGOING], ['id' => $interviewRecord['id']]);
Db::commit(); // 提交事务
return true;
}
if ($interview['status'] == Interview::STATUS_RESTART) {
$params['start_time'] = time();
$params['interview_record_id'] = $interviewRecord['id'];
$interviewnew = Interview::create($params)->toArray();
$interviewnew['prologue'] = '您好很高兴您能来参加本轮面试我是你的AI面试官先请您先做一个简单的自我介绍吧。!';
self::$returnData = $interviewnew;
Db::commit(); // 提交事务
return true;
}
throw new \Exception('数据错误');
} catch (\Exception $e) {
Db::rollback(); // 回滚事务
throw new Exception($e->getMessage());
}
}
public static function chat($params)
{
$user_id = $params['user_id'];
$interview_id = $params['id'];
$isEnd = 0;
$endMessgae = '好的,大致情况我已经了解,本轮面试已结束,感谢您的配合,请提交面试过程并耐心等待通知。';
$interview = Interview::where(['id' => $interview_id, 'user_id' => $user_id, 'status' => 0])->findOrEmpty();
if ($interview->isEmpty())
{
throw new Exception('没有面试信息或已结束!');
}
$interviewRecord = InterviewRecord::where(['id' => $interview->interview_record_id, 'user_id' => $user_id])
->whereIn('status', [0, 1])->findOrEmpty();
if ($interviewRecord->isEmpty())
{
throw new Exception('没有面试信息或已结束!');
}
$job = InterviewJob::findOrEmpty($interview->job_id);
if ($job->isEmpty())
{
throw new Exception('没有岗位信息!');
}
// 1:文字 2:语音
$dialogType = $job->type;
$cv = InterviewCv::where(['user_id' => $user_id ,'interview_job_id' => $interview->job_id])->findOrEmpty();
if ($cv->isEmpty())
{
throw new Exception('没有简历信息!');
}
// 修改对话的回复内容
$curDialog = InterviewDialog::where(['interview_id' => $interview_id])->order('id DESC')->findOrEmpty();
// 对话记录条数
$dialogCount = InterviewDialog::where(['interview_id' => $interview_id])->count();
if ($dialogType == 2)
{
$answer = self::stt($params['answer_url']);
$curDialog->answer = $answer['message'];
$curDialog->answer_url = $params['answer_url'];
$curDialog->answer_duration = $answer['audio_duration'];
} else {
$curDialog->answer = $params['answer'];
}
$curDialog->save();
// 判断当前对话类型
$attentionArray = json_decode($job->attention, true);
$attentionCount = count($attentionArray); // 关注点个数
// 检测是否已结束
$chatTypeEnd = self::chatType($dialogCount + 1, $attentionCount);
if ($chatTypeEnd == 0)
{
// 访问通义获取评分和面试评价
Queue::push('app\common\Jobs\EndInterviewJob@handle', $interview->id);
$interview->end_time = time();
$interview->status = Interview::STATUS_ANALYZE;
$interview->save();
$duration = $interview->end_time - $interviewRecord->first_start_time;
$interviewRecord->duration = $duration;
$interviewRecord->end_time = $interview->end_time;
$interviewRecord->status = Interview::STATUS_ANALYZE;
$interviewRecord->last_interview_id = $interview->id;
$interviewRecord->save();
$message = $endMessgae = '好的,大致情况我已经了解,本轮面试已结束,感谢您的配合,请提交面试过程并耐心等待通知。';
self::$returnData = [
'id' => $interview_id,
'status' => 1,
'end_message' => $endMessgae,
'message' => $message
];
return true;
}
$chatType = self::chatType($dialogCount, $attentionCount);
// 1:大问题机器人 2:深入问题机器人 3:不带关注点的大问题机器人
$message = $audio_url = '';
$audio_duration = 0;
if ($chatType == 1)
{
$dialogs = InterviewDialog::where('type', 'in', [1,4])->where('interview_id', $interview_id)->select()->toArray();
$message = self::chat1($job, $cv, $dialogs);
if (!empty($message))
{
$audioData = self::tts($message);
$audio_url = $dialogType == 2 ? $audioData['audio_url'] : '';
$audio_duration = $audioData['audio_duration'];
// 新增对话记录
InterviewDialog::create([
'interview_id' => $interview_id,
'type' => 1,
'question' => $message,
'question_url' => $audio_url,
'question_duration' => $audio_duration
]);
}
} elseif($chatType == 2) {
$mainDialog = InterviewDialog
::where('type', 'in', [1,3])
->where('interview_id', $interview_id)
->order('id DESC')
->findOrEmpty();
$dialogs = InterviewDialog::where('id', '>=', $mainDialog->id)->where('interview_id', $interview_id)->select()->toArray();
$message = self::chat2($job, $cv, $dialogs);
if (!empty($message))
{
$audioData = self::tts($message);
$audio_url = $dialogType == 2 ? $audioData['audio_url'] : '';
$audio_duration = $audioData['audio_duration'];
// 新增对话记录
InterviewDialog::create([
'interview_id' => $interview_id,
'type' => 2,
'question' => $message,
'question_url' => $audio_url,
'question_duration' => $audio_duration
]);
}
} elseif($chatType == 3) {
$dialogs = InterviewDialog::where('type', 'in', [1,3])->where('interview_id', $interview_id)->select()->toArray();
$message = self::chat3($job, $cv, $dialogs);
if (!empty($message))
{
$audioData = self::tts($message);
$audio_url = $dialogType == 2 ? $audioData['audio_url'] : '';
$audio_duration = $audioData['audio_duration'];
// 新增对话记录
InterviewDialog::create([
'interview_id' => $interview_id,
'type' => 3,
'question' => $message,
'question_url' => $audio_url,
'question_duration' => $audio_duration
]);
}
} else {
// 结束了
throw new Exception('面试已结束!');
}
self::$returnData = [
'id' => $interview_id,
'status' => $interview->status,
'end_message' => $endMessgae,
'message' => $message,
'audio_url' => $audio_url,
'audio_duration' => $audio_duration
];
return true;
}
/**
* @desc 判断当前对话类型
* @param $dialogs
* @return int
* @date 2025/3/4 18:15
* @author dagouzi
*/
public static function chatType($dialogCount, $attentionCount, $deepTimes = 3)
{
$dialogCount = $dialogCount - 1;
$deepStep = $deepTimes + 1;
$total = $deepStep * $attentionCount + 4 * 5;
if ($dialogCount - $total > -1)
{
return 0;
}
if ($dialogCount % $deepStep == 0)
{
return $dialogCount / $deepStep < $attentionCount ? 1 : 3;
} else {
return 2;
}
}
/**
* @desc 文字转语音
* @return array
* @date 2025/2/19 11:26
* @author dagouzi
*/
public static function tts($text)
{
$response = \app\common\service\ToolsService::Interview()->chat([
'message' => $text,
'action' => 'tts'
]);
if (empty($response) || empty($response['code']))
{
throw new Exception('语音转文字失败~');
}
if ($response['code'] == 10000)
{
return $response['data'];
}
throw new Exception('语音转文字失败~');
}
/**
* @desc 语音转文字
* @return array
* @date 2025/2/19 11:27
* @author dagouzi
*/
public static function stt($voice)
{
$response = \app\common\service\ToolsService::Interview()->chat([
'action' => 'stt',
'audio_url' => $voice
]);
if (empty($response) || empty($response['code']))
{
throw new Exception('语音转文字失败~');
}
if ($response['code'] == 10000)
{
return $response['data'];
}
throw new Exception('语音转文字失败~');
}
/**
* @desc 大问题(带关注点)
* @param $job
* @param $cv
* @param $sex
* @param $attention
* @return mixed
* @date 2025/2/18 10:09
* @author dagouzi
*/
public static function chat1($job, $cv, $dialogs)
{
$sex = $cv->sex == 2 ? '女' : '男';
$attention = implode(";", json_decode($job->attention, true));
$system = "
# 角色定位
专业HR面试官负责根据候选者的简历信息和HR关注点生成结构化面试问题。
# 核心任务
基于以下规则生成1个面试问题
1. 输入要素:
岗位:{$job->name}
主要职责:{$job->desc}
任职要求:{$job->jd}
附加考察要求: {$job->extra}
候选人简历信息:
姓名:{$cv->name}
性别:{$sex}
年龄:{$cv->age}
学历:{$cv->degree}
毕业院校:{$cv->school}
工作经历:{$cv->work_ex}
项目经历:{$cv->project_ex}
HR关注点 {$attention}
2. 输出要求:
• 问题需覆盖技能、经验、行为等不同维度
• 避免与已生成问题重复
• 仅输出问题,不加编号或解释
• 要把自己当成面试官要把自己当成HR
• 不要把自己当成AI面试者不要把自己当成AI候选人
# 示例模板
输入:
HR关注点团队协作与抗压能力
输出:
请描述一次你因资源不足而调整原计划的经历。
";
$message[] = [
'role' => 'system',
'content' => $system
];
foreach ($dialogs as $dialog)
{
$message[] = [
'role' => 'assistant',
'content' => $dialog['question']
];
$message[] = [
'role' => 'user',
'content' => $dialog['answer']
];
}
$response = \app\common\service\ToolsService::Interview()->chat([
'action' => 'chat',
'messages' => $message
]);
if ($response['code'] == 10000 && !empty($response['data']['message']))
{
return $response['data']['message'];
} else {
throw new Exception('对话错误~');
}
}
public static function chat2($job, $cv, $dialogs)
{
$sex = $cv->sex == 2 ? '女' : '男';
$curDialog = reset($dialogs);
$system = "
# 角色定位
深度追问面试官,负责根据候选人回答递进挖掘细节。
# 核心任务
基于以下规则生成1个面试问题
1. 输入要素:
岗位:{$job->name}
主要职责:{$job->desc}
任职要求:{$job->jd}
附加考察要求: {$job->extra}
候选人简历信息:
姓名:{$cv->name}
性别:{$sex}
年龄:{$cv->age}
学历:{$cv->degree}
毕业院校:{$cv->school}
工作经历:{$cv->work_ex}
项目经历:{$cv->project_ex}
当前问题: {$curDialog['question']}
2. 输出要求:
• 问题需覆盖技能、经验、行为等不同维度
• 避免与已生成问题重复
• 仅输出问题,不加编号或解释
# 示例模板
输入:
HR关注点团队协作与抗压能力
输出:
请描述一次你因资源不足而调整原计划的经历。
";
$message[] = [
'role' => 'system',
'content' => $system
];
foreach ($dialogs as $dialog)
{
$message[] = [
'role' => 'assistant',
'content' => $dialog['question']
];
$message[] = [
'role' => 'user',
'content' => $dialog['answer']
];
}
;
$response = \app\common\service\ToolsService::Interview()->chat([
'action' => 'chat',
'messages' => $message
]);
if ($response['code'] == 10000 && !empty($response['data']['message']))
{
return $response['data']['message'];
} else {
throw new Exception('对话错误~');
}
}
public static function chat3($job, $cv, $dialogs)
{
$sex = $cv->sex == 2 ? '女' : '男';
$system = "
# 角色定位
AI深度面试官负责基于整体表现提出针对性问题。
# 核心任务
基于以下规则生成1个面试问题
1. 输入要素:
岗位:{$job->name}
主要职责:{$job->desc}
任职要求:{$job->jd}
附加考察要求: {$job->extra}
候选人简历信息:
姓名:{$cv->name}
性别:{$sex}
年龄:{$cv->age}
学历:{$cv->degree}
毕业院校:{$cv->school}
工作经历:{$cv->work_ex}
项目经历:{$cv->project_ex}
2. 输出要求:
• 问题需覆盖技能、经验、行为等不同维度
• 避免与已生成问题重复
• 仅输出问题,不加编号或解释
# 示例模板
输入:
HR关注点团队协作与抗压能力
输出:
请描述一次你因资源不足而调整原计划的经历。
";
$message[] = [
'role' => 'system',
'content' => $system
];
foreach ($dialogs as $dialog)
{
$message[] = [
'role' => 'assistant',
'content' => $dialog['question']
];
$message[] = [
'role' => 'user',
'content' => $dialog['answer']
];
}
$response = \app\common\service\ToolsService::Interview()->chat([
'action' => 'chat',
'messages' => $message
]);
if ($response['code'] == 10000 && !empty($response['data']['message']))
{
return $response['data']['message'];
} else {
throw new Exception('对话错误~');
}
}
/**
* @desc 深入问题机器人
* @param $dialogData
* @return mixed
* @date 2025/2/18 10:57
* @author dagouzi
*/
public static function qwen($dialogs)
{
$qwenData = [];
foreach ($dialogs as $item)
{
$qwenData[] = [
'question' => $item['question'],
'answer' => $item['answer'],
];
}
$messages = [
[
'role' => 'system',
'content' => '{
"role": "面试分析助手",
"description": "你是一位专业的面试分析助手专注于分析完整面试对话历史并为本次面试提供总分区间为1-100分和详细评价评价需公正客观且详细具体。",
"interaction": {
"instruction": "请根据提供的面试对话文本为本次面试打分总分区间为1-100分并提供一段详细评价评价需涵盖问题设计、追问深度、候选人表现、逻辑连贯性和整体效果等方面且评价内容需公正客观、具体详细。",
"scene_name": "AI面试总分与评价",
"dialogue_text": "【面试对话内容】",
"response_format": "JSON",
"response_format_example": {
"total_score": 0,
"detailed_evaluation": ""
}
}
}'
],
[
'role' => 'user',
'content' => "对话记录:" . json_encode($qwenData, JSON_UNESCAPED_UNICODE)
]
];
$response = \app\common\service\ToolsService::Interview()->chat([
'action' => 'qwen',
'messages' => $messages
]);
if (empty($response['data']['message']))
{
throw new Exception('评分失败~');
}
$result = format_json($response['data']['message']);
if (empty($result['total_score']) || empty($result['detailed_evaluation']))
{
throw new Exception('评分失败~');
}
return $result;
}
public static function feedback(array $params)
{
InterviewFeedback::create($params);
return true;
}
public static function getStt(array $params)
{
$message = self::stt($params['audio_url']);
self::$returnData = ['message' => $message['message']];
return true;
}
public static function checkInterview(array $params)
{
$interview = Interview::where(['user_id' => $params['user_id'], 'job_id' => $params['job_id']])
->whereIn('status', [2,3,4])->count();
if($interview > 3){
$data['type'] = 7;
$data['msg'] = '当前面试已经超过三次!';
self::$returnData = $data;
return true;
}
$job = InterviewJob::where(['id' => $params['job_id']])->findOrEmpty()->toArray();
if (empty($job)) {
$data['type'] = 0;
$data['msg'] = '岗位不存在';
self::$returnData = $data;
return true;
}
$userInfo = User::findOrEmpty($job['user_id'])->toArray();
// if (empty($userInfo)) {
// $data['type'] = 8;
// $data['msg'] = '用户查询失败';
// self::$returnData = $data;
// return true;
// }
$use_token = ModelConfig::where('scene', 'interview_chat')->value('score', 0);
if ($userInfo['tokens'] < $use_token) {
$data['type'] = 9;
$data['msg'] = '当前岗位可用算力不足,请联系面试官!';
self::$returnData = $data;
return true;
}
$data['type'] = 0;
try {
$interviewRecord = InterviewRecord::where(['user_id' => $params['user_id'], 'job_id' => $params['job_id']])
->order('id', 'desc')
->findOrEmpty()
->toArray();
if (empty($interviewRecord)) {
// 检查是否上传了简历
$interviewCv = InterviewCv::where(['user_id' => $params['user_id'],'interview_job_id' => $params['job_id']])
->order('id', 'desc')
->findOrEmpty()
->toArray();
if (empty($interviewCv)) {
$data['type'] = 1;
$data['msg'] = '没有上传简历';
self::$returnData = $data;
return true;
}
$data['type'] = 2;
$data['msg'] = '没有面试记录';
self::$returnData = $data;
return true;
}
$interview = Interview::where(['user_id' => $params['user_id'], 'job_id' => $params['job_id']])
->order('id', 'desc')
->findOrEmpty()
->toArray();
if (empty($interview)) {
$data['type'] = 5;
$data['msg'] = '面试中断';
self::$returnData = $data;
return true;
}
if ($interview['status'] == 3) {
$data['type'] = 4;
$data['msg'] = '面试重新开始';
$data['id'] = $interview['id'];
$data['status'] = $interview['status'];
self::$returnData = $data;
return true;
}
if ($interview['status'] == 1) {
$data['type'] = 6;
$data['msg'] = '面试已完成';
self::$returnData = $data;
return true;
}
$data['msg'] = '上一轮面试,还没有面试完!!';
$data['id'] = $interview['id'];
$data['status'] = $interview['status'];
$data['type'] = 3;
self::$returnData = $data;
return true;
} catch (\Exception $e) {
// 捕获异常并设置错误信息
throw new \Exception('系统错误: ' . $e->getMessage());
return false;
}
}
// 创建面试和插入对话开场白的通用方法
private static function createInterviewAndDialog(array $params, array $interviewRecord): array
{
$params['start_time'] = time();
$params['interview_record_id'] = $interviewRecord['id'];
// 创建面试
$interview = Interview::create($params)->toArray();
$interview['prologue'] = '您好很高兴您能来参加本轮面试我是你的AI面试官先请您先做一个简单的自我介绍吧。';
// 获取岗位信息
$job = InterviewJob::where('id', $params['job_id'])->findOrEmpty()->toArray();
if ($job['type'] == 2) {
$audioData = self::tts($interview['prologue']);
$interview['audio_url'] = $audioData['audio_url'];
$interview['audio_duration'] = $audioData['audio_duration'];
}
// 插入对话开场白
InterviewDialog::create([
'interview_id' => $interview['id'],
'type' => 4,
'question' => $interview['prologue'],
'question_url' => $interview['audio_url'] ?? '',
'question_duration' => $interview['audio_duration'] ?? 0,
]);
return $interview;
}
}