plan_itinerary,是行程规划的"计算大脑",承担了最核心的数学逻辑。
✅为什么行程方案设计不用Skill要用工具?
整个行程规划流程分为两个阶段,恰好对应使用了2种能力: 数据收集天然适合 Skill:调用外部 API 本质就是按照文档拼接命令参数,LLM 擅长理解"搜索上海到杭州的航班"然后映射到正确的 CLI 命令。 但方案计算为什么不能也用 Sk LLMentor
设计哲学
LLM 负责"主观"(偏好判断、打分) 工具负责"客观"(组合、归一化、排序、存储) 这种 LLM/Tool 职责分离是关键设计决策:如果让 LLM 自己做组合排序,它无法保证数值正确性(容易算错价格、遗漏组合);如果让工具理解偏好文本,又需要额外 NLP 或嵌入模型。
参数设计
@Tool(name = "plan_itinerary",
description = "往返行程规划客观计算引擎:输入候选交通/酒店 + 你(LLM)预先产出的偏好分,"
+ "在内存里做去×住×返组合、过滤非法组合、算总价/总耗时并归一化、做 policy 软约束检查、"
+ "按三维权重算综合分,选出 4 类代表方案,将结果存入 Redis 并返回摘要。"
+ "本工具只做客观数学,绝不解读偏好文本;偏好判断必须由你在 scores 参数里给出。")
public String planItinerary(
@ToolParam(required = true) String origin, // 出发城市
@ToolParam(required = true) String destination, // 目的城市
@ToolParam(required = true) String departure_date, // 去程日期 YYYY-MM-DD
@ToolParam(required = true) String return_date, // 返程日期 YYYY-MM-DD
@ToolParam(required = false) String preferences, // 用户偏好自由文本(来自 LTM)
@ToolParam(required = true) String candidates, // 候选 JSON
@ToolParam(required = true) String scores, // LLM 打的偏好分 JSON
@ToolParam(required = false) String policy, // 差旅政策 JSON
AgentSessionContext sessionCtx // 透明注入,LLM 不感知
) { ... }
注意 sessionCtx 通过 ToolExecutionContext 自动注入,无 @ToolParam 注解——LLM 不需要知道它的存在。
计算流水线

核心计算代码
// 组合过滤:返程不能早于去程到达
private JSONObject tryBuildCombo(JSONObject outbound, JSONObject hotel, JSONObject inbound) {
LocalDateTime outArr = parseDt(outbound, "arrival_time");
LocalDateTime inDep = parseDt(inbound, "departure_time");
if (inDep.isBefore(outArr)) return null; // 非法组合,直接丢弃
int totalTransit = durationMinutes(outbound) + durationMinutes(inbound);
int nights = (int) ChronoUnit.DAYS.between(
LocalDate.parse(departure_date), LocalDate.parse(return_date));
double totalPrice = getDbl(outbound,"price") + getDbl(inbound,"price")
+ getDbl(hotel,"price_per_night") * nights;
// checkPolicy: 软约束打分,不删除组合
PolicyResult pr = checkPolicy(totalPrice, hotel, outbound, inbound, policy);
// ... assemble combo JSON ...
}
// Min-Max 归一化(越低越好 → higherIsBetter=false)
private double minmaxScore(double value, double min, double max, boolean higherIsBetter) {
if (max == min) return 50.0; // 所有候选相同时给中性分
double normalized = (value - min) / (max - min);
return higherIsBetter ? normalized * 100 : (1 - normalized) * 100;
}
// 三维加权综合分
double overall = round2(time_score * 0.3 + price_score * 0.2 + preference_score * 0.5);