From 46a2e8027a053d4ba7a4dfe357e562c9b41a1047 Mon Sep 17 00:00:00 2001 From: Administrator <15274802129@163.com> Date: Thu, 04 Sep 2025 17:29:19 +0800 Subject: [PATCH] feat(ai): 添加阿里百炼工作流策略实现 --- src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmApplicationAppIdEnum.java | 37 ++++++ src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmStrategyEnum.java | 1 src/main/java/cc/mrbird/febs/ai/service/ApiMemberTalkStreamService.java | 2 src/main/java/cc/mrbird/febs/ai/service/impl/ApiMemberTalkStreamServiceImpl.java | 54 +++++++++ src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkStreamController.java | 12 ++ src/main/java/cc/mrbird/febs/ai/strategy/Impl/AliApplicationLlmStrategyServiceImpl.java | 208 ++++++++++++++++++++++++++++++++++ 6 files changed, 314 insertions(+), 0 deletions(-) diff --git a/src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkStreamController.java b/src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkStreamController.java index b13f133..1dea19b 100644 --- a/src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkStreamController.java +++ b/src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkStreamController.java @@ -83,6 +83,18 @@ return apiMemberTalkStreamService.answer(dto); } + @ApiOperation("回答(流式)") + @ApiResponses({ + @ApiResponse(code = 200, message = "流式响应", response = cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkStreamVo.class), + }) + @PostMapping("/answerV2") + public Flux<FebsResponse> answerV2(@RequestBody @Validated AiTalkAnswerStreamDto dto) { + if (StrUtil.isEmpty(dto.getId()) || StrUtil.isEmpty(dto.getReqContext())|| StrUtil.isEmpty(dto.getReqContext())){ + return Flux.just(new FebsResponse().fail().message("参数异常")); + } + return apiMemberTalkStreamService.answerV2(dto); + } + @ApiOperation(value = "保存AI回答", notes = "保存AI回答") @PostMapping(value = "/saveAnswer") public FebsResponse saveAnswer(@RequestBody @Validated ApiMemberTalkAnswerSavaDto dto) { diff --git a/src/main/java/cc/mrbird/febs/ai/service/ApiMemberTalkStreamService.java b/src/main/java/cc/mrbird/febs/ai/service/ApiMemberTalkStreamService.java index 93da81c..e2d593f 100644 --- a/src/main/java/cc/mrbird/febs/ai/service/ApiMemberTalkStreamService.java +++ b/src/main/java/cc/mrbird/febs/ai/service/ApiMemberTalkStreamService.java @@ -23,5 +23,7 @@ Flux<FebsResponse> answer(AiTalkAnswerStreamDto dto); + Flux<FebsResponse> answerV2(AiTalkAnswerStreamDto dto); + FebsResponse saveAnswer(ApiMemberTalkAnswerSavaDto dto); } diff --git a/src/main/java/cc/mrbird/febs/ai/service/impl/ApiMemberTalkStreamServiceImpl.java b/src/main/java/cc/mrbird/febs/ai/service/impl/ApiMemberTalkStreamServiceImpl.java index 41c55c7..ff0df55 100644 --- a/src/main/java/cc/mrbird/febs/ai/service/impl/ApiMemberTalkStreamServiceImpl.java +++ b/src/main/java/cc/mrbird/febs/ai/service/impl/ApiMemberTalkStreamServiceImpl.java @@ -182,6 +182,57 @@ return llmStrategyFactory.getCalculationStrategyMap().get(modelName).llmInvokeStreamingNoThink(llmStrategyDtoList); } + @Override + public Flux<FebsResponse> answerV2(AiTalkAnswerStreamDto dto) { + + + String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid(); + Integer type = dto.getType(); + String memberTalkId = dto.getId(); + AiMemberTalk aiMemberTalk = this.getById(memberTalkId); + if (ObjectUtil.isNull(aiMemberTalk)){ + throw new FebsException("对话不存在"); + } + + LambdaQueryWrapper<AiProductRoleLink> productLinkQuery = Wrappers.lambdaQuery(AiProductRoleLink.class); + productLinkQuery.eq(AiProductRoleLink::getProductId,aiMemberTalk.getProductId()); + productLinkQuery.last("limit 1"); + AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery); + if(ObjectUtil.isNull(aiProductRoleLink)){ + throw new FebsException("产品没有关联AI陪练"); + } + + String productRoleId = aiProductRoleLink.getProductRoleId(); + AiProductRole aiProductRole = aiProductRoleService.getById(productRoleId); + if (ObjectUtil.isNull(aiProductRole)){ + throw new FebsException("产品AI陪练不存在"); + } + LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery = Wrappers.lambdaQuery(AiMemberTalkItem.class); + memberTalkItemQuery.eq(AiMemberTalkItem::getMemberId,memberUuid); + memberTalkItemQuery.eq(AiMemberTalkItem::getMemberTalkId,memberTalkId); + memberTalkItemQuery.eq(AiMemberTalkItem::getType,1); + memberTalkItemQuery.orderByDesc(AiMemberTalkItem::getCreatedTime); + memberTalkItemQuery.last("limit 1"); + AiMemberTalkItem aiMemberTalkItem = aiMemberTalkItemService.getByQuery(memberTalkItemQuery); + + String question = aiMemberTalkItem.getContext(); + String promptHead = aiProductRole.getPromptHead(); + String answer = dto.getReqContext(); + + List<LlmStrategyDto> llmStrategyDtoList = new ArrayList<>(); + LlmStrategyDto llmStrategyDto = this.buildLlmStrategyDtoList(promptHead, 1); + llmStrategyDtoList.add(llmStrategyDto); + llmStrategyDto = this.buildLlmStrategyDtoList(question, 3); + llmStrategyDtoList.add(llmStrategyDto); + llmStrategyDto = this.buildLlmStrategyDtoList(answer, 2); + llmStrategyDtoList.add(llmStrategyDto); + llmStrategyDto = this.buildLlmStrategyDtoList(String.valueOf(type), 4); + llmStrategyDtoList.add(llmStrategyDto); + String modelName = LlmStrategyEnum.getName(aiService.getSystemSetAiType()); + + return llmStrategyFactory.getCalculationStrategyMap().get(modelName).llmInvokeStreamingNoThink(llmStrategyDtoList); + } + private String buildPrompt(String question,String answer,String promptHead, String promptTemplate,Integer type){ AiPromptJsonReq aiPromptJsonReq = new AiPromptJsonReq(); // aiPromptJsonReq.setQuestion( question); @@ -204,6 +255,9 @@ if (type == 3){ llmStrategyDto.setRole(Role.ASSISTANT.getValue()); } + if (type == 4){ + llmStrategyDto.setRole(Role.TOOL.getValue()); + } llmStrategyDto.setContent(Str); return llmStrategyDto; diff --git a/src/main/java/cc/mrbird/febs/ai/strategy/Impl/AliApplicationLlmStrategyServiceImpl.java b/src/main/java/cc/mrbird/febs/ai/strategy/Impl/AliApplicationLlmStrategyServiceImpl.java new file mode 100644 index 0000000..bd1e116 --- /dev/null +++ b/src/main/java/cc/mrbird/febs/ai/strategy/Impl/AliApplicationLlmStrategyServiceImpl.java @@ -0,0 +1,208 @@ +package cc.mrbird.febs.ai.strategy.Impl; + +import cc.mrbird.febs.ai.strategy.LlmStrategyService; +import cc.mrbird.febs.ai.strategy.enumerates.LlmApplicationAppIdEnum; +import cc.mrbird.febs.ai.strategy.enumerates.LlmStrategyContextEnum; +import cc.mrbird.febs.ai.strategy.param.LlmStrategyDto; +import cc.mrbird.febs.common.entity.FebsResponse; +import cc.mrbird.febs.common.exception.FebsException; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +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.app.Application; +import com.alibaba.dashscope.app.ApplicationParam; +import com.alibaba.dashscope.app.ApplicationResult; +import com.alibaba.dashscope.app.FlowStreamMode; +import com.alibaba.dashscope.common.Message; +import com.alibaba.dashscope.common.Role; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.exception.NoApiKeyException; +import com.alibaba.dashscope.utils.JsonUtils; +import io.reactivex.Flowable; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Component("AliApplicationLlmStrategyServiceImpl") +public class AliApplicationLlmStrategyServiceImpl implements LlmStrategyService { + + private static final String apiKey = "sk-babdcf8799144134915cee2683794b2f"; + + private final String bizParam_1 = "prompt_ai_system"; + private final String bizParam_2 = "question"; + private final String bizParam_3 = "query"; + + private HashMap getPrompt(List<LlmStrategyDto> dto) { + HashMap<String, String> bizParamsMap = new HashMap<>(); + for (LlmStrategyDto dtoItem : dto){ + if (StrUtil.equals(dtoItem.getRole(), Role.SYSTEM.getValue())){ + bizParamsMap.put(bizParam_1, dtoItem.getContent()); + } + if (StrUtil.equals(dtoItem.getRole(),Role.ASSISTANT.getValue())){ + bizParamsMap.put(bizParam_2, dtoItem.getContent()); + } + if (StrUtil.equals(dtoItem.getRole(),Role.USER.getValue())){ + bizParamsMap.put(bizParam_3, dtoItem.getContent()); + } + } + return bizParamsMap; + } + + private String getQuery(List<LlmStrategyDto> dto) { + String query = null; + for (LlmStrategyDto dtoItem : dto){ + if (StrUtil.equals(dtoItem.getRole(),Role.USER.getValue())){ + query = dtoItem.getContent(); + break; + } + } + return query; + } + + private String getAppId(List<LlmStrategyDto> dto) { + String appId = null; + for (LlmStrategyDto dtoItem : dto){ + if (StrUtil.equals(dtoItem.getRole(),Role.TOOL.getValue())){ + int code = Integer.parseInt(dtoItem.getContent()); + appId = LlmApplicationAppIdEnum.HIGH_LIGHT.getAppIdByCode(code); + break; + } + } + return appId; + } + + @Override + public FebsResponse llmInvokeNonStreaming(List<LlmStrategyDto> dto) { + return null; + } + + @Override + public Flux<FebsResponse> llmInvokeStreamingWithThink(List<LlmStrategyDto> dto) { + + if (CollUtil.isEmpty(dto)){ + throw new FebsException("百炼大模型初始化异常"); + } + List<Message> messages = new ArrayList<>(); + for (LlmStrategyDto dtoItem : dto){ + if (StrUtil.equals(dtoItem.getRole(),Role.SYSTEM.getValue())){ + messages.add(Message.builder() + .role(Role.SYSTEM.getValue()) + .content(dtoItem.getContent()) + .build()); + } + if (StrUtil.equals(dtoItem.getRole(),Role.USER.getValue())){ + messages.add(Message.builder() + .role(Role.USER.getValue()) + .content(dtoItem.getContent()) + .build()); + } + if (StrUtil.equals(dtoItem.getRole(),Role.ASSISTANT.getValue())){ + messages.add(Message.builder() + .role(Role.ASSISTANT.getValue()) + .content(dtoItem.getContent()) + .build()); + } + } + + GenerationParam generationParam = GenerationParam.builder() + // 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx") + .apiKey(apiKey) + // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models + .model("qwen-plus") + .messages(messages) + .resultFormat(GenerationParam.ResultFormat.MESSAGE) + .enableThinking(true) + .incrementalOutput(true) + .build(); + + long startTime = System.currentTimeMillis(); + Generation gen = new Generation(); + Flowable<GenerationResult> result; + try { + result = gen.streamCall(generationParam); + } catch (NoApiKeyException | InputRequiredException e) { + throw new FebsException(StrUtil.format("百炼大模型输出失败:{}",e.getMessage())); + } + + return Flux.from(result) + .map(message -> { + HashMap<String, String> stringStringHashMap = new HashMap<>(); + if (StrUtil.isNotEmpty(message.getOutput().getChoices().get(0).getMessage().getReasoningContent())){ + stringStringHashMap.put(LlmStrategyContextEnum.THINK.name(),message.getOutput().getChoices().get(0).getMessage().getReasoningContent()); + + System.out.print(message.getOutput().getChoices().get(0).getMessage().getReasoningContent()); + } + if (StrUtil.isNotEmpty(message.getOutput().getChoices().get(0).getMessage().getContent())){ + stringStringHashMap.put(LlmStrategyContextEnum.CONTENT.name(),message.getOutput().getChoices().get(0).getMessage().getContent()); + System.out.print(message.getOutput().getChoices().get(0).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)); + }); + } + + @Override + public Flux<FebsResponse> llmInvokeStreamingNoThink(List<LlmStrategyDto> dto) { + if (CollUtil.isEmpty(dto)){ + throw new FebsException("百炼工作流初始化异常"); + } + HashMap prompt = getPrompt(dto); + String query = getQuery(dto); + String appId = getAppId(dto); + if (prompt == null || prompt.size() == 0){ + throw new FebsException("百炼工作流初始化异常"); + } + if (query == null){ + throw new FebsException("百炼工作流初始化异常"); + } + if (appId == null){ + throw new FebsException("百炼工作流初始化异常"); + } + long startTime = System.currentTimeMillis(); + ApplicationParam param = ApplicationParam.builder() + // 若没有配置环境变量,可用百炼API Key将下行替换为:.apiKey("sk-xxx")。但不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。 + .apiKey(apiKey) + .appId(appId) //替换为实际的应用 ID + .flowStreamMode(FlowStreamMode.MESSAGE_FORMAT) + .prompt(query) + .bizParams(JsonUtils.toJsonObject( prompt)) + .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<>(); + 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)); + }); + } +} diff --git a/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmApplicationAppIdEnum.java b/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmApplicationAppIdEnum.java new file mode 100644 index 0000000..12eef5b --- /dev/null +++ b/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmApplicationAppIdEnum.java @@ -0,0 +1,37 @@ +package cc.mrbird.febs.ai.strategy.enumerates; + +import lombok.Getter; + +@Getter +public enum LlmApplicationAppIdEnum { + + NORMAL(5,"8ea7ab1665de4a88b868849318d72f45","通用"), + + KEY_KNOWLEDGE(4,"8ea7ab1665de4a88b868849318d72f45","知识点总结"), + + REFERENCE_ANSWER(3,"5caf9f87434b4c97becd00024dc6a2b3","标准答案"), + + SUGGESTION(2,"8abbd36ccdd64cd99198546d76a8253b","建议"), + + HIGH_LIGHT(1,"44d818ab83a54131a59b1d2c2fa4a14b","亮点"); + + private final int code; + private final String appId; + private final String value; + + LlmApplicationAppIdEnum(int code, String appId, String value) { + + this.code = code; + this.appId = appId; + this.value = value; + } + + public static String getAppIdByCode(int code) { + for (LlmApplicationAppIdEnum c : LlmApplicationAppIdEnum.values()) { + if (c.code == code) { + return c.appId; + } + } + return null; + } +} diff --git a/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmStrategyEnum.java b/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmStrategyEnum.java index fe13c1d..b06d73c 100644 --- a/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmStrategyEnum.java +++ b/src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmStrategyEnum.java @@ -10,6 +10,7 @@ public enum LlmStrategyEnum { + ALI_APPLICATION(3,"AliApplicationLlmStrategyServiceImpl","阿里百炼工作流"), ALI(2,"AliLlmStrategyService","阿里百炼大模型"), HS(1,"HsLlmStrategyService","火山大模型"); -- Gitblit v1.9.1