✅AgentScope Java进阶:Plan-and-Execute
经典的 plan-and-execute 范式有两种:
双 Agent 模型(LangGraph):Planner 跑一次出 plan → Executor 逐步执行 → 完不成再回 Planner 重规划。优点是职责清晰,缺点是状态在两个 Agent 之间穿梭、prompt 上下文冗长。
单 Agent 自驱模型(ASJ):同一个 ReActAgent 既能调”规划工具”又能调”执行工具”——LLM 自己决定什么时候建计划、什么时候推进、什么时候改、什么时候收尾。框架只做两件事:
ASJ 选了后者。代价是 prompt 工程要更精细(hint 必须根据状态动态生成),收益是统一了 ReAct 主循环——没有任何特殊”plan 阶段”和”execute 阶段”,所有动作都还是 reasoning↔acting。
在ASJ中,Plan-and-Execute最简单的用法只有一行:
1
| ReActAgent agent = ReActAgent.builder() .name("Assistant").model(model).toolkit(toolkit) .enablePlan() .build();
|
复杂一点的话,可以自定义一些配置:
1 2 3 4
| PlanNotebook nb = PlanNotebook.builder() .planToH int(new DefaultPlanToH int()) ReActAgent agent = ReActAgent.builder().planNotebook(nb).build();
|
build() 的运行,最终会走到 configurePlan(agentToolkit):
1 2
| private void configurePlan(Toolkit agentToolkit) {
|
上面提到10个工具,按照职责可以分为四组:
生命周期组(4 个)
| Field |
Value |
| 工具 |
作用 |
| create_plan(name, description,expected_outcome, subtasks) |
创建新 plan |
| finish_plan(state, outcome) |
完成或放弃 plan |
| view_historical_plans() |
列出历史 plans |
| recover_historical_plan(plan_id) |
恢复历史 plan |
结构修改组(2 个)
| Field |
Value |
| 工具 |
作用 |
| update_plan_info(name?, description?, expected_outcome?) |
改 plan 元数据,三个字段可选 |
| revise_current_plan(subtask_idx, action, subtask) |
action ∈ {add, revise, delete},按 idx 位置增删改子任务 |
子任务执行组(3 个)
| Field |
Value |
| 工具 |
作用 |
| update_subtask_state(idx, state) |
状态机推进 |
| finish_subtask(idx, outcome) |
完成子任务 |
| view_subtasks(indexes) |
查看子任务详情 |
状态查询组(1 个)
| Field |
Value |
| 工具 |
作用 |
| get_subtask_count() |
返回 total/done/in_progress/todo/abandoned 五元统计 |
PlanNotebook.getCurrentHint() 调 planToHint.generateHint(currentPlan, this),DefaultPlanToHint 根据当前 plan 的状态生成五种不同的 prompt:
NO_PLAN:用户提了个问题,你判断要不要建计划——如果是简单问题直接答,复杂任务才 create_plan。
AT_THE_BEGINNING:plan 刚建好,所有子任务 TODO——下一步把第 0 个子任务置 IN_PROGRESS 开干。
WHEN_A_SUBTASK_IN_PROGRESS:有一个子任务正在执行——继续推进、用 finish_subtask 收尾、或必要时改 plan。
WHEN_NO_SUBTASK_IN_PROGRESS:前几个完成了但当前没活跃——把下一个置 IN_PROGRESS。
AT_THE_END:全部子任务结束——调 finish_plan 总结收尾。
prompt 末尾还会拼接 RULE_COMMON(语言一致性、用户可能直接改 plan、不要私自改 plan)和 RULE_WAIT_FOR_CONFIRMATION(如果 needUserConfirm=true)等”系统级规则”。
整段 hint 包在 … 标签里,作为一条 USER 角色消息追加到 inputMessages 末尾。LLM 在每一轮 reasoning 都能看到plan 当前进展,从而做出与全局目标一致的决策——这就是单 Agent 模型省掉 Executor 的根因。
Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import io.agentscope.core.ReActAgent; import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; import io.agentscope.core.memory.InMemoryMemory; import io.agentscope.core.message.*; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.plan.PlanNotebook; import io.agentscope.core.tool.*; import reactor.core.publisher.Mono; import java.util.Hash Map; import java.util.Map; public class PlanExecuteDemo { static Map<String, String> files = new Hash Map<>();
@Tool(name = "write_file", description = "Write content to a file") public Mono<String> write( @ToolParam(name = "name") String n, @ToolParam(name = "content") String c) { files.put(n, c); return Mono.just("Saved " + n); }
@Tool(name = "read_file", description = "Read content from a file") public Mono<String> read( @ToolParam(name = "name") String n) { return Mono.just(files.getOrDefault(n, "(not found)")); }
@Tool(name = "calc", description = "Evaluate a simple math expression like 10*5") public Mono<String> calc( @ToolParam(name = "expr") String e) { String[] parts = e.split("\\*"); return Mono.just(e + " = " + (Integer.parseInt(parts[0].trim()) * Integer.parseInt(parts[1].trim()))); } public static void main(String[] args) { Toolkit toolkit = new Toolkit(); toolkit.registerTool(new PlanExecuteDemo()); PlanNotebook nb = PlanNotebook.builder() .maxSubtasks(8) .needUserConfirm(false) nb.addChangeHook("log", (notebook, plan) -> { if (plan != null) { System.out.println("\n>>> Plan changed:\n" + plan.toMarkdown(false)); } }); ReActAgent agent = ReActAgent.builder() .name("Planner") .sysPrompt("You break down complex tasks into a plan and execute step by step.") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen-plus") .stream(true) .formatter(new DashScopeChatFormatter()).build()) .toolkit(toolkit) .memory(new InMemoryMemory()) .maxIters(50) .planNotebook(nb) .build(); Msg user = Msg.builder().role(MsgRole.USER).content(TextBlock.builder().text( "Calculate 10*5, save the result to result.txt, then read it back to verify. " + "This is a multi-step task, please plan first." ).build()).build(); Msg resp = agent.call(user).block(); System.out.println("\nFinal: " + resp.getTextContent()); } }
|