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

作者 : admin 本文共9408个字,预计阅读时间需要24分钟 发布时间: 2024-06-17 共1人阅读

One能聊天介绍:基于ChatGPT实现的微信小程序,适配H5和WEB端。包含前后端,支持打字效果输出流式输出,支持AI聊天次数限制,支持分享增加次数等功能
One能聊天开源地址:https://github.com/oldinaction/ChatGPT-MP
One能聊天演示环境:可关注【阿壹族】公众号,并回复【One能聊天】查看
下文将介绍在One能聊天项目中接入百度千帆大模型 — 文心一言

开通服务

首先介绍一下百度AI相关产品矩阵
如下图百度力推的千帆大模型超级工厂,他包含

  • 千帆大模型平台:其中大模型开发就是自己训练一个大模型,这种比较有技术含量,少部分企业才会用到;大模型调用则包含百度开放的文心大模型(即文心一言,ERNIE 4.0和ERNIE 3.5为模型版本分类,对标ChatGPT),还包括一些第三方模型供调用
  • 千帆AppBuilder:是提供开发者基于文心大模型可以快速开发出一个AI应用,创建的应用可以集成一些官方的组件(如天气查询、快递查询等),也可以集成自定义组件(通过画布拖拽,自行编排组件逻辑,如调用企业内部API或调用大模型接口),另外还可导入知识库供大模型使用(支持txt/pdf/doc/url等模式)。通过AppBuilder创建的应用官方提供一个访问链接供普通用户使用(界面是通用的AI聊天界面),开发者也可以通过SDK调用创建的AI应用从而集成到实际的业务系统中。这部分会在后续文章中做详细说明
  • 千帆AI原生应用商店:就是百度自己开发的AI应用。如超级助理,下载浏览器插件即可使用,支持划词翻译、网页解读、OCR识别等功能

本文主要对文心大模型ERNIE的API调用做详细说明
One能聊天接入百度千帆大模型 —— 文心一言插图
创建应用:进入 https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application 创建,可勾选启用的模型,如ERNIE-3.5-8KERNIE-4.0-8KYi-34B-Chat(免费)等
One能聊天接入百度千帆大模型 —— 文心一言插图(1)
部分模型计费说明如下
One能聊天接入百度千帆大模型 —— 文心一言插图(2)

单次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);
}
}

调用测试
One能聊天接入百度千帆大模型 —— 文心一言插图(3)

多轮对话流式输出

  • 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)。
我:" + 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();
}
}

效果展示

One能聊天接入百度千帆大模型 —— 文心一言插图(4)

  • 相关文章:One能聊天接入百度千帆AppBuilder
本站无任何商业行为
个人在线分享 » One能聊天接入百度千帆大模型 —— 文心一言
E-->