LangGraph4j框架ReActAgent實現
ReAct-Agent概念
ReAct-Agent 是一種大模型應用中的智能體架構。ReAct 是 Re (Reasoning,推理)和 Act(Action,行動)兩個單詞的簡寫,用通俗的話來說,它可以讓大模型像人一樣“思考”和“行動”,實現更強的任務處理能力。這里把它拆解為兩個關鍵部分來說明:
Re(Reasoning,推理)
大模型通過“推理”步驟來理解任務或者問題。就像你面對一個復雜的問題時,先分析現狀、列出條件,然后在腦海中一步步推導出答案一樣,ReAct 中的“推理”部分會利用模型的語言理解能力,生成邏輯清晰的分析路徑。
Act(Action,行動)
僅僅推理還不夠,智能體還需要“行動”,也就是執行特定的操作。比如:
- 通過調用某個工具(例如計算器、數據庫查詢、外部api接口)來獲取信息或解決問題。
- 根據現有信息做出決定并采取下一步行動。
在 ReAct 框架中,“行動”這一步和“推理”緊密結合。模型不會一次性給出答案,而是以“邊想邊做”的方式,循環地進行推理和行動,直到完成任務。
實際運作流程
- 模型先“想”一想:分析問題,給出可能需要的步驟。
- 模型再“做”一做:如果需要,它可以去查外部信息、調用工具,或者生成一個具體操作。
- 根據結果再“想” :拿到行動的反饋后,重新推理,調整步驟,直到問題解決。
ReAct 的特點
- 動態靈活:不像傳統模型“一問一答”,ReAct 模型會動態地調整策略。
- 能調用外部工具:通過執行操作(例如數據庫查詢或API調用),可以解決大模型本身無法直接處理的問題。
- 更接近人類思維:這種“想一步,做一步”的方式更像人類解決復雜問題的過程。
通俗來說,ReAct 就是讓模型“像人一樣,先動腦后動手,再動腦接著手”,在不斷循環中完成任務。
LangGraph4j 中的 ReAct-Agent
ReactAgent 接口提供了構建和運行狀態圖(StateGraph)的功能。它包含兩個主要內部類:CallAgent 和 ExecuteTools,分別用于調用 AI 模型生成響應和執行工具請求。
整個流程體現了典型的 ReAct 模式:
- Reasoning (推理): CallAgent 生成思考并決定是否調用工具。
- Action (動作): ExecuteTools 執行具體工具操作。
- 循環直到獲得最終答案(final_response)為止。
狀態圖設計
-
使用 StateGraph 定義執行流程:
-
起始節點為 START → reasoning
-
reasoning根據是否需要調用工具 (shouldContinue) 決定下一步:
- “continue” → action
- “end” → END
-
action 節點執行完工具后再次回到 reasoning
-
new StateGraph<>(State.SCHEMA, stateSerializer).addNode("reasoning", node_async(callAgent)).addNode("action", node_async(executeTools)) .addEdge(START, "reasoning").addConditionalEdges("reasoning",edge_async(shouldContinue),Map.of("continue", "action", "end", END)).addEdge("action", "reasoning");
CallAgent
-
負責調用 LangChain4J 的聊天模型或流式聊天模型來生成 AI 響應。
-
根據是否啟用流式輸出選擇不同的執行策略:
- 非流式模式直接調用 ChatModel.execute() 獲取完整響應。
- 流式模式使用 StreamingChatModel.chat() 并結合 StreamingChatGenerator 來處理逐步生成的結果。
-
將響應結果映射為結構化格式,支持兩種情況:
- 如果需要執行工具,則返回 messages 字段表示待執行的工具調用。
- 如果任務完成,則返回 final_response 表示最終答案。
/*** CallAgent 是一個 NodeAction 實現類,用于調用 Agent 模型生成響應。* 可以根據是否開啟流式輸出選擇不同的執行策略。*/
class CallAgent implements NodeAction<State> {private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CallAgent.class);final Agent agent;/*** 使用指定的代理構造 CallAgent。** @param agent 關聯到此 CallAgent 的代理*/public CallAgent(Agent agent) {this.agent = agent;}/*** 將來自AI消息的響應結果映射為結構化格式。** @param response 包含AI消息的響應* @return 包含代理結果的Map* @throws IllegalStateException 如果響應的結束原因是不支持的*/private Map<String,Object> mapResult(ChatResponse response ) {var content = response.aiMessage();if (response.finishReason() == FinishReason.TOOL_EXECUTION || content.hasToolExecutionRequests() ) {return Map.of("messages", content);}if( response.finishReason() == FinishReason.STOP || response.finishReason() == null ) {return Map.of(FINAL_RESPONSE, content.text());}throw new IllegalStateException("不支持的結束原因: " + response.finishReason() );}/*** 應用給定狀態的動作并返回結果。** @param state 動作應用到的狀態* @return 包含代理結果的Map* @throws IllegalArgumentException 如果狀態中沒有提供輸入*/@Overridepublic Map<String,Object> apply(State state ) {log.trace("[ReactAgent] callAgent...");List<ChatMessage> messages = state.messages();if( messages.isEmpty() ) {log.error("[ReactAgent] callAgent result: {}", "沒有提供輸入!");throw new IllegalArgumentException("沒有提供輸入!");}if( agent.isStreaming()) {StreamingChatGenerator<State> generator = StreamingChatGenerator.<State>builder().mapResult(this::mapResult).startingNode("agent").startingState(state).build();agent.execute(messages, generator.handler());log.info("[ReactAgent] callAgent result: {}", generator);return Map.of("_generator", generator);}else {var response = agent.execute(messages);log.info("[ReactAgent] callAgent result: {}", response);return mapResult(response);}}
}
ExecuteTools
- 處理由 AI 生成的工具調用請求。
- 使用 LangChain4jToolService 執行這些工具,并將結果整合后返回。
- 如果沒有找到工具請求,則返回提示信息。
/*** ExecuteTools 是一個 NodeAction 實現類,負責執行 AI 生成的工具調用請求。* 通常用于處理用戶輸入后由 Agent 決定調用哪些外部工具的情況。*/
class ExecuteTools implements NodeAction<State> {private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExecuteTools.class);/*** 將要執行的工具服務。*/final LangChain4jToolService toolService;/*** 使用指定的工具映射構造 ExecuteTools 實例。** @param toolMap 要執行的工具映射,不能為空*/public ExecuteTools(Map<ToolSpecification, ToolExecutor> toolMap) {this.toolService = new LangChain4jToolService(toolMap);}/*** 基于提供的代理狀態應用工具執行邏輯。** @param state 當前代理執行器的狀態* @return 包含執行中間步驟的地圖* @throws IllegalArgumentException 如果沒有提供代理結果* @throws IllegalStateException 如果找不到要執行的操作或工具*/@Overridepublic Map<String,Object> apply(State state ) {log.trace("[ReactAgent] executeTools..");var toolExecutionRequests = state.lastMessage().filter(m -> ChatMessageType.AI==m.type()).map(m -> (AiMessage)m).filter(AiMessage::hasToolExecutionRequests).map(AiMessage::toolExecutionRequests);if( toolExecutionRequests.isEmpty() ) {log.info("[ReactAgent] executeTools result: {}" , "沒有找到工具執行請求!");return Map.of(FINAL_RESPONSE, "沒有找到工具執行請求!");}var result = toolExecutionRequests.get().stream().map(toolService::execute).filter(Optional::isPresent).map(Optional::get).toList();log.info("[ReactAgent] executeTools result: {}" , result);return Map.of("messages", result );}
}
ReactAgent構建
public interface ReactAgent {/*** 狀態序列化的支持類型。* 支持標準格式(STD)和 JSON 格式(JSON)。*/enum Serializers {STD(new LangChain4jStateSerializer<>(State::new) ),JSON(new LangChain4jJacksonStateSerializer<>(State::new));private final StateSerializer<State> serializer;/*** 使用指定的序列化器構造新的Serializers枚舉實例。** @param serializer 狀態序列化器*/Serializers(StateSerializer<State> serializer) {this.serializer = serializer;}/*** 獲取狀態序列化器。** @return 狀態序列化器*/public StateSerializer<State> object() {return serializer;}}/*** Builder 類用于構建 Agent 的狀態圖(StateGraph)。* 包含構建節點、邊及條件判斷邏輯。*/class Builder extends Agent.Builder<Builder> {private StateSerializer<State> stateSerializer;/*** 設置狀態序列化** @param stateSerializer 狀態序列化* @return 更新的實例*/public Builder stateSerializer(StateSerializer<State> stateSerializer) {this.stateSerializer = stateSerializer;return this;}/*** 構建狀態圖(StateGraph)。** @return 構建的 StateGraph* @throws GraphStateException 如果圖形狀態中存在錯誤*/@Overridepublic StateGraph<State> build() throws GraphStateException {if (streamingChatModel != null && chatModel != null) {throw new IllegalArgumentException("chatLanguageModel 和 streamingChatLanguageModel 是互斥的!");}if (streamingChatModel == null && chatModel == null) {throw new IllegalArgumentException("需要 chatLanguageModel 或 streamingChatLanguageModel!");}var agent = new Agent(this);if (stateSerializer == null) {stateSerializer = Serializers.STD.object();}final var callAgent = new CallAgent(agent);final var executeTools = new ExecuteTools(toolMap());final EdgeAction<State> shouldContinue = (state) ->state.finalResponse().map(res -> "end").orElse("continue");return new StateGraph<>(State.SCHEMA, stateSerializer).addNode("agent", node_async(callAgent)).addNode("action", node_async(executeTools)).addEdge(START, "agent").addConditionalEdges("agent",edge_async(shouldContinue),Map.of("continue", "action", "end", END)).addEdge("action", "agent");}}/*** 創建一個新的 GraphBuilder 實例。** @return 新的 Builder 實例*/static Builder builder() {return new Builder();}
}
ReactAgent測試驗證
1、創建StateGraph和CompiledGraph
@Bean
public StateGraph<State> reactAgent(@Qualifier("chatModel") ChatModel chatModel) throws GraphStateException {return ReactAgent.builder().name("reactAgent智能體").chatModel(chatModel).toolsFromObject(new TestTools()).toolsFromObject(new TavilySearchTool(searchApiKey)).build();
}
@Bean(value = "reactAgentGraph")
public CompiledGraph<State> reactAgentGraph(@Qualifier("reactAgent") StateGraph<State> reactAgent)throws GraphStateException {return reactAgent.compile();
}
2、創建工具類
public class TestTools {@Tool("返回給定城市的天氣預報")public String getWeather(@P("應返回天氣預報的城市") String city) {return "The weather in " + city + " is sunny with a high of 25 degrees.";}@Tool("求兩個數的和")double add(@P(value = "數字",required = true) int a,@P(value = "數字",required = true) int b) {return a + b;}@Tool("求平方根")double squareRoot(@P(value = "數字",required = true) double x) {return Math.sqrt(x);}
}
3、創建接口
@Slf4j
@RestController
@RequestMapping("/react")
public class ReactController {@Resource(name = "reactAgentGraph")private CompiledGraph<State> reactAgentGraph;@GetMapping("/chat")public String simpleChat(@RequestParam("query") String query) {GraphRepresentation graph = reactAgentGraph.getGraph(GraphRepresentation.Type.PLANTUML, "React智能體");log.info("React-Agent PlantUML Graph:\n{}", graph.content());Optional<State> queryResult = reactAgentGraph.invoke(Map.of("messages", UserMessage.from(query)));return queryResult.get().finalResponse().get();}
}
4、執行結果日志:
[ReactAgent] callAgent result: ChatResponse { aiMessage = AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = "call_e9837fbced09464b8f9490", name = "getWeather", arguments = "{"arg0": "北京"}" }] }, metadata = QwenChatResponseMetadata{id="0b109d6b-5234-9db1-8305-ef5f666f7bbb", modelName="qwen-plus-latest", tokenUsage=TokenUsage { inputTokenCount = 432, outputTokenCount = 20, totalTokenCount = 452 }, finishReason=TOOL_EXECUTION, searchInfo=SearchInfo[searchResults=[]], reasoningContent=null} }
[ReactAgent] executeTools result: [ToolExecutionResultMessage { id = "call_e9837fbced09464b8f9490" toolName = "getWeather" text = "The weather in 北京 is sunny with a high of 25 degrees." }]
[ReactAgent] callAgent result: ChatResponse { aiMessage = AiMessage { text = "北京的天氣晴朗,最高氣溫為25度。" toolExecutionRequests = [] }, metadata = QwenChatResponseMetadata{id="212e9aef-5f02-98d4-8fae-3201c44d02b4", modelName="qwen-plus-latest", tokenUsage=TokenUsage { inputTokenCount = 482, outputTokenCount = 13, totalTokenCount = 495 }, finishReason=STOP, searchInfo=SearchInfo[searchResults=[]], reasoningContent=null} }