Advertisement

One能聊天接入百度千帆大模型 —— 文心一言

阅读量:

阿壹族

采用基于ChatGPT的技术构建了一个微信小程序,并适用于H5端和Web端应用。该小程序包含前端与后端功能模块,并支持实时打字界面并采用流式输出技术传递信息。具体功能包括但不限于AI驱动的自动回复机制、消息数量限制设置以及分享权限调整功能等

阿壹族

开通服务

请简要介绍百度AI生态产品的全貌

  • 千帆大模型平台:其中大模型开发即为自行训练一个大模型的过程,具有一定技术门槛,少部分企业才会采用这一技术;而大模型调用则包括百度开放文心系列(即文心一言,ERNIE 4.0及ERNIE 3.5作为具体版本参考ChatGPT),同时还提供第三方可用的大模型供调用
    • 千帆AppBuilder:这是一个帮助开发者基于文心大模型快速构建一个AI应用的工具,创建的应用不仅可以集成官方组件(如天气查询、快递查询等)、还可以自定义组件(通过画布拖拽的方式进行功能编排,如调用企业内部API或调用大模型接口),另外还支持导入知识库文件并采用多种格式(txt、pdf、doc、url等多种形式)。通过AppBuilder生成的应用官方会提供访问链接供普通用户使用(界面设计通用的AI聊天界面),同时开发者也可通过SDK接口将创建的应用整合到实际业务系统中。这部分将在后续文章中进行详细讲解
    • 千帆AI原生应用商店:这是百度自身开发的一套AI应用生态系统,例如超级助理等产品,安装后直接在浏览器中使用即可实现各种功能(包括划词翻译、网页解读、OCR识别等功能)

本文主要对文心大模型ERNIE的API调用做详细说明

image.png

生成一个新应用时,请访问 https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application 进行操作。如果您希望提高性能和资源利用率,请选择启用以下几种预设模型:包括但不限于ERNIE系列中的不同版本以及Yi系列中的中大型对话模型。

image.png

部分模型计费说明如下

image.png

单次API调用案例

复制代码
    @RequestMapping("/baidu/ernieBotTurbo")
    public Result baiduErnieBotTurbo(@RequestBody Map<String, Object> params) {
    BaiduConfig baiduConfig = SpringU.getBean(BaiduConfig.class);
    BaiduService baiduService = new BaiduService(baiduConfig.getApiKey(), baiduConfig.getApiSecret());
    BaiduChatMessage chatMessage = BaiduChatMessage.builder()
            .content((String) params.get("content"))
            .role("user")
            .build();
    ErnieBotTurboStreamParam postParam = ErnieBotTurboStreamParam.builder()
            .user_id(StpUtil.getLoginIdAsString())
            .messages(MiscU.Instance.toList(chatMessage))
            .build();
    ErnieBotTurboResponse ernieBotTurboResponse = baiduService.ernieBotTurbo(postParam);
    return Result.success(ernieBotTurboResponse);
    }
    
    // 该方法是同步请求API,会等大模型将数据完全生成之后,返回响应结果,可能需要等待较长时间,视生成文本长度而定
    public ErnieBotTurboResponse ernieBotTurbo(ErnieBotTurboStreamParam param) {
    if (param == null) {
        log.error("参数异常:param不能为空");
        throw new RuntimeException("参数异常:param不能为空");
    }
    if (param.isStream()) {
        param.setStream(false);
    }
    String fullChatUrl = SpringU.getBean(BaiduConfig.class).getFullChatUrl();
    String post = HttpUtil.post(fullChatUrl + BaiduConfig.getToken(appKey, secretKey), JSONUtil.toJsonStr(param));
    return JSONUtil.toBean(post, ErnieBotTurboResponse.class);
    }
    
    public class BaiduConfig {
    @Value("${aezo-chat-gpt.baidu.api-key:}")
    private String apiKey;
    @Value("${aezo-chat-gpt.baidu.api-secret:}")
    private String apiSecret;
    @Value("${aezo-chat-gpt.baidu.chat-url:yi_34b_chat}")
    private String chatUrl;
    
    /** * Yi-34B-Chat 免费使用 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/vlpteyv3c
     * 模型对应路径如,更多参考官方文档:
     * Yi-34B-Chat: yi_34b_chat
     * ERNIE-Lite-8K-0922: eb-instant
     * ERNIE-Speed-8K: ernie_speed
     */
    private static final String CHAT_URL_TPL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/%s?access_token=";
    
    public static String getToken(String appKey, String secretKey) {
        String url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + appKey + "&client_secret=" + secretKey;
        String s = HttpUtil.get(url);
        Token bean = JSONUtil.toBean(s, Token.class);
        return bean.getAccess_token();
    }
    
    public String getFullChatUrl() {
        return String.format(CHAT_URL_TPL, chatUrl);
    }
    }

调用测试

image.png

多轮对话流式输出

  • One能聊天中进行接收用户消息处理
复制代码
    private void onMessageBaidu(String msg, Map<String, Object> promptData, String messageContext) {
    BaiduConfig baiduConfig = SpringU.getBean(BaiduConfig.class);
    BaiduService baiduService = new BaiduService(baiduConfig.getApiKey(), baiduConfig.getApiSecret());
    BaiduEventSourceListener baiduEventSourceListener = new BaiduEventSourceListener(this.session);
    
    List<Message> messages = new ArrayList<>();
    if (StrUtil.isNotBlank(messageContext)) {
        messages = JSONUtil.toList(messageContext, Message.class);
        // 要求最终请求的会话条数必须是奇数,且必须是 U1 A1 U2 A2 U3 A3...的对话形式
        if(messages.size() % 2 != 0) {
            // 原始会话是奇数(加上新的一条输入就变成偶数了)
            int index = 0;
            Iterator<Message> iterator = messages.iterator();
            while (iterator.hasNext()) {
                Message next = iterator.next();
                if(index % 2 == 0) {
                    if(!"user".equals(next.getRole())) {
                        iterator.remove();
                    } else {
                        index++;
                    }
                } else {
                    if(!"assistant".equals(next.getRole())) {
                        iterator.remove();
                    } else {
                        index++;
                    }
                }
            }
        }
        if(messages.size() >= 10) {
            messages.remove(0);
            messages.remove(1);
        }
        Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
        messages.add(currentMessage);
    } else {
        if(promptData != null && ValidU.isNotEmpty(promptData.get("description"))) {
            String prompt = (String) promptData.get("description");
            msg = "请按以下要求和我对话:" + prompt + "(如果前面的提示词中漏掉说明返回的语音,请默认使用中文返回结果即respond in Chinese)。\n我:" + msg;
        }
        Message currentMessage = Message.builder().content(msg).role(Message.Role.USER).build();
        messages.add(currentMessage);
    }
    
    List<BaiduChatMessage> baiduChatMessages = messages.stream().map(x -> {
        BaiduChatMessage baiduChatMessage = new BaiduChatMessage();
        BeanUtil.copyProperties(x, baiduChatMessage);
        return baiduChatMessage;
    }).collect(Collectors.toList());
    
    ErnieBotTurboStreamParam postParam = ErnieBotTurboStreamParam.builder()
            .user_id(this.uid)
            .messages(baiduChatMessages)
            .build();
    
    baiduService.ernieBotTurboStream(postParam, baiduEventSourceListener);
    MessageLocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), MessageLocalCache.TIMEOUT);
    }
    
    // 该方法是通过流的方式请求API,大模型每生成一些字符,就会通过流的方式相应给客户端,
    // 我们是在 BaiduEventSourceListener.java 的 onEvent 方法中获取大模型响应的数据,其中data就是具体的数据,
    // 我们获取到数据之后,就可以通过 SSE/webscocket 的方式实时相应给前端页面展示
    public void ernieBotTurboStream(ErnieBotTurboStreamParam param, EventSourceListener eventSourceListener) {
    if (Objects.isNull(eventSourceListener)) {
        log.error("参数异常:EventSourceListener不能为空");
        throw new RuntimeException("参数异常:EventSourceListener不能为空");
    }
    if (param == null) {
        log.error("参数异常:param不能为空");
        throw new RuntimeException("参数异常:param不能为空");
    }
    if (!param.isStream()) {
        param.setStream(true);
    }
    try {
        EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
        ObjectMapper mapper = new ObjectMapper();
        String fullChatUrl = SpringU.getBean(BaiduConfig.class).getFullChatUrl();
        String requestBody = mapper.writeValueAsString(param);
        Request request = new Request.Builder()
                .url(fullChatUrl + BaiduConfig.getToken(appKey, secretKey))
                .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
                .build();
        //创建事件
        EventSource eventSource = factory.newEventSource(request, eventSourceListener);
    } catch (JsonProcessingException e) {
        log.error("请求参数解析是失败!", e);
        throw new RuntimeException("请求参数解析是失败!", e);
    }
    }
  • 将文心一言返回的消息推送给用户
复制代码
    @Slf4j
    public class BaiduEventSourceListener extends EventSourceListener {
    
    private Session session;
    
    public BaiduEventSourceListener(Session session) {
        this.session = session;
    }
    
    @SneakyThrows
    @Override
    public void onOpen(EventSource eventSource, Response response) {
        log.info("baidu建立sse连接...");
        session.getBasicRemote().sendText("{\"role\": \"assistant\"}");
    }
    
    @SneakyThrows
    @Override
    public void onEvent(EventSource eventSource, String id, String type, String data) {
        // {"id":"as-jddwwxm2j3","object":"chat.completion","created":1712913324,"sentence_id":0,"is_end":false,"is_truncated":false,"result":"你好!","need_clear_history":false,"usage":{"prompt_tokens":1,"completion_tokens":2,"total_tokens":3}}
        // {"id":"as-jddwwxm2j3","object":"chat.completion","created":1712913325,"sentence_id":1,"is_end":false,"is_truncated":false,"result":"有什么我可以帮助你的吗?","need_clear_history":false,"usage":{"prompt_tokens":1,"completion_tokens":2,"total_tokens":3}}
        // {"id":"as-jddwwxm2j3","object":"chat.completion","created":1712913325,"sentence_id":2,"is_end":true,"is_truncated":false,"result":"","need_clear_history":false,"usage":{"prompt_tokens":1,"completion_tokens":8,"total_tokens":9}}
        log.info("baidu返回数据:{}", data);
        String uid = session.getPathParameters().get("uid");
        ObjectMapper mapper = new ObjectMapper();
        // 读取Json
        ErnieBotTurboResponse completionResponse = mapper.readValue(data, ErnieBotTurboResponse.class);
        Message deltaMessage = Message.builder()
                .content(completionResponse.getResult())
                .build();
        String delta = mapper.writeValueAsString(deltaMessage);
        session.getBasicRemote().sendText(delta);
    
        // 缓存返回消息
        if(!"assistant".equals(deltaMessage.getRole()) && deltaMessage.getContent() != null && !"".equals(deltaMessage.getContent())) {
            StringBuffer msgBuffer = MessageBackLocalCache.CACHE.get(uid);
            if(msgBuffer == null) {
                msgBuffer = new StringBuffer();
                MessageBackLocalCache.CACHE.put(uid, msgBuffer);
            }
            msgBuffer.append(deltaMessage.getContent());
        }
    }
    
    @SneakyThrows
    @Override
    public void onClosed(EventSource eventSource) {
        log.info("baidu关闭sse连接...");
        session.getBasicRemote().sendText("[DONE]");
    
        // 记录返回消息
        String uid = session.getPathParameters().get("uid");
        StringBuffer msgBuffer = MessageBackLocalCache.CACHE.get(uid);
        if(msgBuffer != null) {
            JdbcTemplate jdbcTemplate = SpringU.getBean(JdbcTemplate.class);
            List<Map<String, Object>> list = jdbcTemplate.queryForList("select id, create_time " +
                    " from chat_msg_his where user_id = ? and msg_ai is null order by id desc limit 1", uid);
            if(ValidU.isNotEmpty(list)) {
                Map<String, Object> chatInfo = list.get(0);
                Date createTime = (Date) chatInfo.get("create_time");
                Date now = new Date();
                long useTime = (now.getTime() - createTime.getTime()) / 1000;
                jdbcTemplate.update("update chat_msg_his set msg_ai = ?, update_time = ?, use_time = ? where id = ?",
                        msgBuffer.toString(), now, useTime, chatInfo.get("id"));
            }
    
            // 需要保留原始会话
            String messageStr = (String) MessageLocalCache.CACHE.get(uid);
            List<Message> messages = JSONUtil.toList(messageStr, Message.class);
            messages.add(Message.builder().role("assistant").content(msgBuffer.toString()).build());
            MessageLocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), MessageLocalCache.TIMEOUT);
        }
        MessageBackLocalCache.CACHE.remove(uid);
    }
    
    @SneakyThrows
    @Override
    public void onFailure(EventSource eventSource, Throwable t, Response response) {
        session.getBasicRemote().sendText("机器人出小差了~");
        String uid = session.getPathParameters().get("uid");
        MessageBackLocalCache.CACHE.remove(uid);
        if (Objects.isNull(response)) {
            return;
        }
        ResponseBody body = response.body();
        if (Objects.nonNull(body)) {
            log.error("baidu sse连接异常data:{},异常:{}", body.string(), t);
        } else {
            log.error("baidu sse连接异常data:{},异常:{}", response, t);
        }
        eventSource.cancel();
    }
    }

效果展示

image.png

相关资源:官方文档:One支持实现即时通讯连接至百度千帆API开发套件

全部评论 (0)

还没有任何评论哟~