赞
踩
One能聊天介绍:基于ChatGPT实现的微信小程序,适配H5和WEB端。包含前后端,支持打字效果输出流式输出,支持AI聊天次数限制,支持分享增加次数等功能
One能聊天开源地址:https://github.com/oldinaction/ChatGPT-MP
One能聊天演示环境:可关注【阿壹族】公众号,并回复【One能聊天】查看
下文将介绍在One能聊天项目中接入百度千帆大模型 — 文心一言
首先介绍一下百度AI相关产品矩阵
如下图百度力推的千帆大模型超级工厂,他包含
本文主要对文心大模型ERNIE的API调用做详细说明
创建应用:进入 https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application 创建,可勾选启用的模型,如ERNIE-3.5-8K
、ERNIE-4.0-8K
、Yi-34B-Chat
(免费)等
部分模型计费说明如下
@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); } }
调用测试
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(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。