(5)LangGraph4j框架ReActAgent实现
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} }