赞
踩
近来工作比较清闲、当然这也得益于AI技术的日益成熟、由于一直使用的是发小公司的AI大模型产品、博主也没有跟上潮流去研究如何接入个人项目、本着知其然知其所以然,近几天就来浅浅的探究一下如何接入个人项目。
由于我个人比较懒,又比较抠,本着能省则省的原则,最终选择国产大模型通义千问作为本次接入的大模型之一、接入模型的方法千篇一律,一通百通,最终还是需要落脚在业的设计上。下面是java项目接入千问流程。
参考文章[:通义千问模型服务]
private static GenerationParam buildGenerationParam(Message userMsg) {
return GenerationParam.builder()
.model("qwen-turbo")
.messages(Arrays.asList(userMsg))
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
.topP(0.8)
.incrementalOutput(true)
.build();
}
进一步得到Message的属性,这便于我们进行相关表结构的设计
public class Message {
String role;
String content;
@SerializedName("tool_calls")
List<ToolCallBase> toolCalls;
@SerializedName("tool_call_id")
String toolCallId;
@SerializedName("name")
String name;
其中需要关注的暂时只有 role content,role对应的为身份标识,content是输出的内容,其中role有以下几种类型
USER("user"),
ASSISTANT("assistant"),
BOT("bot"),
SYSTEM("system"),
ATTACHMENT("attachment"),
TOOL("tool");
常用的就是user和assistant一个是用户发送,一个是系统回复类型。
基于demo代码设计可知
基于以上两点,我在数据库建立了两张数据表一张为问题表,一张为回复表用以解决历史对话问题,新增了会话id即sessionId属性用来控制连续一段时间的对话。以下是代码实现、仅供参考,如有高见请私信或者评论指出,不胜感激。
CREATE TABLE `chat_answer_history` ( `id` varchar(50) NOT NULL COMMENT '主键id', `user_id` bigint NOT NULL COMMENT '用户id', `session_id` varchar(30) NOT NULL COMMENT '会话id', `question_id` bigint NOT NULL COMMENT '问题id', `answer` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '答案', `message_role` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '角色', `message_tool_calls` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, `message_tool_call_id` varchar(64) DEFAULT '', `message_name` varchar(64) DEFAULT '', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', PRIMARY KEY (`id`), KEY `session_id_index` (`session_id`) USING BTREE, KEY `question_id_index` (`question_id`) USING BTREE, KEY `user_id_index` (`user_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='答案历史对话表'; CREATE TABLE `chat_question_history` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `user_id` bigint NOT NULL COMMENT '用户id', `session_id` varchar(30) NOT NULL COMMENT '会话id', `question` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '问题', `question_file_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '文件名称', `question_file_url` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '文件地址', `model` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '模型类型', `assistant` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '定义模型方向', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_by` varchar(255) DEFAULT NULL COMMENT '创建者', `message_role` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '角色', PRIMARY KEY (`id`), KEY `session_id_index` (`session_id`) USING BTREE, KEY `user_id_index` (`user_id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=215 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='提问历史对话表';
此处给出基本实现逻辑
import com.alibaba.dashscope.aigc.generation.GenerationResult; import com.kingoffice.common.core.web.controller.BaseController; import com.kingoffice.common.core.web.page.TableDataInfo; import com.kingoffice.common.security.utils.SecurityUtils; import com.kingoffice.system.domain.vo.ChatHistoryVo; import com.kingoffice.system.domain.vo.ChatQuestionVo; import com.kingoffice.system.service.aigc.IChatQuestionHistoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Transient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import java.util.Comparator; import java.util.List; @RestController @RequestMapping("/chat") public class ChatController extends BaseController{ @Autowired private IChatQuestionHistoryService questionHistoryService; /** * 获取gpt返回 * * @param chatQuestionVo * @return */ @Transient @PostMapping(path = "/getChat") public Flux<GenerationResult> gptChat( @RequestBody ChatQuestionVo chatQuestionVo) { return questionHistoryService.gptChat(chatQuestionVo); } /** * 历史对话 */ @GetMapping("/historyList") public TableDataInfo historyList() { startPage(); List<ChatHistoryVo> list = questionHistoryService.historyList(SecurityUtils.getUserId()); list.sort(Comparator.comparing(ChatHistoryVo::getCreateTime)); return getDataTable(list); } } //入参 @Data public class ChatQuestionVo { /** * 问题 */ private String question; /** * 会话id */ private String sessionId; /** * 调用模型 */ private String model; /** * 助手类型 */ private String assistant; private String questionFileName; private String questionFileUrl; }
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.aigc.generation.models.QwenParam; import com.alibaba.dashscope.common.Message; import com.alibaba.dashscope.common.ResultCallback; import com.alibaba.dashscope.common.Role; import com.alibaba.dashscope.utils.Constants; import com.kingoffice.common.core.constant.ChatModelConstants; import com.kingoffice.common.core.utils.DateUtils; import com.kingoffice.common.security.utils.SecurityUtils; import com.kingoffice.system.domain.aigc.ChatAnswerHistory; import com.kingoffice.system.domain.aigc.ChatQuestionHistory; import com.kingoffice.system.domain.vo.ChatHistoryVo; import com.kingoffice.system.domain.vo.ChatQuestionVo; import com.kingoffice.system.mapper.aigc.ChatAnswerHistoryMapper; import com.kingoffice.system.mapper.aigc.ChatQuestionHistoryMapper; import com.kingoffice.system.service.aigc.IChatQuestionHistoryService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 提问历史对话Service业务层处理 * * @author kim * @date 2024-07-19 */ @Service public class ChatQuestionHistoryServiceImpl implements IChatQuestionHistoryService { @Autowired private ChatQuestionHistoryMapper chatQuestionHistoryMapper; @Autowired private ChatAnswerHistoryMapper chatAnswerHistoryMapper; @Value("${chat.accessKeyAli}") private String accessKeyAli; @Value("${chat.accessKeyMoon}") private String accessKeyMoon; @Override public Flux<GenerationResult> gptChat(ChatQuestionVo question) { StringBuilder contentBuilder = new StringBuilder(); Constants.apiKey = accessKeyAli; // 密钥 Generation gen = new Generation(); // 创建流 // 获取用户信息 Long userId = SecurityUtils.getUserId(); String username = SecurityUtils.getUsername(); if (StringUtils.isEmpty(question.getSessionId())) { //创建新的会话 question.setSessionId(String.valueOf(System.currentTimeMillis())); } else { long sessionTime = Long.parseLong(question.getSessionId()); if (System.currentTimeMillis() - sessionTime > 5 * 60 * 1000) { //会话超时 自动更新会话 question.setSessionId(String.valueOf(System.currentTimeMillis())); } } // 加载历史message,并创建新的message List<Message> messages = conversationHistory(question.getSessionId()); messages.add(Message.builder().role(Role.USER.getValue()).content(question.getQuestion()).build()); // 保存用户问题历史记录 GenerationParam param = buildGenerationParam(messages); // GenerationParam param = buildQwenParam(messages); return Flux.create(sink -> { try { gen.streamCall(param, new ResultCallback<GenerationResult>() { @Override public void onEvent(GenerationResult message) { String newContent = message.getOutput().getChoices().get(0).getMessage().getContent(); //改造message属性 为此属性赋值sessionId message.getOutput().getChoices().get(0).getMessage().setToolCallId(question.getSessionId()); contentBuilder.append(newContent); if ("stop".equals(message.getOutput().getChoices().get(0).getFinishReason())) { ChatQuestionHistory history = new ChatQuestionHistory(); BeanUtils.copyProperties(question, history); history.setUserId(userId); history.setCreateBy(username); history.setMessageRole(Role.USER.getValue()); history.setCreateTime(new Date()); chatQuestionHistoryMapper.insertChatQuestionHistory(history); ChatAnswerHistory answerHistory = new ChatAnswerHistory(); answerHistory.setId(message.getRequestId()); answerHistory.setAnswer(contentBuilder.toString()); answerHistory.setUserId(userId); answerHistory.setSessionId(question.getSessionId()); answerHistory.setQuestionId(history.getId()); answerHistory.setMessageRole(message.getOutput().getChoices().get(0).getMessage().getRole()); answerHistory.setCreateBy(username); answerHistory.setCreateTime(new Date()); chatAnswerHistoryMapper.insertChatAnswerHistory(answerHistory); contentBuilder.setLength(0); // 清空StringBuilder } sink.next(message); } @Override public void onError(Exception err) { sink.error(err); } @Override public void onComplete() { sink.complete(); } }); } catch (Exception e) { sink.error(e); } }); } @Override public List<ChatHistoryVo> historyList(Long userId) { return chatQuestionHistoryMapper.historyList(userId); } private GenerationParam buildGenerationParam(List<Message> messages) { return GenerationParam.builder() .model(ChatModelConstants.Models.QWEN_MAX) .messages(messages) .resultFormat(GenerationParam.ResultFormat.MESSAGE) .topP(0.8) .incrementalOutput(true) .build(); } //接入开源模型 chatGLM private QwenParam buildQwenParam(List<Message> messages) { return QwenParam.builder().model("chatglm3-6b").messages(messages) .resultFormat(QwenParam.ResultFormat.MESSAGE) .topP(0.8) .incrementalOutput(true) // get streaming output incrementally .build(); } private List<Message> conversationHistory(String sessionId) { //根据用户id 会话id 查询10条记录 List<Message> messages = new ArrayList<>(); if (StringUtils.isEmpty(sessionId)) { return messages; } messages = chatAnswerHistoryMapper.conversationHistory(SecurityUtils.getUserId(), sessionId); return messages; } }
此处给出sql
<select id="conversationHistory" resultType="com.alibaba.dashscope.common.Message"> SELECT content, role FROM ( SELECT question AS content, message_role AS role, create_time FROM kim_chat_question_history WHERE user_id = #{userId} AND session_id = #{sessionId} UNION ALL SELECT answer AS content, message_role AS role, create_time FROM kim_chat_answer_history WHERE user_id = #{userId} AND session_id = #{sessionId} ) AS combined_data ORDER BY create_time LIMIT 10 </select> <select id="historyList" resultType="com.kingoffice.system.domain.vo.ChatHistoryVo"> SELECT t1.question, t1.id AS questionId, t1.create_time AS createTime, t2.answer, t2.id AS answerId FROM kim_chat_question_history t1 INNER JOIN kim_chat_answer_history t2 ON t1.id = t2.question_id WHERE t1.user_id = #{userId} ORDER BY t1.create_time DESC </select>
至此,后端已实现多轮对话以及流式输出,代码仍有待完善的地方,比如对于token的控制,参数校验,会话时长等等都是需要考虑的问题,后续在给出具体的优化建议,另外关注到Spring新出了SpringAI组件可以快速的实现众多大模型的调用,还有阿里也推出了同样的微服务组件,感兴趣的小伙伴可以去看下,我们此处不再赘述,下一篇我们进行前端调用后端流式接口的demo展示。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。