gogo-agent

行程规划智能体的功能介绍及开发

ItineraryPlanAgent = "搜+算+审+修"闭环的行程规划专家——它从审批单或对话中获取需求,调 CLI/MCP 搜索候选交通酒店,让 LLM 对候选打偏好分后交给确定性计算引擎做组合排序,存 Redis 后委托审核…

TL;DR

ItineraryPlanAgent = "搜+算+审+修"闭环的行程规划专家——它从审批单或对话中获取需求,调 CLI/MCP 搜索候选交通酒店,让 LLM 对候选打偏好分后交给确定性计算引擎做组合排序,存 Redis 后委托审核…

ItineraryPlanAgent = "搜+算+审+修"闭环的行程规划专家——它从审批单或对话中获取需求,调 CLI/MCP 搜索候选交通酒店,让 LLM 对候选打偏好分后交给确定性计算引擎做组合排序,存 Redis 后委托审核子智能体五维检查,若有问题自动修复并重跑,最终给用户一份结构化可对比的方案。 它不做预订——预订由 BookingAgent 接手;它只管"出方案",并保证方案客观合规。

用户 ──→ MasterAgent(路由/意图识别)
             │
             ├── ItineraryPlanAgent ←── 本文主角(搜索规划审核)
             ├── ItineraryManageAgent(差旅单/冲突检测)
             └── BookingAgent(预订/退改)

MasterAgent 识别到用户意图为"规划行程"时,将任务路由到 ItineraryPlanAgent。该 agent 独立完成搜索 → 组合 → 审核 → 修复闭环,最终回传方案 Markdown 让 Master 呈现给用户。

核心能力概览

能力 实现方式 价值
多源候选搜索 tuniu-cli Skill + weather/visa MCP 一次拿齐机票/火车/酒店候选
LLM 主观偏好评分 Agent 在调工具前手动打分 → scores JSON 把不可量化偏好变成可计算数值
客观组合排序 plan_itinerary 工具(纯数学引擎) 时间/价格/政策客观归一化,3 维加权排序
五维结构化审核 itinerary_review_agent 子智能体 完整性/时间/出发地/预算/路线自动审查
审后修复闭环 最多 2 轮重跑 plan_itinerary 去除违规候选、自动收敛到合规方案
Plan-and-Execute + PlanNotebook 框架级支持 降低 Token 浪费,带前端进度推送
长期记忆偏好召回 LongTermMemory + retrieve/record 跨会话复用用户差旅偏好

不管什么场景,ItineraryPlanAgent 干的事本质上是这条流水线,只是不同场景会跳过或重复其中某些步:

① 拿需求   → 查审批单 / 读对话 / 追问用户
② 拿约束   → 召回偏好(长期记忆) + 查差旅政策
③ 做计划   → create_plan(写进 PlanNotebook)
④ 搜候选   → 加载 tuniu-cli → 搜去程/返程/酒店
⑤ 打偏好分 → LLM 对每条候选打 0-100 分
⑥ 组合排序 → plan_itinerary(客观计算引擎,存 Redis)
⑦ 审核     → itinerary_review_agent(五维检查)
⑧ 判修复   → 有 FAIL/WARNING 就剔除违规候选,回到 ⑥(最多 2 轮)
⑨ 出方案   → 倒金字塔 Markdown,停下等用户确认

三个角色分工要记牢: - LLM(Agent 本体):负责主观——理解需求、判断偏好、打分、组织文案。 - plan_itinerary 工具:负责客观——组合、算钱、算时间、归一化、按权重排序。绝不解读偏好文本。 - 审核子 agent:负责把关——五维检查每套方案是否合规。

User Case

Agent 构建代码全解

// ItineraryPlanAgent.java —— @Configuration + @Scope("prototype")
// 关键模型与参数
private static final int MAX_ITERATIONS = 15;

@Bean(name = "itineraryPlanAgent")
@Scope("prototype")   // 每次对话新建一个实例,避免状态串扰
public ReActAgent build() {

    // ① 工具注册——并行执行开启
    Toolkit toolkit = new Toolkit(ToolkitConfig.builder()
            .parallel(true)  // 允许同一轮推理中并发调多工具
            .build());
    toolkit.registration().tool(policyTools).apply();           // 差旅政策查询
    toolkit.registration().tool(travelOrderReadTools).apply();  // 已审批差旅单
    toolkit.registration().tool(bookingReadTools).apply();      // 外部预订记录
    toolkit.registration().tool(queryUserInfoTools).apply();    // 用户信息+base城市
    toolkit.registration().tool(apiKeyTools).apply();           // 第三方 API Key
    toolkit.registration().tool(destinationLiveTools).apply();  // 天气+目的地新闻
    toolkit.registration().tool(itineraryPlannerTool).apply();  // 核心组合排序引擎
    toolkit.registration()
        .mcpClient(weatherMcpClient)
        .enableTools(weatherMcpEnabledTools).apply();           // 天气 MCP
    toolkit.registration()
        .mcpClient(oriznVisaMcpClient)
        .enableTools(oriznMcpEnabledTools).apply();             // 签证 MCP

    // ② 子智能体作为工具——委托审核
    toolkit.registration()
        .subAgent((SubAgentProvider<ReActAgent>) () ->
                applicationContext.getBean("itineraryReviewAgent", ReActAgent.class),
            SubAgentConfig.builder()
                .toolName("itinerary_review_agent")
                .description("委托行程审核子智能体对行程方案进行五维结构化审核")
                .forwardEvents(false)  // 不把子 agent 事件冒泡到父
                .build())
        .apply();

    // ③ Skill(tuniu-cli 搜索)
    SkillBox skillBox = buildPlanningSkillBox(toolkit);

    // ④ PlanNotebook —— Plan-and-Execute 核心组件
    PlanNotebook planNotebook = PlanNotebook.builder()
            .needUserConfirm(false)  // 自动执行,无需等用户逐步确认
            .build();
    // 注册变化钩子:步骤状态变更时实时推送前端
    if (capturedSessionId != null) {
        planNotebook.addChangeHook("plan_progress_notifier",
            (nb, plan) -> progressNotifierHook.sendPlanUpdate(capturedSessionId, plan));
    }

    // ⑤ AutoContextMemory —— 超长对话自动压缩
    AutoContextMemory memory = AgentMemoryFactory.create(stableModel);

    // ⑥ 组装 ReActAgent
    ReActAgent agent = ReActAgent.builder()
            .name("ItineraryPlanAgent")
            .model(strongModelWithThinking)            // qwen3.7-max + enableThinking
            .memory(memory)
            .longTermMemory(longTermMemory)            // 跨会话偏好记忆
            .longTermMemoryMode(LongTermMemoryMode.AGENT_CONTROL) // Agent 自主决定何时读写
            .toolkit(toolkit)
            .toolExecutionContext(createToolCtx())     // 注入 sessionCtx(userId 等透明参数)
            .skillBox(skillBox)
            .hooks(List.of(
                dynamicTimeInjectionHook,              // 优先级 1:稳定前缀 + 动态时间
                new AutoContextHook(),                 // 上下文超长时自动压缩
                new PlanAwareThinkingHook(planNotebook, 2048), // 按阶段开关深度思考
                cliResultCompressHook,                 // CLI 结果去重压缩
                flightApiKeyHook,                      // 航班 API Key 注入
                tuniuApiKeyHook,                       // 途牛 API Key 注入
                rghUserIsolationHook,                  // 用户隔离
                skillContentCollapseHook,              // Skill 内容折叠
                sessionPersistenceHook,                // 会话持久化
                executionLoggerHook,                   // 执行日志
                toolCircuitBreakerHook,                // 工具熔断器
                progressNotifierHook,                  // 前端进度推送
                activeAgentPersistenceHook,            // 活跃 agent 记录
                new PendingToolRecoveryHook()          // 断点续跑恢复
            ))
            .planNotebook(planNotebook)
            .toolExecutionConfig(getToolConfig())       // 工具超时 1min/重试 3 次
            .sysPrompt(PromptLoader.loadStatic("itinerary-plan-agent-system.md"))
            .maxIters(MAX_ITERATIONS)                   // 最多 15 轮 ReAct
            .generateOptions(getEnableThinkingGenerateOptions(2048)) // 缓存 + 思考预算
            .build();

    return agent;
}

为什么 MAX_ITERATIONS = 15?

行程规划是最复杂的任务,一次完整闭环的典型调用序列:

查审批单 → 查偏好(LTM) → 查政策 → 加载 Skill → 搜交通 → 搜酒店
→ 打分 → plan_itinerary → itinerary_review_agent → 判断修复
→ [可选: 重跑 plan_itinerary → 重审核] → 输出 Markdown

即使不修复也需 ~10 轮工具调用,留 15 轮可覆盖 2 次修复。

为什么 parallel(true)?

查审批单、查偏好、查政策之间无数据依赖,LLM 一次推理同时产出多个 tool_call 后可并发执行。Agent 搜去程和搜酒店也可以在同一轮并发,显著缩短端到端延时。

Plan-and-Execute

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

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

核心工具链详解

plan_itinerary——客观组合排序引擎

✅为什么行程方案设计不用Skill要用工具?

整个行程规划流程分为两个阶段,恰好对应使用了2种能力: 数据收集天然适合 Skill:调用外部 API 本质就是按照文档拼接命令参数,LLM 擅长理解"搜索上海到杭州的航班"然后映射到正确的 CLI 命令。 但方案计算为什么不能也用 Sk LLMentor

✅行程方案设计工具设计细节

plan_itinerary,是行程规划的"计算大脑",承担了最核心的数学逻辑。 设计哲学 LLM 负责"主观"(偏好判断、打分) 工具负责"客观"(组合、归一化、排序、存储) 这种 LLM/Tool 职责分离是关键设计决策:如果让 LL LLMentor

为什么偏好权重 0.5 最高?

差旅场景中,用户偏好(靠窗/直飞/早班/免费退改)对满意度的影响往往大于几十块钱的差价或半小时的时间差,差旅场景对语言价格并不算敏感,只要在预算范围内即可。 0.5 的权重确保偏好强匹配的方案不会被纯价格/时间压下去,同时 0.3 + 0.2 保留了客观指标的话语权。

itinerary_review_agent——五维结构化审核

审核不是由一个工具完成的,而是委托给一个完整的子智能体:

✅为什么行程审核不用Skill要用子智能体?

我们的项目中,在行程规划后,我们需要对行程进行审核,检查时间是否正确,目的地是否正确, 是否符合差旅政策等等。 这个审核的节点,有很多实现方式,比如直接写在行程规划的智能体的prompt中,比如做成一个skill让行程规划智能体执行,比如做 LLMentor

Redis 跨工具中转

✅ReviewAgent如何获取到PlanAgent的规划结果?

ReviewAgent和PlanAgent之间需要传递方案,但是我们不是通过参数传递,而是通过 Redis 中转。PlanAgent 把规划结果写进 Redis,ReviewAgent 从同一个 key 读出来。(如果不考虑集群部署,可以考 LLMentor

系统提示词设计要点

系统提示词(itinerary-plan-agent-system.md)共 177 行,核心设计理念:

信息获取优先级

APPROVED 差旅单(最权威)> 当前对话上下文 > 追问用户 Agent 拿到用户请求后,第一件事是查有没有已审批的差旅单。有则直接用,省去多轮确认。

闭环流程强制约束

提示词用"必走/不可跳过/严禁"等强约束词确保流程不被 LLM 跳过:

多目标方案 + 审核 + 修复闭环(必走,不可跳过)
严禁跳过工具、直接在文案里虚构"修复版"方案
方案输出后必须停止,等待用户确认(最高优先级约束)

这些约束解决了 LLM 最常犯的三类错误:跳过审核直接输出、编造修复方案、自行替用户决策。

Skill 懒加载规则

搜索类 skill(如 tuniu-cli)必须在本步骤开始时才加载,
严禁在最初查审批单/常驻地/偏好/政策那一批调用里提前加载。
原因:技能正文加载后若长时间不用会被上下文折叠,
等到真正搜索时说明已丢失。

这是实际运行中发现的坑——过早加载 Skill 内容会被 AutoContextMemory 压缩折叠,等真正需要时 LLM 已"忘记"用法。

时间处理规则

详尽覆盖了"明天/后天/下周X/本周末/月底/下个月X号"等所有相对时间表述的转换规则,避免 LLM 推算星期出错。配合 DynamicTimeInjectionHook 注入的实时时间使用。

输出格式强约束

🏆 推荐方案(置顶,80% 用户只看这里) → 📋 方案对比表 → 🔍 方案明细 → 🧭 Agent 为你做了什么(评估足迹) → 👉 下一步 禁止暴露内部工具名、函数名、Redis key 等技术细节——面向用户的输出必须全中文自然语言。

长期记忆与偏好系统

我们给PlanAgent提供了长期记忆的工具:

.longTermMemory(longTermMemory)
.longTermMemoryMode(LongTermMemoryMode.AGENT_CONTROL)

AGENT_CONTROL 模式意味着 Agent 自主决定何时读写长期记忆,框架不自动触发。系统提示词定义了具体触发条件: 何时记录:用户说"以后都选高铁一等座""只住华住"等偏好表达时,调 record_to_memory 保存。 何时检索:规划前主动调 retrieve_from_memory 召回历史偏好,作为 plan_itinerary 的 preferences 参数和 LLM 打分依据。 何时不操作:闲聊、一次性事实查询、不确定是否为偏好的表述。 这确保了跨会话的偏好积累——用户说一次"我不坐红眼航班",以后每次规划都会自动避开。

Hook 链与执行顺序

ItineraryPlanAgent 注册了 14 个 Hook,按优先级排列: | 优先级 | Hook | 作用 | | --- | --- | --- | | 1 | DynamicTimeInjectionHook | 注入当前时间(保持 system prompt 前缀稳定以利用缓存) | | — | AutoContextHook | 上下文超 128K 时自动压缩旧消息 | | — | PlanAwareThinkingHook | 按 Plan/Execute/Summary 阶段切换 thinking | | 4 | CliResultCompressHook | CLI 返回结果去重+删冗余字段 | | — | FlightApiKeyHook | 航班 API Key 动态注入 | | — | TuniuApiKeyHook | 途牛 API Key 动态注入 | | — | RghUserIsolationHook | 用户隔离 | | — | SkillContentCollapseHook | 长时间未用的 Skill 内容折叠 | | — | SessionPersistenceHook | 会话状态持久化 | | — | ExecutionLoggerHook | 执行日志记录 | | 6 | ToolCircuitBreakerHook | Redis + Lua 熔断器 | | — | ProgressNotifierHook | WebSocket 前端进度推送 | | — | ActiveAgentPersistenceHook | 活跃 agent 记录 | | — | PendingToolRecoveryHook | 断点续跑恢复 |

其中 PlanAwareThinkingHook 和 ProgressNotifierHook 是行程规划独有的(行程管理 agent 没有),体现了规划任务的两个特殊需求:长推理任务需要降本、用户需要看到进度。

版本提示

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

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

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