
常见的React Agent是推理—行动循环框架,具体思路如下图

实现过程中,往往可以在设计实现的时候,往往会出现如下几种:
1、规划-执行模式

2、规划-人工检查-执行模式
相比较规划-执行,增加了human-in-the-loop的人工交互,进行审批通过/跳过/拒绝/修改规划,即先完成Plan,然后对计划进行人工检查,在执行每个步骤的结果也进行人工检查,是否符合预期。
3、规划-人工检查-执行-监督评估模式
主要引入监督评估,通过大模型自己做处理,人工检查是可选,实现流程相对比较复杂。
1、自定义Agent实现逻辑方式
比如一个是PlanAgent,一个是HumanAgent,一个是ExecuteAgent,然后
// 简单内存存储:reviewId -> plan
private final Map<String, ExecutionPlan> planStore = new ConcurrentHashMap<>();
/**
* 步骤1:制作计划并创建人工审核会话,返回 reviewId + plan
*/
@PostMapping("/start")
public StartResponse start(@RequestBody StartRequest req) {
PlanMakerAgent planMaker = new PlanMakerAgent(chatModel);
ExecutionPlan plan = planMaker.createPlan(req.userRequest());
String reviewId = humanReviewAgent.startReview(plan);
planStore.put(reviewId, plan);
return new StartResponse(reviewId, plan);
}
/**
* 步骤2+3:等待审核决定后执行计划(阻塞该请求直到审核结果到达或超时)
* 建议在前端先调用 HumanReviewController 完成 /approve|/reject,再调用该接口触发执行
*/
@PostMapping("/{reviewId}/execute")
public ExecuteResponse execute(@PathVariable String reviewId,
@RequestParam(defaultValue = "PT30M") String timeoutIso) {
ExecutionPlan plan = planStore.get(reviewId);
if (plan == null) {
throw new IllegalArgumentException("reviewId not found: " + reviewId);
}
PlanExecutorAgent executor = new PlanExecutorAgent(chatModel, humanReviewAgent);
// 等待人工审核
HumanReviewAgent.ReviewResult decision = humanReviewAgent.awaitDecision(reviewId, Duration.parse(timeoutIso));
if (!decision.approved()) {
String logPath = txtFileSaveTool.saveWithTimestamp("plan_rejected", "计划被拒绝: " + decision.reason());
return new ExecuteResponse("REJECTED", "计划被拒绝: " + decision.reason(), null, logPath);
}
// 执行计划
ExecutionResult result = executor.executePlan(plan);
// 将执行过程整理为文本并保存
String textLog = buildTextLog(plan, result);
String logPath = txtFileSaveTool.saveWithTimestamp("plan_run", textLog);
return new ExecuteResponse(result.getStatus().name(), result.getMessage(), result, logPath);
}Plan制定Agent
/**
* 计划制作Agent
* 负责根据用户需求制作详细的执行计划
*/
@Slf4j
public class PlanMakerAgent {
private final ChatClient chatClient;
public PlanMakerAgent(OpenAiChatModel chatModel) {
this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
/**
* 根据用户需求创建执行计划
*/
public ExecutionPlan createPlan(String userRequest) {
log.info("开始制作计划,用户需求: {}", userRequest);
String systemPrompt = """
你是一个专业的计划制作专家。根据用户的需求,制作一个详细的执行计划。
计划应该包含以下要素:
1. 计划标题和描述
2. 详细的执行步骤
3. 每个步骤的预期结果
4. 所需的工具和资源
5. 预估的执行时间
请以JSON格式返回计划,格式如下:
{
"title": "计划标题",
"description": "计划描述",
"steps": [
{
"stepNumber": 1,
"title": "步骤标题",
"description": "步骤详细描述",
"tools": ["工具1", "工具2"],
"expectedResult": "预期结果",
"estimatedTime": "预估时间"
}
],
"totalEstimatedTime": "总预估时间",
"requiredTools": ["所有需要的工具"],
"notes": "注意事项"
}
""";
String userPrompt = String.format("""
用户需求:%s
请为这个需求制作一个详细的执行计划。
特别注意:
1. 如果涉及搜索,请考虑使用GoogleSearchTool或BrowserUseTool
2. 如果需要总结搜索结果,请使用SearchSummaryTool
3. 如果需要生成PPT,请使用PPTGenerationTool
4. 步骤要具体可执行,避免过于抽象
5. 考虑步骤之间的依赖关系
""", userRequest);
try {
String response = chatClient.prompt()
.system(systemPrompt)
.user(userPrompt)
.call()
.content();
log.info("计划制作完成,响应: {}", response);
// 解析JSON响应
ExecutionPlan plan = parseExecutionPlan(response);
plan.setStatus(ExecutionPlan.PlanStatus.DRAFT);
plan.setCreatedTime(System.currentTimeMillis());
return plan;
} catch (Exception e) {
log.error("制作计划失败: {}", e.getMessage(), e);
throw new RuntimeException("制作计划失败: " + e.getMessage(), e);
}
}
/**
* 解析执行计划JSON
*/
private ExecutionPlan parseExecutionPlan(String jsonResponse) {
try {
// 清理可能的markdown代码块标记
String cleanJson = jsonResponse.trim();
if (cleanJson.startsWith("```json")) {
cleanJson = cleanJson.substring(7);
}
if (cleanJson.startsWith("```")) {
cleanJson = cleanJson.substring(3);
}
if (cleanJson.endsWith("```")) {
cleanJson = cleanJson.substring(0, cleanJson.length() - 3);
}
cleanJson = cleanJson.trim();
return JSON.parseObject(cleanJson, ExecutionPlan.class);
} catch (Exception e) {
log.error("解析计划JSON失败: {}", e.getMessage(), e);
// 创建一个默认的计划
ExecutionPlan defaultPlan = new ExecutionPlan();
defaultPlan.setTitle("解析失败的计划");
defaultPlan.setDescription("由于JSON解析失败,创建了默认计划");
ExecutionPlan.PlanStep step = new ExecutionPlan.PlanStep();
step.setStepNumber(1);
step.setTitle("手动处理");
step.setDescription("需要手动处理原始响应: " + jsonResponse);
step.setTools(List.of("手动操作"));
step.setExpectedResult("完成任务");
step.setEstimatedTime("未知");
defaultPlan.setSteps(List.of(step));
defaultPlan.setTotalEstimatedTime("未知");
defaultPlan.setRequiredTools(List.of("手动操作"));
defaultPlan.setNotes("原始响应解析失败,请检查格式");
return defaultPlan;
}
}
}人工检查Agent
/**
* 人工审核服务(HTTP 交互版)
* - startReview: 创建审核会话并返回 reviewId
* - awaitDecision: 在服务器端等待人工决策(通过 Controller 提交)
* - approve/reject/modify: 提交人工决策
*/
@Service
@Slf4j
public class HumanReviewAgent {
private final Map<String, CompletableFuture<ReviewResult>> sessions = new ConcurrentHashMap<>();
/**
* 发起审核并返回 reviewId
*/
public String startReview(ExecutionPlan plan) {
String id = UUID.randomUUID().toString();
sessions.put(id, new CompletableFuture<>());
return id;
}
/**
* 等待人工决策,超时抛出异常
*/
public ReviewResult awaitDecision(String reviewId, Duration timeout) {
CompletableFuture<ReviewResult> future = sessions.get(reviewId);
if (future == null) {
throw new IllegalArgumentException("reviewId not found: " + reviewId);
}
try {
return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException("await decision failed: " + e.getMessage(), e);
} finally {
sessions.remove(reviewId);
}
}
public void approve(String reviewId, String reason) {
complete(reviewId, new ReviewResult(true, reason, "APPROVE"));
}
public void reject(String reviewId, String reason) {
complete(reviewId, new ReviewResult(false, reason, "REJECT"));
}
public void modify(String reviewId, String suggestion) {
complete(reviewId, new ReviewResult(false, suggestion, "MODIFY"));
}
private void complete(String reviewId, ReviewResult result) {
CompletableFuture<ReviewResult> future = sessions.get(reviewId);
if (future == null) {
throw new IllegalArgumentException("reviewId not found: " + reviewId);
}
future.complete(result);
}
/**
* 失败后简单确认:是否继续。
* 备注:最小实现先直接返回 true(继续),避免阻塞;
* 若需要真正的人机交互,可扩展为使用 CompletableFuture 并通过 Controller 完成决策。
*/
public boolean simpleConfirm(String message) {
log.warn("[Confirm] {} -> default=continue(true)", message);
return true;
}
/**
* 审核结果
*/
public record ReviewResult(boolean approved, String reason, String action) { }
}
2、通过graph的方式
例如LangGraph,SpringAI Graph等实现方式,好处就是可以借助现成实现,例如HumanNode,StateGraph的方式组件执行图以及保存状态。
import com.alibaba.cloud.ai.graph.*;
import com.alibaba.cloud.ai.graph.action.AsyncNodeAction;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;
import com.alibaba.cloud.ai.graph.checkpoint.constant.SaverConstant;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.alibaba.cloud.ai.graph.node.HumanNode;
import com.alibaba.cloud.ai.graph.state.StateSnapshot;
/**
* 使用 Alibaba Cloud AI Graph(StateGraph) 实现的 计划-人工-执行 控制器
* - start: 生成计划并创建人工审核会话
* - execute: 等待人工审核后,使用 StateGraph 为每个步骤构建节点并顺序执行
*/
@RestController
@RequestMapping("/api/graph-workflow")
@Slf4j
public class PlanHumanExecuteAgentController {
private final CompiledGraph compiledGraph;
// 内存存储:reviewId -> plan
private final Map<String, ExecutionPlan> planStore = new ConcurrentHashMap<>();
/**
* 构建 StateGraph(包含 HumanNode)
*/
private CompiledGraph buildGraph() throws Exception {
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(new SimpleLoggerAdvisor())
.defaultToolCallbacks(ToolBuilder.getPlanReviewExecuteAgentToolCallbacks())
.build();
ReactAgent executorAgent = new ReactAgent(
"planExecutor",
chatClient,
ToolBuilder.getPlanReviewExecuteAgentToolCallbacks(),
6);
executorAgent.getAndCompileGraph();
// 创建 HumanNode 并包装为 AsyncNodeAction
HumanNode humanNode = new HumanNode();
// 构建 StateGraph:START -> HumanNode -> ExecutorAgent -> END
OverAllStateFactory stateFactory = OverAllState::new;
StateGraph graph = new StateGraph(stateFactory)
.addNode("human", AsyncNodeAction.node_async(humanNode))
.addNode("executor", executorAgent.asAsyncNodeAction("input", "final_output"))
.addEdge(StateGraph.START, "human")
.addEdge("human", "executor")
.addEdge("executor", StateGraph.END);
// 添加 PlantUML 打印
GraphRepresentation representation = graph.getGraph(GraphRepresentation.Type.PLANTUML,
"human flow");
log.info("\n=== expander UML Flow ===");
log.info(representation.content());
log.info("==================================\n");
// 配置 MemorySaver 和中断点
SaverConfig saverConfig = SaverConfig.builder()
.register(SaverConstant.MEMORY, new MemorySaver())
.build();
return graph.compile(CompileConfig.builder()
.saverConfig(saverConfig)
.interruptBefore("human") // 在 human 节点前中断
.build());
}
/**
* 步骤1:制作计划并创建人工审核会话,返回 reviewId + plan
*/
@PostMapping("/start")
public StartResponse start(@RequestBody StartRequest req) {
PlanMakerAgent planMaker = new PlanMakerAgent(chatModel);
ExecutionPlan plan = planMaker.createPlan(req.userRequest());
String reviewId = UUID.randomUUID().toString();
planStore.put(reviewId, plan);
return new StartResponse(reviewId, plan);
}
/**
* 步骤2:启动执行流程(会在 HumanNode 前中断,等待人工审核)
*/
@PostMapping("/{reviewId}/execute")
public ExecuteResponse execute(@PathVariable String reviewId,
@RequestParam(defaultValue = "PT30M") String timeoutIso) {
ExecutionPlan plan = planStore.get(reviewId);
if (plan == null) {
throw new IllegalArgumentException("reviewId not found: " + reviewId);
}
try {
// 准备初始输入
Map<String, Object> inputs = new HashMap<>();
inputs.put("input", buildExecutorPrompt(plan));
// 使用 reviewId 作为 threadId
RunnableConfig runnableConfig = RunnableConfig.builder()
.threadId(reviewId)
.build();
log.info("启动 StateGraph 执行,将在 HumanNode 前中断,reviewId={}", reviewId);
// 执行到 HumanNode 前会自动中断
compiledGraph.invoke(inputs, runnableConfig);
return new ExecuteResponse("PENDING",
"执行已启动,等待人工审核。请调用 /approve、/reject 或 /modify 接口进行审核",
null,
null);
} catch (Exception e) {
log.error("Graph 执行失败", e);
String msg = e.getMessage();
String logPath = txtFileSaveTool.saveWithTimestamp("plan_error", "执行异常: " + msg);
return new ExecuteResponse("FAILED", "执行异常: " + msg, null, logPath);
}
}
/**
* 批准审核并恢复执行
*/
@PostMapping("/reviews/{reviewId}/approve")
public ExecuteResponse approve(@PathVariable String reviewId, @RequestBody(required = false) ReasonRequest req) {
String reason = req != null ? req.reason() : "";
log.info("收到批准请求,reviewId={}, reason={}", reviewId, reason);
try {
// 恢复执行
return resumeExecution(reviewId, true, reason);
} catch (Exception e) {
log.error("批准后恢复执行失败", e);
return new ExecuteResponse("FAILED", "批准后恢复执行失败: " + e.getMessage(), null, null);
}
}
/**
* 拒绝审核
*/
@PostMapping("/reviews/{reviewId}/reject")
public Map<String,Object> reject(@PathVariable String reviewId, @RequestBody(required = false) ReasonRequest req) {
String reason = req != null ? req.reason() : "";
log.info("收到拒绝请求,reviewId={}, reason={}", reviewId, reason);
ExecutionPlan plan = planStore.get(reviewId);
if (plan != null) {
plan.setStatus(ExecutionPlan.PlanStatus.REJECTED);
}
String logPath = txtFileSaveTool.saveWithTimestamp("plan_rejected", "计划被拒绝: " + reason);
return Map.of("status", "REJECTED", "reviewId", reviewId, "reason", reason, "logPath", logPath);
}
/**
* 恢复执行(从 HumanNode 继续)
*/
private ExecuteResponse resumeExecution(String reviewId, boolean approved, String reason) throws Exception {
ExecutionPlan plan = planStore.get(reviewId);
if (plan == null) {
throw new IllegalArgumentException("reviewId not found: " + reviewId);
}
RunnableConfig runnableConfig = RunnableConfig.builder()
.threadId(reviewId)
.build();
// 获取当前状态
StateSnapshot stateSnapshot = compiledGraph.getState(runnableConfig);
OverAllState state = stateSnapshot.state();
state.withResume();
// 设置人工反馈
Map<String, Object> feedbackData = new HashMap<>();
feedbackData.put("approved", approved);
feedbackData.put("reason", reason);
state.withHumanFeedback(new OverAllState.HumanFeedback(feedbackData, reason));
log.info("恢复执行,reviewId={}, approved={}", reviewId, approved);
// 从当前节点继续执行
var snapshot = compiledGraph.invoke(state.data(), runnableConfig).get();
String finalOut = Objects.toString(snapshot.data().get("final_output"), null);
// 整理为 ExecutionResult
ExecutionResult execResult = new ExecutionResult(plan);
StepResult sr = new StepResult(0, "Agent 执行器");
sr.setSuccess(finalOut != null);
sr.setResult(finalOut);
execResult.addStepResult(sr);
execResult.setStatus(ExecutionResult.Status.COMPLETED);
execResult.setMessage("计划执行完成");
plan.setStatus(ExecutionPlan.PlanStatus.COMPLETED);
// 保存文本日志
String textLog = buildTextLog(plan, execResult);
String logPath = txtFileSaveTool.saveWithTimestamp("plan_run", textLog);
return new ExecuteResponse(execResult.getStatus().name(), execResult.getMessage(), execResult, logPath);
}
private String buildTextLog(ExecutionPlan plan, ExecutionResult result) {
StringBuilder sb = new StringBuilder();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
sb.append("# ").append(plan.getTitle() != null ? plan.getTitle() : "执行计划").append("\n\n");
sb.append("时间:").append(LocalDateTime.now().format(fmt)).append("\n");
if (plan.getDescription() != null) sb.append("描述:").append(plan.getDescription()).append("\n\n");
sb.append("## 步骤结果\n");
if (result != null && result.getStepResults() != null) {
result.getStepResults().forEach(sr -> {
sb.append(String.format("[StepResult] %s | success=%s | result=%s\n",
sr.getStepTitle(), sr.isSuccess(), sr.getResult()));
});
}
sb.append("\n状态:").append(result != null ? result.getStatus() : "N/A").append("\n");
if (result != null && result.getMessage() != null) sb.append("消息:").append(result.getMessage()).append("\n");
return sb.toString();
}
private String buildExecutorPrompt(ExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("你是一个执行代理,请根据以下计划逐步执行各个步骤;可以按需调用已注册的工具(如 GoogleSearchTool、BrowserUseTool、SearchSummaryTool、FileSaverTool)。\n");
sb.append("要求:\n");
sb.append("- 严格逐步执行,不要跳步;\n");
sb.append("- 每一步尽量给出清晰结果;\n");
sb.append("- 如果需要将文本保存到文件,请调用 FileSaverTool 对应方法;\n");
sb.append("- 最终请输出所有步骤结果的汇总说明。\n\n");
sb.append("计划标题:").append(plan.getTitle()).append("\n");
sb.append("计划描述:").append(plan.getDescription()).append("\n\n");
sb.append("执行步骤:\n");
for (ExecutionPlan.PlanStep step : plan.getSteps()) {
sb.append(String.format("步骤%d:%s\n", step.getStepNumber(), step.getTitle()));
sb.append(String.format(" 描述:%s\n", step.getDescription()));
sb.append(String.format(" 预期结果:%s\n", step.getExpectedResult()));
if (step.getTools() != null && !step.getTools().isEmpty()) {
sb.append(String.format(" 可用工具:%s\n", String.join(", ", step.getTools())));
}
}
return sb.toString();
}
} 
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。