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