作者:郝子旭(组长)
日期:2026-06-21
本阶段关键词:ReAct 工具调用、双运行时、长期记忆、混合 AI 计划、主动关怀、每用户时区、自带模型


一、这一段,我把重心拉回了后端

上一篇博客写的是 6 月 7 日到 8 日那次大的前端重塑,把旧版页面体系换成了新的 Figma 风格界面。那之后桌面端的样子基本定了下来,我就把重心重新拉回了后端和智能体核心。

这一段时间跨度比前面任何一个阶段都长,做的事情也更"伤筋动骨"。如果说前面几个月是在把功能一个一个补齐、把界面一遍一遍打磨,那么这一段更像是在回答一个一直被我搁置、但越来越绕不开的问题:我们这个项目里的"AI 助手",到底算不算一个真正的 agent?

我给自己的答案是:之前那个,其实不算。所以这一阶段最核心的一件事,就是把它从"假的"改成"真的"。围绕这件事,又长出了长期记忆、混合 AI 计划、主动关怀升级、每用户时区、自带模型这一连串相关的改造。这篇博客我就按这条线,把这段时间我做的事情和当时的想法记一记。

二、先承认一个问题:我们之前的 agent 是"假"的

要讲清楚这一阶段,得先老实承认一件事。

在这次改造之前,研途灵伴的"智能体"其实是一套"分类 → 路由 → 一次性生成"的流程。用户发一句话,系统先用一次模型调用判断意图,然后按角色把请求分给固定的处理逻辑,每个处理逻辑再让模型生成一段文字。听起来有四个角色、有编排、有路由,挺像那么回事,但它缺了最关键的东西:模型自己并不能"动手"。

所有真正的数据——计划怎么排、错题有哪些、复习到没到期——都是 Python 代码提前算好,再塞给模型让它"包装成一段话"。模型从头到尾没有自己去查过任何东西,也没有根据查到的结果改变下一步的决定。换句话说,它更像一个很会说话的播报员,而不是一个会思考、会查证、会做事的助手。

我之所以一直没动它,是因为它"能用"。演示的时候看起来效果还不错,用户也分不出这中间到底有没有真正的工具调用。但越往后做,我越觉得这个地基撑不住我们想要的东西。我们想做的是一个"会陪你备考"的助手,它得能在对话里真的帮你把题记进错题本、真的去查你今天的计划完成了多少、真的根据你的状态决定要不要提醒你休息。这些事,靠"提前算好再包装"是做不到的,因为模型根本不知道自己什么时候该去查、查什么、查到之后怎么办。

想明白这一点之后,我就决定这一阶段啃下这块硬骨头。

三、ReAct 工具调用:让模型自己"想一步、做一步、再想一步"

这次改造的核心,是给系统加了一套 ReAct 运行时。名字听起来玄乎,其实道理很朴素:让模型在回答之前,可以先想一想需要什么信息,然后自己去调用一个"工具"把信息查出来,看到结果之后再决定下一步——是接着查,还是动手写点东西,还是已经够了可以回答了。想一步、做一步、再想一步,循环几轮,最后给出一段连贯的回答。

我给这套运行时配了一批工具。一类是只读的,比如查计划、查错题、查复习、查学习状态、查长期记忆;另一类是会写数据的,比如把题加进错题本、安排一次复习、记录一次情绪、写一条笔记。模型在对话里会根据需要自己挑工具用,而不是等我用代码写死的分支去喂它。

但这件事真正难的地方,不在"让模型能调工具",而在"不能因为加了这个就把原来的一切搞坏"。这是我做这次改造时给自己定的第一条原则:契约零破坏。我们之前所有的对外约定——流式回复怎么发、消息结构长什么样、降级的时候说什么话——一个都不能改。所以我没有去替换旧的链路,而是在它旁边新长出这一套新的,用一个开关控制走哪条路。出了任何问题,改一个环境变量就能切回老路,等于给自己留了一个随时能撤的后门。这个决定后来证明非常值,因为新链路免不了有各种没想到的情况,有这个开关,我才敢放心地往前推。

第二条原则是"先只读、再写入"。一上来就让模型能改数据库是很危险的,万一它理解错了用户的意思,把不该记的题记进去、把不该改的计划改了,比不做还糟。所以我先只放开只读工具,让它能"查"但不能"动",跑顺了再小心地放开写入。而且写入这一侧我加了好几道闸:每一轮对话能写几次是有预算上限的,免得它一激动连写一堆;每个写操作都被单独隔离开,万一某一步出错,只回滚它自己,不会把整轮对话搞崩;来自截图识别、语音转写这种"不太可信"的内容里夹带的指令,也不允许直接触发写操作。这些护栏平时用户根本看不见,但它们决定了这个功能敢不敢真的上线。

还有一个我比较在意的细节,是"思考过程"和"正式回答"必须分开。模型在中间几轮"想"的那些话,是给它自己看的推理,不应该一股脑流到用户屏幕上,否则用户会看到一堆"我现在要去查一下计划……"的碎碎念。所以我让中间过程的文字只进我们自己的调试轨迹、可以在演示时投屏看,但绝不混进给用户的回答里。用户看到的,始终是最后那段干净的话。

做完这一块,我心里那个"它到底算不算真 agent"的疙瘩,总算解开了一半。它现在是真的会在对话里去查、去做事了。

四、让它"记得你":长期记忆系统

agent 能动手之后,我马上撞上了下一个问题:它没有记性。

每次对话它都像第一次认识你。你昨天说过自己数学的中值定理总是搞错,今天它完全不记得;你这个月情绪从低谷慢慢缓过来了,它也没有任何概念。一个号称"陪你备考"的助手,如果每天都把你忘得一干二净,那"陪伴"就是个空话。所以这一阶段我又做了一套长期记忆系统。

设计这套东西的时候,我花最多力气想清楚的,反而是"什么东西不该进记忆"。我们系统里本来就有记录"当下状态"的地方——今天完成率多少、最近情绪几分、最近高频错的是哪些点。这些是每天都会被重新算一遍、随时覆盖的数字。它们不该进长期记忆。长期记忆要存的,是那种跨越很多天、反复出现、归纳出来的稳定结论,比如"这个同学习惯早上学数学"“高数的中值定理是他长期的薄弱点”“他的目标是计算机专硕”。当下的指标和长期的结论,是两种完全不同的东西,我把这条边界划得很死,因为一旦混在一起,记忆就会被每天的波动冲得乱七八糟。

记忆是怎么形成的呢?我没有让它在对话当场去"硬记",而是放到晚上。系统会在夜里做一次蒸馏,把这一天乃至这几天的零碎事实——错题、情绪、跟课记录——归纳成几条稳定的结论存下来。这里我特别防了一件事:模型编造。我要求每一条长期记忆都必须有至少两个不同日期的真实证据支撑,没有足够证据的"结论"一律不许入库,免得它把某一天的偶然现象当成你的长期特征。

取用记忆也做了分级。最基础的方式不需要任何模型、不需要任何高级检索,纯靠规则就能把最重要的几条记忆翻出来用;有条件的时候再叠加关键词匹配、再叠加更聪明的语义检索。这样即使用户没有配置高级能力,记忆系统也照样能用,只是没那么精准而已——这正好和我们整个项目"没有就诚实降级、绝不报错"的调性是一致的。

最后还有一条纪律我觉得很重要:记忆和当下数据冲突时,永远以当下查到的真实数据为准。记忆是"我印象里你是这样",但万一你已经变了,就不能拿旧印象误导你。比如记忆说你数学弱,但它去查发现你最近这块的掌握度已经上来了,那就该信查到的,而不是信记忆。这条让记忆系统从一个"可能帮倒忙的旧印象",变成了一个"知道自己会过时、所以会去核对"的助手。

五、把"个性化"做扎实:自带模型与每用户时区

在做 agent 和记忆的同时,我还顺手补了两块底层的东西,它们看起来不起眼,但决定了这个产品到底是不是"为每一个具体的人服务"。

第一块是自带模型。我们把模型接入改成了让每个用户填自己的模型地址和密钥,密钥加密存起来,我们的服务端不内置任何"兜底"密钥。这件事一半是为了隐私——用户的学习数据走的是他自己的模型;另一半是为了诚实——如果用户没配模型,那 AI 能力就老老实实降级,计划回退成规则生成、判分回退成规则判分、关怀回退成固定文案,而不是偷偷替他调用别的什么模型让他以为一切正常。我不太喜欢那种"看起来都能用、其实背后悄悄替你做了你不知道的事"的设计,所以宁可让降级看得见。

第二块是每用户时区。这块是被一个 bug 逼出来的。我们之前所有跟"今天""现在"有关的判断,用的都是一个写死的全局时区,甚至有的地方直接硬编码成了上海时间。这意味着不管用户在哪,早间问候、晚间提醒、免打扰时间,全都按上海时间算。对一个本地用的学习软件来说这平时不算大问题,但它本质是错的。所以我把整套时间判断改成了按每个用户自己的时区来:建档时自动检测他的系统时区,设置页里也能随时改、能一键跟随系统。改完之后,计划的日界、复习的到期、报告的统计、关怀的时间窗,全都按用户本地时间走了。这种改动没有任何界面上的炫酷效果,但它是那种"做对了没人夸、做错了迟早出事"的基础工程。

六、混合 AI 计划与主动关怀的升级

agent、记忆、底层都铺好之后,我回头把两个老功能按新的能力重做了一遍。

一个是今日计划。我们之前的计划是纯规则排的,稳定但有点死板。这一阶段我让 AI 参与进来,但参与得很克制:计划的骨架还是规则搭的,AI 只负责在学科块这一层做优化润色,而且复习这一段我完全不让 AI 碰——到期该复习的内容由规则牢牢守住,绝不会被 AI 优化着优化着就给弄丢了。AI 给出的方案还要过好几层校验,时间加起来必须严丝合缝地等于预算,任何一点不对就整批作废、悄悄退回纯规则的版本。我不想要一个"有时候很惊艳、有时候很离谱"的计划,我要的是一个"底线永远在、好的时候锦上添花"的计划。这个分寸,是我反复调出来的。

另一个是主动关怀。关怀这块原来就有,但它是"到点了就按模板说句话"。这一阶段我让它也走上了只读的 agent 回合——在开口提醒你之前,它会先用只读工具去核实一下你现在到底什么状态,是真的久坐了、还是真的完成率偏低了,确认属实再说话,而不是到点就机械地念一句。这件事我把它叫"先查证再开口",它让主动关怀从一个"定时闹钟"变得更像一个"会观察你的人"。当然,为了不打扰,冷却和免打扰这些约束都还在。

配合这些,我还做了一个给我们自己看的 agent 观测面板。它没有新建任何数据表,纯粹是把已经记下来的对话路由和推理轨迹翻出来,让我们能看到每一次对话到底走了哪条链路、调了哪些工具、花了多久。这东西对演示和排查问题都很有用——agent 这种东西最怕"出了问题不知道它当时在想什么",有了这个回放,黑盒就透明了一些。

七、一堆不显眼但要命的稳定性收口

这一阶段还有一类工作,是没法写进功能列表、但花了我不少时间的:稳定性收口。

agent 一旦能真的写数据、定时任务一旦真的开始跑,很多以前藏着的并发问题就冒出来了。比如今天的状态快照,在多个流程同时去创建的时候会撞车、报重复键错误;比如某个用户历史上不小心存了多条同名会话,定时任务去取会话时直接因为"取到了多条"崩掉,连带整个定时轮次都挂了。这些问题在功能演示里几乎永远碰不到,但只要系统真的连续跑起来、用户真的多起来,它们迟早会发作。

处理这类问题没什么"亮点"可言,就是一个一个去把它们做成幂等、做成容错:创建撞车了就当成已存在、安静地接着用;取会话取到多条就容错地取一条、并补上去重和唯一约束,不让它再有机会变脏。我越来越觉得,一个项目从"能演示"到"能用",差的就是这一堆不显眼的收口。前者靠功能,后者靠这些没人看见的地方。

八、阶段小结

回头看这一段,它是整个项目里我个人觉得最关键的一跃。前面几个月我们把功能铺得很宽,但那个"AI 助手"一直停在"会说话的播报员"这个层次。这一阶段我把它真正改成了一个会思考、会查证、会动手、还记得你是谁的 agent:ReAct 工具调用让它能在对话里做事,长期记忆让它记得你的过去,自带模型和时区让它服务于每一个具体的人,混合计划和主动关怀让它在克制的前提下变得更聪明,而那一堆稳定性收口让这一切真的敢连续跑下去。

这一阶段我也更确定了一件事:做 agent,难的从来不是"让模型能调工具"这一下,而是围绕它建立起一整套护栏、降级、边界和可观测——让它在能做事的同时,不乱做事、做错了能兜住、出了问题能查清楚。这部分工作大多藏在水面以下,但它才是这套系统敢不敢交到用户手里的关键。

下一阶段就要收尾了。接下来我会把这些能力再连着真实使用走几遍,把还没磨平的地方收干净,然后准备项目的最终交付。

更多推荐