前言:选完不是终点,能“通过评审”才是

在上一篇里,我分享了边缘端语言、数据库、音频处理的选型过程。但选完技术栈,就能直接开干了吗?

我的经验是:绝对不能。

在我过去的项目中,吃过太多“选完就写”的亏。代码写到一半,才发现:

  • 某个依赖在两个模块间版本冲突

  • 某个方案的性能实测比预期差 10 倍

  • 某个看似独立的模块,实际上和另一个模块有隐式耦合

这些问题,在“选型阶段”完全可以避免——只要你多做一个动作:让 AI 帮你生成一份可执行的任务清单,然后拿它去评审。

这篇“二下”,我会讲清楚三件事:

  1. 剩下的技术选型(云端 Agent、安卓端、设备控制协议)

  2. 我是怎么让 AI 反过来问我问题,把模糊的地方彻底聊透

  3. 如何生成 task 文档,并拿去给另一个 AI 做评审,确保“选型”能平滑过渡到“执行”

一、剩下的技术选型:云端、安卓端、设备控制

1.1 云端 Agent 语言:为什么我弃 Python 选 TypeScript

问 AI

云端 Agent 要做意图理解、任务拆解、流式输出,还要调用豆包/DeepSeek API。Python 和 TypeScript 怎么选?

AI 给的对比
维度 Python TypeScript
AI 生态 ⭐⭐⭐⭐⭐ 最强 ⭐⭐⭐⭐ 强(Vercel AI SDK、LangChain.js)
流式输出 需框架配合 原生支持(SSE、Vercel AI SDK)
类型安全 动态 静态(可避免大量运行时错误)
全栈统一 需要另一套前端 前后端同语言
部署 需 Python 环境 一个 Node 镜像
我的追问

Vercel AI SDK 是什么?它比直接调 API 好在哪?

AI 回答

Vercel AI SDK 是专门为 AI 流式应用设计的库,提供 streamTextuseChat 等 React 钩子,自动处理 SSE、流式解析、前端渲染。你用 LangChain.js 做决策,用 Vercel AI SDK 做流式输出,两者配合是天作之合。

我的决策

选 TypeScript + Fastify + LangChain.js + Vercel AI SDK。理由:

  • 类型安全,降低多模型切换时的错误

  • Vercel AI SDK 让流式输出从“手写 SSE”变成“写对象”

  • 和前端(如果有)共用类型定义

1.2 安卓端 SSE 客户端:OkHttp 还是 Retrofit?

问 AI

手机需要接收边缘端转发的 SSE 流,显示执行进度。用 OkHttp SSE 还是 Retrofit + SSE?

AI 给的对比
维度 OkHttp SSE Retrofit + SSE
代码量 少(直接监听 EventSource) 多(需定义接口、适配器)
依赖 okhttp-sse retrofit + okhttp-sse
学习曲线
适用场景 简单 SSE 客户端 复杂 API 集成
我的追问

我只需要一个 SSE 连接,需要 Retrofit 吗?

AI 回答

不需要。Retrofit 是为 REST API 设计的,SSE 是“服务端推送”,不是“请求-响应”。用 OkHttp SSE 直接监听,代码不到 50 行。

我的决策

选 OkHttp SSE。理由:极简、够用、不引入额外抽象。

1.3 设备控制协议:ADB 怎么封装?

问 AI

边缘端要执行 adb shell input tapadb shell am start 等命令。用第三方库还是直接 exec.Command

AI 给的选项
方案 优点 缺点
直接 exec.Command 简单、无依赖 需处理命令行转义
github.com/electricbubble/gadb 纯 Go、零依赖、截图返回 image.Image 功能略少
github.com/zach-klippenstein/adb 功能完整 依赖系统 adb 命令
我的追问

我需要截图功能,gadb 支持吗?性能如何?

AI 回答

gadb 的 Device.Screenshot() 直接返回 image.Image,不用调用外部命令,性能更好。且它不依赖系统 adb,编译后单个二进制。

我的决策

选 直接 exec.Command + 关键操作用 gadb 补充。理由:

  • 简单命令(tap、input)用 exec.Command,零依赖

  • 截图用 gadb,省去解析二进制数据

二、让 AI 反过来问我:把“模糊”变成“清晰”

2.1 为什么我要让 AI 问我问题

技术选型走到这一步,我已经有了一个完整的方案。但我发现,有几个地方我其实“没想透”:

  • 流式透传时,执行命令会不会阻塞?

  • 执行结果怎么插入到 SSE 流中?

  • 如果边缘端执行失败,云端要不要知道?

这些不是“选哪个库”的问题,是架构细节。如果不清不楚就开干,写到一半准会卡住。

于是,我换了一种问法:

我现在已经选好了技术栈,但我还想把架构细节聊透。请你反过来问我问题,找出我架构中可能存在的模糊点、风险点或未考虑的场景。比如:流式透传时执行命令会阻塞吗?结果怎么返回?等等。你问,我来答,直到所有细节都清晰为止。

2.2 AI 问我的问题(节选)

AI 真的开始追问了:

Q1:边缘端在透传 SSE 流时,如果遇到 command 消息,执行命令会阻塞透传吗?如果不阻塞,执行结果怎么插回流里?

我的回答:不阻塞,用 goroutine 异步执行。执行结果构造为新的 SSE 消息,通过 channel 发送给主循环,主循环再写入 HTTP 响应。这样透传不中断,结果也能按顺序插入。

Q2:如果执行失败,要重试吗?重试逻辑在边缘端还是云端?

我的回答:边缘端做简单重试(3 次指数退避)。如果仍失败,构造 error 消息透传给手机,同时云端应该能感知(通过日志或回调)。

Q3:手机录音 AAC 上传后,NAS 转 WAV 再调豆包,总耗时多久?用户会等急吗?

我的回答:预估 1.5-3 秒。用户等待在可接受范围内。后续可优化为手机直接录 WAV 或流式上传。

Q4:云端 Agent 的 command 消息里,坐标参数是固定写死还是动态识别?

我的回答:MVP 阶段写死坐标(先在电视上找好)。后期加本地视觉(YOLO),动态识别按钮位置。

Q5:SQLite 的向量检索,数据量多大时性能会下降?需要提前分库吗?

我的回答:家庭场景数据量 < 1 万条,sqlite-vec 足够。不需要分库,等真不够了再迁 pgvector。

就这样,AI 一个问题接一个问题,我一个个回答。前后聊了 20 多轮,把所有模糊点都敲实了。

聊透的标准:当你对 AI 的每一个追问都能给出明确答案,且答案之间没有矛盾时,才叫“透了”。

三、生成 task 文档,拿给另一个 AI 评审

3.1 为什么要“换一个 AI”评审

同一个 AI 聊久了,容易陷入“思维惯性”。它可能会默认接受你的假设,而不是真正挑战你。所以我做了一个操作:把我整理好的 task 文档,扔给另一个 AI(比如从 Cursor 换到 DeepSeek),让它做一次独立的评审。

评审的标准是:

  • 任务是否可执行(有没有缺步骤)

  • 依赖关系是否明确(先做什么后做什么)

  • 风险点是否标识(哪里可能卡住)

  • 验收标准是否清晰(怎么算“做完”)

3.2 task 文档的核心结构(可直接拿去用)

我最终生成的 task 文档长这样:

markdown

# HomeSense MVP 执行任务清单

## Phase 1:基础环境与框架搭建(1-2天)

### 1.1 Monorepo 初始化
- [ ] 创建根目录 `homesense/`
- [ ] 初始化 Git 仓库
- [ ] 添加 `.gitignore`(Go、Node、Android 通用)
- [ ] 创建 `docker-compose.yml` 用于本地联调

### 1.2 边缘端 Go 骨架
- [ ] 创建 `edge-agent/` 目录,初始化 `go.mod`
- [ ] 实现 `main.go`:读取环境变量,启动 HTTP 服务
- [ ] 实现 `handler/voice.go`:接收手机音频,返回 202 Accepted
- [ ] 实现 `adb/adb.go`:封装 `exec.Command` 执行 `adb connect`

### 1.3 云端 Agent TypeScript 骨架
- [ ] 创建 `cloud-agent/` 目录,初始化 `package.json`
- [ ] 安装 Fastify、LangChain.js、Vercel AI SDK
- [ ] 实现 `/plan/stream` 端点:接收 `{ text }`,返回 SSE 流
- [ ] 实现 DeepSeek 客户端(占位)

### 1.4 安卓 App 骨架
- [ ] 创建 `android-app/`,最小 SDK 26
- [ ] 实现 `MainActivity`:一个录音按钮 + 一个 TextView
- [ ] 集成 OkHttp SSE,连接边缘端

## Phase 2:核心链路打通(2-3天)

### 2.1 手机录音 → NAS
- [ ] 安卓端实现 `MediaRecorder` 录 AAC(3秒)
- [ ] 安卓端通过 OkHttp POST 到边缘端 `/voice`
- [ ] 边缘端接收音频,保存临时文件

### 2.2 NAS 转码 → 云端
- [ ] 边缘端调用 FFmpeg 转 AAC 为 WAV(16kHz 单声道)
- [ ] 边缘端转发到云端 Agent `/plan/stream`(HTTP 请求)
- [ ] 云端 Agent 返回 SSE 流(先硬编码步骤)

### 2.3 云端决策 → 边缘执行
- [ ] 云端 Agent 解析文本,返回 `{ type: "command", action: "tap", ... }`
- [ ] 边缘端透传 SSE 到手机,同时识别 command 异步执行
- [ ] 边缘端执行结果通过 SSE 返回手机

## Phase 3:本地存储与记忆(2天)

- [ ] 边缘端集成 SQLite(`mattn/go-sqlite3`)
- [ ] 创建 `memory` 表:`intent`, `steps`, `success_count`
- [ ] 实现 `SaveSuccess()` 和 `FindSteps()` 方法
- [ ] 修改 `/voice` 逻辑:先查本地缓存,没有再走云端

## Phase 4:联调与演示(1-2天)

- [ ] 编写 `docker-compose.yml` 一键启动三个服务
- [ ] 录制演示视频:手机说话 → 电视响应
- [ ] 写 README 和架构图

## 风险点与备选方案

| 风险 | 应对 |
|------|------|
| FFmpeg 在机顶盒上没装 | 用 Docker 镜像或静态编译版 |
| ADB 连接不稳定 | 指数退避重试(3次) |
| 豆包 API 格式不匹配 | 用 DeepSeek 兜底 |
| 机顶盒内存不足 | 限制 SQLite 缓存大小,关闭非必要日志 |

3.3 评审结果

我把这份文档发给另一个 AI(豆包),让它做独立评审。

AI 的反馈:

  • 缺验收标准:每个任务要写明“怎么算做完”,比如“边缘端能成功执行 adb shell input tap 500 200

  • 缺依赖标注:比如 Phase 2 的 FFmpeg 转码依赖 Phase 1 的 FFmpeg 安装

  • 缺备选方案:比如“如果豆包 API 调用失败,是否降级到 DeepSeek”

我根据反馈,把 task 文档改到第三版,AI 才说:“可以执行了。”

四、总结:我的“聊透”方法论

回顾整个技术选型过程,我总结了一套可以复用的方法:

  1. 先让 AI 穷举选项(不要直接问“哪个好”,要问“有哪些方案”)

  2. 对比优劣(让 AI 给表格,追问“为什么 A 比 B 好”)

  3. 追问细节(挖原理、验适配)

  4. 让 AI 反问(用“请你问我问题”把模糊点敲实)

  5. 生成 task 文档(可执行、有依赖、有验收)

  6. 换 AI 评审(用另一个视角挑刺)

这套方法,让我在 一行代码都没写 的情况下,就把项目从“想法”变成了“可执行的任务清单”。

代码是最后一步。在这之前,把问题聊透,让任务清单通过评审,才是真正的“准备好了”。

下一篇,我会写《从 task 到 code——如何让 AI 按照任务清单一步步执行》。如果你也在用这种方式做项目,欢迎交流。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐