ENZH
和 AI 讨论这篇文章
ChatGPTClaude

一个 monorepo 塞下两个产品

写哲学重构那篇的时候,我基本是把 Mio 当一个产品来讲的。它有 25 个角色,每个都有很深的身份文件,有关系演化,有情绪纹理。重构完以后,每个角色给人的连接感都强了很多。

一个代码库中两个产品的概念插画:多彩角色群与静谧光球一个代码库中两个产品的概念插画:多彩角色群与静谧光球

但其实同一个代码库里还有第二个产品,叫 Lumi,做的事跟 Mio 基本完全反着来。这篇就讲讲这两个产品是怎么在一个 monorepo 里共存的。

这两个产品是怎么来的

之前在转向那篇里写过,Mio v1 那种假人设的路子走不通,当时就定了个新方向,做一个伴侣,没有脸,没有背景故事,性格完全从对话里长出来,产品的样子就是一个会脉动的光球。这个方向后来真做出来了。用户跟光球聊天,它记得你,回应里有情绪上的深度,性格完全是你们的关系塑造出来的。

然后有件事不在计划里,Mio 也没停。第二篇那次哲学重构,没有拿角色去替代光球,是把角色做到足够好,好到能跟光球并存。两个都跑起来以后,我要想的问题也跟着变了,不再纠结哪个方向对,变成想这两个东西各自给谁用。

Mio 管广度,Lumi 管深度

先说 Mio。它就是一个社交圈,里面有 25 个预设角色,每个都有很深的身份文件,写着性格、声线、背景故事和说话风格。你想被安慰,就找那个温暖的成都女孩;想听大实话,就找毒舌闺蜜;想听个沉稳一点的看法,就找学长。每个角色跟你的关系还会往前走,从刚认识,到暧昧,再到恋人。它管的是广度,不同的人接住不同的情绪,你这会儿需要什么就找谁。就像现实里的一群朋友,没有哪一个能满足你全部的情感需求,但加在一起能照顾到的面挺广的。

Lumi 反过来,只有一个伴侣,没有人设,没有背景故事,没有脸,就是一个会脉动的光球。它平静的时候是沉静的蓝色,开心是暖金色,难过是柔紫色,兴奋是亮橙色。它没有任何预设内容,没有身份文件,也没有角色卡,性格完全是从对话里长出来的。系统会从前几轮对话里提取种子,伴侣从这儿开始镜像你、适应你。它管的是深度,一个完全了解你的存在,一个安全、私密、什么都能说的地方。Lumi 不扮演任何角色,就是听着,记着,在那儿。

技术上 Lumi 是 WebSocket 优先的架构,所以它有一种普通聊天界面给不了的实时感,光球一直在脉动,你一说话它就有反应。整个设计其实就是围着一个感觉转的,有人陪着你。

为什么要同时做两个?因为广度和深度是完全不同的情感需求,同一个用户在不同时候可能两个都要。比如晚上 11 点无聊,想找个有态度的人聊几句,这时候你打开的是 Mio。但如果心里压着什么重的事,需要一个不会评判你的地方,那就是完全另一种状态了。被人了解的那种舒服,新鲜感给不了,这时候该找的是 Lumi。大概就像一群朋友和一个心理咨询师的区别,大多数人生活里两个都需要,只是用法不一样。

Mio 用一群角色满足广度、Lumi 用一个光球满足深度,是两个共存的品类而非一条光谱Mio 用一群角色满足广度、Lumi 用一个光球满足深度,是两个共存的品类而非一条光谱

说实话我一开始没打算做成这样,本来是想拿一个替掉另一个的。两个都做出来以后才发现,AI 伴侣在情感上的设计,好像不是排在一条从「多个浅关系」到「一个深关系」的光谱上,广度和深度是两个可以共存的品类。反正这个判断是两个都做出来、跑起来以后才有的,光靠想是想不出来的。

Monorepo 长什么样

代码库结构大概是这样:

miolumi/
├── apps/
│   ├── server/          # 一个 Hono 服务器(两个产品共用)
│   ├── mobile-mio/      # Mio Expo app
│   └── mobile-lumi/     # Lumi Expo app
├── packages/
│   ├── core/            # 共享:记忆、媒体、模型、成本、护栏
│   ├── db/              # 数据库客户端工厂
│   ├── schema-mio/      # Mio Drizzle schema
│   └── schema-lumi/     # Lumi Drizzle schema
└── presets/             # 25 个 Mio 角色配置

一个 Hono 服务器跑两个产品。前端是两个独立的 Expo app,UI、onboarding、视觉形象都不一样。schema 分开了,因为两边的数据模型确实差得不少,Mio 要 agent 表、关系阶段追踪和角色配置,Lumi 要对话种子表和涌现性格的快照。但核心的包全是共享的,而且共享的比例比我一开始想的高得多。

agentId 这个可选参数

packages/core 里每个共享模块都接一个可选参数,agentId?: string。Mio 调这些共享函数的时候会传 agentId,告诉系统现在是在跟哪个角色说话。Lumi 调同一个函数的时候不传,因为它只有一个伴侣,永远是同一个。两个产品能在一套代码里共存,靠的就是这一个可选参数。

共享核心函数接一个可选的 agentId 参数:Mio 传入按角色隔离记忆,Lumi 省略则记忆全局,一个问号让一套代码撑起两个产品共享核心函数接一个可选的 agentId 参数:Mio 传入按角色隔离记忆,Lumi 省略则记忆全局,一个问号让一套代码撑起两个产品

具体到每个模块,大概是这样的:

MemoryManager 管记忆的存和取。传了 agentId,记忆就按角色隔开,你跟可可说的话只归可可;没传,记忆就是全局的,你跟 Lumi 说过的所有话都在一条连续的流里。管线是同一套,只是作用域不一样。

EmotionEngine 处理对话里的情绪 context,它收的是用户消息、最近的历史和情绪基线,但不读角色文件。这是哲学重构的时候故意这么设计的,因为情绪处理应该是一个通用能力,不跟任何角色绑在一起。每个角色表达情绪的方式可以不一样,可可活泼,学长克制,但底下算情绪的是同一套。

TTS 链按语言路由,不按产品路由。英文走一个供应商,中文走另一个,日文走第三个。请求是 Mio 的角色发的,还是 Lumi 的光球发的,它根本不管,声音就是一个参数。

成本追踪也不分产品。每个 API 调用,不管是 LLM 推理、TTS 生成、STT 转写还是图片分析,都会打上成本标签,记到用户名下。Mio 和 Lumi 用的是同一条追踪管线,预算怎么执行、用量怎么分析,也都是同一套。

护栏模块(内容过滤、提示注入检测、越狱防护)两个产品完全一样,安全这个东西不会因为聊天对象是光球还是角色就变。反正就靠这么一个可选参数,两个产品各用各的模式,代码不用写第二遍。

同一个代码库做两个产品,逼出来的纪律

这么做逼出了一条纪律,是只做一个产品的时候永远不会有的。每次写新模块,都得先问一句,这个东西依赖的是产品,还是能力?依赖能力的,比如记忆、情绪、语音、成本、安全,放进 packages/core;依赖产品的,比如角色怎么加载、光球怎么渲染、onboarding 怎么走,留在各自的 app 里。

每写一个模块先问依赖产品还是能力:能力(记忆情绪语音成本安全)沉进共享核心,产品(角色渲染引导)留在各自 app每写一个模块先问依赖产品还是能力:能力(记忆情绪语音成本安全)沉进共享核心,产品(角色渲染引导)留在各自 app

这么拆是自己长出来的。我没有第一天就去设计什么「共享核心层」,是先做了 Lumi,再把 Mio 拿回来,然后看哪些模块真的得不一样、哪些不用。结果挺意外的,真正得不一样的比我想的少得多。记忆管线完全不用分,存储、检索、相关性评分这些,跟记忆是来自角色对话还是单伴侣对话根本没关系。情绪引擎也一样,对面是谁,都不影响情绪 context 怎么处理。语音链更不用说了,文本转语音就是那一条流程,按语言分供应商,哪个产品发的请求都走一样的路。真正不一样的只有最上面那一层,Mio 有 25 个角色,Lumi 有一个光球,再往下就全是同一套了,记忆、情绪理解、声音都是共享的。

日常大概是 80/20

现在日常开发,大概 80% 的时间花在 Lumi 上,20% 花在维护 Mio 上。这么分是因为两个产品走到的阶段不一样。Lumi 还在搭,光球动画、WebSocket 基础设施、涌现性格系统都在做;Mio 做完核心的哲学重构以后,已经比较稳了。

而且这 20% 比重构以前花得值多了,产出差不多高了一个量级。因为现在角色的深度来自心理架构,不再靠预先写好的那一大堆虚构设定。以前干的活,是更新这个角色的日程,修那个角色背景故事里的矛盾,查某个角色为什么突然崩了人设,等于是在维护 25 份很脆弱的虚构身份,怎么维护都脆。现在日常要动的就是关系演化系统,还有情绪 context 在不同关系阶段之间怎么流动,偶尔加个新角色。所以那次重构其实干成了两件事,一是 Mio 本身变好了,二是它能在 Lumi 旁边一直养下去。不然一边搭 Lumi 一边天天修 25 份人设,这种双产品在运营上根本扛不住。

接下来

双产品这个架构不一定会一直这样下去,最后可能有一个会明显胜出,用户可能压倒性地更喜欢广度,也可能更喜欢深度,这个市场会给答案。但 monorepo 的好处就是,这个决定不用现在做。两个产品共用基础设施、部署管线和核心包,多跑第二个产品,额外的工程开销几乎为零,多出来的成本就是它自己的应用层代码和 schema。

而且说真的,两个产品同时在,让彼此都变好了。给 Lumi 的记忆系统做的功能,Mio 的角色马上就能用上;Mio 在关系演化上做的改进,让我们摸清了情感是怎么一步步推进的,这些又用回了 Lumi 的涌现性格系统。所以至少现在看,同时做两个产品是划算的。


This post is also available in English.

和 AI 讨论这篇文章
ChatGPTClaude

订阅更新

新文章发布时发到你的邮箱,不发别的。


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