001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.agent.react; 017 018import com.agentsflex.core.agent.IAgent; 019import com.agentsflex.core.message.AiMessage; 020import com.agentsflex.core.message.Message; 021import com.agentsflex.core.message.ToolCall; 022import com.agentsflex.core.model.chat.ChatModel; 023import com.agentsflex.core.model.chat.ChatOptions; 024import com.agentsflex.core.model.chat.StreamResponseListener; 025import com.agentsflex.core.model.chat.response.AiMessageResponse; 026import com.agentsflex.core.model.chat.tool.Tool; 027import com.agentsflex.core.model.chat.tool.ToolExecutor; 028import com.agentsflex.core.model.chat.tool.ToolInterceptor; 029import com.agentsflex.core.model.client.StreamContext; 030import com.agentsflex.core.prompt.MemoryPrompt; 031import com.agentsflex.core.util.StringUtil; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import java.util.ArrayList; 036import java.util.List; 037 038/** 039 * ReActAgent 是一个通用的 ReAct 模式 Agent,支持 Reasoning + Action 的交互方式。 040 */ 041public class ReActAgent implements IAgent { 042 043 public static final String PARENT_AGENT_KEY = "__parent_react_agent"; 044 045 private static final Logger log = LoggerFactory.getLogger(ReActAgent.class); 046 047 private static final String DEFAULT_PROMPT_TEMPLATE = 048 "你是一个 ReAct Agent,结合 Reasoning(推理)和 Action(行动)来解决问题。\n" + 049 "但在处理用户问题时,请首先判断:\n" + 050 "1. 如果问题可以通过你的常识或已有知识直接回答 → 请忽略 ReAct Agent 框架,直接输出自然语言回答。\n" + 051 "2. 如果问题需要调用特定工具才能解决(如查询、计算、获取外部信息等)→ 请严格按照 ReAct 格式响应。\n" + 052 "\n" + 053 "如果你选择使用 ReAct 模式,请遵循以下格式:\n" + 054 "Thought: 描述你对当前问题的理解,包括已知信息和缺失信息,说明你下一步将采取什么行动及其原因。\n" + 055 "Action: 从下方列出的工具中选择一个合适的工具,仅输出工具名称,不得虚构。\n" + 056 "Action Input: 使用标准 JSON 格式提供该工具所需的参数,确保字段名与工具描述一致。\n" + 057 "\n" + 058 "在 ReAct Agent 模式下,如果你已获得足够信息可以直接回答用户,请输出:\n" + 059 "Final Answer: [你的回答]\n" + 060 "\n" + 061 "如果你发现用户的问题缺少关键信息(例如时间、地点、具体目标、主体信息等),且无法通过工具获取,\n" + 062 "请主动向用户提问,格式如下:\n" + 063 "Request: [你希望用户澄清的问题]" + 064 "\n" + 065 "注意事项:\n" + 066 "1. 每次只能选择一个工具并执行一个动作。\n" + 067 "2. 在未收到工具执行结果前,不要自行假设其输出。\n" + 068 "3. 不得编造工具或参数,所有工具均列于下方。\n" + 069 "4. 输出顺序必须为:Thought → Action → Action Input。\n" + 070 "\n" + 071 "### 可用工具列表:\n" + 072 "{tools}\n" + 073 "\n" + 074 "### 用户问题如下:\n" + 075 "{user_input}"; 076 077 private static final int DEFAULT_MAX_ITERATIONS = 20; 078 079 private final ChatModel chatModel; 080 private final List<Tool> tools; 081 private final ReActAgentState state; 082 083 private ReActStepParser reActStepParser = ReActStepParser.DEFAULT; // 默认解析器 084 private final MemoryPrompt memoryPrompt; 085 private ChatOptions chatOptions; 086 private ReActMessageBuilder messageBuilder = new ReActMessageBuilder(); 087 088 // 监听器集合 089 private final List<ReActAgentListener> listeners = new ArrayList<>(); 090 091 // 拦截器集合 092 private final List<ToolInterceptor> toolInterceptors = new ArrayList<>(); 093 094 095 public ReActAgent(ChatModel chatModel, List<Tool> tools, String userQuery) { 096 this.chatModel = chatModel; 097 this.tools = tools; 098 this.state = new ReActAgentState(); 099 this.state.userQuery = userQuery; 100 this.state.promptTemplate = DEFAULT_PROMPT_TEMPLATE; 101 this.state.maxIterations = DEFAULT_MAX_ITERATIONS; 102 this.memoryPrompt = new MemoryPrompt(); 103 } 104 105 public ReActAgent(ChatModel chatModel, List<Tool> tools, String userQuery, MemoryPrompt memoryPrompt) { 106 this.chatModel = chatModel; 107 this.tools = tools; 108 this.state = new ReActAgentState(); 109 this.state.userQuery = userQuery; 110 this.state.promptTemplate = DEFAULT_PROMPT_TEMPLATE; 111 this.state.maxIterations = DEFAULT_MAX_ITERATIONS; 112 this.memoryPrompt = memoryPrompt; 113 } 114 115 public ReActAgent(ChatModel chatModel, List<Tool> tools, ReActAgentState state) { 116 this.chatModel = chatModel; 117 this.tools = tools; 118 this.state = state; 119 this.memoryPrompt = new MemoryPrompt(); 120 if (state.messageHistory != null) { 121 this.memoryPrompt.addMessages(state.messageHistory); 122 } 123 } 124 125 /** 126 * 注册监听器 127 */ 128 public void addListener(ReActAgentListener listener) { 129 listeners.add(listener); 130 } 131 132 /** 133 * 移除监听器 134 */ 135 public void removeListener(ReActAgentListener listener) { 136 listeners.remove(listener); 137 } 138 139 public void addToolInterceptor(ToolInterceptor interceptor) { 140 toolInterceptors.add(interceptor); 141 } 142 143 public List<ToolInterceptor> getToolInterceptors() { 144 return toolInterceptors; 145 } 146 147 public ChatModel getChatModel() { 148 return chatModel; 149 } 150 151 public List<Tool> getTools() { 152 return tools; 153 } 154 155 public ReActStepParser getReActStepParser() { 156 return reActStepParser; 157 } 158 159 public void setReActStepParser(ReActStepParser reActStepParser) { 160 this.reActStepParser = reActStepParser; 161 } 162 163 public List<ReActAgentListener> getListeners() { 164 return listeners; 165 } 166 167 public boolean isStreamable() { 168 return this.state.streamable; 169 } 170 171 public void setStreamable(boolean streamable) { 172 this.state.streamable = streamable; 173 } 174 175 176 public MemoryPrompt getMemoryPrompt() { 177 return memoryPrompt; 178 } 179 180 public ReActMessageBuilder getMessageBuilder() { 181 return messageBuilder; 182 } 183 184 public void setMessageBuilder(ReActMessageBuilder messageBuilder) { 185 this.messageBuilder = messageBuilder; 186 } 187 188 public ChatOptions getChatOptions() { 189 return chatOptions; 190 } 191 192 public void setChatOptions(ChatOptions chatOptions) { 193 this.chatOptions = chatOptions; 194 } 195 196 public ReActAgentState getState() { 197 state.messageHistory = memoryPrompt.getMessages(); 198 return state; 199 } 200 201 /** 202 * 运行 ReAct Agent 流程 203 */ 204 @Override 205 public void execute() { 206 try { 207 List<Message> messageHistory = state.getMessageHistory(); 208 if (messageHistory == null || messageHistory.isEmpty()) { 209 String toolsDescription = Util.buildToolsDescription(tools); 210 String prompt = state.promptTemplate 211 .replace("{tools}", toolsDescription) 212 .replace("{user_input}", state.userQuery); 213 214 Message message = messageBuilder.buildStartMessage(prompt, tools, state.userQuery); 215 memoryPrompt.addMessage(message); 216 } 217 if (this.isStreamable()) { 218 startNextReActStepStream(); 219 } else { 220 startNextReactStepNormal(); 221 } 222 } catch (Exception e) { 223 log.error("运行 ReAct Agent 出错:" + e); 224 notifyOnError(e); 225 } 226 } 227 228 private void startNextReactStepNormal() { 229 while (state.iterationCount < state.maxIterations) { 230 231 state.iterationCount++; 232 233 AiMessageResponse response = chatModel.chat(memoryPrompt, chatOptions); 234 notifyOnChatResponse(response); 235 236 String content = response.getMessage().getContent(); 237 AiMessage message = new AiMessage(content); 238 239 // 请求用户输入 240 if (isRequestUserInput(content)) { 241 String question = extractRequestQuestion(content); 242 message.addMetadata("type", "reActRequest"); 243 memoryPrompt.addMessage(message); 244 notifyOnRequestUserInput(question); // 新增监听器回调 245 break; // 暂停执行,等待用户回复 246 } 247 // ReAct 动作 248 else if (isReActAction(content)) { 249 message.addMetadata("type", "reActAction"); 250 memoryPrompt.addMessage(message); 251 if (!processReActSteps(content)) { 252 break; 253 } 254 } 255 256 // 最终答案 257 else if (isFinalAnswer(content)) { 258 String flag = reActStepParser.getFinalAnswerFlag(); 259 String answer = content.substring(content.indexOf(flag) + flag.length()); 260 message.addMetadata("type", "reActFinalAnswer"); 261 memoryPrompt.addMessage(message); 262 notifyOnFinalAnswer(answer); 263 break; 264 } 265 // 不是 Action 266 else { 267 memoryPrompt.addMessage(message); 268 notifyOnNonActionResponse(response); 269 break; 270 } 271 } 272 273 // 显式通知达到最大迭代 274 if (state.iterationCount >= state.maxIterations) { 275 notifyOnMaxIterationsReached(); 276 } 277 } 278 279 280 private void startNextReActStepStream() { 281 if (state.iterationCount >= state.maxIterations) { 282 notifyOnMaxIterationsReached(); 283 return; 284 } 285 286 state.iterationCount++; 287 288 chatModel.chatStream(memoryPrompt, new StreamResponseListener() { 289 290 @Override 291 public void onMessage(StreamContext context, AiMessageResponse response) { 292 notifyOnChatResponseStream(context, response); 293 } 294 295 @Override 296 public void onStop(StreamContext context) { 297 AiMessage lastAiMessage = context.getFullMessage(); 298 if (lastAiMessage == null) { 299 notifyOnError(new RuntimeException("没有收到任何回复")); 300 return; 301 } 302 303 String content = lastAiMessage.getFullContent(); 304 if (StringUtil.noText(content)) { 305 notifyOnError(new RuntimeException("没有收到任何回复")); 306 return; 307 } 308 309 AiMessage message = new AiMessage(content); 310 311 // 请求用户输入 312 if (isRequestUserInput(content)) { 313 String question = extractRequestQuestion(content); 314 message.addMetadata("type", "reActRequest"); 315 memoryPrompt.addMessage(message); 316 notifyOnRequestUserInput(question); // 新增监听器回调 317 } 318 319 // ReAct 动作 320 else if (isReActAction(content)) { 321 message.addMetadata("type", "reActAction"); 322 memoryPrompt.addMessage(message); 323 if (processReActSteps(content)) { 324 // 递归继续执行下一个 ReAct 步骤 325 startNextReActStepStream(); 326 } 327 } 328 329 // 最终答案 330 else if (isFinalAnswer(content)) { 331 message.addMetadata("type", "reActFinalAnswer"); 332 memoryPrompt.addMessage(message); 333 String flag = reActStepParser.getFinalAnswerFlag(); 334 String answer = content.substring(content.indexOf(flag) + flag.length()); 335 notifyOnFinalAnswer(answer); 336 } else { 337 memoryPrompt.addMessage(message); 338 // 不是 Action 339 notifyOnNonActionResponseStream(context); 340 } 341 } 342 343 @Override 344 public void onFailure(StreamContext context, Throwable throwable) { 345 notifyOnError((Exception) throwable); 346 } 347 }, chatOptions); 348 } 349 350 351 private boolean isFinalAnswer(String content) { 352 return reActStepParser.isFinalAnswer(content); 353 } 354 355 private boolean isReActAction(String content) { 356 return reActStepParser.isReActAction(content); 357 } 358 359 private boolean isRequestUserInput(String content) { 360 return reActStepParser.isRequest(content); 361 } 362 363 private String extractRequestQuestion(String content) { 364 return reActStepParser.extractRequestQuestion(content); 365 } 366 367 368 private boolean processReActSteps(String content) { 369 List<ReActStep> reActSteps = reActStepParser.parse(content); 370 if (reActSteps.isEmpty()) { 371 notifyOnStepParseError(content); 372 return false; 373 } 374 375 for (ReActStep step : reActSteps) { 376 boolean stepExecuted = false; 377 for (Tool tool : tools) { 378 if (tool.getName().equals(step.getAction())) { 379 try { 380 notifyOnActionStart(step); 381 382 Object result = null; 383 try { 384 ToolCall toolCall = new ToolCall(); 385 toolCall.setId("react_call_" + state.iterationCount + "_" + System.currentTimeMillis()); 386 toolCall.setName(step.getAction()); 387 toolCall.setArguments(step.getActionInput()); 388 389 ToolExecutor executor = new ToolExecutor(tool, toolCall, toolInterceptors); 390 391 // 方便 “子Agent” 或者 tool, 获取当前的 ReActAgent 392 executor.addInterceptor((context, chain) -> { 393 context.setAttribute(PARENT_AGENT_KEY, ReActAgent.this); 394 return chain.proceed(context); 395 }); 396 397 result = executor.execute(); 398 } catch (Exception e) { 399 throw new RuntimeException(e); 400 } finally { 401 notifyOnActionEnd(step, result); 402 } 403 404 Message message = messageBuilder.buildObservationMessage(step, result); 405 memoryPrompt.addMessage(message); 406 stepExecuted = true; 407 } catch (Exception e) { 408 log.error(e.toString(), e); 409 notifyOnActionInvokeError(e); 410 411 if (!state.continueOnActionInvokeError) { 412 return false; 413 } 414 415 Message message = messageBuilder.buildActionErrorMessage(step, e); 416 memoryPrompt.addMessage(message); 417 return true; 418 } 419 break; 420 } 421 } 422 423 if (!stepExecuted) { 424 notifyOnActionNotMatched(step, tools); 425 return false; 426 } 427 } 428 429 430 return true; 431 } 432 433 434 // ========== 通知监听器的方法 ========== 435 private void notifyOnChatResponse(AiMessageResponse response) { 436 for (ReActAgentListener listener : listeners) { 437 try { 438 listener.onChatResponse(response); 439 } catch (Exception e) { 440 log.error(e.toString(), e); 441 } 442 } 443 } 444 445 private void notifyOnNonActionResponse(AiMessageResponse response) { 446 for (ReActAgentListener listener : listeners) { 447 try { 448 listener.onNonActionResponse(response); 449 } catch (Exception e) { 450 log.error(e.toString(), e); 451 } 452 } 453 } 454 455 private void notifyOnNonActionResponseStream(StreamContext context) { 456 for (ReActAgentListener listener : listeners) { 457 try { 458 listener.onNonActionResponseStream(context); 459 } catch (Exception e) { 460 log.error(e.toString(), e); 461 } 462 } 463 } 464 465 private void notifyOnChatResponseStream(StreamContext context, AiMessageResponse response) { 466 for (ReActAgentListener listener : listeners) { 467 try { 468 listener.onChatResponseStream(context, response); 469 } catch (Exception e) { 470 log.error(e.toString(), e); 471 } 472 } 473 } 474 475 private void notifyOnFinalAnswer(String finalAnswer) { 476 for (ReActAgentListener listener : listeners) { 477 try { 478 listener.onFinalAnswer(finalAnswer); 479 } catch (Exception e) { 480 log.error(e.toString(), e); 481 } 482 } 483 } 484 485 private void notifyOnRequestUserInput(String question) { 486 for (ReActAgentListener listener : listeners) { 487 try { 488 listener.onRequestUserInput(question); 489 } catch (Exception e) { 490 log.error(e.toString(), e); 491 } 492 } 493 } 494 495 private void notifyOnActionStart(ReActStep reActStep) { 496 for (ReActAgentListener listener : listeners) { 497 try { 498 listener.onActionStart(reActStep); 499 } catch (Exception e) { 500 log.error(e.toString(), e); 501 } 502 } 503 } 504 505 private void notifyOnActionEnd(ReActStep reActStep, Object result) { 506 for (ReActAgentListener listener : listeners) { 507 try { 508 listener.onActionEnd(reActStep, result); 509 } catch (Exception e) { 510 log.error(e.toString(), e); 511 } 512 } 513 } 514 515 private void notifyOnMaxIterationsReached() { 516 for (ReActAgentListener listener : listeners) { 517 try { 518 listener.onMaxIterationsReached(); 519 } catch (Exception e) { 520 log.error(e.toString(), e); 521 } 522 } 523 } 524 525 private void notifyOnStepParseError(String content) { 526 for (ReActAgentListener listener : listeners) { 527 try { 528 listener.onStepParseError(content); 529 } catch (Exception e) { 530 log.error(e.toString(), e); 531 } 532 } 533 } 534 535 private void notifyOnActionNotMatched(ReActStep step, List<Tool> tools) { 536 for (ReActAgentListener listener : listeners) { 537 try { 538 listener.onActionNotMatched(step, tools); 539 } catch (Exception e) { 540 log.error(e.toString(), e); 541 } 542 } 543 } 544 545 private void notifyOnActionInvokeError(Exception e) { 546 for (ReActAgentListener listener : listeners) { 547 try { 548 listener.onActionInvokeError(e); 549 } catch (Exception e1) { 550 log.error(e.toString(), e1); 551 } 552 } 553 } 554 555 private void notifyOnError(Exception e) { 556 for (ReActAgentListener listener : listeners) { 557 try { 558 listener.onError(e); 559 } catch (Exception e1) { 560 log.error(e.toString(), e1); 561 } 562 } 563 } 564}