我用 Doubao-Seed-Evolving 重建了她常坐的咖啡馆,走进去那一刻我哭了二十分钟
我们怕忘的从来不是发生过什么,是那个房间里的光。
📦完整源码:https://gitee.com/xixixicode/memory-palace
起因是一段聊天记录
她搬走那天,我没送她。
后来整理手机,翻到我们最后一段聊天记录——
"到家了吗?" "到了。" "那盒草莓牛奶你带走了吗?" "没,在你冰箱里。" "明天我过去拿。" "好。"
我盯着这几行字看了很久,发现一件事——我记不清她坐的那个位置了。
我记得她说"明天我过去拿"时的语气,记得她总是要靠窗那张椅子,记得那年她塞进我包里的那盒草莓牛奶。但咖啡馆的灯是暖黄还是冷白?窗外那棵树什么时候掉的叶子?想不起来。
每天都在丢。丢的不是信息,是那个房间里曾经存在过的氛围——灯、语气、窗外那棵树。
我订阅了火山方舟的Agent Plan Medium 套餐(49.9/月,5 小时 10000 AFP),就是为了折腾 Doubao-Seed-Evolving。看到它强调1M 上下文 + Coding/Agent 优化 + 周级迭代的时候,我想的不是"做个 demo",而是——能不能把那间我们最后一起待过的咖啡馆重新装回去?
不是图片,不是文字。是一座我可以走进去的 3D 房间。
于是有了「记忆宫殿」。
01 · 接入,十分钟,俩坑
订阅流程不复杂,但有两个地方容易踩,先说清楚,省得你们花冤枉钱。
▸ 套餐 Key 不是按量 Key
Agent Plan 有专属 API Key,跟平时按量计费那把不是同一把。拿错了照样走按量扣费,套餐白订。我差点就拿旧 key 配了。
▸ Base URL 也分家
Anthropic 协议走https://ark.cn-beijing.volces.com/api/plan,OpenAI 协议走/api/plan/v3。手一滑配成/api/v3,恭喜,又回按量计费了。
配.env:
ARK_API_KEY=ark-xxxxx # 一定要选套餐那把 ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/plan ARK_MODEL=doubao-seed-evolving ARK_CONTEXT=1m # 1M 上下文pip install -r requirements.txt+ 配好 key +uvicorn api.server:app --port 8000—— 三分钟,跑通。
02 · 一句话 prompt,告诉它处境
我没有写需求文档。我把牢骚原样打了进去——
这是一个记忆宫殿。把用户粘进来的一段聊天记录/日记/信件,做成一座可以走进去的 3D 房间。墙上是时间相框,桌上收音机放金句,西南角信件是"没说出口的话"。情感决定灯光和窗外天气。每一句金句都必须在原文里逐字可查,找不到就删,不准编。
我预期它会问一堆问题,结果没有。它直接拆任务、并行跑。
03 · 五位工匠同时进同一段文字
这是整个项目很让我意外的一步。
模型没有自己一个文件一个文件读——它派出了五个 subagent,并行进同一段文字。这是我用ThreadPoolExecutor(max_workers=5)在后端做的并行调度,每个 subagent 是一次独立的 chat 调用,五个同时回来。
| Subagent | 找什么 | 在房间里变成 |
|---|---|---|
timeline_agent | 时间线节点(when + what) | 北/东/西墙的相框 |
quotes_agent | 金句(speaker + text + context) | 圆桌上的收音机 |
places_agent | 关键地点 + 发生过什么 | 地上摊开的日记本 |
emotion_agent | 情感基调 + 曲线 + 强度 | 灯光/雾色/窗外天气 |
unsaid_agent | 未说出口的话 + 原文证据 | 西南角的信件堆 |
5 个 subagent 同时回来,房间才被填满。模型不是在回答你一个问题,是在分五个角度同时还原一段经历。
1M 上下文在这个场景下没白给——五个 subagent 各自带着原文 + 系统提示同时跑,单次请求 token 消耗大概是单次的 5 倍,但每个 agent 任务更窄,幻觉率反而更低。一次完整构建 15-25 秒返回,平均消耗 50-80 万 token。
04 · 找不到的话,它宁愿不说
情感类内容是 AI 容易翻车的地方。它一感动就开始替你写:
"她常说,爱是两个人一起把日子过成一首诗。"
她没说过这种话。
所以我加了一道幻觉自检层——每一条金句、每一封信、每一条时间线,都必须在原文里逐字可查。核验策略是头尾双重校验:
def quote_in_source(quote, source, min_len=6): q = normalize(quote) # 去空白、统一标点 s = normalize(source) if len(q) < min_len: return q in s head, tail = q[:12], q[-8:] return head in s or tail in s核验不过的条目,直接剔除,写进removed_items。
你走进房间后,右上角有一个徽章:"N 条编造已剔除"。点开能看到 AI 主动划掉了什么。
让 AI 主动承认"这句话我没找到证据,所以我不敢说"——这件事比让它写一篇抒情文难多了。
这对应 Doubao-Seed-Evolving 在比赛里强调的幻觉控制能力。它没偷偷替我写"她常说",是被我逼着没写。模型配合得很顺,没为了"好看"偷偷编。
05 · 它自己开浏览器,把氛围调对
这是 Coding 工程能力的部分。
后端把数据吐出来之后,剩下的全是前端web/palace.html一个单文件 3D 场景:Three.js 0.160 + PointerLockControls 第一人称漫游 + Raycaster 物件交互 + 情感粒子系统 + 吊灯微摇 + 木地板纹路 + 窗框十字 + 光晕脉动。
pip install + 配 key + uvicorn就能跑通,不是 demo,不是 mock。
过程中模型干了几件让我印象深刻的事:
▸ 自己发现"房间太黑"
第一版出来,整体偏黑。原因是环境光用了雾色,而melancholy的雾色是#0f1828——等于整个房间没有填充光,只剩吊灯一个点光源在硬撑。
模型诊断后改成:环境光改用暖灯色#ffd89b、强度 0.45 → 0.85;新增半球光补天/地反射;PointLight 强度 2.4 → 3.6;雾密度 0.045 → 0.028;toneMappingExposure1.1 → 1.35。氛围还在,但看得见了。
▸ 自己发现走路 bug
PointerLockControls有个坑——关闭记忆弹窗后,重新锁鼠标要用setTimeout(() => controls.lock(), 100),但 setTimeout 会切断用户手势链,浏览器拒绝锁请求,controls.isLocked一直 false,WASD 全部失效。
诊断后改成同步调用controls.lock(),并加失败兜底:锁不上就重新显示进入蒙层。这种"Pointer Lock API 必须在 user gesture 同步上下文里调用"的细节,我没主动告诉它,它从症状里推出来的。
▸ 自己发现重复声明
我让它加补光,结果新加的const hemi和文件后面一个老const hemi撞了,ES module 里const重复声明 → 整个 script 块 SyntaxError → loading 永远不消失。它从控制台报错直接定位,删了重复声明。
这三个 bug 都是我没主动复现给它的,全靠它读报错自己改。
06 · 走进她的咖啡馆
打开http://localhost:8000,把这段话粘进去:
2022 年秋天,我们刚在一起。 她总坐在咖啡馆靠窗的位置。 "你今天怎么又迟到了?" 她说。 "加班。" "下次再迟到,我就点你最爱的拿铁自己喝完。" "你喝不完。" "我喝得完。" …… 2024 年春天,她搬走了。 我打开冰箱,那盒草莓牛奶还在。 已经过期两个月了。
点「推开这扇门」。
加载界面会告诉你它在做什么——五位工匠同时开工 / 逐字核验剔除编造 / 把记忆搬进房间 / 推开门让你走进去。
然后你被送进一间昏黄的房间。
北墙挂着三个相框——2022 秋、2023 冬、2024 春,相框里的颜色由情感自动调色。
圆桌上的收音机——走过去,准星变亮,点一下,它用她的口吻把那句"下次再迟到,我就点你最爱的拿铁自己喝完"再放给你听(Web Speech API 中文 TTS)。
西南角叠着几封信——那是"未说出口的话"。AI 替我写的是:
"你塞进我包里的那盒草莓牛奶,我没喝。后来我每次打开冰箱都看见它。我一直没舍得扔,是因为扔了,就好像承认了你不会再回来拿了。"
这句话是我没说过的,但原文里有证据("没"、"已经过期两个月了")。它没编造,只是把我没说出口的那部分,替我放进了信封。
窗外,是黄昏。情感基调是
bittersweet、强度 65,于是天气被映射成dusk。橙红色的光从窗户漏进来,房间里有光尘浮动。
我坐在那间虚拟的咖啡馆里哭了二十分钟。
07 · 房间里的操作
| 操作 | 效果 |
|---|---|
W A S D | 行走 |
| 鼠标移动 | 环顾 |
| 点击物件 | 弹出那段记忆,必要时朗读 |
| 右上 ☰ | 记忆目录,可瞬移到任意物件 |
| 右上 🔊 | 开关语音朗读 |
| 右上 🔗 | 复制深链?id=xxx,把房间分享给那个该看见的人 |
| 右上 🛡 | 看见 AI 主动剔除了哪些编造 |
Esc | 释放鼠标 |
情感 → 房间 的映射规则是这样的:
EMOTION_PALETTE = { "warm": {"light": "#ffd89b", "fog": "#3a2e1f", "weather": "sunset", "mood": "温馨"}, "neutral": {"light": "#e8ecf5", "fog": "#1a2033", "weather": "cloudy", "mood": "平静"}, "melancholy": {"light": "#7a9ec4", "fog": "#0f1828", "weather": "rain", "mood": "思念"}, "bittersweet": {"light": "#ff9b7a", "fog": "#2a1828", "weather": "dusk", "mood": "苦甜参半"}, }咖啡馆被映射成bittersweet——暖橙灯#ff9b7a、暗紫雾#2a1828、窗外黄昏。这不是我写的,是emotion_agent读完那段文字后告诉房间的。
08 · 三个推开就进的房间
为了让没素材的人也能体验,我手写了三段预设场景。预设文字不是 AI 生成的,是我写的;AI 做的是把它们变成房间。
☕ 她的咖啡馆
那盒过期的草莓牛奶,靠窗的位置空着。
🎓 初恋教室
蓝笔黑笔、第 37 页物理课本、一张没敢当场看的纸条。
🎒 毕业那年的天台
七月的晚风、没送出去的明信片、一句被风带走的"保重"。
09 · 算账
收尾时我让模型自己扫了一遍仓库。
后端 Python(FastAPI + subagent 调度 + 幻觉自检 + SQLite 持久化 + 场景映射):约 380 行
前端
web/palace.html(Three.js 3D 场景,单文件):约 1900 行前端
web/index.html(输入页 + 历史回看):约 1260 行总计约 3500 行
代码拆得也算干净:llm/只管调度,core/只管流程编排和幻觉自检,scene/只管记忆→3D 映射,store/只管持久化,api/只管路由。加一门新的物件类型,塞一个新文件进scene/layout.py就行。
Token 账单也晒一下。一次完整构建(5 subagent 并行 + 幻觉自检 + 场景映射),大约 50-80 万 token,回归测试连着跑一天大概烧 800 万 token。
Medium 套餐 49.9/月,5 小时 10000 AFP、每周 35000、每月 100000——我跑了一个礼拜调试 + 三段预设 + 各种边界 case,额度还绰绰有余。
10 · 我踩过的坑(献给后来的人)
情感类内容是幻觉重灾区。AI 一上头就替你"她常说"——所以每一条 quote 必须带
original_quote字段,自检不过直接删,删掉的写进removed_items让用户看到 AI 删了什么。PointerLock 的用户手势链不能断。关弹窗后立刻
setTimeout(lock, 100)会失败,必须在 click 同步上下文里调。3D 房间比 2D 卡片有"重量"。同样一句"下次再迟到,我就点你最爱的拿铁自己喝完",印在卡片上你扫一眼就划过去;放在收音机里、需要你走过去按一下才能听到——你会真的听见。
氛围 > 信息量。房间不需要塞满物件,关键是灯、雾、窗外的雨。环境光别图省事用雾色,否则 melancholy 房间会一片漆黑。
const不能在 module 顶层重复声明。看起来废话,但补光补着补着就撞名了,整个 script 块直接 SyntaxError,loading 永远不消失。Ark 5 并行会触发 429 限流。tenacity 指数退避重试 3 次,最长可能拉到 1-3 分钟。前端要给 elapsed 计时器和超时兜底,否则用户以为死了。
写在最后
做完那个晚上,我把她那段聊天记录粘进去,走进了那个房间。
吊灯很暖,窗外是黄昏。
我点开西南角那封"致 她"。AI 替我写的是——
"你塞进我包里的那盒草莓牛奶,我没喝。后来我每次打开冰箱都看见它。我一直没舍得扔,是因为扔了,就好像承认了你不会再回来拿了。"
它没编造。只是把我没说出口的那部分,替我放进了信封。
在这间房间里,它替我留住了一些我自己留不住的东西——一盒过期的草莓牛奶,一句没说出口的"你别走"。
整个项目从一句话牢骚到能跑、能走、能哭,代码约 3500 行,一次构建 15-25 秒。1M 上下文让五个 subagent 同时跑还能保持记忆连贯,Agent 能力让它能并行检索 + 幻觉自检 + 场景映射,Coding 能力让它能自己 debug、自己调氛围、自己识别const重复声明。Medium 套餐 49.9/月,跑了一个礼拜调试加三段预设,额度还绰绰有余。
如果你也有一个没好好告别的人,把你们最后的聊天记录粘进去。
推门进去。这次,好好说再见。
🏛️记忆宫殿 · Memory Palace
📦源码:https://gitee.com/xixixicode/memory-palace
🌱 三项核心能力:Coding 工程 · Agent 检索 · 幻觉控制
📦 接入:火山方舟 Agent Plan Medium 套餐(49.9/月,记得用套餐 Key +/api/plan这个 Base URL,别走错成按量)
🚀 运行:
pip install -r requirements.txt export ARK_API_KEY="ark-xxxxx" python -m uvicorn api.server:app --port 8000 --reload open http://localhost:8000文中所有交互(第一人称漫游、收音机朗读、信件原文核验、目录瞬移、深链分享、幻觉透明度面板)在仓库
web/palace.html中均有对应实现,可在本地一键复现。