ItineraryManageAgent 是差旅单(Travel Order)的「全生命周期管家」——从用户口头表达出差意图到收集信息、冲突检测、提交审批、查询进度、修改计划、取消申请、联动取消关联预订,全部由它对话式驱动完成。它不做行程规划、不做预订下单,只管「差旅单本身的 CRUD + 审批流」这一块。 在 MasterAgent 的路由体系里,用户说「我要出差」「提交差旅申请」「查一下我的审批」「取消上次的出差」等意图会被路由到它。
在多智能体体系中的位置
用户输入
↓
QueryRewriting + IntentRecognition
↓
MasterAgent(路由)
├→ ItineraryManageAgent ← 本文主角(差旅单 CRUD + 审批)
├→ ItineraryPlanAgent (行程方案规划)
├→ BookingAgent (预订执行)
└→ InfoAgent (目的地信息问答)
它与其他 Agent 的边界非常清晰: - 与 ItineraryPlanAgent 的区别:它管的是「差旅单审批流」(行政侧),不管「机票酒店怎么选」(业务侧)。提交审批成功后,它会主动问用户「是否需要为您规划行程」——引导流转到规划 Agent。 - 与 BookingAgent 的区别:它只管 cancel_booking(取消已有预订),不管 saveOrder(新下单)。取消预订只是差旅单取消/修改后的联动操作。
Agent 装配配置
- 用 strongModel(关闭思考)而非 strongModelWithThinking:差旅单管理是结构化 CRUD,流程明确无歧义,靠详尽的程序性提示词即可;不需要全局权衡或复杂推理。
- maxIters=10:典型流程(收集信息→冲突检测→确认→提交)不超过 6~8 轮,10 轮足够且防止跑飞。
- 注册了 7 个工具类:覆盖差旅单读写、审批查询、政策查询、冲突检测、预订查询、预订取消、用户信息查询。工具全面但每个都聚焦单一职责。
差旅单的四种操作
系统提示词把功能分为四个板块,每个板块对应明确的工具调用链: | 操作 | 核心工具调用链 | 前置检查 | 用户确认点 | | --- | --- | --- | --- | | 提交新申请 | query_user_base_location | 冲突检测 | 信息确认 + 冲突确认(HIGH) | | 查询行程/审批 | query_travel_order | 无 | 无 | | 取消申请 | query_travel_order | 状态校验 | 不可恢复确认 + APPROVED 二次确认 | | 修改申请 | query_travel_order | 冲突检测 + 状态校验 | 变更确认 + APPROVED 二次确认 |
工具集
差旅单写工具(TravelOrderWriteTools) 这是最核心的工具类,包含四个 @Tool: submit_travel_approval——一次性创建差旅单 + 审批单并双向关联:
@Tool(name = "submit_travel_approval",
description = "一次性创建差旅申请单和审批单,并将两者双向关联...")
public String submitTravelApproval(AgentSessionContext sessionCtx,
@ToolParam(name = "destination") String destination,
@ToolParam(name = "departure_city") String departureCity,
@ToolParam(name = "departure_date") String departureDate, // 必须 YYYY-MM-DD
@ToolParam(name = "return_date") String returnDate,
@ToolParam(name = "purpose") String purpose) {
// ⓪ 幂等校验:相同用户 + 出行要素若已存在生效中差旅单 → 直接返回已有单
TravelOrder duplicate = findDuplicateActiveOrder(userId, destination, ...);
if (duplicate != null) { return buildExistingSubmitResult(duplicate); }
// ① 创建差旅单(DRAFT)
// ② 构建审批表单 → 提交审批单(PENDING)
// ③ 差旅单写入审批单ID,状态升级为 SUBMITTED
// ④ 查库验证写入成功
}
关键设计:幂等校验(防重复提交)+ 双向关联(差旅单记审批ID,审批单记差旅单ID)+ 写后验证(查库确认状态正确落库)。 cancel_travel_order——取消差旅单 + 同步撤销审批 + 返回关联预订:
// APPROVED 状态需 force=true 二次确认才能取消
if (TravelOrderStatus.APPROVED == currentStatus && !Boolean.TRUE.equals(force)) {
return new ConfirmRequiredResult(false, true, orderId, currentStatus.getCode(),
"该差旅单已审批通过,取消后不可恢复且可能影响已预订行程...");
}
// 取消差旅单 + 撤销审批 + 查询关联预订
List<BookingSummary> associatedBookings = queryAssociatedBookings(userId, orderId);
modify_travel_order——修改字段 + 撤销旧审批 + 重新发起新审批 + 返回关联预订:
// 幂等/空操作短路:所有字段与当前值一致 → 跳过撤审重提
boolean noChange = (isBlank(destination) || equalsSafe(destination, order.getDestination())) && ...;
if (noChange) { return "提交的信息与当前差旅单一致,未做任何修改。"; }
// 撤旧审批 → 更新字段 → 提新审批
query_approval_status——按实例ID或按用户查最近审批:
// 未传实例ID → 自动查用户最近一条审批单
if (isBlank(processInstanceId)) {
recordOpt = approvalService.findLatestByUserId(userId);
}
行程冲突检测工具(TravelOrderConflictTools) 这是提交和修改前的必调前置工具,覆盖两大类冲突:
@Tool(name = "check_travel_order_conflicts",
description = "检查用户当前要提交/修改的差旅单是否与已有生效中差旅单存在冲突...")
public String checkTravelOrderConflicts(
AgentSessionContext sessionCtx,
String departureCity, String destination,
String departureDate, String returnDate,
@ToolParam(required = false) String excludeOrderId) // 修改时排除自身
冲突类型与处理规则: | 冲突类型 | 严重等级 | 场景 | prompt 要求 | | --- | --- | --- | --- | | 时间重叠 + 同城 | LOW | 同城市重复提交 | 一句话提示 | | 时间重叠 + 跨城 | HIGH | 物理上不可能同时在两地 | 必须用户明确确认才能继续 | | 同日衔接时间不足 | MEDIUM/HIGH | 当天要跨城但通勤不够 | 告知建议,用户选择 | | 次日路径断裂 | MEDIUM | 前一天在 A 城、第二天从 B 城出发 | 告知建议,用户选择 |
冲突检测依赖 CityTransitTimeService 估算城际通勤时间(优先查显式配置,其次按一线/新一线/二线分层推断,兜底 4 小时):
// 城市分层推断
TIER1_MINUTES = 270; // 一线/新一线互达 4.5h
TIER2_MINUTES = 300; // 含二线 5h
TIER_OTHER_MINUTES = 360; // 含未知城市 6h
其他工具 - query_travel_order(TravelOrderReadTools):按 ID 精确查询或按 status/日期范围过滤列表。支持 MyBatis-Plus LambdaQueryWrapper 组合条件。 - query_booking_record(BookingReadTools):查外部预订记录(机票/酒店/火车票),支持按 bookingId、travelOrderId、bizType、status 多维过滤。 - cancel_booking(BookingWriteTools):取消预订,机票/火车票调 tuniu-cli 平台接口,酒店仅标记内部状态。幂等设计(已取消的重复调用直接返回成功)。 - query_travel_policy / check_travel_policy(PolicyTools):差旅政策查询/合规校验,带会话级按城市分桶缓存。 - query_user_contact_info / query_user_base_location(QueryUserInfoTools):用户信息查询,带会话级缓存。
系统提示词的核心设计模式
itinerary-manage-agent-system.md (一)时间处理规则——把相对时间解析逻辑完整写进 prompt
当前日期:{{current_date}}({{current_weekday}})。
"明天"= 当前日期 + 1 天。
"后天"= 当前日期 + 2 天。
"下周X"= 本周日之后的下一个星期 X;若今天已是星期 X,则 7 天后。
"月底"= 本月最后一天。
用户仅说"X 号"而未说月份 → 默认本月;若该日期已过则下月。
这块配合 DynamicTimeInjectionHook 工作——Hook 在每轮推理前注入当前准确时间(作为第二条 system message,不破坏第一条的缓存前缀稳定性),而 prompt 里的规则教模型如何基于注入的时间做相对日期转换。
✅上下文工程——优化系统提示词利用KV缓存
我们在前面讲Manus的上下文工程实践的时候,讲过KV Cache 大模型是自回归的:生成第 N 个 token 时,要「看到」前面所有 token。Transformer 的注意力机制里,每个历史 token 都会被算成一对 Key / LLMentor (二)冲突处理的分级决策树——替代「自己判断」
工具返回 has_conflict=true 时,按严重等级处理:
- HIGH:必须先向用户展示冲突明细并征得用户明确同意后才能提交。
- MEDIUM:告知用户冲突与建议,让用户选择。
- LOW:一句话提示即可,不需要额外确认。
这段把「模型该怎么根据冲突等级做决策」完全枚举化了——模型不需要"想"该不该阻断,只需要看等级做对应动作。 (三)输出约束——禁止暴露内部名称
所有面向用户的回复绝不允许出现工具函数名、字段名、子智能体名、文件路径
(如 query_travel_order、check_travel_order_conflicts 等 snake_case 名称);
统一使用中文自然语。
这条约束在多智能体系统里很重要:用户不应该知道幕后有几个 Agent、用了什么工具名。
如何定义一个好的工具
✅如果定义一个好的工具
工具是 LLM 与真实世界之间的合约。好的工具 = 好的合约。Agent中的工具的好坏和原来我们开发一个接口的好坏的评判标准可差别太大了 我认为,一个好的工具,应该是:命名精确(LLM 一看就知道什么时候该调)、参数明确(LLM 不需要猜格 LLMentor