氢能站Agent系列3 -- 从对话到调度方案
AI 助手之前只会"查数据",这次让它"给建议":用户在对话框说
安排一辆车明天上午给增城新塘站送 500kg 氢
AI 回出一套方案——目的站在哪、哪几辆车可用、司机是谁、配哪个氢罐。
整个过程分四步。
第一步:模型自己决定"这是调度请求"
Agent 用 DeepSeek function calling 做路由:每次对话,把历史消息和工具清单一起发给模型,让模型判断该调工具还是直接闲聊。工具清单现在有两个:
| 工具 | 用途 |
|---|---|
query_hydrogen_business_data | NL→SQL 查业务数据 |
plan_hydrogen_dispatch | 调度规划(新增) |
路由的"规则"就写在工具描述里:
description: "为氢气运输调度生成派车建议:根据目的加氢站、补氢量,\
查询当前可用的运氢车头与可配氢罐,按距离排序给出推荐。\
仅当用户明确表达「派车/调度/安排车辆送氢到某站」类意图时调用;\
单纯查询数据(如查车辆列表、查调度单)不要调用。",
最后那句反例很关键——没有它,"查一下最近的调度单"也会被路由进规划分支。工具描述就是路由规则,正例反例都要写。
模型命中工具后,还会顺手把口语参数提取成结构化字段:
{
"destination": "增城新塘", // 站名简称,原话照搬
"amount_kg": 500,
"expected_time": "明天上午"
}
第二步:三条 SQL 生成方案
拿到参数,执行分支在一个数据库事务里跑三步:
① 目的站定位 —— 名称模糊匹配,"增城新塘" → 广州增城新塘氢能站(拿到坐标)
② 候选车排序 —— approved 状态、且没有在途调度单占用的运氢车头,按到目的站的距离排前 3。距离不调地图 API,直接在 SQL 里用 haversine 公式算直线距离 ×1.25 近似道路里程(这是项目后端的既有约定,60 km/h 折算耗时):
round(((6371 * acos(LEAST(1.0,
cos(radians($lat)) * cos(radians(t.current_lat))
* cos(radians(t.current_lng) - radians($lng))
+ sin(radians($lat)) * sin(radians(t.current_lat))
))) * 1.25)::numeric, 1) AS road_km_est
彩蛋:60 km/h 时速下,公里数在数值上就等于分钟数,eta_min 不用再算。
车辆没上报实时位置怎么办?不剔除——距离记 NULL 排在有位置的车后面(ORDER BY road_km_est ASC NULLS LAST),让 AI 如实告诉用户"该车暂无位置,无法估算"。过滤条件太狠,会把"数据没准备好"放大成"功能不可用"。
③ 氢罐匹配 —— idle 状态、额定容量 ≥ 500kg 的罐,按容量从小到大取 3 个:够用就行,经济优先。
第三步:结果回填,模型组稿
三步查询的结果打包成一个 JSON,以 tool 角色消息回填进对话,再发起一次流式请求让模型生成最终回答:
{
"station": { "station_name": "广州增城新塘氢能站", "address": "...", ... },
"candidates": [ { "plate": "津CH2003", "driver_name": "张卫东",
"road_km_est": null, "eta_min": null }, ... ],
"idle_tanks": [ { "tank_no": "HT-2024-011", "rated_capacity": 500 }, ... ],
"estimate_note": "road_km_est 为直线×1.25 估算,60km/h 折算;null 表示该车暂未上报位置"
}
模型拿到这份"原材料",输出的是结构化建议:候选司机表格、氢罐两档对比、操作建议,并主动标注"司机均未上报实时位置"。数据局限写进 estimate_note 喂给模型,它就不会装懂。
原理小结:Agent 只想,不动手
用户一句话
│ function calling(模型判断意图 + 提取参数)
▼
plan_hydrogen_dispatch
│ 一个事务三条 SQL:站点定位 → 候选车排序 → 氢罐匹配
▼
JSON 结果回填对话
│ 第二次流式请求
▼
结构化派车建议(流式输出)
关键设计:这个工具是纯只读的。Agent 连库用的是没有写权限的角色,"采纳建议、创建调度单"留给下一步——前端在对话流里渲染建议卡片,用户点「采纳」后由前端带 token 调主后端落单。
模型负责想,人负责按按钮,后端负责把关。
下一步:采纳落单——AI 的建议在对话流里渲染成卡片,用户点「采纳」后由前端带 token 调主后端创建调度单;同时接入高德路径规划,把估算距离换成实测里程。