dev.to #ai短讯
Sitspot — 我的位置,叫我回到户外
这是 Hacktoberfest Open-Source AI Challenge 第一周的参赛作品。作者居住在瓦赛(孟买附近),家附近有公园、湖和海滩,但上个月一次都没去。原因是印度城市十月的天气复杂:下午38度高温,空气质量每小时变化,海边只有在退潮和天黑前才舒适。没人会同时查五个应用来寻找那宝贵的40分钟好时光。
这是 Hacktoberfest 开源 AI 挑战赛第 1 周:“Touch Grass”活动的投稿
我做了什么
我住在孟买附近的瓦赛。离我的办公桌不到 20 分钟路程的地方,有一个公园、一个湖岸和三个海滩。上个月我一个都没去。不是因为不想去,而是因为在一座印度城市里,十月份的时间把控至关重要:下午体感温度高达 38°C,空气质量每小时都在波动,而海岸线只有在退潮时和天黑前才宜人。没人会去查五个应用来寻找那宝贵的 40 分钟好时光。
Sitspot 会监控你选择的几个真实地点,仅当其中一个地点出现真正适宜的外出窗口时,才会召唤你出门。
- 你只需一次性添加 3–5 个地点(一个公园、一个海滩、你的露台)——这几乎是你将花费的全部屏幕时间。
- 每小时,它会为每个地点获取天气、空气质量、潮汐时间、太阳位置以及最近的 eBird 观测数据,并使用 TabPFN 预测观鸟体验的好坏。
- 一个简单的经过测试的决策函数(代码,而非模型)会对每个地点 × 每小时进行评分——并且有五条严格的安全规则可以否决任何提议:日落后 30 分钟内不去海边,高潮附近不去,体感温度 ≥ 38°C 或美国 AQI ≥ 200 时不去,安静时段绝对禁止外出。
- 当一个窗口满足条件时,Gemma 会写一段简短且务实的邀请信息(“……请在 6:40 前离开,好天气将持续到 8:00。空气质量较差(美国 AQI 150),所以请保持行程简短轻松。要去吗?”),然后你的手机震动。
- 点击它,Sitspot 就会和你对话(ElevenLabs 语音,Gemma 作为大脑)。你可以问“有多远?”,或者说“好的,走吧”。
- 到达地点后,点击“我到了”,然后把手机放进口袋:BirdNET 在手机后台运行并识别听到的声音,同时给出诚实的置信度描述。音频永远不会离开手机。
- 晚上它会写一段简短的野外笔记,每晚它会根据你接受或拒绝的建议进行学习。
屏幕交互只是其中很小的一部分:大约 3 分钟的设置,然后收到震动提示,点击一下,你就出门了。
演示
- 实时应用:https://sitspot.vercel.app
- 视频(手机屏幕录制):https://www.youtube.com/shorts/BJddW_OH_ZY
关于视频的诚实说明。在我构建应用的整个星期里,瓦赛的空气质量对我不利——大多数傍晚美国 AQI 在 200–299 之间,体感温度 35–39°C。Sitspot 完全做到了它应该做的:拒绝邀请我去任何地方;即使它的“发送测试邀请”按钮在没有窗口通过安全规则时也会拒绝。因此,该录像是使用了一个测试邀请(真实的流程——Gemma 脚本、推送、语音、访问、野外笔记——仅跳过了分数阈值,从未跳过安全规则)在一个空气清洁的测试地点进行的,录制于家中。你可以在主屏幕上看到我真实的瓦赛地点,标记为 AQI 229 · 非常不健康。在口袋模式下,它听到了一只家鸦(这里到处都是)和一只仓鸮——在下午 2:50 室内听到“可能”是仓鸮,这几乎肯定是误报。这个“可能”正是关键所在:该应用旨在表达其确定程度,绝不会凭空捏造鸟类。
预报显示周末空气质量会好转;第一次真正的清晨邀请已排入我的公园日程。
代码
Yashgurav002 / sitspot
你的地点,呼唤着你。利用开放数据监控你附近的几个真实地点,并在其中一个出现真正适宜的窗口时召唤你。使用开放模型:Gemma、BirdNET(设备端)、TabPFN。Hacktoberfest 2026 Touch Grass。
你的地点,呼唤着你。
你添加的是你实际可以去的那 3–5 个真实地点:一条小溪、一座堡垒、一个海滩、一个露台。Sitspot 每小时使用开放数据(天气、空气质量、潮汐、太阳、最近的 eBird 观测)监控这些地点。当其中一个地点出现真正适宜的窗口时,你的手机震动,点击它即可在浏览器中与 Sitspot 进行语音对话(ElevenLabs 语音,Gemma 大脑)。通话会告诉你地点、带有真实数据的理由以及出发时间。如果你去了,点击“我到了”,把手机放进口袋,然后步行前往。晚上,它会根据实际发生的情况写一段简短的野外笔记,并根据你接受和拒绝的内容进行学习。
实时:https://sitspot.vercel.app · 撰写文档:docs/devto-post.md
电话呼叫:ElevenLabs + Twilio 的路径已实现(apps/api/src/delivery/call.ts),但新的 Twilio 试用账户会阻止将流式通话音频传输给 AI 代理……
一个 pnpm monorepo:Next.js 16 PWA(apps/web)、Hono API(apps/api)、Temporal 工作流(workflows/),以及用于策略、代理、LLM 客户端、数据拉取器和数据库的小型包。涵盖 TypeScript 和 Python 的约 300 个测试;CI 在每次推送时运行类型检查 + 测试。
我是如何构建它的
开放模型,每个模型只做它最擅长的一件事:
| 任务 | 开放模型 | 运行位置 |
|---|---|---|
| 邀请脚本 + 晚间笔记 | Gemma 4 31B | Google AI Studio(免费层) |
| 实时语音回复 | Gemma 3 1B | 我的笔记本电脑,通过 Ollama(首个 token 延迟 p50 为 379 毫秒) |
| 鸟类识别 | BirdNET(TF.js 构建版) | 手机浏览器内,在 Web Worker 中运行(每 3 秒窗口约 120–340 毫秒) |
| “早上 7 点在这里观鸟效果好吗?” | TabPFN | Python 作业,每小时一次,在我的笔记本电脑上 |
| 笔记搜索嵌入 | nomic-embed-text | Ollama |
以上下文为先,而非工具调用。服务器将所有事实(天气、潮汐时间、目击记录、你的偏好)收集到一个小的上下文中;模型只负责生成文本。然后验证器检查每一项输出:每个数字必须出现在事实中,不能命名未被检测或报告的鸟类,沿海脚本必须说“站在坚实的地面上”,绝不能在夜间鼓励人们靠近海岸,置信度词汇必须与检测分数匹配。如果失败,会重试一次并将问题反馈回去,否则使用确定性模板。在我的 40 轮对抗性评估(Gemma 3 1B)中,原始模型在 5% 的情况下命名了不存在的鸟类;而到达用户的比例为 0%。在我强制在代理内部执行“夜间禁止靠近海岸”的规则后,不安全建议到达用户的比例从 12.5% 降至 2.5%——评估在任何一个真实用户发现之前就已经捕捉到了这个问题。
Temporal 使其具有持久性。每天有一个工作流每小时进行评估;每个邀请都是独立的工作流(休眠直到发送时间 → 重新检查条件 → 编写脚本 → 发送 → 等待回答 → 等待到达 → 等待访问结束 → 撰写笔记)。我在每个等待点故意杀死工作进程 20 次:20/20 成功恢复,0 重复通知。
TabPFN,老实说。我在 19,588 份真实的 eBird 清单(2022–2026 年,Palghar/Thane/Mumbai)上与 Open-Meteo 历史数据进行了训练。在保留的 2026 赛季中,TabPFN 在 ROC-AUC 上与简单的热点 × 小时基线持平(0.649 vs 0.650)——未达到我设定的 +0.05 目标。但在邀请阈值处,它具有最高的精确度(0.627 vs 0.549),同时标记的小时数要少得多,这正是需要极少打扰你的应用所需要的。主要限制在于:eBird 的速率限制迫使我使用每日数据源,因此 96% 的行缺少清单持续时间,标签存在噪声。结果文件:evaluation/tabpfn_results.md。
记忆。如果说“工作日小溪太远”,这就成为一条规则——工作日的溪流时段会被推迟,并且理由会引用你自己的话。每一个偏好都必须存储用户的实际语句。过夜期间,它会微调你的阈值并学习你在哪些时间段会说“是”。
零卢比托管。Web 应用托管在 Vercel 上;API、Temporal 工作进程和 Ollama 运行在我笔记本电脑后的免费 ngrok 域名背后,Vercel 代理 /api 路径,从而保持单一来源。Postgres 使用的是 PGlite(嵌入式,带 pgvector)——可以通过 DATABASE_URL 替换为任何 Postgres。
行不通的事情(以及我改用的方法)。我想要真正的电话呼叫。ElevenLabs + Twilio 已连接并在 Twilio 端经过测试——但新的 Twilio 试用账户会阻止将流式通话音频传输给 AI 代理,因此“呼叫”发生在浏览器中:推送通知打开呼叫页面,Sitspot 通过 ElevenLabs 进行语音播报。Twilio 直连代码在仓库中,且在付费账户上可用。
为什么开放创新很重要?
- 可验证的隐私。Bird ID 在手机端运行。你的麦克风音频永远不会离开你的手机;只有“家鸦,置信度 0.71,时间 07:31”这样的数据会被发送。如果采用封闭的音频 API,就意味着要将你散步时的录音流式传输到某人的服务器上。
- 在无信号的地方也能工作。红树林和海滩的信号覆盖通常很差。模型在浏览器中运行;检测结果会暂存在 IndexedDB 中,随后进行同步。
- 可替换的大脑。所有的 LLM 调用都通过一个兼容 OpenAI 的客户端进行。我通过更改环境变量,将脚本从本地的 Gemma 3 4B 迁移到了 AI Studio 上的 Gemma 4 31B,同时为了降低延迟,让语音功能仍保留在本地运行的 Gemma 3 1B 上。当发现 Gemma 4 会在回答之前先进行“思考”(且这一行为无法关闭)时,我在代码中一处修改便将其去除了。
- 开放数据输入,诚实结果输出。来自数千名观鸟者的 eBird 检查清单、Open-Meteo 的预报和历史数据、OpenStreetMap 的瓦片地图,以及一个我可以实际评估的开放表格模型——包括公开它达到了基线水平的结果。
- 运行成本为零。使用免费额度、一台笔记本电脑和开源权重。如果使用封闭 API,一个每小时检查五个地点的常驻代理会产生真实的月度账单。
我的 Agent 会话
使用 Claude Code 作为我的编码代理构建:它编写了产品需求文档(PRD),将其拆分为 20 个任务,按包并行运行子代理,并且每个任务在提交前都必须通过其测试。
奖项类别
- 最佳 Gemma 应用 —— Gemma 4 31B 负责撰写每封邀请函和备注;Gemma 3 1B 在本地运行,作为语音代理的大脑;两者均受到验证器的约束。
- 最佳 TabPFN 应用 —— 基于 19,600 份真实 eBird 检查清单训练的每小时“适宜观鸟”预报,并与上述三个基线进行了基准测试,得出诚实的结果。
- 最佳 ElevenLabs 应用 —— Sitspot 的声音:一个 ElevenLabs 代理,其 LLM 是我的自有开源模型端点(Gemma),因此其声音建立在相同的事实和安全规则之上。
- 最佳 Temporal 应用 —— 具有信号、重新检查和重试机制的持久化邀请工作流;在强制杀死工作进程后,20/20 的任务能恢复执行,零重复。
致谢:TabPFN 由 Prior Labs 提供;BirdNET (Kahl 等人,2021,CC BY-NC-SA 4.0);eBird / 康奈尔大学鸟类学实验室;Open-Meteo (CC BY 4.0);© OpenStreetMap 贡献者。
译文已达到本站中文翻译的字数上限,剩余内容请查看原文。