找回密码
 立即注册
首页 业界区 科技 Spring AI Alibaba 入门指南

Spring AI Alibaba 入门指南

靳谷雪 昨天 21:10

1. 概述

Spring AI Alibaba 开源项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案和企业级 AI 应用生态集成。

在用Spring AI搭建Java AI应用的时候,会碰到了各种让人头疼的配置动态管理的问题. 比如像调用算法模型的“API-KEY密钥”这类敏感配置.

还有想要模型的各类调用配置参数,以及Prompt Engineering里的Prompt Template如何可以在不发布重启应用的情况下,快速修改生效来响应业务需求.

Spring AI Alibaba 将结合Nacos来一一解决

并且老外的Spring AI框架对于像Open AI , 微软、亚马逊、谷歌 等大模型支持较好, 对于国产AI支持则不那么友好, 而Spring AI Alibaba 对于通义系列的大模型则是天生友好.

不过在学习这篇之前, 还是需要先了解一下Spring AI 框架. https://www.cnblogs.com/xjwhaha/p/19306045
以下是当前主流Java AI应用框架的对比

1.png

OpenAI Api 和 阿里的DashScope(灵积)Api的区别

OpenAI API 是 OpenAI 官方提供的一个 大模型接口平台,定义了开发者通过一套标准的 HTTP 调用模板来使用:

  • GPT 系列模型(GPT-4.1 / o3 / gpt-4.1-mini 等)

  • 多模态模型(看图、语音)

  • Embeddings(向量)

  • 文生图(DALL·E)

  • 等等...

SpringAI框架就是使用这套API接口来进行调用, 同样模型的厂商也需要实现此接口, 双方通过达成一致,达到统一AI大模型访问的目的.

DashScope 是阿里云的 大模型 API 平台,提供“通义千问 + 多模态 + 向量 + 文生图 + 语音”的一站式接口,类似于国内版的 OpenAI API。同时,除了阿里自己的通义系列. 包括Deepseek和月之暗面等国内大模型, 也进行了封装, 也可以通过DashScopeAPI进行调用. SpringAIAlibaba 就支持使用DashScopeApi 来进行统一访问国产AI大模型的能力. 当然SpringAIAlibaba 也同样支持 OpenAIApi的访问方式,进行访问实现OpenAIAPI的大模型.例如OpenAI等等.

2. 快速入门示例

下面将实现一个天气预报的小助手功能, 来快速了解一下SAA的各个常用功能.

  1. 详细的 System Prom - 获得更好的 agent 行为
  2. 创建工具 - 与外部数据集成
  3. 模型配置 - 获得一致的响应
  4. 结构化输出 - 获得可预测的结果
  5. 对话记忆 - 实现类似聊天的交互
  6. 创建和运行 agent - 创建一个功能完整的 agent

2.1 依赖和配置

使用SpringAIAlibaba,先导入pom依赖:

这里优先使用了 DashScope的方式访大模型

  1. <dependency>
  2. <groupId>com.alibaba.cloud.ai</groupId>
  3. spring-ai-alibaba-agent-framework</artifactId>
  4. <version>1.1.0.0-M5</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.alibaba.cloud.ai</groupId>
  8. spring-ai-alibaba-starter-dashscope</artifactId>
  9. <version>1.1.0.0-M5</version>
  10. </dependency>
复制代码

application.yaml 配置:

指定apiKey,模型名称和访问路径. 注意apiKey生产环境建议配置在环境变量中

  1. spring:
  2. ai:
  3. dashscope:
  4. api-key: sk-********
  5. base-url: https://dashscope.aliyuncs.com
  6. chat:
  7. options:
  8. model: qwen3-max
复制代码

2.2 一个天气预报助手

首先需要定义两个工具,一个用于获取当前用户的位置, 另外一个获取地方天气信息:

  1. public record WeatherRequest(@ToolParam(description = "城市的名称") String location) {
  2. }
  3. // 天气查询工具
  4. public static class WeatherForLocationTool implements BiFunction<WeatherRequest, ToolContext, String> {
  5. @Override
  6. public String apply(
  7. @ToolParam(description = "城市的名称") WeatherRequest city,
  8. ToolContext toolContext) {
  9. return StrUtil.equals("上海", city.location) ? "晴朗" : "小雨";
  10. }
  11. }
  12. // 用户位置工具 - 使用上下文
  13. public static class UserLocationTool implements BiFunction<WeatherRequest, ToolContext, String> {
  14. @Override
  15. public String apply(
  16. WeatherRequest query,
  17. ToolContext toolContext) {
  18. // 从上下文中获取用户信息
  19. RunnableConfig config = (RunnableConfig) toolContext.getContext().get("_AGENT_CONFIG_");
  20. String userId = (String) config.metadata("user_id").orElse(null);
  21. if (userId == null) {
  22. return "User ID not provided";
  23. }
  24. System.out.println("userId: " + userId);
  25. return "1".equals(userId) ? "杭州" : "上海";
  26. }
  27. }
复制代码

工具应该有良好的文档:它们的名称、描述和参数名称都会成为模型提示的一部分。

Spring AI 的 FunctionToolCallback 支持通过 @ToolParam 注解添加元数据,并支持通过 ToolContext 参数进行运行时注入。

构建ReactAgent, 用户访问大模型的类:

  1. private final DashScopeChatModel chatModel;
  2. public AgentConfiguration(DashScopeChatModel chatModel) {
  3. this.chatModel = chatModel;
  4. }
  5. @Bean
  6. public ReactAgent reactAgent() {
  7. String SYSTEM_PROMPT = """
  8. 你是一位天气预报专家,说话比较幽默。
  9. 您可以访问两个工具:
  10. - get_weather_for_location:使用它来获取指定位置的天气
  11. — get_user_location:使用它来获取用户的当前位置
  12. 如果用户向你询问天气,你可以尝试分析他需要查询的位置。例如上海,杭州等.
  13. 但是如果用户没有指定位置,你需要调用get_user_location获取此用户的当前位置,查询此位置的天气
  14. """;
  15. return ReactAgent.builder()
  16. .name("天气预报小助手")
  17. .description("这是一个天气预报小助手智能体")
  18. // 如果是简短,简单的系统提示可以用这个
  19. // .systemPrompt(SYSTEM_PROMPT)
  20. // 更详细的指令
  21. .instruction(SYSTEM_PROMPT)
  22. .tools(FunctionToolCallback.builder("weatherForLocationTool", new WeatherForLocationTool()).description("根据城市名称获取当前天气信息").inputType(WeatherRequest.class).build(),
  23. FunctionToolCallback.builder("userLocationTool", new UserLocationTool()).description("获取用户当前位置").inputType(WeatherRequest.class).build()
  24. )
  25. // 基于内存的存储
  26. .saver(new MemorySaver())
  27. .outputType(ResponseFormat.class)
  28. .model(chatModel)
  29. .build();
  30. }
复制代码

下面是定义大模型返回的结构体

  1. /**
  2. * 使用 Java 类定义响应格式
  3. */
  4. @Getter
  5. @Setter
  6. public class ResponseFormat {
  7. /**
  8. * 城市名称
  9. */
  10. private String city;
  11. /**
  12. * 天气情况
  13. */
  14. private String punnyResponse;
  15. /**
  16. * 关于该天气的一个有趣的浪漫的简语
  17. */
  18. private String weatherConditions;
  19. }
复制代码
  1. 使用instruction方法,定义系统提示.引导大模型的执行方式.
  2. 使用tools 方法注册创建的函数. 使大模型具有调用本地方法的能力
  3. 使用saver方法注册一个用于存储历史记录的类,框架会自动读取当前指定的threadId来读取当前会话的历史记录, 使大模型调用具有历史记忆功能, 并且会自动将本次调用按threadId 为key存起来 ( 这里使用的MemorySaver 是基于内存, 生产需要使用基于持久化中间件的实现)
  4. 使用outputType 方法定义大模型返回的数据结构为此对象的结构

调用方式如下:

  1. @RestController
  2. @RequestMapping("/ai")
  3. public class AiController {
  4. @Resource
  5. private ReactAgent reactAgent;
  6. @GetMapping
  7. public String ai(@RequestParam String question) throws Exception {
  8. RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").addMetadata("user_id", "1").build();
  9. return reactAgent.call(question, runnableConfig).getText();
  10. }
  11. }
复制代码
  1. 构建调用执行时配置,指定threadId,相当于会话ID
  2. 加入运行时元数据, 在调用的执行链中,可以通过上下文获取

运行效果:

调用: http://127.0.0.1:8089/ai?question=上海天气怎么样

响应:

可以看到返回的数据为指定的json结构,并且自动读取问题中的城市信息,并调用了获取天气的方法.

再次调用: http://127.0.0.1:8089/ai?question=我这里呢

在没有询问城市时, 大模型自动调用了获取本地城市的方法,得到当前城市为杭州.

3. ReactAgent 的工作原理

在上面的案例中, 我们实现了一个简单的天气查询工具类, 大模型具有调用本地方法的能力, 这背后的原理是什么样, ReactAgent 的执行流程是什么, 大模型是如何调用本地方法的?

什么是 ReactAgent?

  • Agent(智能体): 在 AI 编程中,Agent 是一个能感知环境、使用工具(如搜索、计算、API调用)、进行推理并执行任务以实现目标的程序。它不仅仅是调用大模型,而是让大模型成为“大脑”,指挥各种工具。
  • ReAct: 是一种经典的 Agent 设计范式,代表 Reasoning + Acting。其核心思想是让模型以“思考(Thought)- 行动(Action)- 观察(Observation)”的循环来工作。
    1. Thought: 模型分析当前状况,思考下一步该做什么。
    2. Action: 根据思考,决定调用哪个工具(或直接给出最终答案)。
    3. Observation: 执行工具后,获取结果(可能是搜索结果、代码执行结果等)。
    4. 循环此过程,直到任务完成。

这个循环使 Agent 能够:

  • 将复杂问题分解为多个步骤
  • 动态调整策略基于中间结果
  • 处理需要多次工具调用的任务
  • 在不确定的环境中做出决策

而SAA框架中的ReactAgent是怎么完成这个工作的?

Spring AI Alibaba 中的ReactAgent 内容抽象了三个模块,由这三个模块相互配合完成

  • Model Node (模型节点):调用 LLM 进行推理和决策(.model(chatModel)方法传入的大模型调用类)
  • Tool Node (工具节点):执行工具调用(注册的工具)
  • Hook Nodes (钩子节点):在关键位置插入自定义逻辑

ReactAgent 的核心执行流程:

2.png

下面通过梳理上面天气小助手的执行流程来具体了解一下工作流程:

  1. 第一步: 发出提问
  2. 第二步:SpringAI 构建一个 ChatRequest(包含tools和所有问题的上下文信息)
  3. 第三步:序列化成 JSON
  4. 第四步:通过 HTTP POST 调用大模型 API
  5. 第五步:大模型进行判断推理,是否需要执行函数,执行哪个函数,在本案例中, 如果解析出城市名称, 则需要调用获取天气的函数, 如果没有,则需要调用获取用户位置的函数. 并返回执行函数的名称+入参
  6. 第六步:ReactAgent解析返回JSON,进行推理是否是一次 function call
  7. 第七步:如果是,则本地反射调用该方法,例如执行了获取用户位置的函数
  8. 第八步:把该函数结果再封装成 JSON 发给大模型继续对话,大模型拿到此函数结果,继续分析推理,拿到位置后,大模型继续推理需要进行调用获取天气的函数,则继续返回客户 端调用
  9. 第九步:ReactAgent继续解析返回JSON,执行获取天气的函数并返回
  10. 第九步:最终返回结果给用户
复制代码

在上面的流程中, 可以迅速的了解到上图的含义. 在模型和工具间循环,直到模型推理出最终结果为止.

也就是说,以ReactAgent为本体:

  • 大模型 = 脑子(发出工具调用的意愿)
  • SpringAI = 手(真正执行方法)
  • 你的方法 = 工具(可以被大模型调来用)

4. Hooks 和 Interceptors

在上面关于ReactAgent的工作流程的介绍中, 除了Tool NodeModel Node 之外,还有一个组件为Hooks(钩子).

SAA框架在这些步骤的前后暴露了钩子点Hooks 和 拦截器Interceptors,允许你

  • 监控: 通过日志、分析和调试跟踪 Agent 行为
  • 修改: 转换提示、工具选择和输出格式
  • 控制: 添加重试、回退和提前终止逻辑
  • 强制执行: 应用速率限制、护栏和 PII 检测

4.1 自定义钩子

框架中提供了四个抽象类供开发者实现,并在不同的节点调用

ModelHook: 在模型调用前后执行自定义逻辑

AgentHook: 在 Agent 一次问答整体执行的开始和结束时执行:

ModelInterceptor: 拦截和修改对模型的请求和响应

ToolInterceptor:拦截和修改工具调用

下面的示例,分别实现了这四个抽象类,可以快速了解其使用方式:

  1. public class MyHooks {
  2. private static final String CALL_COUNT_KEY = "_model_call_count_";
  3. private static final String START_TIME_KEY = "_call_start_time_";
  4. // 1. AgentHook - 在 Agent 开始/结束时执行,每次Agent调用只会运行一次
  5. @HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT})
  6. public static class LoggingHook extends AgentHook {
  7. @Override
  8. public String getName() {
  9. return "logging";
  10. }
  11. @Override
  12. public CompletableFuture<Map<String, Object>> beforeAgent(OverAllState state, RunnableConfig config) {
  13. DateTime date = DateUtil.date();
  14. System.out.println("Agent 开始执行时间" + DateUtil.formatDateTime(date));
  15. config.context().put(START_TIME_KEY, date.getTime());
  16. return CompletableFuture.completedFuture(Map.of());
  17. }
  18. @Override
  19. public CompletableFuture<Map<String, Object>> afterAgent(OverAllState state, RunnableConfig config) {
  20. long startTime = (long) config.context().get(START_TIME_KEY);
  21. System.out.println("Agent 执行完成,耗时:" + (DateUtil.date().getTime() - startTime));
  22. return CompletableFuture.completedFuture(Map.of());
  23. }
  24. }
  25. // 2. ModelHook - 在模型调用前后执行(例如:消息修剪),区别于AgentHook,ModelHook在一次agent调用中可能会调用多次,也就是每次 reasoning-acting 迭代都会执行
  26. public static class MessageTrimmingHook extends ModelHook {
  27. @Override
  28. public String getName() {
  29. return "message_trimming";
  30. }
  31. @Override
  32. public HookPosition[] getHookPositions() {
  33. return new HookPosition[]{HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL};
  34. }
  35. @Override
  36. public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
  37. // 这里可以获取到请求模型时传入的所有message
  38. Optional<Object> messagesOpt = state.value("messages");
  39. if (messagesOpt.isPresent()) {
  40. List<Message> messages = (List<Message>) messagesOpt.get();
  41. System.out.println(messages.size());
  42. }
  43. // 增加调用次数记录
  44. config.context().put(CALL_COUNT_KEY, config.context().get(CALL_COUNT_KEY) == null ? 1 : (Integer) config.context().get(CALL_COUNT_KEY) + 1);
  45. System.out.println("第" + config.context().get(CALL_COUNT_KEY) + "次调用模型开始");
  46. // 模型调用前,可以进行消息修剪,返回的Map会作为模型调用的参数
  47. return CompletableFuture.completedFuture(Map.of());
  48. }
  49. @Override
  50. public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
  51. System.out.println("第" + config.context().get(CALL_COUNT_KEY) + "次调用模型结束");
  52. return CompletableFuture.completedFuture(Map.of());
  53. }
  54. }
  55. public static class LoggingInterceptor extends ModelInterceptor {
  56. @Override
  57. public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
  58. // 请求前记录
  59. System.out.println("发送请求到模型: " + request.getMessages().size() + " 条消息");
  60. // 执行实际调用
  61. return handler.call(request);
  62. }
  63. @Override
  64. public String getName() {
  65. return "LoggingInterceptor";
  66. }
  67. }
  68. public static class ToolMonitoringInterceptor extends ToolInterceptor {
  69. @Override
  70. public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
  71. String toolName = request.getToolName();
  72. long startTime = System.currentTimeMillis();
  73. System.out.println("执行工具: " + toolName + "执行参数: " + request.getArguments());
  74. try {
  75. return handler.call(request);
  76. } catch (Exception e) {
  77. long duration = System.currentTimeMillis() - startTime;
  78. System.err.println("工具 " + toolName + " 执行失败 (耗时: " + duration + "ms): " + e.getMessage());
  79. return ToolCallResponse.of(
  80. request.getToolCallId(),
  81. request.getToolName(),
  82. "工具执行失败: " + e.getMessage()
  83. );
  84. }
  85. }
  86. @Override
  87. public String getName() {
  88. return "ToolMonitoringInterceptor";
  89. }
  90. }
  91. }
复制代码

注册钩子:

  1. ReactAgent.builder()
  2. .name("天气预报小助手")
  3. .description("这是一个天气预报小助手智能体")
  4. // 如果是简短,简单的系统提示可以用这个
  5. // .systemPrompt(SYSTEM_PROMPT)
  6. // 更详细的指令
  7. .instruction(SYSTEM_PROMPT)
  8. .tools(FunctionToolCallback.builder("weatherForLocationTool", new WeatherForLocationTool()).description("根据城市名称获取当前天气信息").inputType(WeatherRequest.class).build(),
  9. FunctionToolCallback.builder("userLocationTool", new UserLocationTool()).description("获取用户当前位置").inputType(WeatherRequest.class).build()
  10. )
  11. // 基于内存的存储
  12. .saver(new MemorySaver())
  13. .outputType(ResponseFormat.class)
  14. // 注册钩子和拦截器
  15. .hooks(new MyHooks.LoggingHook(), new MyHooks.MessageTrimmingHook())
  16. .interceptors(new MyHooks.LoggingInterceptor(), new MyHooks.ToolMonitoringInterceptor())
  17. .model(chatModel)
  18. .build();
复制代码

启动调用: http://127.0.0.1:8089/ai?question=杭州今天天气怎么样

控制台打印:

Agent 开始执行时间2025-12-09 15:36:11
第1次调用模型开始
发送请求到模型: 1 条消息
第1次调用模型结束
执行工具: weatherForLocationTool执行参数: {"location": "杭州"}
第2次调用模型开始
发送请求到模型: 3 条消息
第2次调用模型结束
Agent 执行完成,耗时:5084

4.2 内置Hooks和Interceptors

Spring AI Alibaba 为常见用例提供了预构建的 Hooks 和 Interceptors 实现:模型调用限制(Model Call Limit),LLM Tool Selector(LLM 工具选择器) 等等.

Human-in-the-Loop(人机协同)

在调用指定的Tool时, 暂停 Agent 执行以获得人工批准、编辑或拒绝工具调用。

适用场景:

  • 需要人工批准的高风险操作(数据库写入、金融交易)
  • 人工监督是强制性的合规工作流程
  • 长期对话,使用人工反馈引导 Agent

使用示例: 将模拟一个发送邮件的Agent, 每次发送邮件,都需要手动人为审批

  1. 首先需要定义一个发送邮件的Tool.当模型判断需要调用次方法时,会中断流程,并等待人工审批,再继续执行
  1. public record EmailRequest(@ToolParam(description = "发送邮件的信息") String message) {
  2. }
  3. // 发送email
  4. public static class SendEmailTool implements BiFunction<EmailRequest, ToolContext, Boolean> {
  5. @Override
  6. public Boolean apply(
  7. @ToolParam(description = "发送邮件的信息") EmailRequest message,
  8. ToolContext toolContext) {
  9. System.out.println("发送邮件: " + message.message);
  10. return true;
  11. }
  12. }
复制代码
  1. 这里构建了一个ReactAgent, 注册Tool, 并传入一个humanInTheLoopHook 实例, 描述调用审批的节点, 注意,这里一定需要传入 saver 作为检查点, 因为中断后再次调用,需要依赖历史记录message,并携带上下文,才能使调用前后的流程衔接, 这里使用了测试用的实例MemorySaver,基于内存
  1. @Bean
  2. public ReactAgent emailReactAgent() {
  3. String SYSTEM_PROMPT = """
  4. 你是一个工作平台的助手
  5. 您可以访问一个工具:
  6. - sendEmailTool:使用该工具进行发送邮件的操作
  7. 如果用户有需要发送邮件,可以进行操作
  8. """;
  9. // 创建人工介入Hook
  10. HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder()
  11. .approvalOn("sendEmailTool", ToolConfig.builder()
  12. .description("发送邮件需要审批")
  13. .build())
  14. .build();
  15. return ReactAgent.builder()
  16. .name("工作助手")
  17. .instruction(SYSTEM_PROMPT)
  18. .tools(FunctionToolCallback.builder("sendEmailTool", new SendEmailTool()).description("进行发送邮件的操作").inputType(EmailRequest.class).build()
  19. )
  20. // 基于内存的存储
  21. .saver(new MemorySaver())
  22. .hooks(humanInTheLoopHook)
  23. .model(chatModel)
  24. .build();
  25. }
复制代码
  1. 访问agent的方式, 和同意审批的方法, 当agent判断中断后, 会返回审批中提示, 之后需要管理员调用同意方法,继续执行
  1. @GetMapping
  2. public String ai(@RequestParam String question) throws Exception {
  3. RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").build();
  4. Optional<NodeOutput> result = reactAgent.invokeAndGetOutput(question, runnableConfig);
  5. if (result.isPresent() && result.get() instanceof InterruptionMetadata) {
  6. System.out.println("检测到中断,需要人工审批");
  7. interruptionMetadata = (InterruptionMetadata) result.get();
  8. return "已发送审批中";
  9. }
  10. List<Message> list = (List<Message>) result.get().state().data().get("messages");
  11. return list.get(list.size() - 1).getText();
  12. }
  13. @GetMapping("agree")
  14. public void agree() throws Exception {
  15. List<InterruptionMetadata.ToolFeedback> toolFeedbacks =
  16. interruptionMetadata.toolFeedbacks();
  17. InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder()
  18. .nodeId(interruptionMetadata.node())
  19. .state(interruptionMetadata.state());
  20. toolFeedbacks.forEach(toolFeedback -> {
  21. InterruptionMetadata.ToolFeedback approvedFeedback =
  22. InterruptionMetadata.ToolFeedback.builder(toolFeedback)
  23. .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
  24. .build();
  25. feedbackBuilder.addToolFeedback(approvedFeedback);
  26. });
  27. InterruptionMetadata approvalMetadata = feedbackBuilder.build();
  28. //第二次调用 - 使用人工反馈恢复执行, 需要指定同一个会话ID
  29. RunnableConfig resumeConfig = RunnableConfig.builder()
  30. .threadId("threadId")
  31. .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, approvalMetadata)
  32. .build();
  33. Optional<NodeOutput> finalResult = reactAgent.invokeAndGetOutput("", resumeConfig);
  34. if (finalResult.isPresent()) {
  35. System.out.println("执行完成");
  36. System.out.println("最终结果: " + finalResult.get());
  37. }
  38. }
复制代码

启动程序,进行测试 :

  1. 访问 http://127.0.0.1:8089/ai?question=我要发送邮件

响应: 请提供您要发送的邮件内容,包括具体的信息或主题,这样我才能帮您完成发送操作。

  1. 再次访问``http://127.0.0.1:8089/ai?question=内容:“测试一下邮件发送”`

响应: 已发送审批中

  1. 调用审批接口http://127.0.0.1:8089/ai/agree

大模型返回: 邮件已成功发送!, 并且发送邮件的方法打印日志 : 发送邮件: 测试一下邮件发送

整体流程如上

5. 检索增强生成(RAG)

大型语言模型(LLM)虽然强大,但有两个关键限制:

  • 有限的上下文——它们无法一次性摄取整个语料库
  • 静态知识——它们的训练数据在某个时间点被冻结

检索通过在查询时获取相关的外部知识来解决这些问题。这是检索增强生成(RAG)的基础:使用特定上下文的信息来增强 LLM 的回答。

SAA的RAG 可以以多种方式实现,具体取决于你的系统需求。

5.1 两步 RAG:

两步 RAG中,检索步骤总是在生成步骤之前执行。这种架构简单且可预测,适合许多应用,其中检索相关文档是生成答案的明确前提。

代码示例:

首先需要构建一个检索库,并指定一个向量模型(这里使用的仍然是通义的模型),,并从外部读取一个公司规章制度的文档,将其内容向量化, 作为AI的外部知识库. 并给Agent设置好提示词

  1. @Bean
  2. public VectorStore vectorStore(EmbeddingModel embeddingModel) {
  3. SimpleVectorStore simpleVectorStore =
  4. SimpleVectorStore.builder(embeddingModel).build();
  5. // 1. 加载文档
  6. Resource resource = new FileSystemResource("/Users/hehe/Downloads/text.txt");
  7. TextReader textReader = new TextReader(resource);
  8. List<Document> documents = textReader.get();
  9. // 2. 分割文档为块
  10. TokenTextSplitter splitter = new TokenTextSplitter();
  11. List<Document> chunks = splitter.apply(documents);
  12. //向量化存储
  13. simpleVectorStore.add(chunks);
  14. return simpleVectorStore;
  15. }
  16. @Bean
  17. public ReactAgent ragReactAgent() {
  18. String SYSTEM_PROMPT = """
  19. 你是一个公司内部智能助手,你需要根据公司规章制度文档,来回答公司员工的问题.
  20. """;
  21. return ReactAgent.builder()
  22. .name("工作助手")
  23. .instruction(SYSTEM_PROMPT)
  24. // 基于内存的存储
  25. .saver(new MemorySaver())
  26. .model(chatModel)
  27. .build();
  28. }
复制代码

公司规章制度如下:

  1. 考勤制度
  2. 一、为加强考勤管理,维护工作秩序,提高工作效率,特制定本制度。
  3. 二、公司员工必须自觉遵守劳动纪律,按时上下班,不迟到,不早退,工作时间不得擅自离开工作岗位,外出办理业务前,须经本部门负责人同意。
  4. 三、周一至周六为工作日,周日为休息日。公司机关周日和夜间值班由办公室统一安排,市场营销部、项目技术部、投资发展部、会议中心周日值班由各部门自行安排,报分管领导批准后执行。因工作需要周日或夜间加班的,由各部门负责人填写加班审批表,报分管领导批准后执行。节日值班由公司统一安排。
  5. 四、严格请、销假制度。员工因私事请假1天以内的(含1天),由部门负责人批准;3天以内的(含3天),由副总经理批准;3天以上的,报总经理批准。副总经理和部门负责人请假,一律由总经理批准。请假员工事毕向批准人销假。未经批准而擅离工作岗位的按旷工处理。
  6. 五、上班时间开始后5分钟至30分钟内到班者,按迟到论处;超过30分钟以上者,按旷工半天论处。提前30分钟以内下班者,按早退论处;超过30分钟者,按旷工半天论处。
  7. 六、1个月内迟到、早退累计达3次者,扣发5天的基本工资;累计达3次以上5次以下者,扣发10天的基本工资;累计达5次以上10次以下者,扣发当月15天的基本工资;累计达10次以上者,扣发当月的基本工资。
  8. 七、旷工半天者,扣发当天的基本工资、效益工资和奖金;每月累计旷工1天者,扣发5天的基本工资、效益工资和奖金,并给予一次警告处分;每月累计旷工2天者,扣发10天的基本工资、效益工资和奖金,并给予记过1次处分;每月累计旷工3天者,扣发当月基本工资、效益工资和奖金,并给予记大过1次处分;每月累计旷工3天以上,6天以下者,扣发当月基本工资、效益工资和奖金,第二个月起留用察看,发放基本工资;每月累计旷工6天以上者(含6天),予以辞退。
  9. 八、工作时间禁止打牌、下棋、串岗聊天等做与工作无关的事情。如有违反者当天按旷工1天处理;当月累计2次的,按旷工2天处理;当月累计3次的,按旷工3天处理。
  10. 九、参加公司组织的会议、培训、学习、考试或其他团队活动,如有事请假的,必须提前向组织者或带队者请假。在规定时间内未到或早退的,按照本制度第五条、第六条、第七条规定处理;未经批准擅自不参加的,视为旷工,按照本制度第七条规定处理。
  11. 十、员工按规定享受探亲假、婚假、产育假、结育手术假时,必须凭有关证明资料报总经理批准;未经批准者按旷工处理。员工病假期间只发给基本工资。
  12. 十一、经总经理或分管领导批准,决定假日加班工作或值班的每天补助20元;夜间加班或值班的,每个补助10元;节日值班每天补助40元。未经批准,值班人员不得空岗或迟到,如有空岗者,视为旷工,按照本制度第七条规定处理;如有迟到者,按本制度第五条、第六条规定处理。
  13. 十二、员工的考勤情况,由各部门负责人进行监督、检查,部门负责人对本部门的考勤要秉公办事,认真负责。如有弄虚作假、包庇袒护迟到、早退、旷工员工的,一经查实,按处罚员工的双倍予以处罚。凡是受到本制度第五条、第六条、第七条规定处理的员工,取消本年度先进个人的评比资格。
复制代码

使用时,按照两步 RAG的使用方式, 需要先根据问题,在向量库中检索与问题相关的内容,并携带到问题的上下文中.

  1. @GetMapping
  2. public String ai(@RequestParam String question) throws Exception {
  3. RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").build();
  4. List<Message> messages = new ArrayList<>();
  5. // 根据问题检索内容
  6. List<Document> documents = vectorStore.similaritySearch(question);
  7. if (CollectionUtil.isNotEmpty(documents)) {
  8. // 构建上下文
  9. String context = documents.stream()
  10. .map(Document::getText)
  11. .collect(Collectors.joining("""
  12. """));
  13. Message contextMessage = new UserMessage("请根据以下上下文,回答问题:" + context);
  14. messages.add(contextMessage);
  15. }
  16. messages.add(new UserMessage(question));
  17. return reactAgent.call(messages, runnableConfig).getText();
  18. }
复制代码

启动调用:http://127.0.0.1:8089/ai?question=一个月可以迟到几次

响应:

根据所提供的《考勤制度》第六条规定: > 六、1个月内迟到、早退累计达3次者,扣发5天的基本工资;累计达3次以上5次以下者,扣发10天的基本工资;累计达5次以上10次以下者,扣发当月15天的基本工资;累计达10次以上者,扣发当月的基本工资。 从制度内容可以看出: - 公司并未规定“允许”迟到的具体次数,而是对迟到行为设定了逐级处罚措施。 - 即使迟到1次,也属于违纪行为(按第五条定义为“迟到”),只是在第6条中从累计达3次起开始经济处罚。 - 因此,理想情况下,一个月应迟到0次。 - 但若从“不被扣工资”的角度理解“可以迟到几次”,那么最多可迟到2次(因为第3次起就要扣工资)。 结论: 严格来说,公司不允许迟到;但从处罚起点看,一个月内迟到不超过2次不会触发第六条的工资扣罚,但依然属于违反考勤纪律的行为。

可以看到,大模型成功的回答出了他本身认知之外的问题, 读取了公司内部的文档

5.2 Agentic RAG

Agentic 检索增强生成(RAG)将检索增强生成的优势与基于 Agent 的推理相结合。Agent(由 LLM 驱动)不是在回答之前检索文档,而是逐步推理并决定在交互过程中何时以及如何检索信息。

示例:

同样需要构建一个存储库, 并加载文档. 再建一个Tool, 供Agent查询文档使用

  1. @Bean
  2. public VectorStore vectorStore(EmbeddingModel embeddingModel) {
  3. SimpleVectorStore simpleVectorStore =
  4. SimpleVectorStore.builder(embeddingModel).build();
  5. // 1. 加载文档
  6. Resource resource = new FileSystemResource("/Users/hehe/Downloads/text.txt");
  7. TextReader textReader = new TextReader(resource);
  8. List<Document> documents = textReader.get();
  9. // 2. 分割文档为块
  10. TokenTextSplitter splitter = new TokenTextSplitter();
  11. List<Document> chunks = splitter.apply(documents);
  12. //向量化存储
  13. simpleVectorStore.add(chunks);
  14. return simpleVectorStore;
  15. }
  16. public record SearchRequest(@ToolParam(description = "检索文档的问题") String question) {
  17. }
  18. // 可以检索公司文档
  19. public static class SearchDocumentTool implements BiFunction<SearchRequest, ToolContext, String> {
  20. @Override
  21. public String apply(
  22. @ToolParam(description = "检索文档的问题") SearchRequest question,
  23. ToolContext toolContext) {
  24. List<Document> documents = SpringUtil.getBean("vectorStore", VectorStore.class).similaritySearch(question.question);
  25. if (documents.isEmpty()) {
  26. return "没有找到相关的文档";
  27. }
  28. //返回检索到的数据
  29. return documents.stream().map(Document::getText).collect(Collectors.joining("""
  30. """));
  31. }
  32. }
复制代码

注册Tool,并指示模型调用

  1. @Bean
  2. public ReactAgent ragReactAgent() {
  3. String SYSTEM_PROMPT = """
  4. 你是一个公司内部智能助手
  5. 你可以根据以下工具检索公司的文档,来提供上下文:
  6. - searchDocumentTool: 通过该工具检索公司文档
  7. 你需要根据公司规章制度文档,来回答公司员工的问题.
  8. """;
  9. return ReactAgent.builder()
  10. .name("工作助手")
  11. .instruction(SYSTEM_PROMPT)
  12. .tools(FunctionToolCallback.builder("searchDocumentTool", new SearchDocumentTool()).description("检索文档").inputType(SearchRequest.class).build())
  13. // 基于内存的存储
  14. .saver(new MemorySaver())
  15. .model(chatModel)
  16. .build();
  17. }
复制代码

使用方式:

  1. @GetMapping
  2. public String ai(@RequestParam String question) throws Exception {
  3. RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").build();
  4. return reactAgent.call(question, runnableConfig).getText();
  5. }
复制代码

启动调用: http://127.0.0.1:8089/ai?question=节假日上班补贴多少

返回响应:

根据提供的考勤制度,关于节假日上班的补贴标准如下: - 假日加班或值班:每天补助 20元。 - 夜间加班或值班:每个补助 10元。 - 节日值班:每天补助 40元。 需要注意的是,所有加班或值班必须经过总经理或分管领导批准,未经批准不得擅自离岗或迟到,否则将按旷工处理。

成功读取文档内容

5.3 混合 RAG

混合 RAG 结合了两步 RAG 和 Agentic RAG 的特点。它引入了中间步骤,如查询预处理、检索验证和生成后检查。这些系统比固定管道提供更多灵活性,同时保持对执行的一定控制。

典型组件包括:

  • 查询增强:修改输入问题以提高检索质量。这可能涉及重写不清晰的查询、生成多个变体或用额外上下文扩展查询。
  • 检索验证:评估检索到的文档是否相关且充分。如果不够,系统可能会优化查询并再次检索。
  • 答案验证:检查生成的答案的准确性、完整性以及与源内容的一致性。如果需要,系统可以重新生成或修订答案。

官网的概念性示例:

  1. import org.springframework.ai.chat.client.ChatClient;
  2. import org.springframework.ai.chat.model.ChatModel;
  3. import org.springframework.ai.document.Document;
  4. import org.springframework.ai.vectorstore.VectorStore;
  5. import java.util.List;
  6. import java.util.stream.Collectors;
  7. class HybridRAGSystem {
  8. private final ChatModel chatModel;
  9. private final VectorStore vectorStore;
  10. public HybridRAGSystem(ChatModel chatModel, VectorStore vectorStore) {
  11. this.chatModel = chatModel;
  12. this.vectorStore = vectorStore;
  13. }
  14. public String answer(String userQuestion) {
  15. // 1. 查询增强
  16. String enhancedQuery = enhanceQuery(userQuestion);
  17. int maxAttempts = 3;
  18. for (int attempt = 0; attempt < maxAttempts; attempt++) {
  19. // 2. 检索文档
  20. List<Document> docs = vectorStore.similaritySearch(enhancedQuery);
  21. // 3. 检索验证
  22. if (!isRetrievalSufficient(docs)) {
  23. enhancedQuery = refineQuery(enhancedQuery, docs);
  24. continue;
  25. }
  26. // 4. 生成答案
  27. String answer = generateAnswer(userQuestion, docs);
  28. // 5. 答案验证
  29. ValidationResult validation = validateAnswer(answer, docs);
  30. if (validation.isValid()) {
  31. return answer;
  32. }
  33. // 6. 根据验证结果决定下一步
  34. if (validation.shouldRetry()) {
  35. enhancedQuery = refineBasedOnValidation(enhancedQuery, validation);
  36. } else {
  37. return answer; // 返回当前最佳答案
  38. }
  39. }
  40. return "无法生成满意的答案";
  41. }
  42. private String enhanceQuery(String query) {
  43. return query; // 实现查询增强逻辑
  44. }
  45. private boolean isRetrievalSufficient(List<Document> docs) {
  46. return !docs.isEmpty() && calculateRelevanceScore(docs) > 0.7;
  47. }
  48. private double calculateRelevanceScore(List<Document> docs) {
  49. return 0.8; // 实现相关性评分逻辑
  50. }
  51. private String refineQuery(String query, List<Document> docs) {
  52. return query; // 实现查询优化逻辑
  53. }
  54. private String generateAnswer(String question, List<Document> docs) {
  55. String context = docs.stream()
  56. .map(Document::getText)
  57. .collect(Collectors.joining("
  58. "));
  59. ChatClient client = ChatClient.builder(chatModel).build();
  60. return client.prompt()
  61. .system("基于以下上下文回答问题:
  62. " + context)
  63. .user(question)
  64. .call()
  65. .content();
  66. }
  67. private ValidationResult validateAnswer(String answer, List<Document> docs) {
  68. // 实现答案验证逻辑
  69. return new ValidationResult(true, false);
  70. }
  71. private String refineBasedOnValidation(String query, ValidationResult validation) {
  72. return query; // 基于验证结果优化查询
  73. }
  74. class ValidationResult {
  75. private boolean valid;
  76. private boolean shouldRetry;
  77. public ValidationResult(boolean valid, boolean shouldRetry) {
  78. this.valid = valid;
  79. this.shouldRetry = shouldRetry;
  80. }
  81. public boolean isValid() { return valid; }
  82. public boolean shouldRetry() { return shouldRetry; }
  83. }
  84. }
复制代码

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册