1. 概述
本文将探讨如何利用Spring AI的核心概念,基于ChatGPT、Ollama、Mistral等大语言模型(LLM)构建AI助手。企业正越来越多地采用AI助手来增强现有业务功能的用户体验:
- 回答用户查询
- 根据用户输入执行事务操作
- 总结长句和文档
这些只是LLM的基础能力,其潜力远不止于此。
2. Spring AI特性
Spring AI框架提供了一系列强大功能来实现AI驱动功能:
- 可无缝集成底层LLM服务和向量数据库的接口
- 通过RAG和函数调用API实现上下文感知的响应生成和操作执行
- 结构化输出转换器,将LLM响应转换为POJO或JSON等机器可读格式
- 通过Advisor API提供的拦截器丰富提示词并应用防护措施
- 通过维护对话状态增强用户参与度
我们可以通过下图直观理解这些组件:
为演示这些特性,我们将在一个传统的订单管理系统(OMS)中构建聊天机器人。典型OMS功能包括:
- 创建订单
- 获取用户订单
3. 前置条件
首先需要OpenAI订阅以使用其LLM服务。然后在Spring Boot应用中添加Spring AI的Maven依赖。这些前置条件已在其他文章中详细说明,此处不再赘述。
我们将使用内存数据库HSQLDB快速启动。创建必要表并插入测试数据:
CREATE TABLE User_Order (
order_id BIGINT NOT NULL PRIMARY KEY,
user_id VARCHAR(20) NOT NULL,
quantity INT
);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (1, 'Jenny', 2);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (2, 'Mary', 5);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (3, 'Alex', 1);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (4, 'John', 3);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (5, 'Sophia', 4);
--and so on..
在Spring的application.properties
文件中配置HSQLDB和OpenAI客户端:
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
spring.datasource.url=jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=none
spring.ai.openai.chat.options.model=gpt-4o-mini
spring.ai.openai.api-key=sk-demo-key-12345
⚠️ 选择适合用例的模型是个复杂的迭代过程,需要大量试错。不过对于本文的简单演示,性价比高的GPT-4o mini模型就足够了。
4. 函数调用API
这是LLM应用中流行的智能体概念的核心支柱。它使应用能够执行精确的复杂任务集合并独立决策。
例如,在传统订单管理应用中构建聊天机器人时,此功能非常有用。聊天机器人可以通过自然语言帮助用户创建订单请求、检索订单历史等。
这些功能由一个或多个应用函数支持。我们在发送给LLM的提示词中定义算法,并附带函数模式。LLM接收此模式并识别执行请求技能的正确函数,然后将决策发送回应用。
最后,应用执行函数并将更多信息返回给LLM:
首先看传统应用的主要组件:
OrderManagementService
类有两个核心功能:创建订单和获取用户订单历史。两个功能都使用OrderRepository
Bean与数据库交互:
@Service
public class OrderManagementService {
@Autowired
private OrderRepository orderRepository;
public Long createOrder(OrderInfo orderInfo) {
return orderRepository.save(orderInfo).getOrderID();
}
public Optional<List<OrderInfo>> getAllUserOrders(String userID) {
return orderRepository.findByUserID(userID);
}
}
现在看Spring AI如何帮助在传统应用中实现聊天机器人:
类图中,OmAiAssistantConfiguration
是Spring配置Bean,它注册了函数回调Bean createOrderFn
和getUserOrderFn
:
@Configuration
public class OmAiAssistantConfiguration {
@Bean
@Description("创建订单。订单ID用orderID标识。"
+ "订单数量用orderQuantity标识。"
+ "用户用userID标识。"
+ "订单数量应为正整数。"
+ "如果缺少用户ID或订单数量等参数,"
+ "则要求用户提供缺失信息。")
public Function<CreateOrderRequest, Long> createOrderFn(OrderManagementService orderManagementService) {
return createOrderRequest -> orderManagementService.createOrder(createOrderRequest.orderInfo());
}
@Bean
@Description("获取用户所有订单。用户ID用userID标识。")
public Function<GetOrderRequest, List<OrderInfo>> getUserOrdersFn(OrderManagementService orderManagementService) {
return getOrderRequest -> orderManagementService.getAllUserOrders(getOrderRequest.userID()).get();
}
}
@Description
注解帮助生成函数模式,应用随后将其作为提示词的一部分发送给LLM。由于函数复用了OrderManagementService
的现有方法,提升了代码复用性。
此外,CreateOrderRequest
和GetOrderRequests
是Record类,帮助Spring AI为下游服务调用生成POJO:
record GetOrderRequest(String userID) {}
record CreateOrderRequest(OrderInfo orderInfo) {}
最后看新的OrderManagementAIAssistant
类,它将用户查询发送给LLM服务:
@Service
public class OrderManagementAIAssistant {
@Autowired
private ChatModel chatClient;
public ChatResponse callChatClient(Set<String> functionNames, String promptString) {
Prompt prompt = new Prompt(promptString, OpenAiChatOptions
.builder()
.withFunctions(functionNames)
.build()
);
return chatClient.call(prompt);
}
}
callChatClient()
方法在Prompt对象中注册函数,然后调用ChatModel#call()
获取LLM服务的响应。
5. 函数调用场景
对于AI助手的用户查询或指令,我们介绍几个基本场景:
- LLM决定并识别要执行的一个或多个函数
- LLM抱怨执行函数的信息不完整
- LLM条件执行语句
已讨论过相关概念,现在用此功能构建聊天机器人。
5.1. 执行一次或多次回调函数
研究当使用包含用户查询和函数模式的提示词调用LLM时的行为。
先看创建订单的例子:
void whenOrderInfoProvided_thenSaveInDB(String promptString) {
ChatResponse response = this.orderManagementAIAssistant
.callChatClient(Set.of("createOrderFn"), promptString);
String resultContent = response.getResult().getOutput().getContent();
logger.info("The response from the LLM service: {}", resultContent);
}
令人惊喜的是,通过自然语言就能获得理想结果:
提示词 | LLM响应 | 观察结果 |
---|---|---|
为用户Jenny创建数量为20的订单,随机生成正整数作为订单ID | 订单已成功创建: – 订单ID: 12345 – 用户ID: Jenny – 订单数量: 20 |
程序使用提示词中的信息创建了订单 |
创建两个订单。第一个为用户Sophia,数量30。第二个为用户Mary,数量40。随机生成正整数作为订单ID | 订单已成功创建: 1. Sophia的订单: 订单ID 1,数量30 2. Mary的订单: 订单ID 2,数量40 如需其他帮助,请随时告知! |
程序创建了两个订单。LLM智能地请求执行了两次函数 |
再看LLM能否理解获取用户订单详情的提示词:
void whenUserIDProvided_thenFetchUserOrders(String promptString) {
ChatResponse response = this.orderManagementAIAssistant
.callChatClient(Set.of("getUserOrdersFn"), promptString);
String resultContent = response.getResult().getOutput().getContent();
logger.info("The response from the LLM service: {}", resultContent);
}
结果与前例类似。LLM成功识别要执行的注册函数,然后Spring Boot应用调用请求的函数并返回一个或多个用户的订单详情。
5.2. 条件执行回调函数
考虑只在满足特定条件时创建订单的程序:
void whenUserIDProvided_thenCreateOrderIfUserHasLessThanTwoOrders(String promptString) {
ChatResponse response = this.orderManagementAIAssistant
.callChatClient(Set.of("getUserOrdersFn", "createOrderFn"), promptString);
String resultContent = response.getResult()
.getOutput()
.getContent();
logger.info("The response from the LLM service: {}", resultContent);
}
在ChatModel
Bean中注册了getUserOrderFn
和CreateOrderFn
函数。LLM必须确定哪个函数能为请求的操作产生最佳结果。
结果同样令人鼓舞:
提示词 | 输出 | 观察结果 |
---|---|---|
为用户Alex创建数量为25的订单。如果用户超过2个订单则不创建。创建订单时随机生成正整数作为订单ID | 用户Alex已有超过2个订单(具体为4个)。因此不会创建新订单。 | LLM能根据前序函数的输出条件调用下游函数 |
为用户David创建数量为25的订单。如果用户超过2个订单则不创建。创建订单时随机生成正整数作为订单ID | 已为用户ID"David"成功创建订单,订单数量25,订单ID为42。 | 这次getUserOrderFn 返回David的订单少于2个,因此LLM决定执行createOrderFn |
创建数量为20的订单 | 请提供用户ID以创建订单。 | LLM一开始就识别到缺少用户ID并中止处理 |
6. Spring AI Advisors API
前几节讨论了应用的功能方面,但所有功能存在一些共同关注点:
- 防止用户输入敏感信息
- 用户查询的日志记录和审计
- 维护对话状态
- 丰富提示词
✅ Advisors API能一致地解决这些问题。已在其他文章中详细解释过。
7. Spring AI结构化输出API和Spring AI RAG
LLM主要生成自然语言响应消息,但下游服务通常理解POJO、JSON等机器可读格式。这时Spring AI生成结构化输出的能力就很重要了。
像Advisors API一样,我们已在其他文章中详细解释过,此处不再赘述。
有时应用需要查询向量数据库对存储的数据进行语义搜索以获取额外信息。然后将向量数据库的获取结果用于提示词,为LLM提供上下文信息。这种称为RAG的技术也可以使用Spring AI实现。
8. 结论
本文探讨了创建AI助手的关键Spring AI特性。Spring AI正不断发展,提供大量开箱即用功能。但无论使用什么编程框架,正确选择底层LLM服务和向量数据库都至关重要。此外,优化这些服务的配置可能很棘手,需要大量努力。但这对于应用的广泛采用非常重要。
本文使用的源代码可在GitHub上查看。