Administrator
2025-09-04 46a2e8027a053d4ba7a4dfe357e562c9b41a1047
feat(ai): 添加阿里百炼工作流策略实现

- 新增 AliApplicationLlmStrategyServiceImpl 类实现 LlmStrategyService 接口
- 添加流式调用和非流式调用方法
- 实现基于阿里百炼的工作流调用
- 新增相关枚举类和错误处理
4 files modified
2 files added
314 ■■■■■ changed files
src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkStreamController.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/ApiMemberTalkStreamService.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/service/impl/ApiMemberTalkStreamServiceImpl.java 54 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/strategy/Impl/AliApplicationLlmStrategyServiceImpl.java 208 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmApplicationAppIdEnum.java 37 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmStrategyEnum.java 1 ●●●● patch | view | raw | blame | history
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) {
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);
}
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;
src/main/java/cc/mrbird/febs/ai/strategy/Impl/AliApplicationLlmStrategyServiceImpl.java
New file
@@ -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));
                });
    }
}
src/main/java/cc/mrbird/febs/ai/strategy/enumerates/LlmApplicationAppIdEnum.java
New file
@@ -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;
    }
}
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","火山大模型");