官網文檔
阿里靈積提供了詳細的官方文檔
如何實現多輪對話
官方文檔中提到只需要把每輪對話中返回結果添加到消息管理器中,就可以實現多輪對話。本質上就是將歷史對話再次發送給接口。
如何實現流式輸出
官方文檔中提出使用streamCall()方法就可以實現流式輸出,在ResultCallback<GenerationResult>參數中可以指點每個事件的處理動作。
流式調用方法沒有返回GenerationResult結果類,如何實現多輪對話
方法一
我們每次調用完成后把得到的結果手動構建消息對象并加入消息管理類。
不知道是不是我使用的sdk版本問題(因為老的版本有出現調用okhttp報錯的情況,我的在阿里云提交工單后,工作人員給我的最新版本是2.10.1,我當前就在使用這個版本)。經過實際測試,msgManager.get()方法可能會出現第一條對話的發送對象是assistant的情況。
如果第一條對話的發送對象不是user或者system,并且user和assistant沒有在歷史對話中輪流出現接口會報錯的!!!!(我沒有報錯的截圖,哈哈哈哈)
Message assistantMsg = Message.builder().role(Role.ASSISTANT.getValue()).content("如何做西紅柿燉牛腩?").build();
msgManager.add(assistantMsg);
方法二
我們自己來控制歷史對話
@Component
public class QwenModelService{private Generation gen;@Resourceprivate AiWebsocketService aiWebsocketService;public void createGen(){gen = new Generation();};private static final Logger logger = LoggerFactory.getLogger(QwenModelService.class);/*** prompt 用戶對話* request 用戶請求對象* identity 用戶身份標識*/public String answer(String prompt, HttpServletRequest request, String identity) {// 通過身份標識在緩存中獲取對話對象、歷史消息對象、參數對象List<AiDialogue> dialogues = CachePool.AI_DIALOGUE_LIST_MAP.get(identity).computeIfAbsent(ConstValuePool.QWEN_DIALOGUES, k -> new LinkedList<>());dialogues.add(AiDialogue.createUserDialogue(prompt));List<Message> msgManager = CachePool.QWEN_MESSAGE_DIALOGUES_MAP.get(identity);QwenParam param = CachePool.QWEN_PARAM_MAP.get(identity);// 如果第一次發送消息需要初始化歷史消息對象if (msgManager == null) {msgManager = new ArrayList<>();CachePool.QWEN_MESSAGE_DIALOGUES_MAP.put(identity, msgManager);Message systemMsg = Message.builder().role(Role.SYSTEM.getValue()).content("You are a helpful assistant.").build();msgManager.add(systemMsg);Message userMsg = Message.builder().role(Role.USER.getValue()).content(prompt).build();msgManager.add(userMsg);}else {msgManager.add(Message.builder().role("user").content(prompt).build());param.setMessages(msgManager);}// 如果第一次發送消息需要初始化參數對象if (param == null) {param = QwenParam.builder().model(Generation.Models.QWEN_MAX).messages(msgManager).resultFormat(QwenParam.ResultFormat.MESSAGE).topP(0.8).enableSearch(true).incrementalOutput(true).build();CachePool.QWEN_PARAM_MAP.put(identity, param);}try {logger.debug("發送的請求為{}",param);// 同步信號量Semaphore semaphore = new Semaphore(0);// 結果拼接對象StringBuilder resultBuilder = new StringBuilder();// 流式調用gen.streamCall(param, new ResultCallback<GenerationResult>(){@Overridepublic void onEvent(GenerationResult generationResult) {String newMessage = generationResult.getOutput().getChoices().get(0).getMessage().getContent();StringBuilder finalResBuilder = resultBuilder.append(newMessage);// 這里是對markdown代碼塊進行判斷,如果當前代碼塊未結束,需要手動結束// 否則前端的代碼塊顯示會出問題// 代碼塊判斷的功能就是對"```"字符串計數,偶數個就是結束了,奇數個就是沒結束if (1 == (1 & StringUtil.countSubStr(finalResBuilder,ConstValuePool.MARKDOWN_CODE_BLOCK_START))) {finalResBuilder = new StringBuilder(finalResBuilder).append(ConstValuePool.MARKDOWN_CODE_BLOCK_END);}// 通過websocket返回給前端aiWebsocketService.sendMessage(finalResBuilder.toString(), identity);}// 結束或者報錯需要釋放同步信號量@Overridepublic void onComplete() {semaphore.release();}@Overridepublic void onError(Exception e) {semaphore.release();logger.error("通義千問運行出錯, 報錯棧如下");Throwable t = e;while (t != null) {logger.error( t.toString());t = e.getCause();}}});semaphore.acquire();String resString = resultBuilder.toString();// 把返回消息加入歷史消息中
msgManager.add(Message.builder().role("assistant").content(resString).build());// 如果歷史消息量過大或者第一條消息發送對象不是user,刪除歷史消息// 下標0是system消息while (msgManager.size() > ConstValuePool.QWEN_MAX_MESSAGE|| !"user".equals(msgManager.get(1).getRole())) {msgManager.remove(1);}// 添加到對話記錄中,方便前端查詢對話記錄dialogues.add(AiDialogue.createAssistantDialogue(resString));return "";} catch (NoApiKeyException e) {logger.error("調用通義千問缺少ApiKey");throw new AiException("沒有ApiKey", e);} catch (Exception e) {logger.error("調用通義千問出現問題:{}",e.getMessage());throw new AiException("出現了一些問題", e);}}}