Spring-AI
Spring AI 使用笔记
Spring-AI 对接 AI 大模型
Spring AI / Overview
https://docs.spring.io/spring-ai/reference/index.html
Spring-AI + WebSocket 对接 ChatGPT 流式 API
Spring AI / AI Models / Chat Models / OpenAI
https://docs.spring.io/spring-ai/reference/api/chat/openai-chat.html
1、引入 spring-ai-openai-spring-boot-starter 依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
2、配置api key
spring:
ai:
openai:
api-key: sk-xxxx
3、websocket service 中,调用 OpenAiChatModel.stream()
流式api,返回 Flux<ChatResponse>
,subscribe 订阅每条响应结果,调用 jakarta.websocket.Session.getBasicRemote().sendText()
发送每条结果到 WebSocket session
@OnMessage
public void onMessage(Session session, String message) {
log.info("Received sessionId={} message={}", session.getId(), message);
// WebSocket是由底层的Servlet容器(如Tomcat)直接创建的,而不是由Spring创建的,所以不能直接使用@Autowired注入Spring管理的Bean,必须SpringUtil.getBean()获取
SpringUtil.getBean(OpenAiChatModel.class)
.stream(new Prompt(webSocketChat.getMsg()))
.subscribe(chatResponse -> {
try {
session.getBasicRemote().sendText(JsonMappers.Normal.toJson(chatResponse.getResults()));
} catch (IOException e) {
log.error("发送消息出错:{}", e.getMessage(), e);
}
}
);
}
4、前端 js 中解析每条内容,拿出其中的文本,拼接到对话框文本中即可。
Spring AI + WebSocket 对接百度云千帆模型
Spring AI AI Models Chat Models QianFan
https://docs.spring.io/spring-ai/reference/api/chat/qianfan-chat.html
1、除了 spring-ai-openai-spring-boot-starter,还需要额外引入 spring-ai-qianfan 依赖,spring-ai-openai-spring-boot-starter 内置了 spring-ai-openai,但没有其他公司的依赖。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<!-- spring-ai-百度千帆 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-qianfan</artifactId>
<version>1.0.0-M6</version>
</dependency>
2、配置 ak、sk 和 模型
spring:
ai:
qianfan:
api-key: xxxxx
secret-key: xxxxx
chat:
options:
model: ernie-speed-128k
注意: ak 和 sk 需要使用应用接入 https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1 这个页面的
和 Access Key 页面的 ak、sk 还不一样 https://console.bce.baidu.com/iam/#/iam/accesslist
此外,我使用的 1.0.0-M6 版本,目前只支持通过 ak、sk 使用 v1 版的千帆api,即 https://aip.baidubce.com/rpc/2.0/ai_custom 开头的api。
最新的千帆已经推出了完全兼容 OpenAI 标准的 v2 版api,但目前 spring-ai-qianfan 还不支持。
3、代码对接流程和 OpenAI 一样,Spring-AI 封装了底层的差异,把模型换成 QianFanChatModel
即可。
Spring-AI 不同版本 ChatResponse 结构不一样
注意下,对接过程中发现 Spring AI(spring-ai-openai-spring-boot-starter) 不同版本的 ChatResponse 结构不一样,注意 debug 下,判断如何读取文本内容,如何判断流式回答结束:
Spring AI(spring-ai-openai-spring-boot-starter) 0.8.0 版本 的 AI模型 流式回答 ChatResponse:
首字符: [{"output":{"content":"","properties":{"role":"ASSISTANT"},"messageType":"ASSISTANT"},"metadata":{"contentFilterMetadata":null,"finishReason":null}}]
中间: [{"output":{"content":"问题","properties":{"role":"ASSISTANT"},"messageType":"ASSISTANT"},"metadata":{"contentFilterMetadata":null,"finishReason":null}}]
结束: [{"output":{"content":null,"properties":{"role":"ASSISTANT"},"messageType":"ASSISTANT"},"metadata":{"contentFilterMetadata":null,"finishReason":"STOP"}}]
文本内容在 output.content 中
可通过 metadata.finishReason == ‘STOP’ 判断结束
Spring AI(spring-ai-openai-spring-boot-starter) 1.0.0-M6 版本 的 AI模型 流式回答 ChatResponse:
首先会把问题给返回: {"systemInfo":false,"sessionId":"1","nickname":"3f477fcb-0c11-4f33-a0ad-cc368201c473","sessionCount":2,"msg":"你能做什么呢?"}
首字符: [{"metadata":{"finishReason":null,"contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT","role":"assistant","id":"as-fyfz1i2kd1"},"toolCalls":[],"media":[],"text":"我可以做"}}]
中间: [{"metadata":{"finishReason":null,"contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT","role":"assistant","id":"as-fyfz1i2kd1"},"toolCalls":[],"media":[],"text":"、科学、文化、娱乐、体育等各种主题的问题,并提供详细的答案和"}}]
结束: [{"metadata":{"finishReason":null,"contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT","role":"assistant","id":"as-fyfz1i2kd1"},"toolCalls":[],"media":[],"text":""}}]
文本内容在 output.text 中
需通过 output.text 为空字符串判断结束
langchain4j
LangChain for Java
https://github.com/langchain4j/langchain4j
前端流式输出(打字机效果)
1、一开始直接将每次返回的 choice.delta.content 内容 append 到 div 末尾,可以实现打字机流式输出,但没有 Markdown 渲染。
2、后来使用一个字符串变量累加每次回答的结果,每收到服务端发来的一次数据,都累加后用 marked 进行 Markdown 渲染并替换 div 内容,可实现打字机流式输出 + 实时 Markdown 渲染及代码高亮,但代码高亮效果不太好。
<script type="text/javascript">
var websocket;
var chatGptStreaming = false; // true: ChatGpt 流式回答中
var chatGptFullResp = ""; // ChatGpt 累积完整回答
//markdown解析,代码高亮设置
marked.setOptions({
highlight: function (code, language) {
// const hljs = require('highlight.js');
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext';
return hljs.highlight(code, { language: validLanguage }).value;
},
});
if ( 'WebSocket' in window) {
// 实例化WebSocket对象,指定要连接的服务器地址与端口建立连接
websocket = new WebSocket("ws://localhost:8001/ws/chat");
// 连接打开事件
websocket.onopen = function() {
console.log("Socket 已打开");
};
// 收到消息事件
websocket.onmessage = function(msg) {
console.log("收到消息:" + msg.data);
// ChatGpt 流式回答结束
if (msg.data === "[DONE]") {
console.log("ChatGpt 流式回答结束");
chatGptStreaming = false;
chatGptFullResp = "";
return;
}
var time = new Date().toLocaleString();
var resp = JSON.parse(msg.data);
$("#div-status").css("background-color", "rgb(82, 196, 26)");
// ChatGpt 流式回答
if (resp.object == "chat.completion.chunk") {
if (!chatGptStreaming) {
chatGptStreaming = true;
}
choices = JSON.parse(event.data).choices;
choices.filter(choice => choice.delta.content).forEach(choice => {
if (choice.delta.content.indexOf("\n") >= 0) {
choice.delta.content = choice.delta.content.replace("\n", "<br>");
}
console.log(chatGptFullResp);
chatGptFullResp += choice.delta.content;
// $(`#${resp.id}`).append(choice.delta.content); // 未加 Markdown 渲染的版本,每次往 div 末尾 append 添加返回的流式内容
// 添加 Markdown 渲染的版本,用全局变量累加记录本地的完整回答,每次渲染后替换 div 内容
$(`#${resp.id}`).html(marked.parse(chatGptFullResp));
});
}
// 始终显示滚动条最底部
$("#div-msg").scrollTop($("#div-msg").prop("scrollHeight"));
};
} else {
console.log("当前浏览器不支持WebSocket");
}
</script>
扒一扒 Chatgpt 背后的 web 开发技术(二)
https://zhaozhiming.github.io/2023/04/18/chatgpt-technical-part-two/
使用marked和highlight.js对GPT接口返回的代码块渲染,高亮显示,支持复制,和选择不同的高亮样式
https://juejin.cn/post/7255557296951738429
下一篇 Python-pytest
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: