马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
1、弁言
前面几篇已经加深了我们对Spring Ai的体系布局,焦点概念,以及也有开端集成实现了一个简单demo。本日,我们通过使用Spring AI框架与OpenAI API集成,构建一个功能完满的智能对话体系,加深我们对Spring AI从概念到现实代码实现的明白,终极完成一个可运行的智能对话应用。
2、所属环境
- IntelliJ IDEA 2024.3
- JDK 17+
- 硅基活动API,这里必要提前注册申请。假如获取API Key这里就不赘述了,可以看我以往的文章搜索查察。
- SpringBootI 3.4.2
3、代码集成
再次赘述一遍,Spring AI所必要的JDK,必须为17+,我这里使用的是Java 21举行演示。
3.1、Spring Boot添加依赖
假如构建一个初始的Spring Boot项目,这里就不赘述了。默认各人应该都会了。添加Spring Ai干系依赖,以及Spring-web干系依赖:- <dependencies>
- <dependency>
- <groupId>group.springframework.ai</groupId>
- <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
- <version>1.1.0</version>
- <exclusions>
- <exclusion>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-function-context</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-function-core</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>group.springframework.ai</groupId>
- <artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
- <version>1.1.0</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- Spring Boot DevTools (Optional for auto-reloading during development) -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- <scope>runtime</scope>
- </dependency>
- </dependencies>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>group.springframework.ai</groupId>
- <artifactId>spring-ai-bom</artifactId>
- <version>1.1.0</version>
- <type>pom</type>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.36</version>
- </dependency>
- </dependencies>
- </dependencyManagement>
复制代码 3.2、设置application.yml
- spring:
- application:
- name: spring-ai-demo
- ai:
- openai:
- # 聊天模型
- chat:
- options:
- # 这里使用deepseek模型
- model: deepseek-ai/DeepSeek-V2.5
- # openai 供应商申请下来的api key
- api-key: xxxx
- # 调用openai的接口地址
- base-url: https://api.siliconflow.cn/
-
- server:
- servlet:
- encoding:
- charset: UTF-8 # 这里强制设置servlet编码为utf-8,避免后续流式输出中文乱码
- enabled: true
- force: true
复制代码 3.3、平凡对话模式
这个是最常见的对话模式,没有任何的语境条件,没有任何的上下文,就是最简单的一问一答的情势。先来实现Service代码:- @Service
- public class ChatService {
- @Autowired
- private OpenAiChatModel openAiChatModel;
- /**
- * 普通对话
- * @param message
- * @return
- */
- public String chat(String message) {
- // 简单的单轮对话
- return openAiChatModel
- .call(new Prompt(message))
- .getResult().getOutput().getContent();
- }
- }
复制代码 controller干系代码:- @RestController
- @RequestMapping("/api/chat")
- public class ChatController {
- private final ChatService chatService;
- public ChatController(ChatService chatService) {
- this.chatService = chatService;
- }
- @GetMapping("/simple")
- public String simpleChat(@RequestParam String message) {
- return chatService.chat(message);
- }
- }
复制代码 直接运行看下回显:- curl -i -X GET \
- 'http://localhost:8080/api/chat/simple?message=你是谁'
复制代码
3.4、上下文对话
上下文对话,必要在对话的时间引入上下文,作为和AI交互的语境。Service干系代码:- public String chatWithContext(String message, String context) {
- // 带上下文的对话
- SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("""
- 你是一个专业的AI助手。请根据以下上下文回答问题:
- {context}
- """);
- Message systemMessage = systemPromptTemplate.createMessage(
- Map.of("context", context)
- );
- UserMessage userMessage = new UserMessage(message);
- Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
- return openAiChatModel.call(prompt).getResult().getOutput().getContent();
- }
复制代码 Controller代码:- @GetMapping("/with-context")
- public String chatWithContext(
- @RequestParam String message,
- @RequestParam String context) {
- return chatService.chatWithContext(message, context);
- }
复制代码 查察结果:- # 当我们在不同的地点询问几点了的时候,看看ai的回答
- curl -i -X GET \
- 'http://localhost:8080/api/chat/with-context?message=几点了&context=现在在北京'
-
-
- curl -i -X GET \
- 'http://localhost:8080/api/chat/with-context?message=几点了&context=现在在纽约'
复制代码
3.5、多轮对话
多轮对话,必要AI大模子记着我们前面的对话记载。多轮对话中,答复和结果会受到前面汗青记载的影响。Service代码:- // 用于保存多轮的会话记录
- private final List<Message> conversationHistory = new ArrayList<>();
- public String multiTurnChat(String message) {
- // 添加用户消息到历史
- conversationHistory.add(new UserMessage(message));
- // 多轮对话
- Prompt prompt = new Prompt(conversationHistory);
- String aiResponse = openAiChatModel.call(prompt).getResult().getOutput().getContent();
- // 添加AI回复到历史
- conversationHistory.add(new MyAssistantMessage(aiResponse));
- return aiResponse;
- }
复制代码 controller代码:- @GetMapping("/multi-turn")
- public String multiTurnChat(@RequestParam String message) {
- return chatService.multiTurnChat(message);
- }
复制代码 运行结果:- # 第一轮会话,我先告诉他我叫小明
- curl -i -X GET \
- 'http://localhost:8080/api/chat/multi-turn?message=我叫小明'
-
- # 第二轮会话,我再问他我是谁
- curl -i -X GET \
- 'http://localhost:8080/api/chat/multi-turn?message=我是谁'
复制代码
这个实现实在Spring AI提供了相应的支持,我们等下反面会讲到。
3.6、流式输出
流式输出有两种差别的方式,一种是Spring AI自己提供的流式调用方式,另一种是常见的SSE的获取方式。
3.6.1、Stream输出
我们先使用Spring AI提供的流式调用方式,Service方法:- /**
- * 流式对话
- * @param message
- * @return
- */
- public Flux<String> chatWithStream(String message) {
- return openAiChatModel.stream(message);
- }
复制代码 controller直接调用即可:- @GetMapping(value = "/with-stream")
- public Flux<String> chatWithStream(@RequestParam String message) {
- return chatService.chatWithStream(message)
- .doOnNext(System.out::println)
- // .delayElements(Duration.ofMillis(500)) // 设置流速
- .doOnComplete(() -> System.out.println("Flux 对话结束"));
- }
- ```
- > Flux是spring webflux提供的流式响应类。想要了解更多,可以去看下Spriing WebFlux。
- 我们直接浏览器运行这个接口,方便查看。如果自己运行的话,会发现浏览器正在一段一段的流式输出,而不是一下子全部内容显示出来。
- 
- 通过控制台的打印,我们也能看到他并不是一次性的渲染出来结果。
- 
- > 这里我用Flux输出的时候,浏览器一直中文乱码。就算设置了produces的编码格式也不行,最后通过前面application.yml里配置了servlet编码格式才解决。
- 原因是http响应编码默认是iso-8859-1,而非utf-8,因此导致中文显示乱码。
- ### 3.6.2、SSE实现
- 除了上面flux的实现方式外,我们可以按需采用sse的输出方式来实现:
- ```java
- @GetMapping(value = "/with-sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE
- )
- public SseEmitterUTF8 chatWithSSE(@RequestParam String message) {
- SseEmitterUTF8 emitter = new SseEmitterUTF8(5000L);
- chatService.getOpenAiChatModel().stream(new Prompt(message))
- .subscribe(
- chunk -> {
- try {
- emitter.send(chunk.getResult().getOutput().getContent());
- } catch (IOException e) {
- emitter.completeWithError(e);
- }
- },
- emitter::completeWithError,
- emitter::complete
- );
- return emitter;
- }
复制代码 同样的,这里必要注意中文乱码标题。默认的SseEmitter编码默以为ISO-8859-1,因此中文是会乱码的。这里的办理方式是重新界说SseEmmiter的相应编码格式;- class SseEmitterUTF8 extends SseEmitter {
- @Override
- protected void extendResponse(ServerHttpResponse outputMessage) {
- super.extendResponse(outputMessage);
- HttpHeaders headers = outputMessage.getHeaders();
- headers.setContentType( new MediaType("text", "event-stream", StandardCharsets.UTF_8));
- }
- public SseEmitterUTF8(Long timeout) {
- super(timeout);
- }
- }
复制代码 使用sse的输出方式,必要指定变乱协议,否则会被看成纯文本输出。- produces = MediaType.TEXT_EVENT_STREAM_VALUE
复制代码 查察结果:
3.7、实现上下影象
上面提到了多轮对话,实在就是上下文影象本领。只是上文中自己实现了一个List聚集来存储会话记载。这里只是简单的演示示例,这么实现无可厚非。但是当我们项目中的对话大概不止一个语境,必要根据我们的会话记载来区分上下文,这时间这个List聚集就大概显得力有未逮。
很荣幸的是,Spring AI支持了如许的上下影象本领:ChatMemory。
我们先来使用他,后续再来先容他是怎样实现的。
起首我们必要界说一个简单的会话影象的管理器,Spring AI提供了关于ChatMemory的内存实现,也就是类似与我们上文中的list。只不外为了区分差别的会话,一定接纳了Map来实现,这里我们只必要声明注入即可:- @Configuration
- class AiConfig {
- /**
- * 会话记忆管理器
- * @return
- */
- @Bean
- public ChatMemory chatMemory() {
- return new InMemoryChatMemory();
- }
- }
复制代码 接下来界说一个简单的带有影象本领的Service:- @Service
- public class ChatMemoryService {
- @Autowired
- private OpenAiChatModel openAiChatModel;
- private final ChatMemory chatMemory;
- public ChatMemoryService(ChatMemory chatMemory) {
- this.chatMemory = chatMemory;
- }
- public Flux<String> chatWithMemoryStream(String conversationId, String message) {
-
- ChatClient.StreamResponseSpec resp = ChatClient.builder(openAiChatModel)
- // 设置历史对话的保存方式,这里我们使用内存保存
- .defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory))
- .build()
- .prompt().user(message)
- .advisors(advisor ->
- // 设置保存的历史对话ID
- advisor.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
- // 设置需要保存几轮的历史对话,用于避免内存溢出,因为这里我们没做持久化
- .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 50)
- ).stream();
- return resp.content();
- }
- }
复制代码 Controller直接调用:- @GetMapping(value = "/with-memory")
- public Flux<String> chatWithMemoryStream(@RequestParam String requestId, @RequestParam String message) {
- return chatMemoryService.chatWithMemoryStream(requestId, message);
- }
复制代码 查察结果,这里我区分两个会话,一个会话requestId为1,告诉他我是小明。第二个会话requestId为2,告诉他我是小白。接着两个会话,我分别问他我是谁。
会话requestId=1:
会话requestId=2,当我换了个会话ID时,由于影象根据会话举行了隔离。他已经无法根据辨认到我是谁:
当我们再次告诉他,我叫小红:
4、小结
通过本文,我们详细先容了怎样使用Spring AI与OpenAI集成构建智能对话体系。从底子设置到高级功能,我们涵盖了实现一个生产级对话体系所需的关键组件。Spring AI的抽象层使得与OpenAI的集成变得简单而机动,同时保持了Spring开发者认识的编程模子。
随着AI技能的不绝发展,这种集成方式将为应用步调带来更多创新的大概性。读者可以在此底子上进一步探索,如实现多模态交互、连合企业知识库构建专业范畴助手等。
别的,代码我已经上传Github,所在:https://github.com/Shamee99/spring-ai-demo。必要的可以自取。
|