《通义千问AI落地—上》:后端接口

一、前言

本文源于微博客且已获授权,请尊重版权.

     通义,由通义千问更名而来,是阿里云推出的语言模型 ,于2023年9月13日正式向公众开放。 属于(AI Generated Content,AIGC)领域, 是一个MaaS(模型即服务)的底座。为多模态大模型(Multimodal Models)。

     通义意为“通情,达义”,具备全副AI能力,致力于成为人们的工作、学习、生活助手。 功能包括多轮对话、文案创作、逻辑推理、多模态理解、多语言支持,能够跟人类进行多轮的交互,也融入了多模态的知识理解,且有文案创作能力,能够续写小说,编写邮件等。

     虽然通义千问已经开源,但是连最基础的 Qwen2-0.5B ,也需要起码16GB的显存以上才能流程云运行(本人4060 8GB显存的显卡跑起来,等待一个问答结果,需要好几分钟),所以,私有化部署大模型所需要承受的硬件代价,不是一般的玩家所能承受的。此路不通,只得另寻他法了,这里所指的他法,就是购买通义千问的Token接口服务,价格也相当实惠:

image.png

其他信息本文不再赘述,请移步 通义千问官网 进行查看和购买。

二、成果展示

     最终的效果如下所示:

image.png

三、接口正文

3.1、maven依赖

      通义千问maven依赖主要有一下两个:

  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-ai</artifactId>
  </dependency>
	  <!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
   <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dashscope-sdk-java</artifactId>
      <version>2.15.2</version>
   </dependency>

其中,dashscope-sdk-java的版本需要根据你的jdk版本调整。

3.2、接口

  1. interface
public interface IChatGPTService {
    void chat(ChatMessageRequest content, Principal principal) throws NoApiKeyException, InputRequiredException;
}
  1. impl

    /**
     * 历史对话记录;  sessionId---> 历史记录
     */
    private static final ConcurrentHashMap<String, List<Message>> history = new ConcurrentHashMap<>();

@Override
    public void chat(ChatMessageRequest msg, Principal principal) throws NoApiKeyException, InputRequiredException {
        String sessionId = msg.getSessionId();

	//用户发送的消息入库
        CompletableFuture.runAsync(() -> {
            saveMsg(msg.getContent(), sessionId, Role.USER, getLocalDate());
        });
        Message message = Message.builder().role(Role.USER.getValue()).content(msg.getContent()).build();

        // 创建QwenParam对象,设置参数
        GenerationParam param = GenerationParam.builder()
                .model(module) // 模型版本 qwen-max
                .messages(getHistory(sessionId)) // 消息内容,如果需要启用多伦连续对话的话,就把用户历史消息以及GPT回复的消息一起放进去
                .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                .topP(0.8)
                .enableSearch(true)
                .apiKey(apiKey)  // 你的apiKey,需要到阿里云百炼官网申请
                .incrementalOutput(true)
                .build();
        
        // 调用生成接口,获取Flowable对象
        Flux<GenerationResult> result = Flux.from(gen.streamCall(param));

        StringBuffer builder = new StringBuffer();

        DateTime finalLocalTime = getLocalDate();

        Flux.from(result)
                // 控制发送频率
                .delayElements(Duration.ofMillis(200)).doOnNext(res -> {
                    String output = res.getOutput().getChoices().get(0).getMessage().getContent();
                    if (output == null || "".equals(output)) {
                        return;
                    }
		    // 将生成的消息通过websocket发送给前端,websocket内容将在下篇文章介绍
                    sendMsg(output, sessionId, principal);

                    builder.append(output);
                }).doFinally(signalType -> {
                    //消息发送结束,告诉前端
                    sendMsg("!$$---END---$$!", sessionId, principal);
		    //消息入库
                    CompletableFuture.runAsync(() -> {
                        saveMsg(builder.toString(), sessionId, Role.ASSISTANT, finalLocalTime);
                        buildHistory(sessionId,
                                Message.builder().role(Role.ASSISTANT.getValue()).content(builder.toString()));
                    });
                }).onErrorResume(str -> {
                    if (str instanceof ApiException) {
                        ApiException exception = (ApiException) str;
                        log.error("接口调用出现错误:{}", exception.getMessage());
                    }
                    sendMsg("GPT接口调用出现错误,该功能暂时无法使用,敬请期待.", sessionId, principal);
                    return Mono.empty();
                }).subscribeOn(Schedulers.boundedElastic()) // 在弹性线程池中执行
                .subscribe();

    }

    /**
     * 每日凌晨自动清理历史对话缓存,防止缓存过大
     */
    @Scheduled(cron = "0 59 23 * * ?")
    private void autoCleanHistory() {
        history.clear();
    }

    /**
     * 构建历史消息
     */
    private void buildHistory(String sessionId, MessageBuilder<?, ?> message) {
        List<Message> historyMessages = history.computeIfAbsent(sessionId, k -> {
            List<ChatMessageVO> list = sessionService.getById(sessionId).getMessages();
            List<Message> getMsgList = new ArrayList<>();

            if (list.isEmpty()) return getMsgList;

            MessageBuilder<?, ?> msg = Message.builder();
            //只取后面60条,历史消息太多,一是过快消耗token,二是压力太大
            list.subList(Math.max(0, list.size() - 60), list.size()).forEach(item -> {
                if (!"".equals(item.getContent())) {
                    msg.content(item.getContent()).role(item.getRole()).build();
                    getMsgList.add(msg.build());
                }
            });
            return getMsgList;
        });
        // 添加消息到列表
        historyMessages.add(message.build());
        history.remove(sessionId);
        history.put(sessionId, historyMessages);
    }

    private List<Message> getHistory(String sessionId) {
        List<Message> list = history.get(sessionId);
        if (list == null || list.isEmpty()) {
            return new ArrayList<>();
        }
        list.removeIf(item -> ("".equals(item.getContent())));

        List<Message> hist = list.subList(Math.max(0, list.size() - 80), list.size());

        history.remove(sessionId);
        history.put(sessionId, hist);
        return hist;
    }

  1. 接口调用
@Controller
@AllArgsConstructor
@Slf4j
@CrossOrigin
public class WebSocketController {
    private final IChatGPTService service;
    private final SocketServiceImpl socketService;
   // 前端通过websocket发送消息给GPT,调用相关接口生成内容
    @MessageMapping("/chat/send")
    public void chat(@Payload ChatMessageRequest message, Principal principal) throws NoApiKeyException,
            InputRequiredException {
        service.chat(message, principal);
    }
}

其中涉及到的一些Java实体如下:

@Data
@Accessors(chain = true)
@RequiredArgsConstructor
public class ChatMessageRequest extends BaseEntity {
    private String content;
    private String role;
    private String sessionId;
}
    //上文提到的getLocalDate函数内容
    public static DateTime getLocalDate() {
        ZoneId zoneId = ZoneId.of("Asia/Shanghai");
        ZonedDateTime now = ZonedDateTime.now(zoneId);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(zoneId);

        String formattedDate = now.format(formatter);

        try {
            ZonedDateTime parsedDate = ZonedDateTime.parse(formattedDate, formatter);
            return new DateTime(parsedDate.toInstant().toEpochMilli());
        }
        catch (DateTimeParseException e) {
            e.printStackTrace();
            return cn.hutool.core.date.DateUtil.parse(cn.hutool.core.date.DateUtil.now());
        }
    }

     通义千问主要的接口调用如上所述。在中篇,我将介绍《通义千问AI落地》前端实现。

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值