java接入AI大模型个人实践(一)_java如何接入ai

一、前言

大家好,你的月亮我的心,我是博主小阿金,欢迎各位工友。 近来工作比较清闲、当然这也得益于AI技术的日益成熟、由于一直使用的是发小公司的AI大模型产品、博主也没有跟上潮流去研究如何接入个人项目、本着知其然知其所以然,近几天就来浅浅的探究一下如何接入个人项目。

二、模型选择、demo测试

由于我个人比较懒,又比较抠,本着能省则省的原则,最终选择国产大模型通义千问作为本次接入的大模型之一、接入模型的方法千篇一律,一通百通,最终还是需要落脚在业的设计上。下面是java项目接入千问流程。

参考文章:通义千问模型服务

  1. 已开通服务并获得API-KEY:开通DashScope并创建API-KEY
  2. 引入Java SDK:java sdk引入流程
  3. 选择适合的交互方式、单轮对话、多轮对话、流式输出、我选择的是市面上常见的多轮对话+流式输出模式,通过demo中的代码可以看出、要实现多轮对话需要将历史对话的上下文,传递给大模型,以实现对话的持续输出,观察流式输出样例中GenerationParam方法可知,我们只需要将连续的对话以Message集合的形式传入即可。
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代码设计可知

  1. 为了实现连续对话、我们必须将对话历史存储在本地
  2. 需要一定的会话控制逻辑,一是确保连续对话的上下文token,二是对于持续会话时间的控制

基于以上两点,我在数据库建立了两张数据表一张为问题表,一张为回复表用以解决历史对话问题,新增了会话id即sessionId属性用来控制连续一段时间的对话。以下是代码实现、仅供参考,如有高见请私信或者评论指出,不胜感激。

表结构

  1. 表结构设计,两表以问题id关联,确保为一个对话,以user_id、与session_id进行区分不同用户的会话历史。
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='提问历史对话表';

四、多轮对话+流式输出代码实现

此处给出基本实现逻辑

1、请求接口

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;


}

2、接口实现

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展示。

大模型岗位需求

大模型时代,企业对人才的需求变了,AIGC相关岗位人才难求,薪资持续走高,AI运营薪资平均值约18457元,AI工程师薪资平均值约37336元,大模型算法薪资平均值约39607元。
在这里插入图片描述

掌握大模型技术你还能拥有更多可能性

• 成为一名全栈大模型工程师,包括Prompt,LangChain,LoRA等技术开发、运营、产品等方向全栈工程;

• 能够拥有模型二次训练和微调能力,带领大家完成智能对话、文生图等热门应用;

• 薪资上浮10%-20%,覆盖更多高薪岗位,这是一个高需求、高待遇的热门方向和领域;

• 更优质的项目可以为未来创新创业提供基石。

可能大家都想学习AI大模型技术,也想通过这项技能真正达到升职加薪,就业或是副业的目的,但是不知道该如何开始学习,因为网上的资料太多太杂乱了,如果不能系统的学习就相当于是白学。为了让大家少走弯路,少碰壁,这里我直接把全套AI技术和大模型入门资料、操作变现玩法都打包整理好,希望能够真正帮助到大家。

读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用

零基础入门AI大模型

今天贴心为大家准备好了一系列AI大模型资源,包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

有需要的小伙伴,可以点击下方链接免费领取【保证100%免费

点击领取 《AI大模型&人工智能&入门进阶学习资源包》*

1.学习路线图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果大家想领取完整的学习路线及大模型学习资料包,可以扫下方二维码获取
在这里插入图片描述
👉2.大模型配套视频👈

很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,每个章节都是当前板块的精华浓缩。(篇幅有限,仅展示部分)

img

大模型教程

👉3.大模型经典学习电子书👈

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。(篇幅有限,仅展示部分,公众号内领取)

img

电子书

👉4.大模型面试题&答案👈

截至目前大模型已经超过200个,在大模型纵横的时代,不仅大模型技术越来越卷,就连大模型相关的岗位和面试也开始越来越卷了。为了让大家更容易上车大模型算法赛道,我总结了大模型常考的面试题。(篇幅有限,仅展示部分,公众号内领取)

img

大模型面试

**因篇幅有限,仅展示部分资料,**有需要的小伙伴,可以点击下方链接免费领取【保证100%免费

点击领取 《AI大模型&人工智能&入门进阶学习资源包》

**或扫描下方二维码领取 **

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员一粟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值