gogo-agent

为什么行程规划智能体要用Plan-and-Exucute?

帮一个出差员工规划一趟"上海到杭州,周五去周日回"的行程,听起来简单。但拆开来看,Agent 至少要完成以下事情:查已审批的差旅单获取约束、召回用户历史偏好、查差旅政策拿到报销标准、搜索去程航班/高铁、搜索返程航班/高铁、搜索酒店、…

TL;DR

帮一个出差员工规划一趟"上海到杭州,周五去周日回"的行程,听起来简单。但拆开来看,Agent 至少要完成以下事情:查已审批的差旅单获取约束、召回用户历史偏好、查差旅政策拿到报销标准、搜索去程航班/高铁、搜索返程航班/高铁、搜索酒店、…

帮一个出差员工规划一趟"上海到杭州,周五去周日回"的行程,听起来简单。但拆开来看,Agent 至少要完成以下事情:查已审批的差旅单获取约束、召回用户历史偏好、查差旅政策拿到报销标准、搜索去程航班/高铁、搜索返程航班/高铁、搜索酒店、对候选做偏好打分、调用规划引擎做笛卡尔积组合排序、归一化打分、政策合规检查、选出代表方案、委托审核子智能体做五维校验、根据审核报告决定是否修复重跑、最终输出 Markdown 方案给用户确认——这是一条典型的多步骤、有先后依赖、中间可能需要回退修复的推理链。

纯 ReAct(Reasoning + Acting)

ReAct 的核心循环是:想一步 → 做一步 → 观察结果 → 再想下一步。它没有全局计划,每一步都是基于当前状态的局部最优决策。

while not done:
    thought = LLM.reason(observation)
    action = LLM.decide(thought)
    observation = execute(action)

优势:灵活,适合探索性任务("帮我查一下杭州天气"),因为不需要提前知道要做几步。 劣势:当任务有 10+ 步时,LLM 容易"走着走着忘了要干什么"——它没有一个持久化的任务清单来告诉自己"我已经做完了第 3 步,接下来该做第 4 步"。在行程规划场景下,具体表现为: - 搜完去程机票后,忘记还要搜返程 - 调完 plan_itinerary 后,忘记还要委托审核 - 审核发现问题后,不知道该修复哪一步

纯规划

先让 LLM 一次性生成完整计划,然后按序机械执行每一步。 plan = LLM.plan(task) for step in plan: execute(step) # 不再推理,直接执行 优势:有全局视角,不会遗漏步骤。 劣势:太刚性。行程规划的现实是——搜索结果不确定(可能没有直飞航班)、审核可能不通过需要回退、用户可能中途追问。纯规划无法处理这些动态场景,因为执行阶段不做推理。

Plan-and-Execute(gogo-agent 的选择)

维度 纯 ReAct 纯规划 Plan-and-Execute
全局视角 无,走一步看一步 有,但不可变 有,且可动态修订
执行灵活性 低(机械执行) 高(子任务内仍是 ReAct)
长任务可靠性 差(容易遗漏步骤) 好(但无法应对意外) 好(计划+回退能力)
用户体验 黑盒(不知道做到哪) 可以展示进度 可以展示进度+动态更新
推理开销 每步都思考(可能浪费) 只规划时思考(执行不推理) 按阶段动态调整(最经济)
适合的任务类型 简单问答、信息查询 步骤完全确定的任务 复杂多步、需要回退修复的任务

Plan-and-Execute 取两者之长:先制定计划,再逐步执行每个子任务,但每个子任务的执行仍然是 ReAct 式的推理-行动循环。关键特性: - 计划是可变的——执行过程中可以修改、回退 - 每个子任务的执行保留推理能力——不是机械执行 - 计划状态显式持久化——Agent 永远知道"我做到哪了"

plan = LLM.plan(task)                    # 阶段一:规划(开启深度思考)
for subtask in plan:
    result = ReAct.execute(subtask)      # 阶段二:执行(关闭深度思考,快速调工具)
    if result.needs_revision:
        plan = LLM.revise(plan, result)  # 可以修订计划
summary = LLM.summarize(results)         # 阶段三:总结(重新开启深度思考)

gogo-agent 怎么实现的 Plan-and-Execute

PlanNotebook:计划的"记事本" gogo-agent 基于 AgentScope 框架的 PlanNotebook 组件实现。PlanNotebook 本质上是一个被注入到 Agent 工具列表里的状态机——LLM 通过调用特定工具来管理计划:

// ItineraryPlanAgent.java 核心代码
PlanNotebook planNotebook = PlanNotebook.builder()
        .needUserConfirm(false)   // 子任务推进无需用户确认
        .build();

// 注册变化钩子:计划状态变化时实时推送给前端
planNotebook.addChangeHook("plan_progress_notifier",
        (nb, plan) -> progressNotifierHook.sendPlanUpdate(capturedSessionId, plan));

// 把 PlanNotebook 挂载到 Agent
ReActAgent agent = ReActAgent.builder()
        .planNotebook(planNotebook)
        // ...
        .build();

PlanNotebook 向 LLM 暴露了这些工具: | 工具名 | 作用 | | --- | --- | | create_plan | 创建一份新计划(含多个子任务) | | update_subtask_state | 标记子任务为 TODO / IN_PROGRESS / DONE / ABANDONED | | finish_subtask | 完成当前子任务 | | revise_current_plan | 修订计划(增删改子任务) | | finish_plan | 标记整个计划完成 | | view_subtasks | 查看当前子任务列表和状态 |

这意味着 LLM 自己决定什么时候建计划、什么时候推进、什么时候修订——框架不强制,但提供了结构化的"记事本"让 LLM 管理自己的工作流。 一次典型执行的时序 结合系统 Prompt 的规则和代码,一次完整的行程规划执行流程如下:

ItineraryPlanAgent 收到任务:"规划上海到杭州出差"

[规划阶段 - 深度思考开启]
  LLM 调用 create_plan → 生成计划:
    1. 查询已审批差旅单获取约束条件
    2. 召回用户偏好 + 查询差旅政策
    3. 搜索去程/返程交通 + 酒店候选
    4. 对候选打偏好分并调用 plan_itinerary 生成方案
    5. 委托审核子智能体做五维审核
    6. 根据审核结果决定是否修复
    7. 输出最终方案

[执行阶段 - 深度思考关闭,快速工具调用]
  子任务1: update_subtask_state(1, IN_PROGRESS)
           → 调用 query_travel_order(status="APPROVED")
           → finish_subtask(1)

  子任务2: update_subtask_state(2, IN_PROGRESS)
           → 调用 retrieve_from_memory 召回偏好
           → 调用 query_travel_policy 查政策
           → finish_subtask(2)

  子任务3: update_subtask_state(3, IN_PROGRESS)
           → 调用 load_skill_through_path 加载搜索技能
           → 调用 execute_shell_command 搜机票
           → 调用 execute_shell_command 搜酒店
           → finish_subtask(3)

  子任务4: update_subtask_state(4, IN_PROGRESS)
           → LLM 对候选打偏好分(纯推理,无工具调用)
           → 调用 plan_itinerary(确定性计算引擎)
           → finish_subtask(4)

  子任务5: update_subtask_state(5, IN_PROGRESS)
           → 调用 itinerary_review_agent 子智能体
           → finish_subtask(5)

  子任务6: 审核发现方案2超预算
           → revise_current_plan 添加修复步骤
           → 重新调用 plan_itinerary + 审核
           → finish_subtask(6)

  子任务7: finish_plan → 输出 Markdown 方案

[总结阶段 - 深度思考重新开启]
  LLM 整合所有方案 + 审核结果 → 输出最终 Markdown

PlanAwareThinkingHook

这是 gogo-agent 最精巧的设计之一。核心逻辑只有一个判断:

public class PlanAwareThinkingHook implements Hook {

    private final PlanNotebook planNotebook;
    private final int thinkingBudget;  // 2048 tokens

    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        switch (event) {
            case PreReasoningEvent e -> handleReasoning(e);
            case PreSummaryEvent e -> enableThinking(e);  // 总结阶段开启
            default -> {}
        }
        return Mono.just(event);
    }

    private void handleReasoning(PreReasoningEvent event) {
        if (isPlanning()) {
            // 规划阶段:保持深度思考(默认行为,什么都不做)
        } else {
            // 执行阶段:关闭思考,快速执行
            disableThinking(event);
        }
    }

    // 判断逻辑极其简单:计划还没创建 = 还在规划阶段
    private boolean isPlanning() {
        return planNotebook.getCurrentPlan() == null;
    }
}

为什么要这样做? - 规划阶段需要深度思考:LLM 需要通盘考虑任务分解策略,这里的推理质量直接决定后续执行效果。开启 thinking(budget=2048)让模型"想清楚再动手"。 - 执行阶段不需要深度思考:每个子任务的动作相对明确(查个政策、搜个机票),此时思考是浪费——关闭 thinking 能节省 token 开销并加快响应速度。 - 总结阶段重新开启:最终要把多套方案、审核意见、修复历史整合成结构化 Markdown,需要综合推理能力。 这个设计的效果是:用同一个模型(qwen3.7-max),通过动态调整推理深度,在规划质量和执行效率之间取得平衡。

ItineraryPlannerTool:确定性计算引擎

这是另一个关键设计决策——把"客观数学"从 LLM 中剥离出来。

@Tool(name = "plan_itinerary",
      description = "往返行程规划客观计算引擎:输入候选交通/酒店 + 你(LLM)预先产出的偏好分,"
                  + "在内存里做去×住×返组合、过滤非法组合、算总价/总耗时并归一化、做 policy 软约束检查、"
                  + "按三维权重算综合分,选出 4 类代表方案...")

这个工具做了什么: 1. 切分去/返程 — 按 direction 字段或出发城市或日期判断 2. 笛卡尔积组合 — 去程 × 酒店 × 返程,生成所有可能组合 3. 过滤非法组合 — 返程出发时间早于去程到达时间的剔除 4. Min-Max 归一化 — 时间和价格映射到 [0, 100] 分数 5. Policy 软约束检查 — 酒店超标、舱位违规等,扣分但不剔除 6. 合成综合分 — 按固定权重(时间30% + 价格20% + 偏好50%)加权 7. 选4个代表方案 — 综合最佳、时间最短、价格最低、最符合偏好 为什么不让 LLM 直接做这些计算? - 精度:5条去程 × 3间酒店 × 4条返程 = 60个组合,每个要算6个维度。LLM 做 60 次浮点运算必然出错。 - 确定性:相同输入必须产出相同排序。LLM 有随机性,工具没有。 - 效率:LLM 推理 60 个组合可能要 3000+ tokens;Java 代码在毫秒级完成。 - 可审计:结果存入 Redis(planner:result:{userId},TTL 24h),审核子智能体可以独立读取验证。 但偏好打分仍由 LLM 负责(scores 参数),因为"用户说'我喜欢早班直飞'到底给这条航班打几分"是主观判断——这正是 LLM 的长项。 这就是 Plan-and-Execute 的分工哲学:LLM 负责规划和判断,工具负责计算和执行。

审核-修复闭环:计划的动态修订

Plan-and-Execute 的真正威力在于计划可以被修订。当审核子智能体发现方案超预算:

ItineraryReviewAgent 返回审核结果:
  - 方案2 ❌ 未通过:总价超预算,去程公务舱超政策标准

ItineraryPlanAgent 判断需要修复:
  → 调用 revise_current_plan:在计划中插入"修复步骤"
  → 从 candidates 中剔除违规选项
  → 重新打分
  → 重新调用 plan_itinerary
  → 重新审核
  → 最多2轮修复

如果是纯 ReAct,Agent 此时可能已经"忘了"前面做了什么——因为它没有显式的任务状态。如果是纯规划,它根本没有"修复"这条路径,因为计划是提前固定的。

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。