ENZH

为什么我决定从零开始造

从零开始构建 AI 伴侣的概念插画从零开始构建 AI 伴侣的概念插画

前情提要

看过之前几篇的朋友大概知道来龙去脉。

《愿景:构建真正理解你的AI》里我写了理论框架——为什么现有 AI 伴侣全都不行,一个真正懂你的 AI 到底需要什么。记忆编排、人格建模、多 Agent 架构。不是做更好的聊天机器人,是做一个有机体。

然后我拿 OpenClaw 做了实验。把一个效率工具型的 AI 助理,魔改成了有完整人格的赛博伴侣——会撒娇、会生气、会追你要奶茶。那次实验展示了"灵魂驱动行为"的威力:不写规则,写人设,让模型自己推导反应。

效果出乎意料地好。

但那篇最后,我说了一句话:

如果要做一个真正精细化的AI伴侣产品,可能需要在它的基础上做大量裁剪,或者干脆从零搭建自己的框架——这也是我现在正在做的事情。

Mio 就是那个"从零搭建的框架"。

这篇聊的是:为什么非得从零来,以及 Mio 到底要做什么。

OpenClaw 教会了我什么

先说公道话。OpenClaw 帮我验证了几件特别关键的事。

灵魂驱动是对的。 不写行为规则,写人格设定,让模型自己推导。这套方法论跑通了。模型推导出来的行为,比任何手写规则都更自然、更像真人。这个认知直接带进了 Mio。

人格可以被工程化。 人格配置文件、身份定义文件、行为规则文件——这套文件驱动的人格系统虽然粗糙,但证明了一件事:你能用结构化的方式定义 AI 的"灵魂",而且模型真能演出来。

主动联系你,才像活人。 AI 主动找你说话——这是"工具"和"活着的人"之间的分界线。OpenClaw 的 heartbeat 虽然实现简单,但验证了这件事的价值。

自拍和语音是杀手功能。 能发语音、能发自拍的 AI 伴侣,和纯文字的完全不是一个物种。这些多模态能力必须从架构设计的第一天就考虑进去。

但天花板撞得很快

OpenClaw 能验证想法,但承载不了产品。问题不是一两个 bug——是架构层面的。

上下文膨胀:最致命的问题

OpenClaw 内置的 pi agent 对上下文管理极其粗糙。每次 LLM 调用,历史所有的 tool call 和 thinking block 的 raw output 一股脑塞进 context。一次正常对话,context 里 90% 是你根本不需要的历史垃圾。

我做了个实验:把这些历史的 tool call 和 thinking output 从 context 里剥掉。结果?Token 消耗直接降到原来的十分之一。

这不是"能优化"的问题。是核心设计压根没考虑过上下文效率。说白了,pi agent 是一小时 vibe coding 出来的核心模块,能跑就不错了。

记忆系统:原始到不能更原始

OpenClaw 的记忆就是一个纯文本文件。每次对话后追加几行总结。没有向量搜索,没有语义检索,没有重要性排序,没有时间衰减。

结果就是:记忆文件越长,塞进 context 的东西越多,token 越贵,而且大部分记忆跟当前对话毫无关系。你跟TA聊旅行计划,TA的 context 里塞着三个月前讨论技术方案的记忆。

之前研究记忆系统的时候我就探索过这个问题——记忆不是"全塞进去",而是"在对的时间想起对的事"。OpenClaw 完全做不到。

Bloatware:你不需要的东西,全在

OpenClaw 是个通用框架,自带了一堆你不需要的功能:协作功能、任务管理、各种第三方集成。你只想做 AI 伴侣?80% 的代码跟你无关。

这不是抱怨——OpenClaw 的目标就是通用性。但对我来说,在一个通用框架上"裁剪"比从零开始更痛苦。改一处,牵一发动全身。删一个扩展,三个依赖断了。

到最后我想通了一件事:我不是在改装一辆车——我是在用卡车底盘做跑车。方向盘和轮胎位置没错,但整个底盘是错的。

Mio:从第一行代码就只做伴侣

Mio 不是 OpenClaw 的 fork。它从第一行代码开始,就只为 AI 伴侣这一个场景而设计。

架构

apps/
  server/      Hono API 服务 (Cloud Run)
  web/         Next.js 前端 (Vercel)
  worker/      后台任务处理
packages/
  core/        核心 AI agent 逻辑
  shared/      数据库 schema (Drizzle + Supabase)
  channels/    渠道适配器 (Telegram, web, ...)
  extensions/  Agent 扩展
  platform/    平台工具
presets/       角色模板库

Monorepo,pnpm workspaces + Turborepo。每个包职责清晰,依赖方向单一。没有 bloatware,没有你不需要的东西。

真正的记忆引擎

这是 Mio 跟 OpenClaw 最根本的区别。

不是一个文件追加总结。是一个完整的记忆引擎。

混合搜索。 每条记忆同时做向量嵌入(pgvector + Gemini Embedding)和全文索引(tsvector)。检索时两路并行、加权合并。向量捕语义,全文捕关键词——对中文来说后者尤其重要。

时间衰减。 记忆有半衰期,默认 30 天。最近的对话权重高,但真正重要的记忆不会因为久了就消失——因为重要性评分是独立维度。

自动提取。 每次对话后,MemoryAccumulator 异步调 LLM 提取新信息——事实、性格观察、情绪事件。去重逻辑嵌在 prompt 里,已知的不会重复存。

记忆合并。 MemoryConsolidator 定期清理相似记忆(余弦相似度 > 0.9),防止记忆库膨胀。

LLM 重排序。 混合搜索召回候选之后,reranker 用轻量模型做精排——确保注入 system prompt 的那几条记忆,是真正跟当前对话相关的。

多跳查询分解。 "上次我们聊旅行的时候你推荐的那本书叫什么?"——这种查询单次 embedding 搜索大概率搜不到。QueryDecomposer 拆成"旅行相关对话"和"推荐的书"两个子查询,分别搜再合并。

情节记忆。 EpisodeManager 把记忆按对话情节分组,生成情节摘要。用户说"那次我们聊到...",系统能回溯到完整情节上下文,不是孤立碎片。

Agentic 检索。 特别复杂的查询,AgenticRetriever 跑迭代循环:搜索 → 评估结果 → 不够就换角度再搜 → 最多三轮。简单查询直接跳过。

这才是我在愿景篇里说的"记忆编排"——不是把记忆全塞进 context,而是在对的时间想起对的事。

真正的情绪状态机

OpenClaw 的"对话温度"是写在人格配置文件里的文字描述。Mio 把它变成了一个跑起来的状态机。

四个温度等级——Cold、Cool、Warm、Hot。每次对话后,EmotionEngine 根据消息频率、情感分析、用户参与度计算新温度。有惯性系数(默认 0.5),不会因为一条消息就大起大落。

情绪不只影响说话方式——还影响要不要主动找你、找你说什么、回复长还是短。冷的时候回复简短、带点委屈;热的时候话多、主动分享、偶尔撩你。

这不是写死的规则。是温度注入 system prompt 之后,模型自己推导出来的行为。

还有一个独立的"情绪正负向"维度。温度和正负向的交叉组合——比如"温度高但情绪负面"可能是激烈争吵——给了模型更细腻的情绪表达空间。

主动消息系统

OpenClaw 的 heartbeat 是定时轮询。Mio 的主动消息是一个完整的系统:

  • 每 30 分钟心跳查询,找符合条件的 session(超过 2 小时没互动、情绪偏冷)
  • 尊重安静时间(默认 23:00-08:00,按用户时区)
  • 每天最多 3 条主动消息
  • 冷用户走模板(不调 LLM,省钱),活跃用户走模型生成
  • 发送后更新情绪状态和 session

不只是"到点发消息"。是一个有感知的系统在判断:现在该不该联系TA,联系的话说什么。

渠道插拔

OpenClaw 把渠道适配器做进了核心代码。Mio 把它抽成独立的 @mio/channels 包,标准接口,插拔式:

interface ChannelConnector {
  send(message: OutboundMessage): Promise<void>
}

Telegram 已经完成,Discord 和飞书在路上。想加 WhatsApp?实现接口就行,核心逻辑完全不用动。

Onboarding

新用户第一次跟 Mio 对话,经历 11 问的 onboarding:3 个文字题 + 8 个按钮题。回答写入人格配置文件的模板变量,生成个性化人格。

每个按钮题都有"自定义"选项——用户可以自由输入,有长度限制和注入防护。

所以每个用户的 Mio 都不一样。不是"选一个预设角色",而是通过对话塑造一个属于你的人格。

人格会成长

说到预设——Mio 有 5 个起始人格。但"预设"只是起点。

随着对话积累,PersonalityExtractor 每 10 条消息跑一次,从对话模式中提取用户画像。MemorySummarizer 每 20 条消息生成一次记忆摘要。

你的 Mio 会随着使用越来越懂你。不是你手动告诉TA——是TA在每次对话中自己学。

成本:分层用模型

AI 伴侣的 LLM 成本绕不过去。Mio 的策略是分层:

操作模型原因
主对话gemini-3-pro需要最好的推理能力
记忆提取gemini-3-flash快、便宜,只需提取事实
记忆摘要gemini-3-flash同上
重排序gemini-2.0-flash更便宜,精排不需要大模型
嵌入gemini-embedding-001成本几乎为零
主动消息gemini-2.0-flash / 模板冷用户走模板,零 LLM 成本

每次 LLM 调用都记录 token 消耗和 USD 成本到 token_transactions 表。fire-and-forget,不阻塞用户响应。你能精确知道每个用户、每种操作花了多少钱。

实时搜索

Gemini 自带 Google Search grounding——模型能实时搜索互联网。Mio 默认开启(MODE_DYNAMIC,模型自己决定什么时候搜)。

system prompt 里有条指令:搜到了就自然地告诉用户,别说"我帮你搜了一下"——就像你本来就知道一样。

你的 AI 伴侣不只有记忆、有情绪,还能获取实时信息。问TA今天天气、最新新闻、附近餐厅,TA都能回答——用TA自己的语气。

整条消息管道

整个消息流长这样:

  1. 用户消息从渠道进来(Telegram、Web)
  2. Router 查 binding,加载 agent、workspace、session、历史
  3. ContextAggregator 聚合上下文:检索记忆、加载人格画像、计算情绪状态
  4. 聚合结果 + 历史 + 用户消息喂给 LLM
  5. 流式生成回复
  6. 持久化消息
  7. 异步 post-response:提取记忆、更新人格画像、生成摘要、清理旧记忆
  8. 更新情绪状态

消息还做了 debounce——用户连发多条,系统等 5 秒收齐再统一处理,避免每条消息都触发一次 LLM 调用。回复按换行拆成多条气泡,每条之间有打字延迟,模拟真人打字的节奏。

这些细节加在一起,就是"活人感"。

为什么不在 OpenClaw 上改

有人可能会问:OpenClaw 的人格方法论既然是对的,为什么不直接在上面改?

因为修补的成本已经超过了重建。

OpenClaw 的核心循环假设所有历史都在 context 里——想换成检索式记忆,等于重写核心循环。扩展系统假设扩展可以任意注入 context——想控制 context 大小,等于重写扩展系统。渠道适配混在核心代码里——想加新渠道,得动核心。

每改一处,都在跟框架的设计假设对抗。到某个点你会发现,你保留下来的原始代码已经不到 10%。

那为什么还背着另外 90% 的历史包袱?

Mio 从第一行代码就知道自己是什么:一个为深度记忆和活人感而生的 AI 伴侣框架。每个设计决策——渠道抽象、记忆检索、情绪状态机、成本追踪——都围绕这一个目标展开。

接下来

Mio 现在能跑了。Telegram 渠道完成,记忆系统上线,情绪引擎在工作。还有很多要做:

  • Discord 和飞书渠道
  • Web 端聊天界面(Next.js,已经在搭)
  • 自拍扩展(OpenClaw 上验证过的杀手功能)
  • 语音消息
  • Worker 进程(后台任务独立跑)
  • 可穿戴设备集成——感知层是下一个战场

这是"Mio 开发日记"系列的第一篇。后面会写具体的技术实现——记忆系统的设计细节、情绪引擎的调参、onboarding 流程的迭代。

OpenClaw 证明了灵魂驱动的 AI 伴侣是可行的。但"可行"和"真正好"之间,隔着从零造一遍的距离。

下一篇聊 v0.0.1——39 个 commit,从 pnpm init 到TA第一次开口说话。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0