在过去十五年间的某个时刻,网络悄悄地决定,每一个重要的网站也都需要一个移动 App。 不是因为 HTML 不再管用——而是因为 App 是一个受控的界面:一组精心挑选的动作,没有 你没选择的界面装饰,一套为单一交互方式而打造的界面。
我们认为,同样的动作正等着为另一群用户、另一组界面而实现。如果人人都把自己的网站 变成一个 Android App,我们可以把网站变成语音应用——以及命令行应用,和以屏幕 阅读器为原生的流程。同样的想法,相反的方向:把一个网站包进一个为你想要的交互方式 而打造的界面,只不过这个界面是你的声音和你的终端,而不是一块触摸屏。
网络几乎无法用耳朵使用
对于一个用鼠标的视力正常用户,现代网站没什么问题。但对于用语音浏览、用屏幕阅读器、 或从终端浏览的人来说,大部分网络都是一个充满敌意的环境:无限滚动的高墙、cookie 横幅、 弹窗、需要指针的菜单、埋在三层交互式杂物之下的内容。信息就在里面。要把它无手动地 取出来,却是一种煎熬。
通常的回答是”网站应该更无障碍些”,它们确实应该。但我们不可能靠客气地请求就修好整个 网络。我们能做的,是拿那些重要的网站,为每一个建立一个干净的、可发声的界面——就像 应用商店当年为触控所做的那样,但这次是为语音和 CLI,并且是开放地做。
两层:一套干净的 API,再加一个语音 skill
每一个这样的语音应用都是叠起来的两块,而这两块我们都已经在做。
第一层——把网站变成 API 的带类型客户端。 这正是我们的 抓取与 API 逆向工程工作:伸进一个没有 可用公开接口的网站,交还结构化的、带类型的对象,而不是脆弱的 HTML——是动词,不是 抓取:
from py_bandcamp import BandCamp
for release in BandCamp.search_albums("king gizzard"):
artist = release.work.credits[0].entity.name if release.work.credits else ""
print(release.work.title, artist, release.uri)
底层的侦察和
反爬虫传输
工具,会在网站变化时保持这份访问可用。那个客户端本身就已经很有用:对一个终端用户
来说,API 就是网站的无障碍版本——我们的 SoundCloud 客户端甚至附带 nds,一个
用来搜索和播放音乐、完全不用浏览器的命令行应用。一旦一个网站成了 API,它就不再是
一件视觉制品,而成了机器——或语音流水线——可以驱动的东西。
第二层——说这套 API 的 OVOS 插件。 客户端之上坐着一个 OpenVoiceOS 插件,它把口语意图映射到 API 调用,并用我们的 离线 TTS 语音把结果读出来。 它刻意不是每个网站一个定制 skill——那条路会走向几十个没人维护得了的一次性 skill。 对任何媒体形态的东西,它是一个 OCP provider 插件:一个把网站的 搜索与播放界面暴露给整个 Open Common Play 框架的小适配器,这样”搜索”、“播放”、 “下一个”和”继续”就已经与其他任何来源的用法完全一致地工作。网站落入一个统一的语音 界面,而不是发明它自己的一套。
结果是:“播放 SomaFM Groove Salad 频道。""在 Bandcamp 上搜索知识共享的 ambient。” 网站,变成了一件你无需看着它就能使用的东西——而且不必为每个网站学一套新的语法。
在 LLM 的时代,一套带类型的 API 就是一个蓄势待发的自然语言界面
这种形态如今比五年前更重要,还有第二个原因。一个干净、带类型的客户端,正是一个大 语言模型成为一个网站的自然语言前端所需要的东西。
给一个 LLM 一组有文档的函数——search_albums、get_recommendations、stream_url——
它就会乐于把”给我找点像 Naxatras 但更重的东西”翻译成正确的调用、把它们串起来,并把
结果读回来。结构化的 API 才是难的部分;顶上的对话式界面,越来越是模型直接就提供
的东西,只要交到它手里的工具类型良好、对自己返回什么诚实。杂乱的 HTML 给不了 LLM 任何
可以抓握的东西。一个带类型的客户端给了它一个控制界面。
所以我们的网站客户端附带一份 SKILL.md——一份用平实语言写成的说明,讲清这套 API
做什么、它的动词、它的返回类型和示例调用,是写给智能体读的。把一个 LLM 驱动的助手
指向它,客户端就立刻成为模型可以使用的工具:没有胶水代码,没有定制集成,只有”这个
网站能做什么,用文字说清楚”。一份文档就把一个抓取器变成了语言模型可以代你操作的
东西。
这是同一份结构化数据同时服务三个前端:给终端用户的 CLI、给免手操作的 OCP/语音插件,以及给自然语言控制的 LLM 工具。API 建一次;三种穿法。
为什么这对看不见屏幕的人最重要
对盲人和低视力用户来说,这不是一个锦上添花的功能——它是可及与被排除之间的区别。屏幕 阅读器只能读出一个页面干净暴露的东西,而大部分页面并不这样。一个专门的语音应用完全 跳过页面:它直接去到结构化数据,并把那个读出来,用一个从第一行代码起就为聆听而 设计的流程。
这与我们的音频优先游戏背后的原则相同——为耳朵而造,而非为眼睛,把盲人 玩家当作主要受众而不是事后补救。为网站而做的语音应用,把这一原则从游戏延伸到网络的 其余部分。
一个网站一个网站地做——但方向是一个语音浏览器
坦白的部分在这里:没有万能的捷径。你无法一次性地把”网络”语音化,因为每个网站都是它 自己的一团乱麻。它必须按网站来做——一次一个客户端、一个 skill、一组精心映射的 意图。这听起来像一个局限,短期内它确实是。
但看看累积指向何方。我们包装的每一个网站,都是网络中又多了一个如今可以用语音、用 CLI 触及的角落。把足够多的它们串在一起——一套共同的元数据词表、一个共享的语音层、一组 一致的”搜索 / 打开 / 阅读 / 播放 / 下一个”意图——你面对的就不再是一堆各自独立的 skill。你面对的是一个语音浏览器的雏形:一种通过说话在网络中穿行的方式,其中一个个 网站只是已经知道如何应答的目的地。
移动时代的赌注是:一个值得使用的网站,就值得一个 App。我们的赌注是:一个值得使用的 网站,就值得一个声音。我们正一个接一个地、公开地建造它们,而每一个都让网络对那些 被视觉网络遗落的人,多了一分可浏览性。
想把某个具体网站变成一个语音或 CLI 应用——为了无障碍、为了你的产品,或只是因为它 应该存在?我们聊聊。