当前位置: 首页 > news >正文

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

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-demoai: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: trueforce: true

3.3、普通对话模式

这个是最常见的对话模式,没有任何的语境前提,没有任何的上下文,就是最简单的一问一答的形式。先来实现Service代码:

@Service
public class ChatService {@Autowiredprivate 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。我们直接浏览器运行这个接口,方便查看。如果自己运行的话,会发现浏览器正在一段一段的流式输出,而不是一下子全部内容显示出来。![](https://files.mdnice.com/user/82183/857437ac-9da3-4c94-9f5d-7dc803581562.png)通过控制台的打印,我们也能看到他并不是一次性的渲染出来结果。![](https://files.mdnice.com/user/82183/db8fb172-2ef7-4792-b23d-d682ea6a11de.png)> 这里我用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 {@Overrideprotected 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*/@Beanpublic ChatMemory chatMemory() {return new InMemoryChatMemory();}
}

接下来定义一个简单的带有记忆能力的Service:

@Service
public class ChatMemoryService {@Autowiredprivate 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 ->// 设置保存的历史对话IDadvisor.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。需要的可以自取。

相关文章:

  • STM32学习2
  • 自学新标日第十九课复习版本
  • 驱动移植【简略版】
  • Vue3中provide和inject的用法示例
  • 第 4 期:DDPM中的损失函数——为什么只预测噪声?
  • 守护进程及gdb调试(新手简略版)
  • 数控铣床自动上下料机械手控制装置设计
  • python豆包语音合成并播放
  • keil5软件配置以及使用技巧
  • Aladdin显卡多任务运行教程
  • 大模型应用_AutoGPT
  • 软件测试之接口测试详解
  • Linux CentOS 更改MySQL数据库目录位置
  • 生态篇|多总线融合与网关设计
  • 函数与数组---------C语言经典题目(1)
  • Vue 3 计算属性与侦听器深度解析:优雅处理响应式数据
  • ​​Nginx快速入门-3:工作流程和模块化
  • day1-小白学习JAVA(mac版)---(jdk安装和环境变量配置)
  • 【Reading Notes】(8.2)Favorite Articles from 2025 February
  • ModbusTCP 转 Profinet 主站网关
  • 新质观察|解构低空经济产业集群发展战略
  • 俄乌就不打击民用基础设施释放对话信号
  • 复旦大学附属中山医院也有儿科了,门诊将于下月底开业
  • 北美票房|《罪人》成首部观众评分为A级的恐怖片
  • 新闻1+1丨全球首场人机共跑马拉松,有何看点?
  • 重庆警方通报“货车轮胎滚进服务区致人死亡”:正进一步调查