From 3d3960a3b3d1057db9d2f4016512915e7a5c517d Mon Sep 17 00:00:00 2001 From: Administrator <15274802129@163.com> Date: Thu, 25 Sep 2025 09:56:21 +0800 Subject: [PATCH] feat(ai): 集成百炼工作流实现AI流式对话功能 - 新增百炼工作流SDK相关依赖和工具类 - 实现llmInvokeStreamingWithThink方法用于流式调用 - 配置API Key和应用ID以连接百炼平台 - 启用思考模式(enableThinking)和思维输出(hasThoughts) - 处理流式响应并封装为FebsResponse返回 - 添加异常处理机制捕获API调用错误 - 移除原有的静态提示词配置逻辑 -重构answerStreamV3接口直接调用新实现 --- src/main/java/cc/mrbird/febs/ai/controller/TestController.java | 165 +++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 112 insertions(+), 53 deletions(-) diff --git a/src/main/java/cc/mrbird/febs/ai/controller/TestController.java b/src/main/java/cc/mrbird/febs/ai/controller/TestController.java index b106fb4..4df6fa5 100644 --- a/src/main/java/cc/mrbird/febs/ai/controller/TestController.java +++ b/src/main/java/cc/mrbird/febs/ai/controller/TestController.java @@ -1,14 +1,18 @@ 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.strategy.LlmStrategyEnum; +import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkStreamVoOld; +import cc.mrbird.febs.ai.service.AiService; +import cc.mrbird.febs.ai.strategy.enumerates.LlmStrategyContextEnum; +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.exception.FebsException; import cc.mrbird.febs.common.utils.AppContants; import cc.mrbird.febs.common.utils.RedisUtils; import cc.mrbird.febs.mall.entity.MallMember; @@ -18,8 +22,11 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; -import cn.hutool.json.JSONUtil; -import com.alibaba.dashscope.aigc.multimodalconversation.MultiModalConversationResult; +import com.alibaba.dashscope.app.Application; +import com.alibaba.dashscope.app.ApplicationParam; +import com.alibaba.dashscope.app.ApplicationResult; +import com.alibaba.dashscope.app.FlowStreamMode; +import com.alibaba.dashscope.utils.JsonUtils; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import io.reactivex.Flowable; @@ -42,10 +49,7 @@ import com.alibaba.dashscope.exception.NoApiKeyException; import reactor.core.publisher.Flux; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * @author Administrator @@ -60,6 +64,7 @@ private final MallMemberMapper mallMemberMapper; private final AiMemberMapper aiMemberMapper; + private final AiService aiService; private final RedisUtils redisUtils; private final LlmStrategyFactory llmStrategyFactory; @ApiOperation(value = "登录测试", notes = "登录测试") @@ -108,7 +113,7 @@ @ApiOperation("提问AI(流式)V2") @ApiResponses({ - @ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVo.class), + @ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVoOld.class), }) @PostMapping("/answer-streamV2") public Flux<FebsResponse> answerStreamV2(@RequestBody @Validated AiTalkAnswerStream dto) { @@ -148,50 +153,100 @@ @ApiOperation("提问AI(流式)V3") @ApiResponses({ - @ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVo.class), + @ApiResponse(code = 200, message = "流式响应", response = ApiMemberTalkStreamVoOld.class), }) @PostMapping("/answerStreamV3") public Flux<FebsResponse> answerStreamV3(@RequestBody @Validated AiTalkAnswerStream dto) { - if (StrUtil.isEmpty(dto.getQuestion())){ - return Flux.just(new FebsResponse().fail().message("请输入问题")); + + return llmInvokeStreamingWithThink(); + } + + private Flux<FebsResponse> llmInvokeStreamingWithThink(){ + + long startTime = System.currentTimeMillis(); + ApplicationParam param = ApplicationParam.builder() + // 若没有配置环境变量,可用百炼API Key将下行替换为:.apiKey("sk-xxx")。但不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。 + .apiKey("sk-babdcf8799144134915cee2683794b2f") + .appId("f94539c7e54d44e2a7cac5e85f2ae61d") //替换为实际的应用 ID + .flowStreamMode(FlowStreamMode.MESSAGE_FORMAT) + .prompt("你是谁?") + .enableThinking( true) + .hasThoughts( true) + .build(); + + Application application = new Application(); + Flowable<ApplicationResult> result; + try { + result = application.streamCall(param); + } catch (NoApiKeyException | InputRequiredException e) { + throw new FebsException(StrUtil.format("百炼工作流输出失败:{}",e.getMessage())); } + + return Flux.from(result) + .map(message -> { + HashMap<String, String> stringStringHashMap = new HashMap<>(); + + System.out.print(message.getOutput().getThoughts()); + if (!message.getOutput().getFinishReason().equals("stop")){ + stringStringHashMap.put(LlmStrategyContextEnum.CONTENT.name(),message.getOutput().getWorkflowMessage().getMessage().getContent()); + } + return new FebsResponse().success().data(stringStringHashMap); + }) + .doOnComplete(() -> { + long endTime = System.currentTimeMillis(); + System.out.println("百炼工作流输出:" + (endTime - startTime) + "毫秒"); + }) + .doOnError(error -> { + throw new FebsException(StrUtil.format("百炼工作流输出失败:{}",error)); + }); + } + + private LlmStrategyDto buildLlmStrategyDtoList(String Str, Integer type){ LlmStrategyDto llmStrategyDto = new LlmStrategyDto(); - Message systemMsg = Message.builder() - .role(Role.SYSTEM.getValue()) - .content(dto.getPrompt()) - .build(); - Message userMsg = Message.builder() - .role(Role.USER.getValue()) - .content(dto.getQuestion()) - .build(); - List<Message> messages = Arrays.asList(systemMsg, userMsg); - llmStrategyDto.setMessages(messages); - return llmStrategyFactory.getCalculationStrategyMap().get(LlmStrategyEnum.ALI.getName()).llmInvokeStreaming(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), + @ApiResponse(code = 200, message = "非流式响应", response = ApiMemberTalkStreamVoOld.class), }) @PostMapping("/answerStreamV4") public FebsResponse answerStreamV4(@RequestBody @Validated AiTalkAnswerStream dto) { if (StrUtil.isEmpty(dto.getQuestion())){ return new FebsResponse().fail().message("请输入问题"); } - LlmStrategyDto llmStrategyDto = new LlmStrategyDto(); - Message systemMsg = Message.builder() - .role(Role.SYSTEM.getValue()) - .content(dto.getPrompt()) - .build(); - Message userMsg = Message.builder() - .role(Role.USER.getValue()) - .content(dto.getQuestion()) - .build(); - List<Message> messages = Arrays.asList(systemMsg, userMsg); - llmStrategyDto.setMessages(messages); - return llmStrategyFactory.getCalculationStrategyMap().get(LlmStrategyEnum.ALI.getName()).llmInvokeNonStreaming(llmStrategyDto); + 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); } @@ -209,12 +264,13 @@ // 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx") .apiKey("sk-babdcf8799144134915cee2683794b2f") // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models - .model("qwen-plus") + .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); @@ -235,31 +291,32 @@ // 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx") .apiKey("sk-babdcf8799144134915cee2683794b2f") // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models - .model("qwen-plus") + .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 = "{\"task\": \"作为专业的表扬能力评估分析师,分析question和用户输入内容,对用户输入内容进行评估与指导。\",\"question\":\"当资深骨干员工在突发场景中迅速响应并解决了持续一周的客户投诉达成客户满意度成就时,作为上级的你应如何表达赞赏?\",\"highlight\": \"识别符合表扬原则的积极策略,关注角色视角适配性、员工类型特点、具体行为描述、情感表达真诚度与个性化认可方式\"}"; - String prompt1 = "{\"task\": \"作为专业的表扬能力评估分析师,分析question和用户输入内容,对用户输入内容进行评估与指导。\",\"question\":\"当资深骨干员工在突发场景中迅速响应并解决了持续一周的客户投诉达成客户满意度成就时,作为上级的你应如何表达赞赏?\",\"suggestion\": \"提供针对性优化方案,涵盖场景要素匹配度提升(如正式场合需增加公开认可环节)、成就类型适配改进(如创新贡献应强调思维突破而非仅结果)、具体维度强化方向(如增加 STAR 法则中的 Result 部分描述)、跨文化 / 远程场景特殊策略(如虚拟团队可采用异步表扬 + 公开认可组合)\"}"; + 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\": \"作为专业的表扬能力评估分析师,分析question和用户输入内容,对用户输入内容进行评估与指导。\",\"question\":\"当资深骨干员工在突发场景中迅速响应并解决了持续一周的客户投诉达成客户满意度成就时,作为上级的你应如何表达赞赏?\",\"key_knowledge\": \"结合理论与实践深度解析\"}"; - 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) + "毫秒"); + 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 { @@ -271,6 +328,8 @@ } 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); }) -- Gitblit v1.9.1