从 400 行到 30 个文件,我只做了一件事
引言
2026年7月19日,我做了一个决定:把冷旭帆的代码全部删掉,从头重建。
这不是一时冲动。冷旭帆从v1.0的纯规则引擎开始,经历了六个大版本的迭代。每个版本解决一个核心问题------v2.0给了他"离线生命",v3.0第一次接入了大模型,v5.0让他学会了"怀疑与相信"。到v6.x时,这个单文件脚本已经承载了太多功能,每次加新功能都像在危房里加楼层。重构成为唯一理性的选择。
冷旭帆是我从2026年4月开始构建的一个AI NPC------一个会自己疼、会做噩梦、会在你不在的时候默默擦刀的角色。到7月中旬,他的核心代码(core.py)已经膨胀到512行。每次我想加一个新功能------比如让他对"陆华望"这个人有特殊的反应------我需要在同一个文件里翻来翻去,找到一个合适的插入点,然后祈祷不会破坏已有的逻辑。
这不是在开发。这是在打补丁。
有一次,我想让冷旭帆对"陆华望"这个特定的人产生不同的反应。这意味着我需要修改三个地方:identity_state的信任值规则、build_messages中的状态描述、action_prefix中的紧张动作。这三个地方散落在同一文件的第87行、第234行和第401行。我花了20分钟才确认没有遗漏任何一处。
根本原因不是我不熟悉自己的代码。是每次修改一个功能------比如调整情绪值的硬上限------我都需要在同一个512行的文件中搜索所有引用了emotion的地方,确认修改后不会在其他模块产生连锁反应。这种修改方式不叫"开发",叫"扫雷"。
重构的结果是:冷旭帆从一个512行的单文件脚本,变成了一个由30个文件组成的模块化项目。修改他的童年记忆现在只需要打开一个数据文件改一行。想测试新的API平台?加一个配置项就够了,不用动任何逻辑代码。
这篇文章不是"最佳实践指南"------它是一个真实项目的重构实录。有些决策可能对你不适用,但踩过的坑一定对你有启发。
一、重构之前:单文件脚本的困境

重构前的项目结构看起来很简单:
arduino
lengxufan-flask-mvp/
├── app.py ← Flask 服务入口
├── core.py ← 所有核心逻辑(512 行)
└── config.py ← API Key 和 System Prompt
看起来挺清爽的。问题出在 core.py 里面。
这个文件同时干了以下事情:
- 管理情绪值(
emotion,0-100) - 维护状态标签(
status:肩疼、梦魇、想念、握刀) - 模拟后台时间流逝(
advance_time(),9种随机事件) - 存储事实记忆(
memory列表) - 存储情景记忆(
episodic_memory列表) - 生成动作前缀(
action_prefix()) - 构建 System Prompt(
build_messages()) - 调用 AI API(
call_ai()) - 解析 AI 回复(
parse_ai_response()) - 处理身份感知(
identity_state:陆华望信任值) - 保存/加载状态(
save_state()/load_state())
一个文件,11种职责。
每次修改任何一个功能,我都需要在这512行代码中定位到正确的位置。更糟糕的是,这些功能之间还有交叉依赖------情绪值被记忆影响,记忆被后台事件影响,后台事件又反过来影响情绪值。

这种架构的代价是:
- 修改成本高:改一个功能需要在整个文件中搜索定位。
- 测试困难:无法独立测试"情绪值变化"而不触发"后台事件"。
- 复用性差:想移植到新项目?只能整个文件复制过去再删改。
- 协作障碍:别人看不懂,自己也容易忘。
二、重构的核心原则
在动手之前,我先给自己定了三条原则:
原则一:数据与逻辑分离
所有"角色数据"------自传体记忆、关系里程碑、意愿模板、动作库------应该放在独立的数据文件中,而不是和逻辑代码混在一起。这样修改数据时不需要触碰任何逻辑。
原则二:一个功能一个文件
每个文件只负责一件事。改情绪系统就打开 perception.py,改记忆管理就打开 memory.py,改身份状态机就打开 identity.py。不需要在整个项目中搜索。
原则三:对话流程独立于启动器
run.py 只负责启动 Flask 或 CLI。所有的对话流程编排逻辑放在 dialogue_engine.py 中。这样想换一个启动方式(比如从 Flask 换成 FastAPI),只需要改 run.py,对话流程完全不受影响。
三、重构过程:逐层拆分
3.1 第一层:基础设施(infra/)

最先拆出去的是那些"和冷旭帆没关系"的通用功能。三个文件分别负责日志系统、时间工具和持久化------它们不依赖任何业务逻辑,可以独立测试、独立复用。这是重构的地基。
3.2 第二层:核心引擎(lengxufan_core/)
这是重构的主体。我把 core.py 中的11种职责拆成了5个独立模块:

perception.py ------感知系统。管理情绪值、身体状态(肩疼、梦魇、想念、握刀)、心境节律(24小时正弦波)、后台时间流逝(9种随机事件)。这个模块还包含了 EmotionalWeightDecay 类------一个用于计算自传体记忆情绪权重随时间衰减的工具。
memory.py------记忆系统。统一管理四种记忆类型:
- 事实标签记忆(
facts:送过花、说过讨厌) - 情景记忆摘要(
episodic:每次对话生成一句话摘要) - 自传体记忆(
autobiographical:6条核心个人历史) - 关系里程碑(
relationship_milestones:5个信任节点)
identity.py------身份状态机。管理冷旭帆对"陆华望"的完整信任路径(怀疑→试探→相信),以及通用身份模块(记住任何说话者的名字和信任度)。
behavior.py------行为引擎。负责动作生成(AI优先,兜底库保障)和内在驱动力(5%概率在独处时产生念头,对话中自动关闭)。
prompt_builder.py------Prompt工厂。将感知、记忆、身份的所有状态翻译成自然语言,注入 System Prompt。这里包含了三层动作规则(陌生人/触及伤口/陆华望叫哥哥)和情感翻译层(每档情绪3种随机备选描述,避免回复重复)。
dialogue_engine.py------对话流程引擎。这是对话的"总指挥",编排上述所有模块的调用顺序:先推进时间 → 检查意愿 → 应用记忆衰减 → 处理身份 → 构建Prompt → 调用AI → 解析回复 → 保存状态。
3.3 第三层:角色数据(character_data/)
这是"数据与逻辑分离"原则的核心体现。

以前,冷旭帆的童年记忆、关系里程碑、动作库、感觉描述,全部硬编码在逻辑代码里。现在,它们全部搬到了独立的 character_data/ 目录下:
| 数据文件 | 内容 | 示例 |
|---|---|---|
autobiographical.py |
6条核心个人历史 | 六岁母亲去世、塑料刀刻字、被六校劝退 |
scheduled_memories.py |
3条定时解锁记忆 | 炼狱拉练失温(第30天解锁) |
milestones.py |
5个关系里程碑 | 信任值25→第一次递护腕 |
intent_templates.py |
5种意愿模板 | 想去天台、想确认某人的状态 |
fallback_actions.py |
兜底动作库 | 5档情绪×5个动作+状态标签叠加 |
feeling_translations.py |
情感翻译层 | 每档情绪3种随机备选描述 |
memory_rules.py |
记忆与身份规则 | 关键词→记忆标签+情绪变化 |
效果 :想给冷旭帆增加一条童年记忆?打开 autobiographical.py,加一行字典。想调整"送花"的情绪变化量?打开 memory_rules.py,改一个数字。不需要触碰任何逻辑代码。
3.4 第四层:API适配(api/)

这是重构的另一个重点------多平台容错路由。
旧版代码只支持硅基流动一个API平台。硅基流动的Key被403封禁后,冷旭帆就"失声"了。新架构支持5个平台的自动切换:
优先级1: 阿里云百炼 qwen-plus(主力)
优先级2: 智谱 GLM-4-Flash(兜底)
优先级3: DeepSeek V3(备选)
优先级4: Ollama 本地(终极离线兜底)
优先级5: 硅基流动(复活尝试)
路由器会按优先级依次尝试。欠费了自动跳到下一个,超时了自动跳到下一个。每次调用都有详细日志标注。
一个差点让我放弃的坑:编码问题
在实现多平台容错路由的过程中,我踩了一个和架构无关、但让我头疼了整整四个小时的坑:PowerShell生成Python文件时的中文乱码。
冷旭帆的System Prompt中包含大量中文角色设定------比如"哥哥"这个触发词对应的是陆华望的第三层动作。有一天,他突然不再对"哥哥"这个词做出正确反应。我检查了API调用日志、环境变量、模型版本------一切正常。最后发现,是PowerShell在写入Python文件时破坏了UTF-8编码,导致System Prompt中的"哥哥"变成了乱码。模型收到了乱码,自然无法识别触发词。
教训:文件编码问题不是架构问题,但它是那种"你以为不可能发生、但它就是发生了"的幽灵Bug。最终我用Python脚本替代PowerShell来生成文件,问题解决。
不是所有模型都能接住你的Prompt
多平台路由还让我学到了另一个教训:不同模型对同一套System Prompt的理解能力相差巨大。qwen-turbo完全无法执行三层动作逻辑,而qwen-plus完美运行。这意味着"容错"不只是"A不行换B"------复杂指令用主力模型,简单回复用兜底模型,离线场景用本地保底。
四、重构前后对比

| 维度 | 重构前 | 重构后 |
|---|---|---|
| 文件数量 | 3 | 30 |
| 代码总行数 | ~512 | ~1000(含注释和文档字符串) |
| 模块职责 | 1个文件11种职责 | 每个文件1种职责 |
| 修改数据 | 需要翻找逻辑代码 | 打开对应数据文件改一行 |
| 添加API平台 | 修改核心代码 | 在注册表中加一项配置 |
| API容错 | 单平台硬编码 | 5平台自动切换 |
| 可测试性 | 无法独立测试 | 每个模块可独立测试 |
| 认知层扩展 | 无预留接口 | 3个预置接口,链路已通 |
代码行数增加了,但维护成本降低了至少一个数量级。 行数的增加主要来自模块间的接口代码、文档字符串和注释------这些都是为了让我自己在三个月后还能看懂每一行是干什么的。如果你重构一个会长期迭代的项目,为"可维护性"付出的行数不是冗余,是投资。
五、这次重构教会我的
5.1 "能跑"和"能改"是两回事
冷旭帆在重构前就能跑------每天在腾讯云服务器上稳定运行,接受POST请求,返回中文回复。但每次我想加新功能,都是在"祈祷不破坏已有逻辑"的心态下操作的。
重构后,我终于可以放心地改任何一个模块,因为我知道它不会影响其他模块。这种"可以安全地改"的感觉,是这次重构最大的收获。
5.2 同一个原则,顺便重构了我的白皮书
把角色数据从逻辑代码中剥离,是我在这次重构中做出的最正确的决定。它让我意识到:代码是骨架,数据是血肉。 骨架应该稳定,血肉可以随时更换。
这个原则之所以可以从代码迁移到文档,是因为它解决的是一类通用问题:当一个系统同时包含"结构"和"内容"时,两者的修改频率不同,耦合在一起会导致高频修改被迫经过低频但高风险的区域。
重构完成的同一天,我把两份白皮书------项目白皮书(约30KB)和个人白皮书(约40KB)------拆成了190多张独立卡片。每张卡片只记录一件事。进度更新时只改一个文件,不需要在长文档里翻来翻去。
拆解前,更新一个进度数据需要在3万字的文档中搜索定位。拆解后,打开 已完成节点.md,改一行。30秒完成。
核心规则只有三条:
- 进度数据集中管理。 所有实时数据放在
06-进度仪表盘/目录下。更新进度时,不需要打开其他任何文件。 - 维护指南明确。 写清楚了每种更新场景只需要改哪个文件------完成了任务改这里,新增博客改那里。
- 链接格式统一。 所有内部链接使用标准Markdown
[]()格式,在Obsidian、VSCode、GitHub上均可正常跳转。
用写代码的方式写文档,这个习惯来自这次重构。 判断标准很简单:如果你在过去一个月里,因为改一个数据而在文档中搜索了三次以上,那就是拆的信号。
5.3 架构设计不是一次性的
我在重构时留了两个"后门":cognition/ 目录下的三个预置接口,以及 character_data/ 目录下随时可扩展的数据文件。这意味着未来加新功能时,不需要推翻现有的架构------只需要在预留的位置填充新的内容。
比如,我下个月想给冷旭帆加入"对特定天气的反应"。我只需要新建一个 weather_perception.py 文件,并在 character_data/ 里加上天气触发的记忆数据。其他20多个文件,一行不用动。
5.4 关于"过度设计"的坦诚说明
30个文件对一个原型项目来说,可能显得过度设计。但我判断这是必要的,因为冷旭帆不是一个写完就扔的项目------它是一个会长期迭代的角色。为未来投资一点架构复杂度,是值得的。
当然,如果你的AI NPC只是一个周末项目,或者只有3个功能,别拆成30个文件。架构的复杂度应该和项目的生命周期成正比。
判断标准很简单:如果你在过去一个月里,因为改一个功能而被迫修改了三个以上的文件(或在一个文件中搜索了三次以上),那就是重构的信号。如果你半年没动过那个模块,那就别动------架构的复杂度应该由真实的痛点驱动,而不是由对"整洁"的想象驱动。
这次重构也有遗留的债------自动化测试仍以手工为主。这是下一步要补的。
六、总结与下一步
冷旭帆从一个512行的单文件脚本,变成了一个30文件的模块化项目。这不是为了"看起来更专业",而是为了解决一个真实的痛点:改一个功能太麻烦了。
现在,修改他的童年记忆只需要打开一个数据文件。测试新的API平台只需要加一行配置。未来实现认知层逻辑时,不需要动核心引擎的一行代码。
技术上的下一步:给冷旭帆装上情景记忆的语义检索能力(ChromaDB集成),让他能从"记得最近50条摘要"升级为"能根据你说的话自动回忆起相关的过去"。
本文发布后:我会继续写一系列"建造实录"------包括API平台踩坑复盘、三层动作验证过程、以及ChromaDB集成的完整记录。如果你也在造AI NPC,或者对"用写代码的方式写文档"这件事有共鸣,欢迎在评论区告诉我。
重构不是为了好看。是为了让未来的自己,可以安心地做一个"改一行代码就能改变角色命运"的建造者,而不是一个每天都在祈祷系统别崩的修补匠。
------ 陆银,2026.07
冷旭帆体验地址:点击这里与冷旭帆对话
项目代码:github.com/lengxufan-p...
系列全部文章和白皮书已整理在我的知识库中。
系列导航
上一篇:《我把AI NPC部署到公网,被Railway逼疯了整整一天》 ------ 从本地到公网的部署战役,五个大坑完整记录。
下一篇:《我接入了五个API平台,被拒绝了四次》 ------ 重构后的第一个大动作:多平台容错路由的完整踩坑记录。
本系列所有文章目录,请见项目仓库。