本文素材来自启元机器人在 AtomGit 的官方开源仓库 primebot/weather_forecast,全部代码为官方原版,我做了解读和标注。

启元机器人 AtomGit 的官方开源仓库地址:https://atomgit.com/primebot

在这里插入图片描述

给机器人写个技能,要写多少代码?

很多人的直觉是:至少得会 ROS 2,得懂节点、话题、服务,最好再修过具身智能的课。启元官方在 AtomGit 上开源的那批技能仓库,给出了一个不太一样的答案——查询天气这个技能,业务逻辑只有三十来行 Python。

这篇文章把这三十行逐行拆开看。看完你会发现,机器人技能开发真正难的从来不是代码量,而是"知道机器人替你做了什么"。

先看全貌:一个技能仓库里有什么

把 weather_forecast 仓库 clone 下来,目录长这样:

weather_forecast/
├── main.py              # 入口,6 行
├── cfg/
│   └── pkg.yaml         # 技能的"身份证"
└── src/weather_forecast/
    └── weather.py       # 全部业务逻辑,约 80 行(含天气码映射表)

没有 package.xml,没有 setup.py,没有一长串 ROS 依赖声明。官方文档里 Skill 的定位是"用一份 JSON 定义机器人的性格与技能",落到这个仓库里就是 cfg/pkg.yaml 这几行:

user_id: "robot_local"
version: "1.0.0"
sdk_version: "v1.0.0"
package_name: "weather_forecast"
author: "primeagent"
description: "查询今天、明天或后天的城市天气预报、气温、降雨概率。
              适用于今天天气、明天天气、天气预报、会不会下雨、温度多少等问题。"

注意最后那个 description。它不是写给人看的文档,更像是给机器人"大脑"看的说明书——用户说"今天天气怎么样",由意图层决定唤起哪个技能,description 里的触发词(“会不会下雨”“温度多少”)就是匹配依据。写技能的时候,这段话值得认真打磨,它决定了你的技能什么时候会被想起来。

入口:main.py 的 6 行

import os
from weather_forecast.weather import speak_forecast

speak_forecast(robot, os.environ.get("ROBOT_SKILL_QUERY", ""))

技能被唤起时,运行环境已经准备好了两样东西:一个已经连上机器人的 robot 对象,和用户那句原话(放在环境变量 ROBOT_SKILL_QUERY 里)。你的代码不需要管网络、不需要管语音识别,从第一行起就站在"已经听懂有人说话了"这个起点上。

核心三步:解析、查数、说话

整个技能的调用链其实就一条直线:

在这里插入图片描述

第一步,把人话洗成城市名和日期。 parse_query 是全技能最有意思的函数:

在这里插入图片描述

def parse_query(text):
    offset = 2 if "后天" in text else 1 if "明天" in text else 0
    city = re.sub(
        r"元仔|今天|明天|后天|查一下|查询|看看|告诉我|天气预报|天气|气温|温度|"
        r"会不会|是否|可能|有雨|下雨|下雪|怎么样|如何|多少|几度|的|吗|呢|吧|[,。?!,.?\s]",
        "",
        text,
    )
    return (city or DEFAULT_CITY)[:40], offset

没有 NLP 模型,没有意图分类器,就是一次正则替换:把唤醒词(“元仔”)、时间词、动词短语、语气词全部删掉,剩下的就是城市名。“元仔,查一下明天上海的天气"洗完之后剩下"上海”,加上"明天"对应的时间偏移 1,齐活。

这种写法看起来"土",但在技能这一层恰恰是对的。语音链路前面已经有 ASR 和 Agent 做过一遍理解了,到了技能手里,句子里的意图信息基本只剩这几个关键词。用 40 个字符的兜底截断、默认城市"上海"这些细节防住脏输入,比引入一个模型划算得多。

第二步,查 Open-Meteo。 这里有两个值得学的选择:

  • 选了 Open-Meteo 而不是商业天气 API,不需要 API Key——技能开源出去,别人 clone 下来就能跑,零配置;
  • 先调地理编码接口把城市名换成经纬度,再查预报,城市名容错(比如打错的区县名)就交给了专业服务。
data = get_json(
    "https://api.open-meteo.com/v1/forecast?"
    + urllib.parse.urlencode({
        "latitude": place["latitude"],
        "longitude": place["longitude"],
        "daily": "weather_code,temperature_2m_max,"
                 "temperature_2m_min,precipitation_probability_max",
        "timezone": "auto",
        "forecast_days": 3,
    })
)

只用标准库 urllib,连 requests 都没引。对一个要被打进技能包、跑在机器人本机上的程序来说,依赖越少越稳。

第三步,把数据变成"一句话"。 天气 API 返回的是数字编码(0 是晴,61 是小雨……),代码里那张 WEATHER 字典把 20 多个天气码翻成中文;最后的播报语句是模板拼出来的:

return f"{name}{label}{WEATHER.get(code, '天气状况未知')},{low}到{high}度,最高降雨概率{rain}%。"

“上海明天多云,18 到 25 度,最高降雨概率 20%。”——一句话,三个信息点,没有废话。给 TTS 写播报语和给屏幕写文案不一样,句子越长机器人越啰嗦,这句话的分寸感是照着"人说出口的天气预报"来的。

一行调用背后的东西

业务代码最后一行是 robot.play_tts(forecast(text))。看起来就是"说话",但对照官方 SDK 的接口文档,这个调用背后其实发生了不少事:请求会带上优先级和 domain 标记进 TTS 队列,播报状态通过独立的话题回传,如果别的技能正在播报,还要按优先级仲裁。这些在官方 aimdk_msgs接口体系里都有明确定义。

官方仓库里的另一个技能 didi_taxi_agent(滴滴打车)就把这层拆开手写了:自己监听 /interaction/tts_status 话题、用 trace_id 追踪每一句播报是否播完、给 TTS 指定 INTERACTION_L6 优先级。也就是说——robot.play_tts 是官方把"复杂的语音管线"包成的一行调用;官方自己写复杂技能时,也会把这层拆开来精细控制。

想从简单技能进阶到复杂技能,这两个仓库对照着读,恰好就是"封装层"和"裸调层"的完整教材。

还能再榨一点:异常兜底的写法

技能对外暴露的只有两个函数,异常处理只包在最外层:

def speak_forecast(robot, text):
    try:
        robot.play_tts(forecast(text))
    except Exception:
        robot.play_tts("天气服务暂时不可用,请稍后再试。")

网络超时、城市不存在、API 改版,最后都会落到这一句播报上。语音交互有个特点:用户听不到报错堆栈,只能听到机器人回不回话。所以技能层宁可粗一点,也要保证"任何情况下机器人都会开口"。这个原则比三十行业务代码更值得带走。

代码里甚至藏了个自带的自测:

def _self_test():
    assert parse_query("元仔,查一下明天上海的天气") == ("上海", 1)
    assert parse_query("北京后天会不会下雨") == ("北京", 2)
    assert parse_query("今天天气怎么样") == (DEFAULT_CITY, 0)

三个 assert 覆盖三种典型问法,本地 python3 weather.py 就能验证解析逻辑,完全不依赖机器人。写机器人技能不一定要等真机在手上才能测试——这个仓库示范了怎么把"能离线测的部分"和"必须上机测的部分"切开。

小结

回到开头的问题:给机器人写个技能要多少代码?官方的答案是:一个 YAML 身份证,一个六行入口,一段几十行的纯 Python 业务逻辑。

框架把 ROS 通信、语音链路、技能调度全都收走了,留给开发者的恰好是最能做出差异的部分:理解用户这句问话、拿到数据、说出一句像人说的话。

这也是我推荐想上手启元二开的人,从 weather_forecast 读起的原因:它足够小,小到你可以在一个下午读透、改一遍(比如把默认城市改成你自己的城市,或者加一个"要不要带伞"的判断),又足够完整,完整到覆盖了技能开发的全流程。

读完了官方的最小骨架,下一篇文章我们去读官方技能里的"复杂版"——滴滴打车技能,看看真实订单面前,语音交互的确认闭环是怎么设计的。


最后,如果你也想给机器人写点自己的东西,欢迎来启元机器人开发者社区 https://dev.primebot.com/ ,SDK、文档和官方技能仓库都在,跑通的代码还可以开源到 PrimeHub 给更多人用。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐