6. 实战(二):用Spring AI+OpenAI构建企业级智能客服

[复制链接]
发表于 2025-9-27 18:34:53 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
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干系依赖:
  1. <dependencies>
  2.         <dependency>
  3.             <groupId>group.springframework.ai</groupId>
  4.             <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
  5.             <version>1.1.0</version>
  6.             <exclusions>
  7.                 <exclusion>
  8.                     <groupId>org.springframework.cloud</groupId>
  9.                     <artifactId>spring-cloud-function-context</artifactId>
  10.                 </exclusion>
  11.                 <exclusion>
  12.                     <groupId>org.springframework.cloud</groupId>
  13.                     <artifactId>spring-cloud-function-core</artifactId>
  14.                 </exclusion>
  15.             </exclusions>
  16.         </dependency>
  17.         <dependency>
  18.             <groupId>group.springframework.ai</groupId>
  19.             <artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
  20.             <version>1.1.0</version>
  21.         </dependency>
  22.         <dependency>
  23.             <groupId>org.projectlombok</groupId>
  24.             <artifactId>lombok</artifactId>
  25.         </dependency>
  26.         <dependency>
  27.             <groupId>org.springframework.boot</groupId>
  28.             <artifactId>spring-boot-starter</artifactId>
  29.         </dependency>
  30.         <dependency>
  31.             <groupId>org.springframework.boot</groupId>
  32.             <artifactId>spring-boot-starter-test</artifactId>
  33.             <scope>test</scope>
  34.         </dependency>
  35.         <dependency>
  36.             <groupId>org.springframework.boot</groupId>
  37.             <artifactId>spring-boot-starter-web</artifactId>
  38.         </dependency>
  39.         <!-- Spring Boot DevTools (Optional for auto-reloading during development) -->
  40.         <dependency>
  41.             <groupId>org.springframework.boot</groupId>
  42.             <artifactId>spring-boot-devtools</artifactId>
  43.             <scope>runtime</scope>
  44.         </dependency>
  45.     </dependencies>
  46.     <dependencyManagement>
  47.         <dependencies>
  48.             <dependency>
  49.                 <groupId>group.springframework.ai</groupId>
  50.                 <artifactId>spring-ai-bom</artifactId>
  51.                 <version>1.1.0</version>
  52.                 <type>pom</type>
  53.             </dependency>
  54.             <dependency>
  55.                 <groupId>org.projectlombok</groupId>
  56.                 <artifactId>lombok</artifactId>
  57.                 <version>1.18.36</version>
  58.             </dependency>
  59.         </dependencies>
  60.     </dependencyManagement>
复制代码
3.2、设置application.yml
  1. spring:
  2.   application:
  3.     name: spring-ai-demo
  4.   ai:
  5.     openai:
  6.       # 聊天模型
  7.       chat:
  8.         options:
  9.           # 这里使用deepseek模型  
  10.           model: deepseek-ai/DeepSeek-V2.5
  11.       # openai 供应商申请下来的api key   
  12.       api-key: xxxx
  13.       # 调用openai的接口地址
  14.       base-url: https://api.siliconflow.cn/   
  15.       
  16. server:
  17.   servlet:
  18.     encoding:
  19.       charset: UTF-8  # 这里强制设置servlet编码为utf-8,避免后续流式输出中文乱码
  20.       enabled: true
  21.       force: true
复制代码
3.3、平凡对话模式

这个是最常见的对话模式,没有任何的语境条件,没有任何的上下文,就是最简单的一问一答的情势。先来实现Service代码:
  1. @Service
  2. public class ChatService {
  3.     @Autowired
  4.     private OpenAiChatModel openAiChatModel;
  5.     /**
  6.      * 普通对话
  7.      * @param message
  8.      * @return
  9.      */
  10.     public String chat(String message) {
  11.         // 简单的单轮对话
  12.         return openAiChatModel
  13.                 .call(new Prompt(message))
  14.                 .getResult().getOutput().getContent();
  15.     }
  16. }
复制代码
controller干系代码:
  1. @RestController
  2. @RequestMapping("/api/chat")
  3. public class ChatController {
  4.     private final ChatService chatService;
  5.     public ChatController(ChatService chatService) {
  6.         this.chatService = chatService;
  7.     }
  8.     @GetMapping("/simple")
  9.     public String simpleChat(@RequestParam String message) {
  10.         return chatService.chat(message);
  11.     }
  12. }
复制代码
直接运行看下回显:
  1. curl -i -X GET \
  2. 'http://localhost:8080/api/chat/simple?message=你是谁'
复制代码

3.4、上下文对话

上下文对话,必要在对话的时间引入上下文,作为和AI交互的语境。Service干系代码:
  1. public String chatWithContext(String message, String context) {
  2.     // 带上下文的对话
  3.     SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("""
  4.         你是一个专业的AI助手。请根据以下上下文回答问题:
  5.         {context}
  6.         """);
  7.     Message systemMessage = systemPromptTemplate.createMessage(
  8.             Map.of("context", context)
  9.     );
  10.     UserMessage userMessage = new UserMessage(message);
  11.     Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
  12.     return openAiChatModel.call(prompt).getResult().getOutput().getContent();
  13. }
复制代码
Controller代码:
  1. @GetMapping("/with-context")
  2. public String chatWithContext(
  3.         @RequestParam String message,
  4.         @RequestParam String context) {
  5.     return chatService.chatWithContext(message, context);
  6. }
复制代码
查察结果:
  1. # 当我们在不同的地点询问几点了的时候,看看ai的回答
  2. curl -i -X GET \
  3. 'http://localhost:8080/api/chat/with-context?message=几点了&context=现在在北京'
  4. curl -i -X GET \
  5. 'http://localhost:8080/api/chat/with-context?message=几点了&context=现在在纽约'
复制代码


3.5、多轮对话

多轮对话,必要AI大模子记着我们前面的对话记载。多轮对话中,答复和结果会受到前面汗青记载的影响。Service代码:
  1. // 用于保存多轮的会话记录
  2. private final List<Message> conversationHistory = new ArrayList<>();
  3. public String multiTurnChat(String message) {
  4.     // 添加用户消息到历史
  5.     conversationHistory.add(new UserMessage(message));
  6.     // 多轮对话
  7.     Prompt prompt = new Prompt(conversationHistory);
  8.     String aiResponse = openAiChatModel.call(prompt).getResult().getOutput().getContent();
  9.     // 添加AI回复到历史
  10.     conversationHistory.add(new MyAssistantMessage(aiResponse));
  11.     return aiResponse;
  12. }
复制代码
controller代码:
  1. @GetMapping("/multi-turn")
  2. public String multiTurnChat(@RequestParam String message) {
  3.     return chatService.multiTurnChat(message);
  4. }
复制代码
运行结果:
  1. # 第一轮会话,我先告诉他我叫小明
  2. curl -i -X GET \
  3. 'http://localhost:8080/api/chat/multi-turn?message=我叫小明'
  4. # 第二轮会话,我再问他我是谁
  5. curl -i -X GET \
  6. 'http://localhost:8080/api/chat/multi-turn?message=我是谁'
复制代码


这个实现实在Spring AI提供了相应的支持,我们等下反面会讲到。
3.6、流式输出

流式输出有两种差别的方式,一种是Spring AI自己提供的流式调用方式,另一种是常见的SSE的获取方式。
3.6.1、Stream输出

我们先使用Spring AI提供的流式调用方式,Service方法:
  1. /**
  2. * 流式对话
  3. * @param message
  4. * @return
  5. */
  6. public Flux<String> chatWithStream(String message) {
  7.     return openAiChatModel.stream(message);
  8. }
复制代码
controller直接调用即可:
  1. @GetMapping(value = "/with-stream")
  2.     public Flux<String> chatWithStream(@RequestParam String message) {
  3.         return chatService.chatWithStream(message)
  4.                 .doOnNext(System.out::println)
  5. //                .delayElements(Duration.ofMillis(500))    // 设置流速
  6.                 .doOnComplete(() -> System.out.println("Flux 对话结束"));
  7.     }
  8.     ```
  9. > Flux是spring webflux提供的流式响应类。想要了解更多,可以去看下Spriing WebFlux。
  10. 我们直接浏览器运行这个接口,方便查看。如果自己运行的话,会发现浏览器正在一段一段的流式输出,而不是一下子全部内容显示出来。
  11. ![](https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9maWxlcy5tZG5pY2UuY29tL3VzZXIvODIxODMvODU3NDM3YWMtOWRhMy00Yzk0LTlmNWQtN2RjODAzNTgxNTYyLnBuZw==)
  12. 通过控制台的打印,我们也能看到他并不是一次性的渲染出来结果。
  13. ![](https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9maWxlcy5tZG5pY2UuY29tL3VzZXIvODIxODMvZGI4ZmIxNzItMmVmNy00NzkyLWIyM2QtZDY4MmVhNmExMWRlLnBuZw==)
  14. > 这里我用Flux输出的时候,浏览器一直中文乱码。就算设置了produces的编码格式也不行,最后通过前面application.yml里配置了servlet编码格式才解决。  
  15. 原因是http响应编码默认是iso-8859-1,而非utf-8,因此导致中文显示乱码。
  16. ### 3.6.2、SSE实现
  17. 除了上面flux的实现方式外,我们可以按需采用sse的输出方式来实现:
  18. ```java
  19. @GetMapping(value = "/with-sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE
  20. )
  21. public SseEmitterUTF8 chatWithSSE(@RequestParam String message) {
  22.     SseEmitterUTF8 emitter = new SseEmitterUTF8(5000L);
  23.     chatService.getOpenAiChatModel().stream(new Prompt(message))
  24.             .subscribe(
  25.                     chunk -> {
  26.                         try {
  27.                             emitter.send(chunk.getResult().getOutput().getContent());
  28.                         } catch (IOException e) {
  29.                             emitter.completeWithError(e);
  30.                         }
  31.                     },
  32.                     emitter::completeWithError,
  33.                     emitter::complete
  34.             );
  35.     return emitter;
  36. }
复制代码
同样的,这里必要注意中文乱码标题。默认的SseEmitter编码默以为ISO-8859-1,因此中文是会乱码的。这里的办理方式是重新界说SseEmmiter的相应编码格式;
  1. class SseEmitterUTF8 extends SseEmitter {
  2.     @Override
  3.     protected void extendResponse(ServerHttpResponse outputMessage) {
  4.         super.extendResponse(outputMessage);
  5.         HttpHeaders headers = outputMessage.getHeaders();
  6.         headers.setContentType( new MediaType("text", "event-stream", StandardCharsets.UTF_8));
  7.     }
  8.     public SseEmitterUTF8(Long timeout) {
  9.         super(timeout);
  10.     }
  11. }
复制代码
使用sse的输出方式,必要指定变乱协议,否则会被看成纯文本输出。
  1. produces = MediaType.TEXT_EVENT_STREAM_VALUE
复制代码
查察结果:

3.7、实现上下影象

上面提到了多轮对话,实在就是上下文影象本领。只是上文中自己实现了一个List聚集来存储会话记载。这里只是简单的演示示例,这么实现无可厚非。但是当我们项目中的对话大概不止一个语境,必要根据我们的会话记载来区分上下文,这时间这个List聚集就大概显得力有未逮。
很荣幸的是,Spring AI支持了如许的上下影象本领:ChatMemory。
我们先来使用他,后续再来先容他是怎样实现的。
起首我们必要界说一个简单的会话影象的管理器,Spring AI提供了关于ChatMemory的内存实现,也就是类似与我们上文中的list。只不外为了区分差别的会话,一定接纳了Map来实现,这里我们只必要声明注入即可:
  1. @Configuration
  2. class AiConfig {
  3.     /**
  4.      * 会话记忆管理器
  5.      * @return
  6.      */
  7.     @Bean
  8.     public ChatMemory chatMemory() {
  9.         return new InMemoryChatMemory();
  10.     }
  11. }
复制代码
接下来界说一个简单的带有影象本领的Service:
  1. @Service
  2. public class ChatMemoryService {
  3.     @Autowired
  4.     private OpenAiChatModel openAiChatModel;
  5.     private final ChatMemory chatMemory;
  6.     public ChatMemoryService(ChatMemory chatMemory) {
  7.         this.chatMemory = chatMemory;
  8.     }
  9.     public Flux<String> chatWithMemoryStream(String conversationId, String message) {
  10.    
  11.         ChatClient.StreamResponseSpec resp = ChatClient.builder(openAiChatModel)
  12.                 // 设置历史对话的保存方式,这里我们使用内存保存
  13.                 .defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory))
  14.                 .build()
  15.                 .prompt().user(message)
  16.                 .advisors(advisor ->
  17.                         // 设置保存的历史对话ID
  18.                         advisor.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId)
  19.                                 // 设置需要保存几轮的历史对话,用于避免内存溢出,因为这里我们没做持久化
  20.                                 .param(AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY, 50)
  21.                 ).stream();
  22.         return resp.content();
  23.     }
  24. }
复制代码
Controller直接调用:
  1. @GetMapping(value = "/with-memory")
  2. public Flux<String> chatWithMemoryStream(@RequestParam String requestId, @RequestParam String message) {
  3.     return chatMemoryService.chatWithMemoryStream(requestId, message);
  4. }
复制代码
查察结果,这里我区分两个会话,一个会话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。必要的可以自取。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表