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应用框架的对比
OpenAI Api 和 阿里的DashScope(灵积)Api的区别
OpenAI API 是 OpenAI 官方提供的一个 大模型接口平台,定义了开发者通过一套标准的 HTTP 调用模板来使用:
SpringAI框架就是使用这套API接口来进行调用, 同样模型的厂商也需要实现此接口, 双方通过达成一致,达到统一AI大模型访问的目的.
DashScope 是阿里云的 大模型 API 平台,提供“通义千问 + 多模态 + 向量 + 文生图 + 语音”的一站式接口,类似于国内版的 OpenAI API。同时,除了阿里自己的通义系列. 包括Deepseek和月之暗面等国内大模型, 也进行了封装, 也可以通过DashScopeAPI进行调用. SpringAIAlibaba 就支持使用DashScopeApi 来进行统一访问国产AI大模型的能力. 当然SpringAIAlibaba 也同样支持 OpenAIApi的访问方式,进行访问实现OpenAIAPI的大模型.例如OpenAI等等.
2. 快速入门示例
下面将实现一个天气预报的小助手功能, 来快速了解一下SAA的各个常用功能.
- 详细的 System Prom - 获得更好的 agent 行为
- 创建工具 - 与外部数据集成
- 模型配置 - 获得一致的响应
- 结构化输出 - 获得可预测的结果
- 对话记忆 - 实现类似聊天的交互
- 创建和运行 agent - 创建一个功能完整的 agent
2.1 依赖和配置
使用SpringAIAlibaba,先导入pom依赖:
这里优先使用了 DashScope的方式访大模型 - <dependency>
- <groupId>com.alibaba.cloud.ai</groupId>
- spring-ai-alibaba-agent-framework</artifactId>
- <version>1.1.0.0-M5</version>
- </dependency>
-
- <dependency>
- <groupId>com.alibaba.cloud.ai</groupId>
- spring-ai-alibaba-starter-dashscope</artifactId>
- <version>1.1.0.0-M5</version>
- </dependency>
-
-
复制代码application.yaml 配置:
指定apiKey,模型名称和访问路径. 注意apiKey生产环境建议配置在环境变量中 - spring:
- ai:
- dashscope:
- api-key: sk-********
- base-url: https://dashscope.aliyuncs.com
- chat:
- options:
- model: qwen3-max
复制代码2.2 一个天气预报助手
首先需要定义两个工具,一个用于获取当前用户的位置, 另外一个获取地方天气信息: - public record WeatherRequest(@ToolParam(description = "城市的名称") String location) {
- }
- // 天气查询工具
- public static class WeatherForLocationTool implements BiFunction<WeatherRequest, ToolContext, String> {
- @Override
- public String apply(
- @ToolParam(description = "城市的名称") WeatherRequest city,
- ToolContext toolContext) {
- return StrUtil.equals("上海", city.location) ? "晴朗" : "小雨";
- }
- }
- // 用户位置工具 - 使用上下文
- public static class UserLocationTool implements BiFunction<WeatherRequest, ToolContext, String> {
- @Override
- public String apply(
- WeatherRequest query,
- ToolContext toolContext) {
- // 从上下文中获取用户信息
- RunnableConfig config = (RunnableConfig) toolContext.getContext().get("_AGENT_CONFIG_");
- String userId = (String) config.metadata("user_id").orElse(null);
- if (userId == null) {
- return "User ID not provided";
- }
- System.out.println("userId: " + userId);
- return "1".equals(userId) ? "杭州" : "上海";
- }
- }
复制代码工具应该有良好的文档:它们的名称、描述和参数名称都会成为模型提示的一部分。
Spring AI 的 FunctionToolCallback 支持通过 @ToolParam 注解添加元数据,并支持通过 ToolContext 参数进行运行时注入。
构建ReactAgent, 用户访问大模型的类: - private final DashScopeChatModel chatModel;
- public AgentConfiguration(DashScopeChatModel chatModel) {
- this.chatModel = chatModel;
- }
- @Bean
- public ReactAgent reactAgent() {
- String SYSTEM_PROMPT = """
- 你是一位天气预报专家,说话比较幽默。
- 您可以访问两个工具:
-
- - get_weather_for_location:使用它来获取指定位置的天气
- — get_user_location:使用它来获取用户的当前位置
- 如果用户向你询问天气,你可以尝试分析他需要查询的位置。例如上海,杭州等.
- 但是如果用户没有指定位置,你需要调用get_user_location获取此用户的当前位置,查询此位置的天气
- """;
- return ReactAgent.builder()
- .name("天气预报小助手")
- .description("这是一个天气预报小助手智能体")
- // 如果是简短,简单的系统提示可以用这个
- // .systemPrompt(SYSTEM_PROMPT)
- // 更详细的指令
- .instruction(SYSTEM_PROMPT)
- .tools(FunctionToolCallback.builder("weatherForLocationTool", new WeatherForLocationTool()).description("根据城市名称获取当前天气信息").inputType(WeatherRequest.class).build(),
- FunctionToolCallback.builder("userLocationTool", new UserLocationTool()).description("获取用户当前位置").inputType(WeatherRequest.class).build()
- )
- // 基于内存的存储
- .saver(new MemorySaver())
- .outputType(ResponseFormat.class)
- .model(chatModel)
- .build();
- }
复制代码下面是定义大模型返回的结构体 - /**
- * 使用 Java 类定义响应格式
- */
- @Getter
- @Setter
- public class ResponseFormat {
- /**
- * 城市名称
- */
- private String city;
- /**
- * 天气情况
- */
- private String punnyResponse;
- /**
- * 关于该天气的一个有趣的浪漫的简语
- */
- private String weatherConditions;
- }
复制代码
- 使用
instruction方法,定义系统提示.引导大模型的执行方式.
- 使用
tools 方法注册创建的函数. 使大模型具有调用本地方法的能力
- 使用
saver方法注册一个用于存储历史记录的类,框架会自动读取当前指定的threadId来读取当前会话的历史记录, 使大模型调用具有历史记忆功能, 并且会自动将本次调用按threadId 为key存起来 ( 这里使用的MemorySaver 是基于内存, 生产需要使用基于持久化中间件的实现)
- 使用
outputType 方法定义大模型返回的数据结构为此对象的结构
调用方式如下: - @RestController
- @RequestMapping("/ai")
- public class AiController {
- @Resource
- private ReactAgent reactAgent;
- @GetMapping
- public String ai(@RequestParam String question) throws Exception {
- RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").addMetadata("user_id", "1").build();
- return reactAgent.call(question, runnableConfig).getText();
- }
- }
复制代码
- 构建调用执行时配置,指定
threadId,相当于会话ID
- 加入运行时元数据, 在调用的执行链中,可以通过上下文获取
运行效果:
调用: 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)”的循环来工作。
- Thought: 模型分析当前状况,思考下一步该做什么。
- Action: 根据思考,决定调用哪个工具(或直接给出最终答案)。
- Observation: 执行工具后,获取结果(可能是搜索结果、代码执行结果等)。
- 循环此过程,直到任务完成。
这个循环使 Agent 能够:
- 将复杂问题分解为多个步骤
- 动态调整策略基于中间结果
- 处理需要多次工具调用的任务
- 在不确定的环境中做出决策
而SAA框架中的ReactAgent是怎么完成这个工作的?
Spring AI Alibaba 中的ReactAgent 内容抽象了三个模块,由这三个模块相互配合完成
- Model Node (模型节点):调用 LLM 进行推理和决策(
.model(chatModel)方法传入的大模型调用类)
- Tool Node (工具节点):执行工具调用(注册的工具)
- Hook Nodes (钩子节点):在关键位置插入自定义逻辑
ReactAgent 的核心执行流程:
下面通过梳理上面天气小助手的执行流程来具体了解一下工作流程: - 第一步: 发出提问
- ↓
- 第二步:SpringAI 构建一个 ChatRequest(包含tools和所有问题的上下文信息)
- ↓
- 第三步:序列化成 JSON
- ↓
- 第四步:通过 HTTP POST 调用大模型 API
- ↓
- 第五步:大模型进行判断推理,是否需要执行函数,执行哪个函数,在本案例中, 如果解析出城市名称, 则需要调用获取天气的函数, 如果没有,则需要调用获取用户位置的函数. 并返回执行函数的名称+入参
- ↓
- 第六步:ReactAgent解析返回JSON,进行推理是否是一次 function call
- ↓
- 第七步:如果是,则本地反射调用该方法,例如执行了获取用户位置的函数
- ↓
- 第八步:把该函数结果再封装成 JSON 发给大模型继续对话,大模型拿到此函数结果,继续分析推理,拿到位置后,大模型继续推理需要进行调用获取天气的函数,则继续返回客户 端调用
- ↓
- 第九步:ReactAgent继续解析返回JSON,执行获取天气的函数并返回
- ↓
- 第九步:最终返回结果给用户
复制代码在上面的流程中, 可以迅速的了解到上图的含义. 在模型和工具间循环,直到模型推理出最终结果为止.
也就是说,以ReactAgent为本体:
- 大模型 = 脑子(发出工具调用的意愿)
- SpringAI = 手(真正执行方法)
- 你的方法 = 工具(可以被大模型调来用)
4. Hooks 和 Interceptors
在上面关于ReactAgent的工作流程的介绍中, 除了Tool Node 和 Model Node 之外,还有一个组件为Hooks(钩子).
SAA框架在这些步骤的前后暴露了钩子点Hooks 和 拦截器Interceptors,允许你
- 监控: 通过日志、分析和调试跟踪 Agent 行为
- 修改: 转换提示、工具选择和输出格式
- 控制: 添加重试、回退和提前终止逻辑
- 强制执行: 应用速率限制、护栏和 PII 检测
4.1 自定义钩子
框架中提供了四个抽象类供开发者实现,并在不同的节点调用
ModelHook: 在模型调用前后执行自定义逻辑
AgentHook: 在 Agent 一次问答整体执行的开始和结束时执行:
ModelInterceptor: 拦截和修改对模型的请求和响应
ToolInterceptor:拦截和修改工具调用
下面的示例,分别实现了这四个抽象类,可以快速了解其使用方式: - public class MyHooks {
- private static final String CALL_COUNT_KEY = "_model_call_count_";
- private static final String START_TIME_KEY = "_call_start_time_";
- // 1. AgentHook - 在 Agent 开始/结束时执行,每次Agent调用只会运行一次
- @HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT})
- public static class LoggingHook extends AgentHook {
- @Override
- public String getName() {
- return "logging";
- }
- @Override
- public CompletableFuture<Map<String, Object>> beforeAgent(OverAllState state, RunnableConfig config) {
- DateTime date = DateUtil.date();
- System.out.println("Agent 开始执行时间" + DateUtil.formatDateTime(date));
- config.context().put(START_TIME_KEY, date.getTime());
- return CompletableFuture.completedFuture(Map.of());
- }
- @Override
- public CompletableFuture<Map<String, Object>> afterAgent(OverAllState state, RunnableConfig config) {
- long startTime = (long) config.context().get(START_TIME_KEY);
- System.out.println("Agent 执行完成,耗时:" + (DateUtil.date().getTime() - startTime));
- return CompletableFuture.completedFuture(Map.of());
- }
- }
- // 2. ModelHook - 在模型调用前后执行(例如:消息修剪),区别于AgentHook,ModelHook在一次agent调用中可能会调用多次,也就是每次 reasoning-acting 迭代都会执行
- public static class MessageTrimmingHook extends ModelHook {
- @Override
- public String getName() {
- return "message_trimming";
- }
- @Override
- public HookPosition[] getHookPositions() {
- return new HookPosition[]{HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL};
- }
- @Override
- public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
- // 这里可以获取到请求模型时传入的所有message
- Optional<Object> messagesOpt = state.value("messages");
- if (messagesOpt.isPresent()) {
- List<Message> messages = (List<Message>) messagesOpt.get();
- System.out.println(messages.size());
- }
- // 增加调用次数记录
- config.context().put(CALL_COUNT_KEY, config.context().get(CALL_COUNT_KEY) == null ? 1 : (Integer) config.context().get(CALL_COUNT_KEY) + 1);
- System.out.println("第" + config.context().get(CALL_COUNT_KEY) + "次调用模型开始");
- // 模型调用前,可以进行消息修剪,返回的Map会作为模型调用的参数
- return CompletableFuture.completedFuture(Map.of());
- }
- @Override
- public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
- System.out.println("第" + config.context().get(CALL_COUNT_KEY) + "次调用模型结束");
- return CompletableFuture.completedFuture(Map.of());
- }
- }
- public static class LoggingInterceptor extends ModelInterceptor {
- @Override
- public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
- // 请求前记录
- System.out.println("发送请求到模型: " + request.getMessages().size() + " 条消息");
- // 执行实际调用
- return handler.call(request);
- }
- @Override
- public String getName() {
- return "LoggingInterceptor";
- }
- }
- public static class ToolMonitoringInterceptor extends ToolInterceptor {
- @Override
- public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
- String toolName = request.getToolName();
- long startTime = System.currentTimeMillis();
- System.out.println("执行工具: " + toolName + "执行参数: " + request.getArguments());
- try {
- return handler.call(request);
- } catch (Exception e) {
- long duration = System.currentTimeMillis() - startTime;
- System.err.println("工具 " + toolName + " 执行失败 (耗时: " + duration + "ms): " + e.getMessage());
- return ToolCallResponse.of(
- request.getToolCallId(),
- request.getToolName(),
- "工具执行失败: " + e.getMessage()
- );
- }
- }
- @Override
- public String getName() {
- return "ToolMonitoringInterceptor";
- }
- }
- }
复制代码注册钩子: - ReactAgent.builder()
- .name("天气预报小助手")
- .description("这是一个天气预报小助手智能体")
- // 如果是简短,简单的系统提示可以用这个
- // .systemPrompt(SYSTEM_PROMPT)
- // 更详细的指令
- .instruction(SYSTEM_PROMPT)
- .tools(FunctionToolCallback.builder("weatherForLocationTool", new WeatherForLocationTool()).description("根据城市名称获取当前天气信息").inputType(WeatherRequest.class).build(),
- FunctionToolCallback.builder("userLocationTool", new UserLocationTool()).description("获取用户当前位置").inputType(WeatherRequest.class).build()
- )
- // 基于内存的存储
- .saver(new MemorySaver())
- .outputType(ResponseFormat.class)
- // 注册钩子和拦截器
- .hooks(new MyHooks.LoggingHook(), new MyHooks.MessageTrimmingHook())
- .interceptors(new MyHooks.LoggingInterceptor(), new MyHooks.ToolMonitoringInterceptor())
- .model(chatModel)
- .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, 每次发送邮件,都需要手动人为审批
- 首先需要定义一个发送邮件的
Tool.当模型判断需要调用次方法时,会中断流程,并等待人工审批,再继续执行
- public record EmailRequest(@ToolParam(description = "发送邮件的信息") String message) {
- }
- // 发送email
- public static class SendEmailTool implements BiFunction<EmailRequest, ToolContext, Boolean> {
- @Override
- public Boolean apply(
- @ToolParam(description = "发送邮件的信息") EmailRequest message,
- ToolContext toolContext) {
- System.out.println("发送邮件: " + message.message);
- return true;
- }
- }
复制代码
- 这里构建了一个ReactAgent, 注册
Tool, 并传入一个humanInTheLoopHook 实例, 描述调用审批的节点, 注意,这里一定需要传入 saver 作为检查点, 因为中断后再次调用,需要依赖历史记录message,并携带上下文,才能使调用前后的流程衔接, 这里使用了测试用的实例MemorySaver,基于内存
- @Bean
- public ReactAgent emailReactAgent() {
- String SYSTEM_PROMPT = """
- 你是一个工作平台的助手
- 您可以访问一个工具:
-
- - sendEmailTool:使用该工具进行发送邮件的操作
- 如果用户有需要发送邮件,可以进行操作
- """;
- // 创建人工介入Hook
- HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder()
- .approvalOn("sendEmailTool", ToolConfig.builder()
- .description("发送邮件需要审批")
- .build())
- .build();
- return ReactAgent.builder()
- .name("工作助手")
- .instruction(SYSTEM_PROMPT)
- .tools(FunctionToolCallback.builder("sendEmailTool", new SendEmailTool()).description("进行发送邮件的操作").inputType(EmailRequest.class).build()
- )
- // 基于内存的存储
- .saver(new MemorySaver())
- .hooks(humanInTheLoopHook)
- .model(chatModel)
- .build();
- }
复制代码
- 访问agent的方式, 和同意审批的方法, 当agent判断中断后, 会返回审批中提示, 之后需要管理员调用同意方法,继续执行
- @GetMapping
- public String ai(@RequestParam String question) throws Exception {
- RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").build();
- Optional<NodeOutput> result = reactAgent.invokeAndGetOutput(question, runnableConfig);
- if (result.isPresent() && result.get() instanceof InterruptionMetadata) {
- System.out.println("检测到中断,需要人工审批");
- interruptionMetadata = (InterruptionMetadata) result.get();
- return "已发送审批中";
- }
- List<Message> list = (List<Message>) result.get().state().data().get("messages");
- return list.get(list.size() - 1).getText();
- }
- @GetMapping("agree")
- public void agree() throws Exception {
- List<InterruptionMetadata.ToolFeedback> toolFeedbacks =
- interruptionMetadata.toolFeedbacks();
- InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder()
- .nodeId(interruptionMetadata.node())
- .state(interruptionMetadata.state());
- toolFeedbacks.forEach(toolFeedback -> {
- InterruptionMetadata.ToolFeedback approvedFeedback =
- InterruptionMetadata.ToolFeedback.builder(toolFeedback)
- .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
- .build();
- feedbackBuilder.addToolFeedback(approvedFeedback);
- });
- InterruptionMetadata approvalMetadata = feedbackBuilder.build();
- //第二次调用 - 使用人工反馈恢复执行, 需要指定同一个会话ID
- RunnableConfig resumeConfig = RunnableConfig.builder()
- .threadId("threadId")
- .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, approvalMetadata)
- .build();
- Optional<NodeOutput> finalResult = reactAgent.invokeAndGetOutput("", resumeConfig);
- if (finalResult.isPresent()) {
- System.out.println("执行完成");
- System.out.println("最终结果: " + finalResult.get());
- }
- }
复制代码启动程序,进行测试 :
- 访问
http://127.0.0.1:8089/ai?question=我要发送邮件
响应: 请提供您要发送的邮件内容,包括具体的信息或主题,这样我才能帮您完成发送操作。
- 再次访问``http://127.0.0.1:8089/ai?question=内容:“测试一下邮件发送”`
响应: 已发送审批中
- 调用审批接口
http://127.0.0.1:8089/ai/agree
大模型返回: 邮件已成功发送!, 并且发送邮件的方法打印日志 : 发送邮件: 测试一下邮件发送
整体流程如上
5. 检索增强生成(RAG)
大型语言模型(LLM)虽然强大,但有两个关键限制:
- 有限的上下文——它们无法一次性摄取整个语料库
- 静态知识——它们的训练数据在某个时间点被冻结
检索通过在查询时获取相关的外部知识来解决这些问题。这是检索增强生成(RAG)的基础:使用特定上下文的信息来增强 LLM 的回答。
SAA的RAG 可以以多种方式实现,具体取决于你的系统需求。
5.1 两步 RAG:
在两步 RAG中,检索步骤总是在生成步骤之前执行。这种架构简单且可预测,适合许多应用,其中检索相关文档是生成答案的明确前提。
代码示例:
首先需要构建一个检索库,并指定一个向量模型(这里使用的仍然是通义的模型),,并从外部读取一个公司规章制度的文档,将其内容向量化, 作为AI的外部知识库. 并给Agent设置好提示词 - @Bean
- public VectorStore vectorStore(EmbeddingModel embeddingModel) {
- SimpleVectorStore simpleVectorStore =
- SimpleVectorStore.builder(embeddingModel).build();
- // 1. 加载文档
- Resource resource = new FileSystemResource("/Users/hehe/Downloads/text.txt");
- TextReader textReader = new TextReader(resource);
- List<Document> documents = textReader.get();
- // 2. 分割文档为块
- TokenTextSplitter splitter = new TokenTextSplitter();
- List<Document> chunks = splitter.apply(documents);
- //向量化存储
- simpleVectorStore.add(chunks);
- return simpleVectorStore;
- }
- @Bean
- public ReactAgent ragReactAgent() {
- String SYSTEM_PROMPT = """
- 你是一个公司内部智能助手,你需要根据公司规章制度文档,来回答公司员工的问题.
- """;
- return ReactAgent.builder()
- .name("工作助手")
- .instruction(SYSTEM_PROMPT)
- // 基于内存的存储
- .saver(new MemorySaver())
- .model(chatModel)
- .build();
- }
复制代码公司规章制度如下: - 考勤制度
- 一、为加强考勤管理,维护工作秩序,提高工作效率,特制定本制度。
- 二、公司员工必须自觉遵守劳动纪律,按时上下班,不迟到,不早退,工作时间不得擅自离开工作岗位,外出办理业务前,须经本部门负责人同意。
- 三、周一至周六为工作日,周日为休息日。公司机关周日和夜间值班由办公室统一安排,市场营销部、项目技术部、投资发展部、会议中心周日值班由各部门自行安排,报分管领导批准后执行。因工作需要周日或夜间加班的,由各部门负责人填写加班审批表,报分管领导批准后执行。节日值班由公司统一安排。
- 四、严格请、销假制度。员工因私事请假1天以内的(含1天),由部门负责人批准;3天以内的(含3天),由副总经理批准;3天以上的,报总经理批准。副总经理和部门负责人请假,一律由总经理批准。请假员工事毕向批准人销假。未经批准而擅离工作岗位的按旷工处理。
- 五、上班时间开始后5分钟至30分钟内到班者,按迟到论处;超过30分钟以上者,按旷工半天论处。提前30分钟以内下班者,按早退论处;超过30分钟者,按旷工半天论处。
- 六、1个月内迟到、早退累计达3次者,扣发5天的基本工资;累计达3次以上5次以下者,扣发10天的基本工资;累计达5次以上10次以下者,扣发当月15天的基本工资;累计达10次以上者,扣发当月的基本工资。
- 七、旷工半天者,扣发当天的基本工资、效益工资和奖金;每月累计旷工1天者,扣发5天的基本工资、效益工资和奖金,并给予一次警告处分;每月累计旷工2天者,扣发10天的基本工资、效益工资和奖金,并给予记过1次处分;每月累计旷工3天者,扣发当月基本工资、效益工资和奖金,并给予记大过1次处分;每月累计旷工3天以上,6天以下者,扣发当月基本工资、效益工资和奖金,第二个月起留用察看,发放基本工资;每月累计旷工6天以上者(含6天),予以辞退。
- 八、工作时间禁止打牌、下棋、串岗聊天等做与工作无关的事情。如有违反者当天按旷工1天处理;当月累计2次的,按旷工2天处理;当月累计3次的,按旷工3天处理。
- 九、参加公司组织的会议、培训、学习、考试或其他团队活动,如有事请假的,必须提前向组织者或带队者请假。在规定时间内未到或早退的,按照本制度第五条、第六条、第七条规定处理;未经批准擅自不参加的,视为旷工,按照本制度第七条规定处理。
- 十、员工按规定享受探亲假、婚假、产育假、结育手术假时,必须凭有关证明资料报总经理批准;未经批准者按旷工处理。员工病假期间只发给基本工资。
- 十一、经总经理或分管领导批准,决定假日加班工作或值班的每天补助20元;夜间加班或值班的,每个补助10元;节日值班每天补助40元。未经批准,值班人员不得空岗或迟到,如有空岗者,视为旷工,按照本制度第七条规定处理;如有迟到者,按本制度第五条、第六条规定处理。
- 十二、员工的考勤情况,由各部门负责人进行监督、检查,部门负责人对本部门的考勤要秉公办事,认真负责。如有弄虚作假、包庇袒护迟到、早退、旷工员工的,一经查实,按处罚员工的双倍予以处罚。凡是受到本制度第五条、第六条、第七条规定处理的员工,取消本年度先进个人的评比资格。
复制代码使用时,按照两步 RAG的使用方式, 需要先根据问题,在向量库中检索与问题相关的内容,并携带到问题的上下文中. - @GetMapping
- public String ai(@RequestParam String question) throws Exception {
- RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").build();
- List<Message> messages = new ArrayList<>();
- // 根据问题检索内容
- List<Document> documents = vectorStore.similaritySearch(question);
- if (CollectionUtil.isNotEmpty(documents)) {
- // 构建上下文
- String context = documents.stream()
- .map(Document::getText)
- .collect(Collectors.joining("""
-
- """));
- Message contextMessage = new UserMessage("请根据以下上下文,回答问题:" + context);
- messages.add(contextMessage);
- }
- messages.add(new UserMessage(question));
- return reactAgent.call(messages, runnableConfig).getText();
- }
复制代码启动调用: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查询文档使用 - @Bean
- public VectorStore vectorStore(EmbeddingModel embeddingModel) {
- SimpleVectorStore simpleVectorStore =
- SimpleVectorStore.builder(embeddingModel).build();
- // 1. 加载文档
- Resource resource = new FileSystemResource("/Users/hehe/Downloads/text.txt");
- TextReader textReader = new TextReader(resource);
- List<Document> documents = textReader.get();
- // 2. 分割文档为块
- TokenTextSplitter splitter = new TokenTextSplitter();
- List<Document> chunks = splitter.apply(documents);
- //向量化存储
- simpleVectorStore.add(chunks);
- return simpleVectorStore;
- }
- public record SearchRequest(@ToolParam(description = "检索文档的问题") String question) {
- }
- // 可以检索公司文档
- public static class SearchDocumentTool implements BiFunction<SearchRequest, ToolContext, String> {
- @Override
- public String apply(
- @ToolParam(description = "检索文档的问题") SearchRequest question,
- ToolContext toolContext) {
- List<Document> documents = SpringUtil.getBean("vectorStore", VectorStore.class).similaritySearch(question.question);
- if (documents.isEmpty()) {
- return "没有找到相关的文档";
- }
- //返回检索到的数据
- return documents.stream().map(Document::getText).collect(Collectors.joining("""
-
- """));
- }
- }
复制代码注册Tool,并指示模型调用 - @Bean
- public ReactAgent ragReactAgent() {
- String SYSTEM_PROMPT = """
- 你是一个公司内部智能助手
- 你可以根据以下工具检索公司的文档,来提供上下文:
-
- - searchDocumentTool: 通过该工具检索公司文档
-
- 你需要根据公司规章制度文档,来回答公司员工的问题.
- """;
- return ReactAgent.builder()
- .name("工作助手")
- .instruction(SYSTEM_PROMPT)
- .tools(FunctionToolCallback.builder("searchDocumentTool", new SearchDocumentTool()).description("检索文档").inputType(SearchRequest.class).build())
- // 基于内存的存储
- .saver(new MemorySaver())
- .model(chatModel)
- .build();
- }
复制代码使用方式: - @GetMapping
- public String ai(@RequestParam String question) throws Exception {
- RunnableConfig runnableConfig = RunnableConfig.builder().threadId("threadId").build();
- return reactAgent.call(question, runnableConfig).getText();
- }
复制代码启动调用: http://127.0.0.1:8089/ai?question=节假日上班补贴多少
返回响应:
根据提供的考勤制度,关于节假日上班的补贴标准如下: - 假日加班或值班:每天补助 20元。 - 夜间加班或值班:每个补助 10元。 - 节日值班:每天补助 40元。 需要注意的是,所有加班或值班必须经过总经理或分管领导批准,未经批准不得擅自离岗或迟到,否则将按旷工处理。
成功读取文档内容
5.3 混合 RAG
混合 RAG 结合了两步 RAG 和 Agentic RAG 的特点。它引入了中间步骤,如查询预处理、检索验证和生成后检查。这些系统比固定管道提供更多灵活性,同时保持对执行的一定控制。
典型组件包括:
- 查询增强:修改输入问题以提高检索质量。这可能涉及重写不清晰的查询、生成多个变体或用额外上下文扩展查询。
- 检索验证:评估检索到的文档是否相关且充分。如果不够,系统可能会优化查询并再次检索。
- 答案验证:检查生成的答案的准确性、完整性以及与源内容的一致性。如果需要,系统可以重新生成或修订答案。
官网的概念性示例: - import org.springframework.ai.chat.client.ChatClient;
- import org.springframework.ai.chat.model.ChatModel;
- import org.springframework.ai.document.Document;
- import org.springframework.ai.vectorstore.VectorStore;
- import java.util.List;
- import java.util.stream.Collectors;
- class HybridRAGSystem {
- private final ChatModel chatModel;
- private final VectorStore vectorStore;
- public HybridRAGSystem(ChatModel chatModel, VectorStore vectorStore) {
- this.chatModel = chatModel;
- this.vectorStore = vectorStore;
- }
- public String answer(String userQuestion) {
- // 1. 查询增强
- String enhancedQuery = enhanceQuery(userQuestion);
- int maxAttempts = 3;
- for (int attempt = 0; attempt < maxAttempts; attempt++) {
- // 2. 检索文档
- List<Document> docs = vectorStore.similaritySearch(enhancedQuery);
- // 3. 检索验证
- if (!isRetrievalSufficient(docs)) {
- enhancedQuery = refineQuery(enhancedQuery, docs);
- continue;
- }
- // 4. 生成答案
- String answer = generateAnswer(userQuestion, docs);
- // 5. 答案验证
- ValidationResult validation = validateAnswer(answer, docs);
- if (validation.isValid()) {
- return answer;
- }
- // 6. 根据验证结果决定下一步
- if (validation.shouldRetry()) {
- enhancedQuery = refineBasedOnValidation(enhancedQuery, validation);
- } else {
- return answer; // 返回当前最佳答案
- }
- }
- return "无法生成满意的答案";
- }
- private String enhanceQuery(String query) {
- return query; // 实现查询增强逻辑
- }
- private boolean isRetrievalSufficient(List<Document> docs) {
- return !docs.isEmpty() && calculateRelevanceScore(docs) > 0.7;
- }
- private double calculateRelevanceScore(List<Document> docs) {
- return 0.8; // 实现相关性评分逻辑
- }
- private String refineQuery(String query, List<Document> docs) {
- return query; // 实现查询优化逻辑
- }
- private String generateAnswer(String question, List<Document> docs) {
- String context = docs.stream()
- .map(Document::getText)
- .collect(Collectors.joining("
- "));
- ChatClient client = ChatClient.builder(chatModel).build();
- return client.prompt()
- .system("基于以下上下文回答问题:
- " + context)
- .user(question)
- .call()
- .content();
- }
- private ValidationResult validateAnswer(String answer, List<Document> docs) {
- // 实现答案验证逻辑
- return new ValidationResult(true, false);
- }
- private String refineBasedOnValidation(String query, ValidationResult validation) {
- return query; // 基于验证结果优化查询
- }
- class ValidationResult {
- private boolean valid;
- private boolean shouldRetry;
- public ValidationResult(boolean valid, boolean shouldRetry) {
- this.valid = valid;
- this.shouldRetry = shouldRetry;
- }
- public boolean isValid() { return valid; }
- public boolean shouldRetry() { return shouldRetry; }
- }
- }
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |