AI Agent技能开发实战:构建香港巴士实时查询的智能助手
1. 项目概述:为AI助手装上香港巴士的“千里眼”
如果你在香港生活或旅行,等巴士绝对是一门“玄学”。站牌上的时间表仅供参考,手机里装了好几个巴士App,查起来步骤繁琐,信息还经常不同步。更别提当你正忙着跟朋友聊天,或者双手提着东西时,只想动动嘴就问出“下一班1A几时到中间道?”。这正是 hk-bus-eta-skill 这个项目要解决的痛点:它不是一个给普通人用的App,而是一个专门为AI助手(比如OpenClaw这类AI Agent)打造的“技能包”。
简单来说,这个技能让AI助手瞬间拥有了查询香港三大巴士公司(九巴KMB、城巴CTB、龙运LWB)实时到站时间的能力。你不再需要打开特定App、输入路线、选择车站。你只需要像跟朋友说话一样,用粤语、英语或混合语言问你的AI助手,它就能在几秒内给你一个清晰、准确的答案。这背后,是作者Tom FONG(以及他的AI助手Mr. Usagi)将开放的政府数据、本地化数据库和智能匹配算法,封装成一个即插即用的AI工具模块。
这个技能的核心价值在于它的 “AI原生”设计 。它不是为了网页或移动端交互而构建,其接口、数据处理逻辑和错误处理,都优先考虑了如何被另一个AI程序高效、稳定地调用。对于开发者而言,这意味着你可以轻松地将香港巴士的实时交通信息能力,集成到你自己的聊天机器人、语音助手或任何自动化工作流中,而无需从零开始研究复杂的API和数据处理。对于终端用户来说,这意味着获取信息的方式变得无比自然和便捷。
2. 核心设计思路:如何让AI“听懂”并“查准”巴士信息?
要让AI准确回答巴士ETA问题,远不止调用一个API那么简单。它需要解决几个关键难题:用户表达模糊(“我在尖沙咀”对应几十个车站)、数据源分散(不同巴士公司API不同)、响应速度要求高。 hk-bus-eta-skill 的架构正是围绕这些挑战设计的。
2.1 双引擎驱动:本地缓存与实时API的权衡
项目采用了一种经典的“缓存优先,实时补充”策略,这是保证速度和可靠性的基石。
- 本地SQLite缓存引擎 :这是整个系统的“记忆库”。首次安装后,它会从香港政府数据平台(DATA.GOV.HK)一次性下载全港所有巴士站点的静态数据(约20MB),包括站名、坐标、所属路线等,并建立本地数据库。这么做的核心目的是实现 “离线”位置匹配 。当用户说“尖沙咀”时,AI不需要联网去查询“尖沙咀有哪些站”,而是瞬间在本地数据库里进行模糊搜索和地理聚类,找出所有相关站点。这解决了API调用中的第一个延迟和不确定性。
- 并行实时API引擎 :这是系统的“感知器官”。一旦通过本地缓存确定了用户查询的具体路线和车站ID,系统会同时向九巴和城巴的官方ETA数据接口发起请求。这里用到了
ThreadPoolExecutor实现并行处理,而不是串行等待一个公司返回后再查另一个。对于涉及跨公司联营路线(比如一些过海隧道巴士)的查询,这种并行化能将响应时间缩短近一半。
设计心得 :这种架构分离了“找地点”和“查时间”两个任务。“找地点”依赖高精度、更新不频繁的静态数据,适合本地缓存;“查时间”依赖秒级变化的动态数据,必须实时获取。两者结合,既实现了快速响应,又保证了信息的实时性。
2.2 智能位置匹配:从模糊口语到精确坐标
这是项目中技术含量最高、也最体现“智能”的部分。用户不会说“请查询路线1A在巴士站编号‘NA29-5-165-0’的到站时间”。他们会说“下一班1A几时到中间道?”甚至“我在海港城,点样搭车去铜锣湾?”。
技能通过以下三层匹配来解决这个问题:
- 模糊文本匹配 :对用户输入的站名或区域名进行模糊搜索。例如,输入“中間道”,能匹配到“中间道”、“中間道巴士站”等。这通常基于字符串相似度算法(如Levenshtein距离)。
- 地理聚类 :这是关键一步。数据库中的站点坐标非常精确,可能导致同一个物理位置有多个稍有不同的坐标点(比如马路两侧)。技能会将半径50米内的站点视为同一个“簇”。当用户查询一个区域时(如“尖沙咀”),系统不是返回几十个独立站点,而是先进行聚类,再从中选取最具代表性的站点或返回一个聚合结果,使结果更清晰。
- 地标联想 :系统内置或通过学习,能将“机场”、“红隧”、“时代广场”等常见地标与周边的巴士站关联起来,进一步提升了自然语言理解的友好度。
2.3 多运营商数据融合与呈现
香港巴士由不同公司运营,数据格式、接口规范不尽相同。技能需要扮演一个“统一网关”的角色。
- 数据标准化 :将从KMB和CTB/LWB API获取的原始数据(可能是JSON、XML等不同格式)解析、清洗,转换成内部统一的、结构化的数据模型。包括处理时间格式(有的返回秒数,有的返回时间戳)、路线状态(正常、延误、尾班车)等。
- 联合路线处理 :对于九巴和城巴联营的路线,技能会合并双方的数据源。例如,查询路线“101”,它会同时获取九巴和城巴提供的该路线到站信息,合并去重后,按照时间顺序呈现给用户,并标注来源
[KMB]或[CTB],让信息一目了然。 - 终点站标记 :对于只下客不上客的终点站,系统会特别标记为
[終點站],避免用户误等。
3. 实操部署与集成指南
对于想要集成此技能的开发者,或者好奇其工作原理的技术爱好者,以下是详细的实操步骤和核心环节解析。
3.1 环境准备与技能安装
假设你已经在运行一个兼容OpenClaw协议的AI Agent环境(例如OpenClaw本身或其他框架)。安装此技能非常简单。
首选方案:从ClawHub技能市场安装(最便捷)
clawhub install hk-bus-eta
这条命令会从中央仓库自动下载、解压并注册该技能到你的AI Agent技能目录中。
备选方案:从GitHub源码安装(适用于开发或定制)
clawhub install https://github.com/tomfong/hk-bus-eta-skill --path hk-bus-eta --as hk-bus-eta
这里 --path 指定了源码包中的子目录, --as 定义了技能在本地注册的名称。
注意事项 :安装过程只复制了技能的定义文件(如
skill.json)和脚本。最核心的巴士站点数据库并不包含在安装包内,需要在下一步初始化。
3.2 首次初始化:构建本地巴士数据库
这是 至关重要且仅需执行一次 的步骤。技能安装后,就像一个没有地图的导航仪,需要先“下载地图”。
python3 ~/.openclaw/workspace/skills/hk-bus-eta/scripts/sync_bus_stops.py
请将 ~/.openclaw/workspace/skills/hk-bus-eta 替换为你的技能实际安装路径。
这个脚本具体做了什么?
- 数据获取 :通过
curl或requests库,访问DATA.GOV.HK上香港运输署公开的“巴士站点数据”数据集。 - 数据解析与清洗 :原始数据可能是CSV或特定格式的文本。脚本会解析每条记录,提取巴士站编号、中英文名称、经纬度坐标、所属路线等关键字段。
- 数据库构建 :将清洗后的数据写入一个本地的SQLite数据库文件(例如
bus_stops.db)。同时,为了加速查询,它很可能还会创建索引,比如在“站名(中文)”、“站名(英文)”、“坐标”等字段上建立索引。 - 地理聚类预处理 :脚本可能会在入库时预先计算站点的地理哈希(如Geohash),或执行初步的聚类分析,为运行时50米聚类匹配做准备。
这个过程大约需要1-2分钟,下载和处理20MB左右的数据。完成后,你的本地技能目录下会生成一个数据库文件,后续所有查询的位置匹配都将基于此数据库,速度极快。
踩坑实录 :务必确保运行初始化脚本的环境具有稳定的网络连接,能够访问
data.gov.hk。如果因为网络问题中断,可能导致数据库不完整,引发后续查询时匹配失败或报错。建议在网络通畅时执行此步骤。
3.3 技能调用方式解析
技能安装并初始化后,你的AI Agent就可以调用它了。调用方式主要分为两种:
1. 自然语言交互(主要使用场景) 这是设计初衷。用户直接对AI说:
- “城巴11喺中環有邊幾個站?巴士最快幾時到?”
- “When does the next bus on route A21 arrive at the airport?”
AI Agent在后台会进行以下操作:
- 意图识别 :判断用户意图是查询巴士ETA。
- 实体抽取 :从问句中提取“路线编号”(如11, A21)和“地点”(如中環, airport)。
- 技能路由 :将提取到的实体参数,按照技能定义的输入格式,调用
hk-bus-eta技能。 - 执行与返回 :技能内部执行查询逻辑,将结果格式化为一段友好的文本(或结构化数据),返回给AI Agent,再由AI Agent组织语言回复给用户。
2. 直接命令执行(用于调试或高级集成) 你也可以绕过AI的对话层,直接调用底层的Python脚本进行测试。
exec python3 ~/.openclaw/workspace/skills/hk-bus-eta/scripts/eta.py 690 Central en
参数解析:
690: 路线编号。Central: 车站名或区域名(支持模糊匹配)。en: 输出语言(en为英文,tc为繁体中文)。
这个脚本是技能能力的核心实现,它串联了本地数据库查询和实时API调用,并处理了结果格式化。
3.4 输出结果解读与定制
技能返回的典型结果结构如下:
🚌 路線 1A - 尖沙咀碼頭
📍 尖沙咀碼頭總站 [終點站]
Google Maps: https://maps.google.com/...
└─ 中秀茂坪
14:32 (3 min) [KMB]
14:45 (16 min) [KMB]
15:02 (33 min) [KMB]
- 路线与目的地 :清晰显示所查路线和开往的方向。
- 车站信息 :显示匹配到的具体车站,
[終點站]是重要提示。 - 地图链接 :提供一键跳转到Google Maps查看车站位置的链接,非常实用。
- 到站时间列表 :按时间顺序列出未来几班车的预估到站时间,格式为“显示时间 (剩余分钟) [运营商]”。这种呈现方式直观地告诉用户“还要等多久”。
如果你想集成到自己的界面,技能很可能也支持返回JSON等结构化数据,方便二次开发。你需要查阅技能的详细技术文档(如 SKILL.md )来了解其完整的输入输出规范。
4. 性能优化与高级特性
在v1.0.2版本中,作者重点优化了性能,这些设计思路值得借鉴。
4.1 并行API获取机制
传统的串行调用方式是:查询路线X -> 等待KMB API返回 -> 再调用CTB API -> 等待返回 -> 合并结果。如果每个API耗时2秒,总耗时就是4秒。
本项目采用 ThreadPoolExecutor 创建线程池,同时向KMB和CTB的API发起请求。这样,总耗时近似等于最慢的那个API的响应时间(假设为2秒),效率提升近一倍。这对于需要同时查询多条路线(如“帮我看看98D和296D谁先来”)的场景,收益更为明显。
实现伪代码逻辑 :
import concurrent.futures
def fetch_kmb_eta(route, stop_id):
# 调用KMB API
return kmb_data
def fetch_ctb_eta(route, stop_id):
# 调用CTB API
return ctb_data
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
future_kmb = executor.submit(fetch_kmb_eta, route, stop_id)
future_ctb = executor.submit(fetch_ctb_eta, route, stop_id)
results = []
for future in concurrent.futures.as_completed([future_kmb, future_ctb]):
try:
data = future.result(timeout=3) # 设置超时
results.append(data)
except Exception as exc:
print(f‘API调用产生异常: {exc}’)
# 合并results
4.2 缓存策略的深入应用
除了初始化的全量站点缓存,在运行时也有缓存策略:
- KMB站点缓存预加载 :因为KMB的站点数据相对稳定,可能在技能启动时或首次查询相关路线时,将其站点信息缓存在内存中,避免频繁查询SQLite。
- CTB全量缓存 :文档提到“full CTB cache (2250+ stops)”,推测是将城巴的所有站点数据也加载到内存,因为城巴的站点数据量可能适中,全量内存缓存可以换来极快的匹配速度。
这种多级缓存(SQLite磁盘缓存 + 内存缓存)是应对高并发、低延迟查询的常见有效手段。
4.3 数据库的维护与更新
巴士路线和站点并非一成不变。政府开放数据平台的数据集会定期更新。因此,项目推荐通过CRON任务定期(如每周日凌晨3:30)执行同步脚本。
# 示例Crontab条目
30 3 * * 0 /usr/bin/python3 /path/to/sync_bus_stops.py >> /path/to/sync.log 2>&1
这样可以确保本地数据库与官方数据保持同步,避免因车站搬迁或新线开通导致查询失败。
实操心得 :对于生产环境,除了定期同步,还应该增加同步失败的通知机制(如发送邮件或日志告警)。同时,在技能逻辑中,可以加入对数据新鲜度的检查,如果数据库太久未更新,可以在查询结果中给出温和的提示。
5. 常见问题与排查技巧实录
在实际部署和使用中,你可能会遇到以下问题。这里记录了我的排查思路和解决方法。
5.1 初始化失败或查询报“数据库错误”
- 现象 :运行
sync_bus_stops.py时卡住或报错,或者首次查询时提示找不到数据库文件。 - 排查步骤 :
- 检查网络 :确认运行环境能访问
https://data.gov.hk。可以尝试用curl或wget手动下载数据源URL测试。 - 检查路径权限 :确保运行脚本的用户对技能安装目录有读写权限,能够创建和写入SQLite数据库文件。
- 查看详细日志 :运行脚本时添加更详细的输出,例如
python3 sync_bus_stops.py -v(如果脚本支持),或直接查看脚本源码,在出错位置附近添加打印语句,看是下载、解析还是写入数据库环节出了问题。 - 手动验证数据库 :初始化完成后,用
sqlite3命令行工具打开生成的.db文件,执行SELECT count(*) FROM bus_stops;之类的简单查询,确认数据已成功导入。
- 检查网络 :确认运行环境能访问
5.2 查询结果不准确或匹配不到车站
- 现象 :输入“尖沙咀碼頭”查不到,但输入“尖沙咀码头总站”可以。
- 排查步骤 :
- 确认数据库版本 :首先检查本地数据库是否太久没更新。对比数据库文件修改时间和DATA.GOV.HK上数据集的更新时间。
- 测试模糊匹配 :直接调用技能的底层匹配函数(如果暴露的话),或者查看数据库里该站点的确切名称。可能是站名收录的差异(如用了全角括号和半角括号的区别)。
- 检查聚类半径 :如果查询一个区域(如“铜锣湾”)返回的结果太少,可能是50米的聚类半径对于某些大型区域过于严格。可以尝试调整源码中
CLUSTER_RADIUS_M这个参数(例如扩大到100米),但需注意这可能将距离较远的两个站错误地合并。 - 使用坐标辅助 :如果技能支持传入用户坐标
[USER_LAT] [USER_LON],可以尝试使用。系统会优先寻找距离该坐标最近的站点簇,匹配成功率更高。
5.3 API调用超时或返回空数据
- 现象 :查询时等待很久,最后报超时错误,或者ETA列表为空。
- 排查步骤 :
- 分步测试 :分别测试KMB和CTB的API。可以写一个小脚本,直接使用
requests库调用官方ETA接口(接口地址通常在技能源码中能找到),看是否能正常返回数据。这能确定是技能问题还是运营商API暂时故障。 - 检查网络代理 :如果运行环境处于特殊网络配置下,可能需要为
requests或curl设置代理。 - 查看错误处理 :技能源码中应有对API返回状态码(非200)和空数据的处理逻辑。检查是否因为API返回了错误格式导致解析失败。可以临时增加日志,打印出API的原始响应内容。
- 调整超时时间 :如果运营商API响应慢,可以适当调整源码中的
timeout参数(例如从3秒调到5秒),但需权衡用户体验。
- 分步测试 :分别测试KMB和CTB的API。可以写一个小脚本,直接使用
5.4 如何为其他地区适配此技能?
这是一个很自然的扩展想法。技能的核心框架(本地缓存+实时API+模糊匹配)具有通用性。
- 更换数据源 :你需要找到目标城市开放的、结构化的巴士站点与实时到站数据API。例如,内地一些城市通过“交通委”或“大数据管理局”提供类似数据。
- 修改数据同步脚本 (
sync_bus_stops.py):重写数据下载和解析逻辑,使其适应新数据源的格式(可能是JSON、XML或CSV),并将数据转换并存入相同结构的SQLite表中。 - 修改ETA查询脚本 (
eta.py):替换API调用地址和参数,调整响应数据的解析逻辑,以匹配新运营商的数据格式。 - 调整地理参数 :不同城市的站点密度不同,50米的聚类半径可能需要调整。同时,地图链接的生成模板(如Google Maps)也可能需要改为本地更常用的地图服务(如高德、百度地图)。
- 语言本地化 :更新提示信息和输出模板为当地语言。
这个过程本质上是一次“技能迁移”,保留了智能匹配和高效查询的引擎,更换了“数据燃料”和“外观界面”。
更多推荐

所有评论(0)