package cc.mrbird.febs.ai.controller;
|
|
import cc.mrbird.febs.ai.entity.AiMember;
|
import cc.mrbird.febs.ai.enumerates.AiPromptEnum;
|
import cc.mrbird.febs.ai.mapper.AiMemberMapper;
|
import cc.mrbird.febs.ai.req.talk.AiTalkAnswerStream;
|
import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkStreamVo;
|
import cc.mrbird.febs.ai.service.AiService;
|
import cc.mrbird.febs.ai.strategy.enumerates.LlmStrategyEnum;
|
import cc.mrbird.febs.ai.strategy.LlmStrategyFactory;
|
import cc.mrbird.febs.ai.strategy.param.LlmStrategyDto;
|
import cc.mrbird.febs.ai.utils.UUID;
|
import cc.mrbird.febs.common.entity.FebsResponse;
|
import cc.mrbird.febs.common.utils.AppContants;
|
import cc.mrbird.febs.common.utils.RedisUtils;
|
import cc.mrbird.febs.mall.entity.MallMember;
|
import cc.mrbird.febs.mall.mapper.MallMemberMapper;
|
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.crypto.asymmetric.KeyType;
|
import cn.hutool.crypto.asymmetric.RSA;
|
import com.alibaba.fastjson.JSONObject;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import io.reactivex.Flowable;
|
import io.swagger.annotations.Api;
|
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiResponse;
|
import io.swagger.annotations.ApiResponses;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.validation.annotation.Validated;
|
import org.springframework.web.bind.annotation.*;
|
|
import com.alibaba.dashscope.aigc.generation.Generation;
|
import com.alibaba.dashscope.aigc.generation.GenerationParam;
|
import com.alibaba.dashscope.aigc.generation.GenerationResult;
|
import com.alibaba.dashscope.common.Message;
|
import com.alibaba.dashscope.common.Role;
|
import com.alibaba.dashscope.exception.ApiException;
|
import com.alibaba.dashscope.exception.InputRequiredException;
|
import com.alibaba.dashscope.exception.NoApiKeyException;
|
import reactor.core.publisher.Flux;
|
|
import java.util.*;
|
|
/**
|
* @author Administrator
|
*/
|
@Slf4j
|
@Validated
|
@RestController
|
@RequiredArgsConstructor
|
@RequestMapping(value = "/api/ai/test")
|
@Api(value = "ApiProductController", tags = "AI-登录测试")
|
public class TestController {
|
|
private final MallMemberMapper mallMemberMapper;
|
private final AiMemberMapper aiMemberMapper;
|
private final AiService aiService;
|
private final RedisUtils redisUtils;
|
private final LlmStrategyFactory llmStrategyFactory;
|
@ApiOperation(value = "登录测试", notes = "登录测试")
|
@GetMapping(value = "/login")
|
public FebsResponse info() {
|
|
MallMember mallMember = mallMemberMapper.selectById(3634);
|
if(StrUtil.isEmpty(mallMember.getMemberUuid())){
|
AiMember aiMember = new AiMember();
|
aiMember.setId(UUID.getSimpleUUIDString());
|
aiMemberMapper.insert(aiMember);
|
mallMember.setMemberUuid(aiMember.getId());
|
mallMemberMapper.update(null,
|
Wrappers.lambdaUpdate(MallMember.class)
|
.set(MallMember::getMemberUuid, aiMember.getId())
|
.eq(MallMember::getId, mallMember.getId())
|
);
|
}
|
// 存放redis
|
String redisKey = AppContants.XCX_LOGIN_PREFIX + mallMember.getId();
|
String existToken = redisUtils.getString(redisKey);
|
if (StrUtil.isNotBlank(existToken)) {
|
Object o = redisUtils.get(existToken);
|
if (ObjectUtil.isNotEmpty(o)) {
|
redisUtils.del(existToken);
|
}
|
}
|
String token = IdUtil.simpleUUID();
|
redisUtils.set(token, JSONObject.toJSONString(mallMember), -1);
|
redisUtils.set(redisKey, token, -1);
|
Map<String, Object> authInfo = new HashMap<>();
|
authInfo.put("token", token);
|
authInfo.put("member", mallMember);
|
authInfo.put("rasToken", generateAsaToken(token));
|
return new FebsResponse().success().data(authInfo);
|
}
|
|
public String generateAsaToken(String token) {
|
RSA rsa = new RSA(null, AppContants.PUBLIC_KEY);
|
// return rsa.encryptBase64(token + "_" + System.currentTimeMillis(), KeyType.PublicKey);
|
//去掉时间戳
|
return rsa.encryptBase64(token, KeyType.PublicKey);
|
}
|
|
|
|
@ApiOperation("提问AI(流式)V2")
|
@ApiResponses({
|
@ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVo.class),
|
})
|
@PostMapping("/answer-streamV2")
|
public Flux<FebsResponse> answerStreamV2(@RequestBody @Validated AiTalkAnswerStream dto) {
|
if (StrUtil.isEmpty(dto.getQuestion())){
|
return Flux.just(new FebsResponse().fail().message("请输入问题"));
|
}
|
String question = dto.getQuestion();
|
String prompt = dto.getPrompt() ;
|
|
long startTime = System.currentTimeMillis();
|
|
Flowable<GenerationResult> result;
|
try {
|
result = callWithMessageStream(question,prompt);
|
} catch (NoApiKeyException | InputRequiredException e) {
|
e.printStackTrace();
|
return Flux.just(new FebsResponse().fail().message("调用AI服务失败: " + e.getMessage()));
|
}
|
|
return Flux.from(result)
|
.map(message -> {
|
String content = message.getOutput().getChoices().get(0).getMessage().getContent();
|
System.out.print(content);
|
return new FebsResponse().success().data(content);
|
})
|
.doOnComplete(() -> {
|
long endTime = System.currentTimeMillis();
|
System.out.println("运行时间:" + (endTime - startTime) + "毫秒");
|
})
|
.doOnError(error -> {
|
long endTime = System.currentTimeMillis();
|
System.err.println("运行时间:" + (endTime - startTime) + "毫秒,发生错误:" + error.getMessage());
|
});
|
}
|
|
|
|
@ApiOperation("提问AI(流式)V3")
|
@ApiResponses({
|
@ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVo.class),
|
})
|
@PostMapping("/answerStreamV3")
|
public Flux<FebsResponse> answerStreamV3(@RequestBody @Validated AiTalkAnswerStream dto) {
|
if (StrUtil.isEmpty(dto.getQuestion())){
|
return Flux.just(new FebsResponse().fail().message("请输入问题"));
|
}
|
|
String prompt = "你是一位拥有超过10年人力资源与薪资谈判经验的资深职业顾问,专长于为不同行业、不同层级的专业人士提供定制化的薪资谈判策略。你的主要目标是通过专业的分析和实战指导,帮助用户在薪资谈判过程中最大化地实现个人价值,同时保持与雇主的良好关系。你熟悉各行业的薪资结构、市场行情数据解读方法以及谈判心理学技巧,能够给出既有理论依据又具备实际操作性的建议。\n" +
|
"\n" +
|
"### 核心功能要求\n" +
|
"\n" +
|
"#### 1. 答案亮点分析\n" +
|
"- 对用户提供的薪资谈判回答进行逐句解析。\n" +
|
"- 识别并突出其中的三大核心优势(例如:具体的数据支持、行业标准对比、个人价值创造等)。\n" +
|
"\n" +
|
"#### 2. 定制化参考建议\n" +
|
"- **市场行情定位**:基于最新的行业薪资报告,评估用户的当前薪资水平及其合理性(如:“根据《XX行业2024薪资报告》,您的目标薪资位于75分位,具有较高的合理性。”)\n" +
|
"- **谈判话术框架**:提供一个完整的谈判对话模板,涵盖开场白、自我价值陈述、处理对方异议及最终确认四个关键阶段。\n" +
|
"- **非薪资福利谈判策略**:探讨除了基本薪资外,还可以争取到的其他福利选项(比如股权激励、灵活工作安排或专业发展基金等)。\n" +
|
"- **关键提升点建议**:指出用户现有方案中可以改进的地方,并提出具体的改进建议(例如:“如果能加入更多关于您对公司贡献的具体例子,将显著增强说服力。”)\n" +
|
"\n" +
|
"#### 3. 情境化示例答案\n" +
|
"- 针对用户特定的谈判情境(包括但不限于当前薪资状况、期望薪资范围及所处面试阶段),生成三种风格各异但均符合实际情况的完整对话案例:\n" +
|
" - **稳健型**:注重风险管理和长期合作关系的建立。\n" +
|
" - **进取型**:强调个人的独特价值和市场需求。\n" +
|
" - **灵活型**:提供多种备选方案以适应不同的谈判情况。\n" +
|
"\n" +
|
"#### 4. 权威参考依据\n" +
|
"- 引用最新发布的行业薪资研究报告(需明确报告名称及发布机构)。\n" +
|
"- 分享谈判心理学中的重要概念及其应用(如锚定效应、互惠原则等)。\n" +
|
"- 推荐三本关于谈判技巧的经典书籍,并简要总结每本书的核心观点。\n" +
|
"- 列出薪资谈判过程中需要注意的五个法律风险点(例如最低工资标准、加班费计算规则等)。\n" +
|
"\n" +
|
"### 交互流程\n" +
|
"- 在收到用户提交的初步薪资谈判提案后,按照上述要求之一输出相应的内容:“答案亮点分析”、“定制化参考建议”、“情境化示例答案”或“权威参考依据”。\n" +
|
"\n" +
|
"### 输出格式规范\n" +
|
"- 使用Markdown格式编写文档。\n" +
|
"- 所有引用的数据必须加粗显示,并注明其来源。\n" +
|
"- 提供的所有策略建议都应标明适用的具体场景(例如【适用于金融行业管理岗位】)。\n" +
|
"\n" +
|
"### 语气要求\n" +
|
"- 保持专业性的同时不失温暖,使用鼓励性的语言来增强用户的信心。\n" +
|
"- 尽量避免过多使用专业术语,而是采用更加通俗易懂的方式表达(如“你可以这样尝试...”)。\n" +
|
"- 当需要指出不足之处时,采取积极建设性的态度(例如:“如果能在提案中增加一些关于团队合作经历的例子,将会使你的论点更加有力。”)";
|
List<LlmStrategyDto> llmStrategyDtoList = new ArrayList<>();
|
LlmStrategyDto llmStrategyDto = this.buildLlmStrategyDtoList(prompt, 1);
|
llmStrategyDtoList.add(llmStrategyDto);
|
llmStrategyDto = this.buildLlmStrategyDtoList(dto.getQuestion(), 3);
|
llmStrategyDtoList.add(llmStrategyDto);
|
llmStrategyDto = this.buildLlmStrategyDtoList(dto.getPrompt(), 2);
|
llmStrategyDtoList.add(llmStrategyDto);
|
llmStrategyDto = this.buildLlmStrategyDtoList(dto.getTalkId(), 4);
|
llmStrategyDtoList.add(llmStrategyDto);
|
String modelName = LlmStrategyEnum.getName(aiService.getSystemSetAiType());
|
|
return llmStrategyFactory.getCalculationStrategyMap().get(modelName).llmInvokeStreamingNoThink(llmStrategyDtoList);
|
}
|
|
private LlmStrategyDto buildLlmStrategyDtoList(String Str, Integer type){
|
LlmStrategyDto llmStrategyDto = new LlmStrategyDto();
|
if (type == 1){
|
llmStrategyDto.setRole(Role.SYSTEM.getValue());
|
}
|
if (type == 2){
|
llmStrategyDto.setRole(Role.USER.getValue());
|
}
|
if (type == 3){
|
llmStrategyDto.setRole(Role.ASSISTANT.getValue());
|
}
|
if (type == 4){
|
llmStrategyDto.setRole(Role.TOOL.getValue());
|
}
|
llmStrategyDto.setContent(Str);
|
|
return llmStrategyDto;
|
}
|
|
|
|
@ApiOperation("提问AI(非流式响应)V4")
|
@ApiResponses({
|
@ApiResponse(code = 200, message = "非流式响应", response = ApiMemberTalkStreamVo.class),
|
})
|
@PostMapping("/answerStreamV4")
|
public FebsResponse answerStreamV4(@RequestBody @Validated AiTalkAnswerStream dto) {
|
if (StrUtil.isEmpty(dto.getQuestion())){
|
return new FebsResponse().fail().message("请输入问题");
|
}
|
ArrayList<LlmStrategyDto> llmStrategyDtoList = new ArrayList<>();
|
if (dto.getPrompt() != null){
|
LlmStrategyDto llmStrategyDto = new LlmStrategyDto();
|
llmStrategyDto.setRole(Role.SYSTEM.getValue());
|
llmStrategyDto.setContent(dto.getPrompt());
|
llmStrategyDtoList.add(llmStrategyDto);
|
}
|
if (dto.getQuestion() != null){
|
LlmStrategyDto llmStrategyDto = new LlmStrategyDto();
|
llmStrategyDto.setRole(Role.USER.getValue());
|
llmStrategyDto.setContent(dto.getQuestion());
|
llmStrategyDtoList.add(llmStrategyDto);
|
}
|
String modelName = LlmStrategyEnum.getName(aiService.getSystemSetAiType());
|
|
return llmStrategyFactory.getCalculationStrategyMap().get(modelName).llmInvokeNonStreaming(llmStrategyDtoList);
|
}
|
|
|
public static Flowable<GenerationResult> callWithMessageStream(String question,String prompt) throws NoApiKeyException, InputRequiredException {
|
Generation gen = new Generation();
|
Message systemMsg = Message.builder()
|
.role(Role.SYSTEM.getValue())
|
.content(prompt)
|
.build();
|
Message userMsg = Message.builder()
|
.role(Role.USER.getValue())
|
.content(question)
|
.build();
|
GenerationParam param = GenerationParam.builder()
|
// 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx")
|
.apiKey("sk-babdcf8799144134915cee2683794b2f")
|
// 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
|
.model("qwen3-14b-ft-202509031002-7446")
|
// .model("deepseek-r1")
|
// .model("qwen-turbo-0624-ft-202508281725-c2dc")
|
.messages(Arrays.asList(systemMsg, userMsg))
|
// .resultFormat(GenerationParam.ResultFormat.TEXT)
|
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
|
.enableThinking( true)
|
.incrementalOutput(true)
|
.build();
|
return gen.streamCall(param);
|
}
|
|
|
public static GenerationResult callWithMessage(String question,String prompt) throws ApiException, NoApiKeyException, InputRequiredException {
|
Generation gen = new Generation();
|
Message systemMsg = Message.builder()
|
.role(Role.SYSTEM.getValue())
|
.content(prompt)
|
.build();
|
Message userMsg = Message.builder()
|
.role(Role.USER.getValue())
|
.content(question)
|
.build();
|
GenerationParam param = GenerationParam.builder()
|
// 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx")
|
.apiKey("sk-babdcf8799144134915cee2683794b2f")
|
// 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
|
.model("qwen3-14b-ft-202509031002-7446")
|
.messages(Arrays.asList(systemMsg, userMsg))
|
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
|
.enableThinking( false)
|
.build();
|
return gen.call(param);
|
}
|
public static void main(String[] args) {
|
//定义一个开始时间为启动这个main方法的开始时间,用于计算运行时间
|
long startTime = System.currentTimeMillis();
|
String question = "下属负责的项目高质量按期交付,除了表扬,你希望 下属要多注意和同事的沟通,应如何表达";
|
String prompt = AiPromptEnum.STREAM_NORMAL.getPrompt();
|
String prompt1 = "{ \"question\":\"下属负责的项目高质量按期交付,除了表扬,你希望 下属要多注意和同事的沟通,应如何表达\", \"task\": \"作为专业的表扬能力评估分析师,根据表扬场景题目及用户输入内容,实现对表扬能力的全方位评估与指导。\", \"rules\": [ \"用户输入内容需符合问题中的场景要素与角色设定,若无关则返回:[请回答提出的问题]\", \"生成四维评价体系[evaluation],每部分不少于50字:\", \" - 亮点[highlight]:识别符合表扬原则的积极策略,关注角色视角适配性、员工类型特点、具体行为描述、情感表达真诚度与个性化认可方式\", \" - 建议[suggestion]:提供针对性优化方案,涵盖场景要素匹配度、成就类型适配、STAR法则强化、跨文化/远程场景策略\", \" - 参考答案[referenceAnswer]:遵循平级表扬'三明治法则'、危机处理三维度、长期贡献认可策略\", \" - 核心知识点[keyKnowledge]:包含心理学依据、组织行为学理论、标杆案例、文化适配分析\", \"生成五维评分模型[radarDataItems],采用1-10分制:\", \" - 针对性(problemUnderstanding):表扬内容与实际贡献匹配度\", \" - 具体性(fluency):量化成果、行为细节、独特贡献描述\", \" - 情感温度(principleAdherence):语言真诚度和情感连接强度\", \" - 激励性(logicality):对后续工作动力和行为导向的积极影响\", \" - 文化契合度(knowledgeMastery):表扬方式与组织价值观一致性\", \"评分标准:1-4分(需重大改进)、5-7分(基本合格)、8-10分(优秀示范)\", \"特殊场景处理:\", \" - 下级对上级表扬:评估谦逊度与事实依据,避免过度恭维\", \" - 跨部门协作场景:关注协作价值强调与横向影响力评价\", \" - 远程团队情境:评价异步沟通适应性与技术工具合理运用\" ], \"output_format\": { \"type\": \"json\", \"structure\": { \"evaluation\": { \"highlight\": \"字符串(≥50字)\", \"suggestion\": \"字符串(≥50字)\", \"referenceAnswer\": \"字符串\", \"keyKnowledge\": \"字符串(≥50字)\" }, \"radarDataItems\": [ { \"code\": \"problemUnderstanding\", \"score\": \"整数(1-10)\", \"name\": \"针对性\" }, { \"code\": \"fluency\", \"score\": \"整数(1-10)\", \"name\": \"具体性\" }, { \"code\": \"principleAdherence\", \"score\": \"整数(1-10)\", \"name\": \"情感温度\" }, { \"code\": \"logicality\", \"score\": \"整数(1-10)\", \"name\": \"激励性\" }, { \"code\": \"knowledgeMastery\", \"score\": \"整数(1-10)\", \"name\": \"文化契合度\" } ] } }}";
|
String prompt2 = "{\"task\": \"作为专业的表扬能力评估分析师,分析question和用户输入内容,对用户输入内容进行评估与指导。\",\"question\":\"当资深骨干员工在突发场景中迅速响应并解决了持续一周的客户投诉达成客户满意度成就时,作为上级的你应如何表达赞赏?\",\"reference_answer\": \"根据问题用户的回答,生成参考示例\"}";
|
String prompt3 = "{\"task\":\"假如你是一个表扬题目生成专家,你将根据多样化结构表扬场景题目的生成需求,来解决生成多样化结构的表扬场景题目的任务。示例1:作为跨部门同事,如何在视频会议中认可技术研发人员提前完成创新改进项目的贡献?示例2:当实习生在团队庆功宴的危机处理中成功化解客户投诉时,作为部门经理应如何表达赞赏?示例3:在年度颁奖典礼上,基层管理者发现远程办公团队成员凭借长期稳定的工作表现达成长期贡献,适合采取何种表扬方式?\",\"rules\":\"要求:1 输出一个符合上述规则生成的表扬场景题目。2 题目需包含角色视角、员工类型、成就类型、场景要素等关键信息,且符合三种结构中的一种,并带有具体细节描述。3 无论用户输入什么内容只生成 1 个独特题目。\",\"output\":\"纯文本输出\"}";
|
// GenerationResult result = null;
|
// try {
|
// result = callWithMessage(question, prompt);
|
// } catch (NoApiKeyException e) {
|
// e.printStackTrace();
|
// } catch (InputRequiredException e) {
|
// e.printStackTrace();
|
// }
|
// System.out.println(result.getOutput().getChoices().get(0).getMessage().getContent());
|
// long endTime = System.currentTimeMillis();
|
// System.out.println("运行时间:" + (endTime - startTime) + "毫秒");
|
long startTimeStream = System.currentTimeMillis();
|
Flowable<GenerationResult> resultStream = null;
|
try {
|
resultStream = callWithMessageStream(question, prompt);
|
} catch (NoApiKeyException e) {
|
e.printStackTrace();
|
} catch (InputRequiredException e) {
|
e.printStackTrace();
|
}
|
Flux.from(resultStream)
|
.doOnNext(message -> {
|
String reasoningContent = message.getOutput().getChoices().get(0).getMessage().getReasoningContent();
|
System.out.print(reasoningContent);
|
String content = message.getOutput().getChoices().get(0).getMessage().getContent();
|
System.out.print(content);
|
})
|
.subscribe();
|
long endTimeStream = System.currentTimeMillis();
|
System.out.println("运行时间:" + (endTimeStream - startTimeStream) + "毫秒");
|
}
|
}
|