导师模拟对话生成
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
package org.jiayunet.controller;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.jiayunet.pojo.dto.mentor.MentorMockChatDto;
|
||||||
|
import org.jiayunet.service.MentorMockChatService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导师模拟对话接口
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/public/mentor")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class MentorMockChatController {
|
||||||
|
|
||||||
|
private final MentorMockChatService mentorMockChatService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成导师模拟对话
|
||||||
|
*
|
||||||
|
* @param mentorId 导师ID
|
||||||
|
*/
|
||||||
|
@GetMapping("/mockChat")
|
||||||
|
public MentorMockChatDto generateMockChat(@RequestParam Long mentorId) {
|
||||||
|
return mentorMockChatService.generate(mentorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.jiayunet.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.jiayunet.pojo.po.MentorMockChat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导师模拟对话Mapper
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
public interface MentorMockChatMapper extends BaseMapper<MentorMockChat> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.jiayunet.pojo.dto.mentor;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导师模拟对话DTO
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class MentorMockChatDto {
|
||||||
|
|
||||||
|
/** 对话消息列表 */
|
||||||
|
private List<Message> messages;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class Message {
|
||||||
|
/** 角色:user=用户,mentor=导师 */
|
||||||
|
private String role;
|
||||||
|
/** 消息内容 */
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.jiayunet.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导师模拟对话表(bg_mentor_mock_chat)
|
||||||
|
* <p>缓存AI生成的导师模拟对话,按导师ID唯一</p>
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "bg_mentor_mock_chat", autoResultMap = true)
|
||||||
|
public class MentorMockChat {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 导师ID */
|
||||||
|
private Long mentorId;
|
||||||
|
|
||||||
|
/** 对话消息列表(JSON数组) */
|
||||||
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
|
private List<ChatMessage> messages;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private Instant createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话消息项
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class ChatMessage {
|
||||||
|
/** 角色:user=用户,mentor=导师 */
|
||||||
|
private String role;
|
||||||
|
/** 消息内容 */
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package org.jiayunet.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jiayunet.ai.AiChatAbility;
|
||||||
|
import org.jiayunet.ai.AiResponseCleanTool;
|
||||||
|
import org.jiayunet.mapper.MentorMockChatMapper;
|
||||||
|
import org.jiayunet.pojo.dto.mentor.MentorMockChatDto;
|
||||||
|
import org.jiayunet.pojo.po.MentorMockChat;
|
||||||
|
import org.jiayunet.remote.jsxq.JsxqClient;
|
||||||
|
import org.jiayunet.remote.jsxq.pojo.dto.MentorFullInfoDto;
|
||||||
|
import org.jiayunet.remote.jsxq.pojo.dto.TeacherTalkDto;
|
||||||
|
import org.jiayunet.tool.HttpTool;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导师模拟对话生成服务
|
||||||
|
* <p>依赖:JsxqClient(获取导师信息和话题)、AiChatAbility(AI生成对话)、MentorMockChatMapper(缓存入库)</p>
|
||||||
|
* <p>使用表:bg_mentor_mock_chat(查询/写入缓存)</p>
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class MentorMockChatService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JsxqClient jsxqClient;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AiChatAbility aiChatAbility;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MentorMockChatMapper mentorMockChatMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导师模拟对话
|
||||||
|
* <p>1. 查库,有缓存直接返回 2. 无缓存则获取导师信息+随机话题 3. AI生成 4. 入库 5. 返回</p>
|
||||||
|
*
|
||||||
|
* @param mentorId 导师ID
|
||||||
|
* @return 模拟对话
|
||||||
|
*/
|
||||||
|
public MentorMockChatDto generate(Long mentorId) {
|
||||||
|
// 1. 查缓存
|
||||||
|
MentorMockChat existing = mentorMockChatMapper.selectOne(new LambdaQueryWrapper<MentorMockChat>().eq(MentorMockChat::getMentorId, mentorId));
|
||||||
|
if (existing != null) {
|
||||||
|
MentorMockChatDto dto = new MentorMockChatDto();
|
||||||
|
dto.setMessages(existing.getMessages().stream()
|
||||||
|
.map(m -> new MentorMockChatDto.Message(m.getRole(), m.getContent()))
|
||||||
|
.toList());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取导师信息
|
||||||
|
MentorFullInfoDto mentorInfo = jsxqClient.selectMentorFullInfo(mentorId);
|
||||||
|
Assert.notNull(mentorInfo, "导师不存在");
|
||||||
|
|
||||||
|
// 3. 获取话题列表(无话题时仅用导师资料生成)
|
||||||
|
List<TeacherTalkDto> talks = jsxqClient.selectTalk(mentorInfo.getId());
|
||||||
|
|
||||||
|
// 4. 构建prompt,调用AI生成
|
||||||
|
String systemPrompt = buildSystemPrompt();
|
||||||
|
String userMessage = buildUserMessage(mentorInfo, talks);
|
||||||
|
String aiResponse = aiChatAbility.chat(systemPrompt, userMessage);
|
||||||
|
String json = AiResponseCleanTool.clean(aiResponse);
|
||||||
|
|
||||||
|
// 5. 解析
|
||||||
|
List<MentorMockChat.ChatMessage> chatMessages;
|
||||||
|
try {
|
||||||
|
chatMessages = HttpTool.objectMapper.readValue(json, new TypeReference<>() {});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("模拟对话AI返回解析失败: {}", json, e);
|
||||||
|
throw new RuntimeException("模拟对话生成失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 入库
|
||||||
|
MentorMockChat record = new MentorMockChat();
|
||||||
|
record.setMentorId(mentorId);
|
||||||
|
record.setMessages(chatMessages);
|
||||||
|
record.setCreateTime(Instant.now());
|
||||||
|
mentorMockChatMapper.insert(record);
|
||||||
|
|
||||||
|
// 7. 返回
|
||||||
|
MentorMockChatDto dto = new MentorMockChatDto();
|
||||||
|
dto.setMessages(chatMessages.stream()
|
||||||
|
.map(m -> new MentorMockChatDto.Message(m.getRole(), m.getContent()))
|
||||||
|
.toList());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建系统提示词
|
||||||
|
*/
|
||||||
|
private String buildSystemPrompt() {
|
||||||
|
return """
|
||||||
|
你是一个对话生成器。根据提供的导师资料和话题信息,生成一段求职者与导师之间的模拟对话。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
1. 对话轮数:2~3轮(随机),每轮由一条用户消息+一条导师消息组成
|
||||||
|
2. 用户消息:简短口语化,10~30字,带有真实困惑或追问
|
||||||
|
3. 导师消息:字数浮动,有长有短,短的10~30字,长的40~80字,体现专业性
|
||||||
|
4. 对话内容必须与导师的真实职业背景和话题方向强相关
|
||||||
|
5. 语气自然,像真实的即时通讯对话,不要书面化
|
||||||
|
6. 用户是求职者/应届生身份,导师根据自己的行业经验给出建议
|
||||||
|
|
||||||
|
返回JSON数组格式:
|
||||||
|
[{"role":"user","content":"..."},{"role":"mentor","content":"..."},...]
|
||||||
|
|
||||||
|
只返回JSON,不要任何额外说明。""";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建用户消息(导师资料+全部话题信息)
|
||||||
|
*/
|
||||||
|
private String buildUserMessage(MentorFullInfoDto mentorInfo, List<TeacherTalkDto> talks) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("【导师资料】\n");
|
||||||
|
|
||||||
|
MentorFullInfoDto.MentorInfo info = mentorInfo.getMentorInfo();
|
||||||
|
if (info != null) {
|
||||||
|
if (info.getNickName() != null) sb.append("昵称:").append(info.getNickName()).append("\n");
|
||||||
|
if (info.getIntroduce() != null) sb.append("介绍:").append(info.getIntroduce()).append("\n");
|
||||||
|
if (info.getEverCompany() != null) sb.append("曾任公司:").append(info.getEverCompany()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证信息
|
||||||
|
MentorFullInfoDto.Verification v = mentorInfo.getVerification();
|
||||||
|
if (v != null) {
|
||||||
|
if (v.getWorking() != null) {
|
||||||
|
sb.append("公司:").append(v.getWorking().getCompanyName())
|
||||||
|
.append(" 职位:").append(v.getWorking().getPosition())
|
||||||
|
.append(" 从业").append(v.getWorking().getTerm()).append("年\n");
|
||||||
|
} else if (v.getStaff() != null) {
|
||||||
|
sb.append("院校:").append(v.getStaff().getSchoolName())
|
||||||
|
.append(" 职位:").append(v.getStaff().getPosition()).append("\n");
|
||||||
|
} else if (v.getStudent() != null) {
|
||||||
|
sb.append("院校:").append(v.getStudent().getSchoolName())
|
||||||
|
.append(" 专业:").append(v.getStudent().getMajor()).append("\n");
|
||||||
|
} else if (v.getCareer() != null) {
|
||||||
|
sb.append("机构:").append(v.getCareer().getAgencyName())
|
||||||
|
.append(" 职位:").append(v.getCareer().getPosition())
|
||||||
|
.append(" 从业").append(v.getCareer().getTerm()).append("年\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签
|
||||||
|
List<MentorFullInfoDto.LabelRelation> labels = mentorInfo.getLabelRelations();
|
||||||
|
if (labels != null && !labels.isEmpty()) {
|
||||||
|
sb.append("标签:");
|
||||||
|
labels.forEach(l -> sb.append(l.getLabelName()).append("、"));
|
||||||
|
sb.setLength(sb.length() - 1);
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全部话题
|
||||||
|
if (talks != null && !talks.isEmpty()) {
|
||||||
|
sb.append("\n【话题列表】\n");
|
||||||
|
for (int i = 0; i < talks.size(); i++) {
|
||||||
|
TeacherTalkDto talk = talks.get(i);
|
||||||
|
sb.append(i + 1).append(". ").append(talk.getTitle());
|
||||||
|
if (talk.getContent() != null) sb.append("(").append(talk.getContent()).append(")");
|
||||||
|
if (talk.getTags() != null) sb.append(" [").append(talk.getTags()).append("]");
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user