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几时到中间道?”甚至“我在海港城,点样搭车去铜锣湾?”。

技能通过以下三层匹配来解决这个问题:

  1. 模糊文本匹配 :对用户输入的站名或区域名进行模糊搜索。例如,输入“中間道”,能匹配到“中间道”、“中間道巴士站”等。这通常基于字符串相似度算法(如Levenshtein距离)。
  2. 地理聚类 :这是关键一步。数据库中的站点坐标非常精确,可能导致同一个物理位置有多个稍有不同的坐标点(比如马路两侧)。技能会将半径50米内的站点视为同一个“簇”。当用户查询一个区域时(如“尖沙咀”),系统不是返回几十个独立站点,而是先进行聚类,再从中选取最具代表性的站点或返回一个聚合结果,使结果更清晰。
  3. 地标联想 :系统内置或通过学习,能将“机场”、“红隧”、“时代广场”等常见地标与周边的巴士站关联起来,进一步提升了自然语言理解的友好度。

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 替换为你的技能实际安装路径。

这个脚本具体做了什么?

  1. 数据获取 :通过 curl requests 库,访问DATA.GOV.HK上香港运输署公开的“巴士站点数据”数据集。
  2. 数据解析与清洗 :原始数据可能是CSV或特定格式的文本。脚本会解析每条记录,提取巴士站编号、中英文名称、经纬度坐标、所属路线等关键字段。
  3. 数据库构建 :将清洗后的数据写入一个本地的SQLite数据库文件(例如 bus_stops.db )。同时,为了加速查询,它很可能还会创建索引,比如在“站名(中文)”、“站名(英文)”、“坐标”等字段上建立索引。
  4. 地理聚类预处理 :脚本可能会在入库时预先计算站点的地理哈希(如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 时卡住或报错,或者首次查询时提示找不到数据库文件。
  • 排查步骤
    1. 检查网络 :确认运行环境能访问 https://data.gov.hk 。可以尝试用 curl wget 手动下载数据源URL测试。
    2. 检查路径权限 :确保运行脚本的用户对技能安装目录有读写权限,能够创建和写入SQLite数据库文件。
    3. 查看详细日志 :运行脚本时添加更详细的输出,例如 python3 sync_bus_stops.py -v (如果脚本支持),或直接查看脚本源码,在出错位置附近添加打印语句,看是下载、解析还是写入数据库环节出了问题。
    4. 手动验证数据库 :初始化完成后,用 sqlite3 命令行工具打开生成的 .db 文件,执行 SELECT count(*) FROM bus_stops; 之类的简单查询,确认数据已成功导入。

5.2 查询结果不准确或匹配不到车站

  • 现象 :输入“尖沙咀碼頭”查不到,但输入“尖沙咀码头总站”可以。
  • 排查步骤
    1. 确认数据库版本 :首先检查本地数据库是否太久没更新。对比数据库文件修改时间和DATA.GOV.HK上数据集的更新时间。
    2. 测试模糊匹配 :直接调用技能的底层匹配函数(如果暴露的话),或者查看数据库里该站点的确切名称。可能是站名收录的差异(如用了全角括号和半角括号的区别)。
    3. 检查聚类半径 :如果查询一个区域(如“铜锣湾”)返回的结果太少,可能是50米的聚类半径对于某些大型区域过于严格。可以尝试调整源码中 CLUSTER_RADIUS_M 这个参数(例如扩大到100米),但需注意这可能将距离较远的两个站错误地合并。
    4. 使用坐标辅助 :如果技能支持传入用户坐标 [USER_LAT] [USER_LON] ,可以尝试使用。系统会优先寻找距离该坐标最近的站点簇,匹配成功率更高。

5.3 API调用超时或返回空数据

  • 现象 :查询时等待很久,最后报超时错误,或者ETA列表为空。
  • 排查步骤
    1. 分步测试 :分别测试KMB和CTB的API。可以写一个小脚本,直接使用 requests 库调用官方ETA接口(接口地址通常在技能源码中能找到),看是否能正常返回数据。这能确定是技能问题还是运营商API暂时故障。
    2. 检查网络代理 :如果运行环境处于特殊网络配置下,可能需要为 requests curl 设置代理。
    3. 查看错误处理 :技能源码中应有对API返回状态码(非200)和空数据的处理逻辑。检查是否因为API返回了错误格式导致解析失败。可以临时增加日志,打印出API的原始响应内容。
    4. 调整超时时间 :如果运营商API响应慢,可以适当调整源码中的 timeout 参数(例如从3秒调到5秒),但需权衡用户体验。

5.4 如何为其他地区适配此技能?

这是一个很自然的扩展想法。技能的核心框架(本地缓存+实时API+模糊匹配)具有通用性。

  1. 更换数据源 :你需要找到目标城市开放的、结构化的巴士站点与实时到站数据API。例如,内地一些城市通过“交通委”或“大数据管理局”提供类似数据。
  2. 修改数据同步脚本 ( sync_bus_stops.py ):重写数据下载和解析逻辑,使其适应新数据源的格式(可能是JSON、XML或CSV),并将数据转换并存入相同结构的SQLite表中。
  3. 修改ETA查询脚本 ( eta.py ):替换API调用地址和参数,调整响应数据的解析逻辑,以匹配新运营商的数据格式。
  4. 调整地理参数 :不同城市的站点密度不同,50米的聚类半径可能需要调整。同时,地图链接的生成模板(如Google Maps)也可能需要改为本地更常用的地图服务(如高德、百度地图)。
  5. 语言本地化 :更新提示信息和输出模板为当地语言。

这个过程本质上是一次“技能迁移”,保留了智能匹配和高效查询的引擎,更换了“数据燃料”和“外观界面”。

更多推荐