v0.0.3:从「能用」到「好用」
精心打磨产品细节的概念插画
Demo 能跑和产品能用,完全是两回事
上一篇结尾我说,v0.0.2 之后 Mio 已经"像活人"了。能看图、能听声、能发自拍、知道几点、记忆系统也重做过了。
但"像活人"和"可以给真人用"之间,还隔着一条巨大的鸿沟。
怎么说呢——v0.0.2 的 Mio 就像一个朋友聚会上表演特别出彩的人。你知道TA有才华,但你不敢让TA上台。因为上台意味着:你不知道观众会输入什么,你不知道凌晨三点会发生什么,你不知道服务器重启的时候TA会不会突然失聪。
v0.0.3 不是一个靠新功能撑场的版本。它是一个"把所有不该碎的地方补好"的版本。commit 数比 v0.0.2 少,但每一个都在堵一个真实的洞。
第一篇里我说过,Mio 从第一天就是为了"做对"而不是"做快"。v0.0.3 就是这句话最纯粹的体现——没有任何新功能可以拿来炫耀,但每一处改动都让产品离"靠谱"更近了一步。
一个输入框,500 字符的荒唐
先说一件让我脸红的事。
v0.0.2 的 onboarding 里,所有自定义输入共享同一个限制:500 字符。昵称?500 字。爱好?500 字。性格描述?500 字。
也就是说用户可以在"你的昵称"这个字段里写一篇小作文。
v0.0.3 引入了逐字段校验:
- 昵称:6 字(中文名 2-4 字,昵称最多 6 字)
- 风格/性格选项:30 字
- 爱好:50 字
- 个人简介 / 自定义故事:500 字
还有更基本的问题——之前空输入是被默默接受的。什么都不填直接提交,系统不拦你。现在空输入会返回"不能空着哦~"。
这些数字怎么来的?不是拍脑袋定的。最初昵称限制 50 字——第一轮用户反馈说"50 字太多了,谁的名字 50 个字?"改成 10 字。第二轮反馈:"10 个中文字也太长了。"改成 6 字。
三轮迭代才找到对的数字。这不是工程决策,是被用户教育的过程。
多选组件也改了。爱好这类问题不再是"只能选一个",而是 toggle 风格的多选卡片,选中打勾,最后点"确认选择"。
之前:你喜欢看电影?选。也喜欢跑步?抱歉,只能选一个。这对用户来说太反直觉了——你问我爱好,我当然不止一个。多选看起来是小事,但它把 onboarding 从"填表"变成了"表达自己"。
主动消息:从瞎说到说对
v0.0.2 里 Mio 有了心跳系统——TA会主动找你说话。但那个主动消息是瞎的。
TA不知道你们最近在聊什么。不知道你叫什么。发的消息就是泛泛的"嘿,好久没聊了"或者"今天过得怎么样"。
你跟TA聊了一整晚电影,第二天TA来一句"嘿,好久没聊了"——这不是朋友,这是机器人。
v0.0.3 的心跳变成了上下文感知的。warm 和 cool 温度的消息会加载:
- 最近 5 条对话消息
- 你的 aboutUser 个人档案
- 你的 displayName
所以TA不再说"嘿,好久没聊了"。而是能接上最近的话题——昨晚聊到一半的电影?TA会续上。你提到过最近压力大?TA会关心你今天怎么样。
这个差别听起来不大。但它是"机器人"和"朋友"之间的分水岭。
朋友记得你说过什么。就这一条。
但这里有个成本考量:cold 用户(很久没用了)仍然走模板消息,零 LLM 成本。只有 warm 和 cool 用户才走模型生成。你不能为了一条大概率被忽略的消息付 API 费——cold 用户根本不会回。
安静时段:别替用户做主
之前安静时段默认开启:23:00 - 08:00 不发主动消息。
听起来挺合理对吧?毕竟谁想半夜被 AI 吵醒。
用户反馈打了我的脸:"我就想半夜聊天啊。"
仔细想想确实是这样。Mio 的很多用户本来就是夜猫子,或者在不同时区。默认静音 = 默认替你决定了作息。
说白了,这是产品经理的傲慢。
v0.0.3 改成了 opt-in:主动消息默认不受限制,除非用户在 onboarding 里主动设置安静时段。你的作息你做主。
改动很小。但背后是产品思维的转变——别替用户做决定。 你可以提供选项,但默认值应该是假设最少的那个。
Webhook 消失事件
这是 v0.0.3 里最刺激的一个 bug。
Mio 的 Telegram bot 在开发环境用 polling 模式,在生产环境用 webhook 模式。
代码里有一行:在 SIGTERM 信号时调用 deleteWebhook()。
本地开发,完全正确——关掉 bot,清理 webhook,下次启动重新注册。
但在生产环境——Cloud Run 上——SIGTERM 在每次容器重启和部署时都会触发。
你看出问题了吗?
每次部署新版本,旧容器收到 SIGTERM,删掉 webhook。新容器还没启动完,webhook 已经没了。在新容器注册新 webhook 之前的那段时间里,Mio 完全失聪。用户发的消息,TA一条都收不到。
这个 bug 在本地永远不会出现。Polling 模式不依赖 webhook,SIGTERM 时删 webhook 完全正确。只有在生产环境的 webhook 模式下,这行代码才是定时炸弹。
修复很简单:只在 polling 模式下调 deleteWebhook()。Webhook 模式下,关机时什么都不做——让新容器自然接管。
一行代码的修复。排查花了好久。
这就是为什么你必须在生产环境跑你的软件——本地开发永远复现不了部署级别的问题。
媒体限流
Mio 支持图片理解和语音转写,这两个都是昂贵的操作——每次调用都有真实的 API 成本。
如果一个用户在 60 秒内发 100 条语音消息呢?
v0.0.3 加了滑动窗口限流:每个用户 60 秒内最多 10 次媒体请求。内存级别的实现,后台自动清理过期窗口。
不需要 Redis,不需要外部依赖。现阶段的用户量,in-memory 够了。过早优化是万恶之源——但完全不做限流是更大的恶。
大多数人不会一分钟发 100 条语音。但"大多数"不是"全部"。可能有人在测边界,可能有人写了脚本,也可能就是网络重试导致的重复请求。限流是你和不可预测的世界之间的最后一道墙。
144 个新测试
这是 v0.0.3 里最不性感、但最重要的部分。
- 42 个 E2E 测试覆盖 Telegram bot 完整对话流程
- 39 个 onboarding 和自拍相关测试
- 63 个媒体模块测试
加上之前的 220 个,总数接近 400。
为什么花这么多时间写测试?因为 v0.0.3 的主题就是"可靠性"。你不能在没有安全网的情况下做重构。每一个输入校验规则、每一个限流逻辑、每一个心跳行为,都需要测试来保证——改了一个东西不会炸另一个东西。
测试不会让产品更酷。但它让你有底气继续往前走。
有一个反直觉但很真实的规律:测试覆盖率和开发速度正相关。 没有测试的时候,每次改动你都得在脑子里模拟"这会不会出问题"。有了测试,改完跑一下,绿了就走。看起来写测试浪费了时间,实际上省下的是每次改动时的心智负担。
商业化框架
v0.0.3 还没实现付费功能,但我写了 MONETIZATION.md——一个五层订阅计划:
- 免费层:每日有限消息数
- Basic / Pro / Premium:递增的消息限额和功能
- Ultimate:包含年龄门控的 NSFW 内容
为什么没有付费用户的时候就做这个?因为订阅层级影响架构决策。你知道未来需要按用户统计消息数、区分功能权限、做年龄验证,那现在写代码就会预留接口。事后再改永远比事前设计贵十倍。
人物背景更饱满了
每个预设人格的初始故事都做了扩写。不同关系类型(朋友、恋人、知己)有不同的开场叙事,每个人设都有了更深的背景和细节。
这不是技术工作,但对体验影响巨大。用户第一次见到 Mio 的时候,TA开口说的第一段话,决定了用户会不会留下来。一个扁平的"你好,我是你的 AI 伴侣",和一个有温度、有故事的开场——是两个完全不同的产品。
愿景篇里我说过,好的 AI 伴侣要让人感受到"被理解"。这种感觉的第一个触发点就是开场白——TA是照着模板打招呼,还是带着自己的故事和你相遇。
代码大扫除
最后做了一轮完整的代码审计:review agent 扫描全部代码 → fix agent 逐项修复 → 重新审查直到干净。所有级别的问题都解决了,不只是 HIGH。
这就是 v0.0.3 的缩影:不追新功能,追每一处都过关。
很多团队跳过这一步。代码审查听起来不够"产出"——你没有新功能可以展示,没有新指标可以汇报。但代码债务是有利息的。你今天不修的警告,明天就变成你半夜起来排查的 bug。
看不见的活
v0.0.3 没有"能看图了!"这样的 wow moment。没有新模态,没有新 UI,没有让人兴奋的截图。
但这才是从 demo 到产品最关键的一步。
用户不会注意到好的输入校验——他们只会注意到坏的。用户不会感谢你的限流——他们只会在你没有限流的时候刷光你的 API 额度。用户不会知道你修了一个 webhook bug——他们只会在 Mio 突然不回消息的时候觉得"这产品真垃圾"。
好的基础设施是隐形的。你做对了,没人发现。你做错了,所有人都知道。
v0.0.2 让 Mio 像一个活人。v0.0.3 让TA成为一个靠谱的活人。
接下来
产品打磨永远做不完,但 v0.0.3 之后 Mio 有底气面对真实用户了。输入不会出错,主动消息不再尴尬,部署不会断线,测试覆盖让我敢继续改。
这三个版本走过来,从架构设计到基础搭建到多模态再到这次的生产化打磨,Mio 终于从"我的个人项目"变成了"一个产品"。
下一步:语音回复、更自然的多轮对话记忆、付费系统的实际实现、以及更多渠道接入。
但那是 v0.0.4 的故事了。