SpringBoot使用百度智能云AppBuilder实现AI对话

1. 创建Appbuilder应用

AppBuilder的快速入门:快速入门 - 千帆AppBuilder-产品文档

SDK调用AppBuilder的组件:应用调用 - 千帆AppBuilder-产品文档

然后获取到 AppIdToken 然后配置到 application.yml 中

baiduQianFan:
  appBuilder:
    appId: xxx
    token: xxxxxxx

2. 添加依赖

<dependency>
    <groupId>com.baidubce</groupId>
	<artifactId>appbuilder</artifactId>
	<version>0.9.4</version>
</dependency>

<dependency>
	<groupId>org.yeauty</groupId>
	<artifactId>netty-websocket-spring-boot-starter</artifactId>
	<version>0.9.5</version>
</dependency>

3. 新建WebSocket服务

package org.jeecg.modules.system.myWebsocket;

import com.alibaba.fastjson.JSON;
import com.baidubce.appbuilder.base.exception.AppBuilderServerException;
import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientIterator;
import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.system.constant.AIChatMessageType;
import org.jeecg.modules.system.entity.AppBuilderNewClient;
import org.jeecg.modules.system.entity.XxstudyAiChat;
import org.jeecg.modules.system.entity.XxstudyAiChatMessage;
import org.jeecg.modules.system.mapper.XxstudyAiChatMapper;
import org.jeecg.modules.system.mapper.XxstudyAiChatMessageMapper;
import org.jeecg.modules.system.util.ImageFileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.yeauty.annotation.*;
import org.yeauty.pojo.Session;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *智慧问答WebSocket服务
 */
@Slf4j
@ServerEndpoint(port = "9097", path = "/ai/chat/{chatId}/{userId}", maxFramePayloadLength = "6553600", allIdleTimeSeconds = "300")
public class WebSocketAiChatServer {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, List<String>> userIdPool = new ConcurrentHashMap<>();
    private static final CopyOnWriteArraySet<WebSocketAiChatServer> webSocketSet = new CopyOnWriteArraySet<>();

    private Session session;
    private String userId;
    private String chatId;
    private final Map<Session, String> sessionToUserIdMap = new ConcurrentHashMap<>();

    @Value("${baiduQianFan.appBuilder.appId}")
    private String appBuilderAppId;

    @Value("${baiduQianFan.appBuilder.token}")
    private String appBuilderToken;

    @Autowired
    private XxstudyAiChatMessageMapper xxstudyAiChatMessageMapper;

    @Autowired
    private XxstudyAiChatMapper xxstudyAiChatMapper;

    @Autowired
    private RedisUtil redisUtil;

    private final String AI_AVATAR = "https://www.ddnn.vip/static/img/logo.png";

    private final String NO_RESULT = "我暂时无法回答这个问题";

    @OnOpen
    public void onOpen(Session session, @PathVariable String chatId, @PathVariable String userId) {
        webSocketSet.add(this);
        this.userId = userId;
        this.chatId = chatId;
        this.session = session;
        String sessionKey = chatId + "||" + userId;
        sessionPool.put(sessionKey, session);
        sessionToUserIdMap.put(session, sessionKey);
        System.setProperty("APPBUILDER_TOKEN", appBuilderToken);
        userIdPool.computeIfAbsent(chatId, k -> new ArrayList<>()).add(sessionKey);
        log.info("【websocketAIChat消息】有新的连接,连接用户为:{},总数为:{}", userId, webSocketSet.size());
    }

    @OnMessage
    public void onMessage(Session session, String message) {
        String userId = sessionToUserIdMap.getOrDefault(session, "");

        if ("ping".equals(message) || message.equals("heartbeat")) {
            sendOneMessage(userId, "pong");
            return;
        }

        processMessage(message);
    }

    private void processMessage(String message) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        log.info("处理AI聊天消息=====" + message);
        try {
            executorService.submit(() -> {
                try {
                    XxstudyAiChatMessage xxstudyAiChatMessage = JSON.parseObject(message, XxstudyAiChatMessage.class);
                    String sceneId = xxstudyAiChatMessage.getSceneId(), chatId = xxstudyAiChatMessage.getChatId(), userId;
                    Integer type = xxstudyAiChatMessage.getType();
                    if (StringUtils.isNotBlank(sceneId)) {
                        String[] scenes = redisUtil.get(sceneId).toString().split("_");
                        chatId = scenes[0];
                        userId = scenes[1];
                        xxstudyAiChatMessage.setChatId(chatId);
                        xxstudyAiChatMessage.setUserId(userId);
                        xxstudyAiChatMessage.setUserName(scenes[2]);
                        xxstudyAiChatMessage.setAvatar(scenes[3]);
                    }
                    //根据会话id查询此会话中所有消息
                    QueryWrapper<XxstudyAiChatMessage> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("chat_id", chatId);
                    queryWrapper.orderByDesc("create_time");
                    List<XxstudyAiChatMessage> xxstudyAiChatMessageList = xxstudyAiChatMessageMapper.selectList(queryWrapper);
                    if (xxstudyAiChatMessageList.isEmpty()) {
                        AppBuilderNewClient appBuilderClient = new AppBuilderNewClient(appBuilderAppId);
                        // 生成会话id(需保存下来,可以传入下次聊天中进行对话续写)
                        String conversationId = appBuilderClient.createConversation();
                        XxstudyAiChat xxstudyAiChat = xxstudyAiChatMapper.selectById(chatId);
                        xxstudyAiChat.setSessionId(conversationId);
                        xxstudyAiChatMapper.updateById(xxstudyAiChat);
                    }
                    if (type == AIChatMessageType.TEXT.getValue()) {
                        //保存消息
                        xxstudyAiChatMessage.setRole(1);
                        xxstudyAiChatMessage.setCreateTime(new Date());
                        xxstudyAiChatMessageMapper.insert(xxstudyAiChatMessage);
                        xxstudyAiChatMessageList = xxstudyAiChatMessageMapper.selectList(queryWrapper);
                        //用户发送文字
                        handleTextMessages(xxstudyAiChatMessageList, chatId);
                    }else if(type == AIChatMessageType.IMAGE.getValue()) {
                        //用户发送图片
                        handleImgMessages(xxstudyAiChatMessage);
                    }else {
                        //用户发送视频
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }

            });
        } finally {
            executorService.shutdown();
        }
    }

    /**
     * 处理用户发送的文本消息
     * @param xxstudyAiChatMessageList 消息列表
     * @param chatId    会话id
     * @throws IOException
     */
    private void handleTextMessages(List<XxstudyAiChatMessage> xxstudyAiChatMessageList, String chatId) throws Exception {
        String userIdKey = chatId + "||" + userId;
        XxstudyAiChatMessage message = new XxstudyAiChatMessage();
        AppBuilderNewClient appBuilderClient = new AppBuilderNewClient(appBuilderAppId);
        XxstudyAiChat xxstudyAiChat = xxstudyAiChatMapper.selectById(chatId);
        StringBuilder result = new StringBuilder();

        message.setChatId(chatId);
        message.setUserName("AI");
        message.setType(1);
        message.setRole(2);
        log.info("【AIChat文本消息】------------");
        // 多轮对话 流式请求
        XxstudyAiChatMessage finalMessage = message;
        AppBuilderClientIterator iterator = appBuilderClient.run(xxstudyAiChatMessageList.get(0).getMessage(), xxstudyAiChat.getSessionId(), new String[]{}, true);
        while(iterator.hasNext())
        {
            AppBuilderClientResult response = iterator.next();
            result.append(response.getAnswer());
            finalMessage.setMessage(response.getAnswer());
            finalMessage.setIsEnd(!iterator.hasNext());
            finalMessage.setAvatar(AI_AVATAR);
            if (!iterator.hasNext() && !StringUtils.isNotBlank(result.toString().trim())) {
                result.append(NO_RESULT);
                finalMessage.setMessage(NO_RESULT);
            }
            sendOneMessage(userIdKey, serializeToJson(finalMessage));
            log.info(response.getAnswer());
        }
        message.setMessage(ImageFileUtils.getMarkDownImageContent(result.toString()));
        message.setAvatar(AI_AVATAR);
        message.setCreateTime(new Date());
        xxstudyAiChatMessageMapper.insert(message);
    }

    /**
     * 处理用户发送的图片消息
     * @param xxstudyAiChatMessage
     * @throws IOException
     */
    private void handleImgMessages(XxstudyAiChatMessage xxstudyAiChatMessage) throws IOException, AppBuilderServerException {
        XxstudyAiChatMessage message = new XxstudyAiChatMessage();
        AppBuilderNewClient appBuilderClient = new AppBuilderNewClient(appBuilderAppId);
        String chatId = xxstudyAiChatMessage.getChatId(), userId = xxstudyAiChatMessage.getUserId();
        StringBuilder result = new StringBuilder();
        XxstudyAiChat xxstudyAiChat = xxstudyAiChatMapper.selectById(xxstudyAiChatMessage.getChatId());
        xxstudyAiChatMessage.setRole(1);
        String userIdKey = chatId + "||" + userId;
        message.setChatId(chatId);
        message.setUserName("AI");
        message.setType(1);
        message.setRole(2);
        XxstudyAiChatMessage finalMessage = message;
        //如果发送过来的是图片则需要上传到会话中
        String fileId = StringUtils.isNotBlank(xxstudyAiChatMessage.getSceneId()) ? appBuilderClient.uploadLocalFile(xxstudyAiChat.getSessionId(), xxstudyAiChatMessage.getMessage()) : "";
        AppBuilderClientIterator iterator = appBuilderClient.run(StringUtils.isNotBlank(xxstudyAiChatMessage.getSceneId()) ? " " : xxstudyAiChatMessage.getMessage(), xxstudyAiChat.getSessionId(), new String[]{fileId}, true);
        xxstudyAiChatMessage.setFileId(fileId);
        xxstudyAiChatMessage.setMessage("![\"图片\"](" + xxstudyAiChatMessage.getMessage() + ")");
        //保存消息
        xxstudyAiChatMessageMapper.insert(xxstudyAiChatMessage);
        if (StringUtils.isNotBlank(xxstudyAiChatMessage.getSceneId())) {
            xxstudyAiChatMessage.setIsEnd(true);
            sendOneMessage(userIdKey, serializeToJson(xxstudyAiChatMessage));
        }
        while(iterator.hasNext())
        {
            AppBuilderClientResult response = iterator.next();
            result.append(response.getAnswer());
            finalMessage.setMessage(response.getAnswer());
            finalMessage.setIsEnd(!iterator.hasNext());
            finalMessage.setAvatar(AI_AVATAR);
            finalMessage.setFileId(fileId);
            if (!iterator.hasNext() && !StringUtils.isNotBlank(result.toString().trim())) {
                result.append(NO_RESULT);
                finalMessage.setMessage(NO_RESULT);
            }
            sendOneMessage(userIdKey, serializeToJson(finalMessage));
            log.info(response.getAnswer());
        }
        log.info("【AIChat文本消息】---图生文返回");
        log.info(result.toString());
        message.setMessage(result.toString());
        message.setAvatar(AI_AVATAR);
        message.setCreateTime(new Date());
        xxstudyAiChatMessageMapper.insert(message);
    }

    private String serializeToJson(XxstudyAiChatMessage message) {
        try {
            return OBJECT_MAPPER.writeValueAsString(message);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    @OnClose
    public void onClose(Session session) {
        // 从 set 中移除当前 WebSocket 对象
        webSocketSet.remove(this);
        // 移除 sessionPool 中的记录
        String sessionKey = sessionToUserIdMap.get(session);
        if (sessionKey != null) {
            sessionPool.remove(sessionKey);
        }
        // 更新 userIdPool 中的信息
        List<String> userList = userIdPool.get(chatId);
        if (userList != null) {
            userList.remove(sessionKey);
            if (userList.isEmpty()) {
                userIdPool.remove(chatId);
            }
        }
        log.info("【websocketAIChat消息】连接断开,断连接用户为:{},剩余总数为:{}", userId, webSocketSet.size());
    }

    @OnError
    public void onError(Session session, Throwable cause) {
        // 获取用户 ID 或其他相关信息
        String sessionKey = sessionToUserIdMap.get(session);
        // 记录详细的错误信息
        log.error("websocketAIChat 错误, 用户: {}, 会话 ID: {}, 原因: {}",
                sessionKey,
                session,
                cause.getMessage(),
                cause);
    }

    public void sendOneMessage(String userId, String message) {
        log.info("【WebSocketAiChat消息】进入发送消息流程....");
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            session.sendText(message);
        }
    }

}

因原有Appbuilder SDK自带的文件上传并不符合需求,所以重写了一个如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.jeecg.modules.system.entity;

import com.baidubce.appbuilder.base.component.Component;
import com.baidubce.appbuilder.base.exception.AppBuilderServerException;
import com.baidubce.appbuilder.base.utils.http.HttpResponse;
import com.baidubce.appbuilder.base.utils.json.JsonUtils;
import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientIterator;
import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientResponse;
import com.baidubce.appbuilder.model.appbuilderclient.AppBuilderClientRunRequest;
import com.baidubce.appbuilder.model.appbuilderclient.ConversationResponse;
import com.baidubce.appbuilder.model.appbuilderclient.FileUploadResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.jeecg.modules.system.util.ImageFileUtils;

public class AppBuilderNewClient extends Component {
    public String appID;

    public AppBuilderNewClient(String appID) {
        this.appID = appID;
    }

    public AppBuilderNewClient(String appID, String secretKey) {
        super(secretKey);
        this.appID = appID;
    }

    public AppBuilderNewClient(String appID, String secretKey, String gateway) {
        super(secretKey, gateway);
        this.appID = appID;
    }

    public String createConversation() throws IOException, AppBuilderServerException {
        return this.innerCreateConversation();
    }

    private String innerCreateConversation() throws IOException, AppBuilderServerException {
        String url = "/app/conversation";
        if (this.appID != null && !this.appID.isEmpty()) {
            Map<String, String> requestBody = new HashMap();
            requestBody.put("app_id", this.appID);
            String jsonBody = JsonUtils.serialize(requestBody);
            ClassicHttpRequest postRequest = this.httpClient.createPostRequestV2(url, new StringEntity(jsonBody, StandardCharsets.UTF_8));
            postRequest.setHeader("Content-Type", "application/json");
            HttpResponse<ConversationResponse> response = this.httpClient.execute(postRequest, ConversationResponse.class);
            ConversationResponse respBody = (ConversationResponse)response.getBody();
            return respBody.getConversationId();
        } else {
            throw new RuntimeException("Param 'appID' is required!");
        }
    }

    public String uploadLocalFile(String conversationId, String fileUrl) throws IOException, AppBuilderServerException {
        return this.innerUploadLocalFileByUrl(conversationId, fileUrl);
    }

    private String innerUploadLocalFile(String conversationId, String filePath) throws IOException, AppBuilderServerException {
        String url = "/app/conversation/file/upload";
        if (this.appID != null && !this.appID.isEmpty()) {
            MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY).setCharset(StandardCharsets.UTF_8);
            builder.addBinaryBody("file", new File(filePath));
            builder.addTextBody("app_id", this.appID);
            builder.addTextBody("conversation_id", conversationId);
            builder.addTextBody("scenario", "assistant");
            ClassicHttpRequest postRequest = this.httpClient.createPostRequestV2(url, builder.build());
            HttpResponse<FileUploadResponse> response = this.httpClient.execute(postRequest, FileUploadResponse.class);
            FileUploadResponse respBody = (FileUploadResponse)response.getBody();
            return respBody.getFileId();
        } else {
            throw new RuntimeException("Param 'appID' is required!");
        }
    }

    private String innerUploadLocalFileByUrl(String conversationId, String fileUrl) throws IOException, AppBuilderServerException {
        String url = "/app/conversation/file/upload";
        if (this.appID != null && !this.appID.isEmpty()) {
            MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY).setCharset(StandardCharsets.UTF_8);
            builder.addBinaryBody("file", ImageFileUtils.getFile(fileUrl));
            builder.addTextBody("app_id", this.appID);
            builder.addTextBody("conversation_id", conversationId);
            builder.addTextBody("scenario", "assistant");
            ClassicHttpRequest postRequest = this.httpClient.createPostRequestV2(url, builder.build());
            HttpResponse<FileUploadResponse> response = this.httpClient.execute(postRequest, FileUploadResponse.class);
            FileUploadResponse respBody = (FileUploadResponse)response.getBody();
            return respBody.getFileId();
        } else {
            throw new RuntimeException("Param 'appID' is required!");
        }
    }

    public AppBuilderClientIterator run(String query, String conversationId, String[] fileIds, boolean stream) throws IOException, AppBuilderServerException {
        String url = "/app/conversation/runs";
        if (this.appID != null && !this.appID.isEmpty()) {
            Map<String, Object> requestBody = new HashMap();
            requestBody.put("app_id", this.appID);
            requestBody.put("query", query);
            requestBody.put("conversation_id", conversationId);
            requestBody.put("file_ids", fileIds);
            requestBody.put("stream", stream);
            String jsonBody = JsonUtils.serialize(requestBody);
            ClassicHttpRequest postRequest = this.httpClient.createPostRequestV2(url, new StringEntity(jsonBody, StandardCharsets.UTF_8));
            postRequest.setHeader("Content-Type", "application/json");
            HttpResponse<Iterator<AppBuilderClientResponse>> response = this.httpClient.executeSSE(postRequest, AppBuilderClientResponse.class);
            return new AppBuilderClientIterator((Iterator)response.getBody());
        } else {
            throw new RuntimeException("Param 'appID' is required!");
        }
    }

    public AppBuilderClientIterator run(AppBuilderClientRunRequest requestBody) throws IOException, AppBuilderServerException {
        String url = "/app/conversation/runs";
        if (this.appID != null && !this.appID.isEmpty()) {
            String jsonBody = JsonUtils.serialize(requestBody);
            ClassicHttpRequest postRequest = this.httpClient.createPostRequestV2(url, new StringEntity(jsonBody, StandardCharsets.UTF_8));
            postRequest.setHeader("Content-Type", "application/json");
            HttpResponse<Iterator<AppBuilderClientResponse>> response = this.httpClient.executeSSE(postRequest, AppBuilderClientResponse.class);
            return new AppBuilderClientIterator((Iterator)response.getBody());
        } else {
            throw new RuntimeException("Param 'appID' is required!");
        }
    }
}

涉及到的表结构

会话表:

消息表:

4. 基本逻辑

此websocket功能包括 普通纯文本对话文生图图生文

主要逻辑是:使用 AppBuilderNewClient 创建会话

AppBuilderNewClient appBuilderClient = new AppBuilderNewClient(appBuilderAppId);

然后使用 

String conversationId = appBuilderClient.createConversation();

获取到会话id并保存在数据库中,下次会话直接传入此id就可以实现续写

然后实现流式请求

AppBuilderClientIterator iterator = appBuilderClient.run(xxstudyAiChatMessageList.get(0).getMessage(), xxstudyAiChat.getSessionId(), new String[]{}, true);
while(iterator.hasNext())
{
    AppBuilderClientResult response = iterator.next();
    result.append(response.getAnswer());
    finalMessage.setMessage(response.getAnswer());
    finalMessage.setIsEnd(!iterator.hasNext());
    finalMessage.setAvatar(AI_AVATAR);
    if (!iterator.hasNext() && !StringUtils.isNotBlank(result.toString().trim())) {
        result.append(NO_RESULT);
        finalMessage.setMessage(NO_RESULT);
    }
    sendOneMessage(userIdKey, serializeToJson(finalMessage));
    log.info(response.getAnswer());
}

图生文时需要先上传文件

String fileId = appBuilderClient.uploadLocalFile(xxstudyAiChat.getSessionId(), imgUrl);

获取到文件id后再传入对话

AppBuilderClientIterator iterator = appBuilderClient.run( xxstudyAiChatMessage.getMessage(), xxstudyAiChat.getSessionId(), new String[]{fileId}, true);

完结下机!

Chainlit 是一个用于快速构建数据应用程序的库,它结合了 Pandas DataFrame 的操作灵活性和 Flask 应用程序的易用性。AppBuilder 是一个基于 Flask 的 Web 应用框架,提供了用户管理、权限控制等功能,常用于创建企业级的数据应用。 使用 Chainlit 和 AppBuilder 实现智能对话的代码示例并不直接支持链式编程,因为它们的功能重点不同。然而,你可以通过组合这两个工具,首先使用 Chainlit 构建数据处理和分析部分,然后使用 AppBuilder 提供前端界面展示并集成一个聊天机器人服务,如 Dialogflow 或自定义的机器学习模型。 这里给出一个简化的思路: ```python from chainlet import Stream import pandas as pd from flask_appbuilder import BaseView, expose # 假设你有一个数据流(Stream),包含对话历史或聊天记录 dialog_data = Stream.from_pandas(pd.read_csv('chat_history.csv')) class ChatbotView(BaseView): route_base = '/chatbot' @expose('/process_message', methods=['POST']) def process_message(self, message): # 使用 Chainlet 处理数据 processed_data = dialog_data.map(lambda row: process_row(row, message)) # 假设 process_row 函数是你的逻辑,对新消息进行分析 response = process_response(processed_data) return response # 这里只是一个基础示例,实际可能需要整合到AppBuilder的视图体系和模板中 ``` 请注意,这只是一个基本的框架,你需要根据实际需求来编写 `process_row` 和 `process_response` 函数,并将其与 AppBuilder 的前端交互结合起来。同时,你可能还需要配置数据库存储聊天记录,以及处理用户输入等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值