一、前言
本文源于微博客且已获授权,请尊重版权.
通义
,由通义千问更名而来,是阿里云推出的语言模型 ,于2023年9月13日正式向公众开放。 属于(AI Generated Content,AIGC
)领域, 是一个MaaS(模型即服务)的底座。为多模态大模型(Multimodal Models)。
通义意为“通情,达义”
,具备全副AI能力,致力于成为人们的工作、学习、生活助手。 功能包括多轮对话、文案创作、逻辑推理、多模态理解、多语言支持,能够跟人类进行多轮的交互,也融入了多模态的知识理解,且有文案创作能力,能够续写小说,编写邮件等。
虽然通义千问已经开源,但是连最基础的 Qwen2-0.5B ,也需要起码16GB的显存以上才能流程云运行(本人4060 8GB显存的显卡跑起来,等待一个问答结果,需要好几分钟),所以,私有化部署大模型所需要承受的硬件代价,不是一般的玩家所能承受的。此路不通,只得另寻他法了,这里所指的他法
,就是购买通义千问的Token接口服务,价格也相当实惠:
其他信息本文不再赘述,请移步 通义千问官网 进行查看和购买。
二、成果展示
最终的效果如下所示:
三、接口正文
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、接口
- interface
public interface IChatGPTService {void chat(ChatMessageRequest content, Principal principal) throws NoApiKeyException, InputRequiredException;
}
- impl
/*** 历史对话记录; sessionId---> 历史记录*/private static final ConcurrentHashMap<String, List<Message>> history = new ConcurrentHashMap<>();@Overridepublic 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;}
- 接口调用
@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());}}