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 調用模板來使用:
-
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的各個常用功能.
- 詳細的 System Prom - 獲得更好的 agent 行為
- 創建工具 - 與外部數據集成
- 模型配置 - 獲得一致的響應
- 結構化輸出 - 獲得可預測的結果
- 對話記憶 - 實現類似聊天的交互
- 創建和運行 agent - 創建一個功能完整的 agent
2.1 依賴和配置
使用SpringAIAlibaba,先導入pom依賴:
這裏優先使用了 DashScope的方式訪大模型
<!-- Spring AI Alibaba Agent Framework -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
<version>1.1.0.0-M5</version>
</dependency>
<!-- DashScope ChatModel 支持(如果使用其他模型,請參考文檔選擇對應的 starter) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.1.0.0-M5</version>
</dependency>
<!-- 【可選】OpenAi ChatModel 支持(如果使用其他模型,請參考文檔選擇對應的 starter) -->
<!--
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.1.0-M4</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; }
}
}