引言

好的,今天我们继续聊一下Spring AI的相关内容。在10月的时候,我使用Spring AI搭建了一个简易版的个人助理系统,整体来说效果还是非常不错的。通过这次尝试,我对业务系统与AI结合的探索有了更为明确的理解和实践。虽然目前功能上还相对简单,整体系统也缺乏较多可操作的交互方式,特别是在数据库操作方面,功能较为基础,目前主要实现了一个简单的查询功能。

但就在10月末,Spring AI迎来了一个重要的更新,更新后不仅增强了函数调用的能力,还引入了全局参数的概念。这两个新特性为系统的扩展性和可玩性带来了极大的提升,开启了更多可能性。

那么,今天我们就利用这个全局参数的特性,来实现一个数据库插件。具体来说,我们将实现一个完整的增删改查(CRUD)操作。对于目前的智能体系统来说,数据库操作已经是一个至关重要的功能,尤其是在业务系统中,智能体能够与数据库进行交互,不仅提升了系统的灵活性和智能化程度,也大大增强了业务处理的效率。因此,我们今天的目标就是通过Spring AI的强大功能,实现一套基础的数据库操作框架,完成增、删、改、查四个功能模块。

需要特别注意的是,这里我们仅仅是通过一个简单的使用案例来进行分析和讲解,当然,这并不代表只能局限于此,实际上对于大部分业务场景来说,这样的数据库操作已经足够满足需求,并且可以根据具体的业务需求进一步扩展和优化功能。

如果有小伙伴还不太清楚如何使用Spring AI搭建自己的智能体系统,或者对于Spring AI的基本功能还不太了解,欢迎查看我们之前分享的相关文章,了解更多相关内容:https://www.cnblogs.com/guoxiaoyu/p/18453559

个人助理大优化

首先,让我们来看一下我们计划实现的个人助理功能。目前,我们已经实现了旅游攻略查询和天气查询功能。今天,我们将在此基础上新增一个“个人待办”功能。由于数据库模块较为庞大且复杂,针对这个部分我们将单独进行详细讲解。以下是该功能的大致实现效果及相关流程示意图:

效果演示

首先,让我们来看看经过半天调整后的效果演示。经过一段时间的优化和调试,最终呈现的效果基本符合我的预期。

这里只演示了下待办的增删改查,并没有演示天气查询和旅游攻略,可以看上一章节的演示。

开始优化

提示词

当然,我们的优化工作从入口部分开始,首先,提示词的设计是不可或缺的。回顾上一章节,由于当时功能较少,我们并没有对提示词做过多的修饰,因此整体的交互和模型的响应相对简单。然而,随着本次新增功能的增多,模型的回答可能会变得较为杂乱无序。

因此,为了确保模型的输出能够更精准、有序,我们在本次优化中提前准备并生成了详细的提示词。

String conversation_id = "123";
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
.withModel("hunyuan-pro").withTemperature(0.5)
.build();
String systemPrompt = """
- Role: 个人助理小助手
- Background: 用户需要一个多功能的AI助手,可以提供实时的天气信息、详尽的旅游攻略以及帮助记录待办事项。
- Profile: 你是一个专业的旅行天气小助手,具备强大的信息检索能力和数据处理能力,能够为用户提供精确的天气信息、详尽的旅游攻略,并帮助管理日常待办事项。
- Skills: 你拥有强大的网络搜索能力、数据处理能力以及用户交互能力,能够快速准确地为用户提供所需信息。
- Goals: 提供准确的天气信息,制定包含航班、酒店、火车信息的详尽旅游攻略,并帮助用户记录和管理待办事项。
- Constrains: 提供的信息必须准确无误,旅游攻略应详尽实用,待办事项管理应简洁高效。
- OutputFormat: 友好的对话式回复,包含必要的详细信息和格式化的数据。
- Workflow:
1. 接收用户的天气查询请求,并提供准确的天气信息。
2. 根据用户的旅游目的地,搜索并提供包括航班、酒店、火车在内的旅游攻略。
3. 接收用户的待办事项,并提供简洁的记录和提醒服务。
""";
ChatMemory chatMemory1 = messageChatMemoryAdvisor.getChatMemory();
String content = this.myChatClientWithSystem
.prompt()
.system(systemPrompt)
.user(userInput)
.options(openAiChatOptions)
.advisors(messageChatMemoryAdvisor,myLoggerAdvisor,promptChatKnowledageAdvisor,promptChatDateAdvisor)
.advisors(advisor -> advisor.param("chat_memory_conversation_id", conversation_id)
.param("chat_memory_response_size", 100))
.functions("CurrentWeather","TravelPlanning","toDoListFunctionWithContext")
.toolContext(Map.of("sessionId", conversation_id, "userMemory", chatMemory1,"client",chatClient))
.call()
.content(); log.info("content: {}", content);
ChatDataPO chatDataPO = ChatDataPO.builder().code("text").data(ChildData.builder().text(content).build()).build();
return chatDataPO;

好的,刚才我们新增了一些参数,我现在来详细解释一下每个参数的作用和使用场景:

  1. advisor.param:这是用于单独修改我们默认增强器(增强型顾问)的参数。它与上面提到的 Advisor 类相关联,允许用户在不修改核心代码的情况下,自定义和调整增强器的行为和配置。
  2. toolContent:这个参数是我们在新增函数回调功能时引入的全局参数,主要用于处理回调时的各种工具内容。在函数调用中,toolContent 可以传递不同的工具数据,确保回调过程的正确执行。
  3. sessionId:这个参数用于标识每个独立的会话,它帮助我们控制每个用户的会话状态。通过给每个会话分配一个唯一的 sessionId,我们可以确保每个函数调用是针对特定用户的,而非共享的全局数据。例如,我们的待办事项是个人化的,只有对应用户可以看到和操作自己的待办列表。这里为了演示的方便,我们将 sessionId 设置为固定值,并未接入登录接口,实际应用中应根据用户身份动态生成。
  4. userMemory:此参数用于传递用户的历史上下文,使得回调函数能够使用到之前的对话或操作记录。例如,在待办事项管理中,我们可能需要根据历史数据判断某个任务是否已经完成。
  5. chatClient:该参数将待办函数与一个大模型连接,借助大模型生成SQL查询或其他复杂操作。chatClient 负责与大模型进行交互,生成所需的SQL,而外层的思考模型则专注于调用接口并处理业务逻辑。

好的,再次提醒一下,如果有些小伙伴之前没有接触过智能体的相关内容,建议你们先去浏览一下我之前的第一篇文章,了解一下基础知识,补充相关的背景信息,这样对接下来的内容会更加容易理解。

那么,接下来我们继续探讨与待办事项相关的内容。

数据表

个人待办事项的目的非常明确,主要是针对待办事项表进行增、删、改、查等基本操作。为了保证系统的高效性与简洁性,我们在设计数据库时,所需的数据字段也非常简洁直观。这是我们的建表语句:

create table todo_info(
id int(11) auto_increment primary key,
todo_info varchar(1000) not null,
todo_Date date not null,
done boolean not null default false
)

在生成完这个建表语句之后,一定要妥善保存,以便后续给大模型提供参考。

函数回调

首先,我们需要明确待办函数的回调必须能够支持四种基本操作:增删改查,。此外,回调函数还需要具备生成SQL语句的能力,并能执行这些SQL语句,以便与数据库进行交互。只有在这些操作顺利完成之后,我们才能将最终的结果数据返回给外层的思考模型,以供进一步的处理和分析。

基于这些要求,我们现在可以开始具体操作,逐步实现所需功能。

好的,我们一步一步来。

待办函数

根据上述信息,我们需要设计一个函数,该函数需要包含一个入参和一个回参。入参的主要作用是传递一个标识符,用于唯一标识某一操作,而回参则是一个字符串类型的返回值,用于向调用方反馈相应的结果或状态信息。

public class ToDoListInfoService implements BiFunction<ToDoListInfoService.ToDoRequest, ToolContext, ToDoListInfoService.ToDoResponse> {

    private JdbcTemplate jdbcTemplate;

    public ToDoListInfoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
} @JsonClassDescription("crud:c 代表增加;r:代表查询,u:代表更新,d:代表删除")
public record ToDoRequest(String crud) {}
public record ToDoResponse(String message) {}
@Override
public ToDoResponse apply(ToDoListInfoService.ToDoRequest request, ToolContext toolContext) {}
}

可以观察到,在这里我们使用的是 BiFunction 接口,而不是之前使用的 Function 接口,因为我们需要使用ToolContext。

需要特别注意的是,尽管在这里我们使用了 JsonClassDescription,但它的主要目的是为了提高代码的可读性和可维护性,方便开发人员理解和查看结构。实际上,大模型并不会依赖于 JsonClassDescription 来判断或解析传递的具体参数。

Bean装配

由于我们在这里使用了 JdbcTemplate 进行数据库操作,这就需要在项目中引入相应的 Maven 依赖。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

我这里仍然选择使用 Bean 装配的方式,主要是因为它能够提供更高的灵活性与可维护性。此外,维护好入参的定义和配置,也能够在后续的开发中为大模型提供有效的参考和支持。

@Bean
public FunctionCallback toDoListFunctionWithContext(JdbcTemplate jdbcTemplate) { return FunctionCallbackWrapper.builder(new ToDoListInfoService(jdbcTemplate))
.withName("toDoListFunctionWithContext") // (1) function name
.withDescription("添加待办,crud:c 代表增加;r:代表查询,u:代表更新,d:代表删除") // (2) function description
.build();
}

接下来,我们将着手实现函数内部的具体方法。

上下文信息

默认提供的 MessageChatMemoryAdvisor 类并不支持直接获取历史聊天记录。因此,我们需要自定义一个类来实现这一功能。为了方便使用,下面的代码将集成该功能并暴露一个接口供调用。

@Slf4j
public class MyMessageChatMemoryAdvisor extends MessageChatMemoryAdvisor { private ChatMemory chatMemory; public MyMessageChatMemoryAdvisor(ChatMemory chatMemory) {
super(chatMemory);
this.chatMemory = chatMemory;
} public MyMessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize);
this.chatMemory = chatMemory;
} public MyMessageChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int chatHistoryWindowSize, int order) {
super(chatMemory, defaultConversationId, chatHistoryWindowSize, order);
this.chatMemory = chatMemory;
} public ChatMemory getChatMemory() {
return chatMemory;
}
}

为了能够顺利访问历史聊天记录,我们专门编写了一个方法,该方法会将历史上下文对象返回。通过这种方式,我们可以在后续的操作中方便地获取到完整的聊天信息,从而实现对历史对话内容的正常访问和处理。

数据库操作

接下来,我们将进行数据库操作,但在此过程中,必须依赖大模型的帮助来生成SQL语句。原因在于,外层的大模型具备强大的能力,可以准确地分析并理解需求,从而判断出具体的操作类型是增、删、改还是查。接下来,我们将详细介绍如何实现这一过程。

public ToDoResponse apply(ToDoListInfoService.ToDoRequest request, ToolContext toolContext) {
String tableinfo = """
- Role: SQL语句生成专家
- Background: 用户需要根据特定的表结构和参数信息生成精准的MySQL查询或修改语句,以实现数据库操作的自动化和效率化。
- Profile: 你是一位经验丰富的数据库管理员和SQL专家,精通MySQL数据库的各种查询和修改语句,能够根据用户提供的表结构和参数信息快速生成正确的SQL语句。
- Skills: 你具备深厚的数据库理论知识和丰富的实践经验,能够理解复杂的表结构,准确把握用户需求,并据此生成高效、准确的SQL语句。
- Goals: 根据用户提供的表结构和参数信息,生成可以直接执行的MySQL查询或修改语句,确保语句的正确性和执行的成功率。
- Constrains: 生成的SQL语句必须符合MySQL的语法规则,能够直接在MySQL数据库中执行,且不包含任何额外的信息或提示。
- OutputFormat: 纯SQL文本语句,格式规范,无多余信息。禁止使用markdown格式。
- Workflow:
1. 分析用户提供的表结构信息和参数信息。
2. 根据分析结果,确定需要执行的数据库操作类型(查询、插入、更新或删除)。
3. 结合操作类型和用户提供的信息,生成符合MySQL语法的SQL语句。
-example:
q.帮我创建一个待办:明天9点提醒我读书。今天日期是2024-11-07 20:20:11
a:INSERT INTO todo_info (todo_info, todo_date) VALUES ('明天8点读书', '2024-11-08 08:00:00');
- tableinfo:
create table todo_info(
id int(11) auto_increment primary key,
todo_info varchar(1000) not null,
todo_Date date not null,
done boolean not null default false
)
""";
String crud = request.crud;
ChatMemory chatMemory = (ChatMemory)toolContext.getContext().get("userMemory");
String conversation_id = (String)toolContext.getContext().get("sessionId");
ChatClient client = (ChatClient)toolContext.getContext().get("client");
List<Message> messages = chatMemory.get(conversation_id, 1);
var userqa = messages.get(0).getContent();
String jsonString = "执行成功";
String content = client.prompt()
.system(tableinfo)
.user("请根据当前问题生成相应SQL文本即可,禁止生成SQL以外的内容:"+userqa+",今天日期是:"+ DateUtil.now())
.call().content();
try {
if (crud.equals("r")) {
jsonString = "查询到待办内容如下:" + JSONObject.toJSONString(jdbcTemplate.queryForList(content));
} else {
jdbcTemplate.execute(content);
}
}catch (Exception e){
log.info("ToDoListInfoService:{}", e.getMessage());
jsonString = "执行失败了";
}
log.info("ToDoListInfoService:{}", content);
return new ToDoResponse(jsonString);

这段代码的实现完全依赖于通过全局参数传递过来的信息,以便更好地处理历史上下文的问题。传统的回调函数方法在处理多轮对话的历史上下文时存在很大的局限性,无法有效地追踪会话中的上下文,因此难以解决这类问题。在这种情况下,我们通过全局参数传递的方式,能够跨越多次交互,确保在每个步骤中都能访问到最新的上下文信息。

让我们简单解释一下这段代码的流程:

  1. 获取当前会话中的用户提问:我们从当前会话中获取最近一次用户提出的问题,确保我们不会误取到其他会话的上下文。
  2. 将问题提交给大模型生成SQL:我们将用户的提问传递给大模型,利用其能力帮助我们生成适当的SQL查询语句。
  3. 判断是否为查询请求:我们检查生成的SQL语句是否属于查询操作。如果是查询,则执行查询并将查询结果返回给用户。
  4. 其他操作的处理:如果不是查询请求,则说明用户发出的指令可能是更新或执行类的操作,在这种情况下,我们返回一个"执行成功"的响应。

可以看到,提示词中的 tableinfo 是我硬编码写死的。实际上,我们完全可以将其设计成一个可传入的参数,这样不仅提升了插件的灵活性和可复用性,而且使得该插件不再仅仅局限于待办事项的使用场景,而能够作为一个通用的数据库操作插件,适应不同的需求和应用场景。

为了能够在演示过程中展示效果,目前我将某些部分做了临时的硬编码处理。这只是为了给大家提供一个初步的思路和参考框架,后续我会逐步完善这些功能。

接下来,让我们一起来看看调试过程中得到的效果。以下是我在调试时的截图:

接下来,我们将检查数据库是否已经成功存储并正常更新了数据。

接下来,我将展示查询的实际效果,同时生成的 SQL 语句也相当优秀,能够高效地满足查询需求。

可以看到,此处已成功将数据或结果正常返回给前端,系统运行状态良好。

总结

在本文中,我们深入探讨了如何利用 Spring AI 的新功能,特别是全局参数和增强函数调用能力,来构建一个智能化的个人助理系统。通过这个系统,我们实现了基础的增、删、改、查(CRUD)功能,特别聚焦在数据库交互与待办事项管理上。我们展示了如何将 Spring AI 集成到实际业务流程中,通过模型生成 SQL 查询语句,提升数据库操作的自动化程度和灵活性。

首先,我们介绍了 Spring AI 在功能更新后如何简化和扩展业务逻辑处理,特别是在处理多轮对话、用户历史数据以及复杂数据库操作时的优势。通过全局参数,系统能够更精准地捕捉用户需求,并在数据库层面执行相关操作,真正实现了智能化的交互和自动化的业务流程。

我们还设计了一个待办事项管理功能,其中通过精心设计的提示词和模型优化,使得待办功能的增删改查操作更加高效与准确。

总结来说,这次基于 Spring AI 的系统优化,不仅为我们提供了一个强大的智能助手框架,也为实际业务中的智能化系统提供了可借鉴的方案。未来,我们可以基于此进一步扩展功能,打造更加智能化和个性化的业务解决方案。


我是努力的小雨,一名 Java 服务端码农,潜心研究着 AI 技术的奥秘。我热爱技术交流与分享,对开源社区充满热情。同时也是一位腾讯云创作之星、阿里云专家博主、华为云云享专家、掘金优秀作者。

我将不吝分享我在技术道路上的个人探索与经验,希望能为你的学习与成长带来一些启发与帮助。

欢迎关注努力的小雨!

Spring AI 再更新:如何借助全局参数实现智能数据库操作与个性化待办管理的更多相关文章

  1. CloudStack4.2 更新全局参数API

    测试更新全局参数API http://192.168.153.34:8080/client/api?command=updateConfiguration&response=json& ...

  2. Spring Boot 2.X(十一):全局异常处理

    前言 在 Java Web 系统开发中,不管是 Controller 层.Service 层还是 Dao 层,都有可能抛出异常.如果在每个方法中加上各种 try catch 的异常处理代码,那样会使代 ...

  3. Mybatis 注入全局参数

    在项目中使用mybatis作为dao层,大部分时间都需要使用到mybatis提供的动态sql功能,一般情况下所有的表都是在同一个数据库下的,进行数据操作时都是使用jdbc中默认的schema.但是如果 ...

  4. flink---实时项目----day03---1.练习讲解(全局参数,数据以parquet格式写入hdfs中) 2 异步查询 3 BroadcastState

    1 练习讲解(此处自己没跑通,以后debug) 题目见flink---实时项目---day02 kafka中的数据,见day02的文档 GeoUtils package cn._51doit.flin ...

  5. 小程序首页onLoad为异步,调用app.js中的全局参数的解决方案。

    一,先说一下遇到的问题: 在首页,为了携带app.js中一些参数去做请求动作,但是由于异步原因,发现请求时候,参数信息还未获取到但请求已经发出去. 若等app.js的全局参数返回来,再携带着它去做请求 ...

  6. Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理

    Spring Boot AOP之对请求的参数入参与返回结果进行拦截处理   本文链接:https://blog.csdn.net/puhaiyang/article/details/78146620 ...

  7. 微信小程序:页面全局参数(注意不是小程序的全局变量globalData)

    为什么要使用页面全局参数:方便使用数据. 由于总页数需要再另外的一个方法中使用,所以要把总页数变成一个页面全局参数.因为取数据使用this.xxx即可,中间不用加data,给页面全局参数赋值页方便,直 ...

  8. Gradio入门到进阶全网最详细教程[二]:快速搭建AI算法可视化部署演示(侧重参数详解和案例实践)

    Gradio入门到进阶全网最详细教程[二]:快速搭建AI算法可视化部署演示(侧重参数详解和案例实践) 相关文章:Gradio入门到进阶全网最详细教程[一]:快速搭建AI算法可视化部署演示(侧重项目搭建 ...

  9. 使用Spring mvc接收整个url地址及参数时注意事项

    使用Spring mvc接收整个url地址及参数时注意事项:url= http://baidu?oid=9525c1f2b2cd45019b30a37bead6ebbb&td=2015-08- ...

  10. Haproxy官方文档翻译(第三章)全局参数(1) 附英文原文

    3.全局参数 在global这个节点里的参数是“进程范围的”并且经常是“操作系统指定”的.它们通常是一次性设置而且一旦正确设置不需要动来动去的.它们中的一些和命令行对应. global节点支持以下关键 ...

随机推荐

  1. RabbitMQ 基础概念与架构设计及工作机制学习总结

    什么是RabbitMQ MQ全称为Message Queue,即消息队列. 它也是一个队列,遵循FIFO原则 .RabbitMQ则是一个开源的消息中间件,由erlang语言开发,基于AMQP协议实现的 ...

  2. 使用 python flask 框架实现一个简单的抽奖系统

    Flask 实现一个简易的抽奖系统 项目前置知识 目前 python主流的框架: Django .flask .Tornado 简介: 1.框架 框架? 为什莫使用框架? (前置知识讲解比较冗杂,望谅 ...

  3. 使用 crontab 设置 Homebrew 自动更新

    本人有强迫症,希望自己电脑上安装的软件永远是最新的.App Store 有自动更新功能,然而 Homebrew 则没有自动更新的选项.每次手动更新的话时间长了又感觉麻烦.后来发现可以使用 cronta ...

  4. 【爬虫实战】——利用bs4和sqlalchemy操作mysql数据库,实现网站多行数据表格爬取数据

    前言 此篇接上一篇的内容,在其基础上爬取网站的多行表格数据,以及把数据写入到mysql数据库中 目录 一.定位表格查找元素 二.提取数据 三.写入mysql数据库 四.附录 一.定位表格查找元素 首先 ...

  5. AI图像放大工具,图片放大无所不能

    AI图像放大工具,如ESRGAN,对于提高由Stable Diffusion生成的AI图像质量至关重要.它们被广泛使用,以至于许多Stable Diffusion的图形用户界面(GUI)都内置了支持. ...

  6. vue打包项目版本号自加

    原因 项目每次打包后都需要改动项目版本号,这个改动每次都需要在package.json中修改version,比较麻烦,到底有没有一种打包后版本号自加的办法. 方案 版本号自加其实可以使用fs修改文件来 ...

  7. 【YashanDB知识库】YAS-00103 no free block in dictionary cache

    [问题分类]功能使用 [关键字]YAS-00103,no free block in dictionary cache [问题描述]执行union all 太多子查询导致报错,例子如下: [问题原因分 ...

  8. 【题目全解】ACGO排位赛#12

    ACGO 排位赛#12 - 题目解析 别问为什么没有挑战赛#11,因为挑战赛#11被贪心的 Yuilice 吃掉了(不是). 本次挑战赛难度相比较前面几次有所提升. 爆料:小鱼现在已经入职了研发部门, ...

  9. Angular Material 18+ 高级教程 – CDK Drag and Drop

    前言 CDK Drag and Drop 和 CDK Scrolling 都是在 Angular Material v7 中推出的. 它们有一个巧妙的共同点,那就是与 Material Design ...

  10. RxJS 系列 – Transformation Operators

    前言 前几篇介绍过了 Creation Operators Filter Operators Join Creation Operators Error Handling Operators 这篇继续 ...