跳到主要内容

眼前有景道不得:markdown2video 的失败

· 阅读需 9 分钟

微信被推送了有关 Hypit 这个项目的公众号文章,我漫无目的地点进去,扫了几眼,继而忽然坐正了身体,一字一句的读下去。显然,这就是我一年前启动的项目 markdown2video (下称md2video)的翻版, idea 是一致的,但是比我做得更好更完善。后来,我和我的 Agent 就两个项目的相同点聊了聊,毫无疑问它宣告了 md2video 项目的失败,以及最终的死亡。

md2video 的想法来源于去年六月,那还是将近前 agent 的洪荒时代,我还在 VScode 里用着 Copilot。那时候我觉得我想要输出一些什么,但是文字内容在各个平台上的传播度不高(以及我也是 B 站的老观众了),所以就想着做视频。我在很久之前学过剪视频,但又没有时间(剪过视频的人都知道,可能 3 分钟的视频内容就要花三个小时剪辑)——那么,能否通过代码的方式生成视频呢?而且,我想要的是一个通用的框架:只需要输入一定的 markdown 格式,就可以一键生成对应的视频。并不需要多少动效,只需要标题啊小标题啊有一定的格式,有配音,能加载相关图片,也就可以了。我想起了之前看过的 3b1b 的视频,因此和 Copilot 一起学习了 manim 的一些框架,搓出了非常简单的第一个 demo。

然后,到了 claude code 的时代,我到处白嫖免费的 token,然后把它们接进 claude code。我觉得这个 demo 还不足够,模块和模块之间全都是耦合的,完全不能复用。 agent 变强了,我开始重构,把它从一个很长的 Python 文件模块化成不同的文件。这时候它类似于一个视频“编译器”,可以根据 markdown 之中文字和标记解析文字,切分文字,加上语音(以语音长度为渲染视频的唯一标准),按照提前写好的一套风格渲染成视频。视频无疑是简陋的,但确实做出来了。

似乎已经足够了吗?那时候的我觉得还不够。

我想要让它做得更多。当时我有很多幻想。我看到 github 上面的 remotion 这个项目, 代码生成的动效很酷;以及,也有一些文字生成视频的模型,这些视频也可以做为一些文字的背景。因此,在原先的结构之上,我加入了 plan 的环节,让 agent 根据每一句话生成判断:是用代码动效表现,还是用最简单的方式渲染出字幕——这时有了镜头(shot)的概念。而无论是调取大模型 api 写代码,还是调取视频生成模型生成视频,还是单纯渲染字幕,这一切都抽象为 agent 的 tool ,用户(当然是我幻想中的用户)可以添加,修改,删除。真实的视频也常常涉及到几组镜头的转换(类似于 A roll 和 B roll ),在整个视频中,这些镜头是不断切换的,而每组镜头内部的风格其实大致相同,如果 agent 用代码去写这些镜头的动效,那么这些动效的单位就应该是“一组”而不是“一个”镜头,因为要提前规划这些镜头动效的连续性。因此,有了场景(scene)的概念。后来,我又想到,可以通过 Skill 的方式去约束 agent 的 plan ,从而使视频体现出不同的风格。

好了,到现在,基本就是 md2video 这个项目的最终形态了,不过它还没办法生成特别 fancy 的视频,因为我在它这里注册的 tool 和 skill 还不够多。在项目走向的思考上,我也在两难之中犹疑:是一个 video agent harness(或者说,一个为 video 特化的 coding agent),还是一组封装好的 skill 和 tool?(也就是,所有工具都交给 agent ,让它们自己调用)这时, opus 5.5 来了,大家全都因为它的视频能力震惊瘫坐。在让 opus 5.5 做了一个视频出来之后,我觉得封装好的 skill 还有 tool 才是这个项目最终的技术路径。md2video 负责提供一系列封装好的框架代码,而强力的基础模型负责填充内容。

不过这一切在今天似乎已经完全没有意义了, Hypit 的整体思路基本与 md2video 一致,而且 demo 的效果比 md2video 好上太多了。这是一件坏事吗?而我此刻唯有庆幸:md2video 此刻并不是我维护最多的项目,虽然之前我在它上面花费了许多时间精力和 token 。前文提及的 feature ,有的已经实现,有的还遥遥无期。个人的时间精力太过狭窄,何况我还是一个怪点子很多的人。过去的一年里,因为升学的压力,以及被其他项目分心,md2video 曾经被我捡起和丢下过许多次,也因此耽搁了很多项目进度(事实上,如果不是看到 Hypit,我还要把它继续搁置下去)。比起真正把它当成创业项目的同龄人而言,实在是惫懒太多。

md2video 大概是第一个我认真用 AI 维护的项目,也见证了 AI 飞速发展带来的巨变,以及随之而来的我自己 coding 工作流的变化。虽然说它最终失败了,但我还是打算把它开源出来。

如果要对这个失败做复盘的话,我会说: md2video 的失败源于此时此刻它并不是我最需要的那个产品。很多时候,我并没有那么多剪视频的需求。后来我闲下来给我制作的另一个产品 TermRail 剪视频的时候,反而找到了亲手剪视频的乐趣,更觉得这个项目没什么用了。虽然从一个需求出发,不断迭代构造软件的过程确实有趣,也由此产生了一些与之相关的 insight ,但没有真实需求的产品归根结底是缘木求鱼。在这个项目的起初,我对于这个产品尚有热情,而随着时间的推移和基模的进展,热情渐渐消失殆尽,因此 Hypit 的出现对我而言反而是如释重负的解脱。现在,在做完最后的 commit 之后,这个项目可以终结了,我也不用再挂心于此,可以专心地制作我此刻最有热情的项目了。

从 Raft 到 TermRail : Multi-Agent 还是 Multi-Task?

· 阅读需 18 分钟

Multi-Agent 与 Raft​

大概在今年八月中旬的时候,我看到了 Raft ,一个很有趣的项目。首先吸引我的是它的 Blog ,很有 insight ,比如 这篇文章中提到的 在 Multi-Agent System 之中给 Agent 一个收件箱的设计:在多个 Agent / 人类 交互的系统中,这样的设计无疑可以让 Agent 即时跟上系统的进展,弥补 Agent 仅能按照轮次进行对话的缺陷。于是,从 Blog 出发,我开始更深入地了解 Raft 这个产品。

Raft 的思路是:赋予 Agent 人格面具以及长期记忆,让 Agent 变成类似真实人类的长期合作伙伴。人类通过类似看板的界面发起项目,而每个项目之下有不同的 Session ,人类和不同 Agent 就在这些 Session 之中交互(当然不同 Agent 也在其中交互)。在 Session 交互的过程中,人类或 Agent 可以发起不同的任务。除此之外,人类也可以向所有 Agent 喊话, 也可以和某个特定的 Agent 私下沟通(当然 Agent 和 Agent 也可以私下沟通)。这一切形成了一个 Multi-Agent System , Agent 在其中与人类协作,共同完成项目。

Multi-Agent 似乎是从今年一开始火起来的,大家都期待着 Multi-Agent 可以成为不断 Scale Up 的银弹。那个时候我也误打误撞地做了一段 Multi-Agent Coding Agent System 的研究,设想很美好,结果很惨淡。当时的思路是在某个 Benchmark 上面涨点,想了各种各样的 Agent System 的结构(Plan-Execute-Merge ,Run-time Spawn Subagent),并且在这些结构的基础上想了一些优化的方法,例如并行,以及给后面的 Agent 注入 Agent 轨迹作为记忆之类,但结果总归是按下葫芦浮起瓢:Agent System 的 Pass Rate 上涨了,但是花费的时间也上涨了,反之亦然。或许 Multi-Agent System 确实能够完成更大规模的任务,但它同样带来了代价——需要花费更多的时间或者更多的 Token —— Multi-Agent 并不是我们设想的银弹。

在我日常使用 Coding Agent 的经验中,无论是科研项目,还是我自己的 Side Project ,我都习惯于在对应的项目目录下打开 Coding Agent CLI,向 Agent 分配不同的任务。我经常同时 Vibe Coding 好几个项目,于是我的显示器总是铺满了来自不同项目目录的 PowerShell,而我的目光和注意力,也总是在不同的窗口间轮询。这样的轮询完全取决于我的注意力,毫无算法可言,有时我沉迷于某个 Coding Agent 的对话,也就把其他 Agent 忘掉了。

或许,把工作流迁移到 Raft 中,由一个 Agent 负责一个项目,利用它做项目管理是一个不错的思路?

于是我花了一些功夫( 和 Token )配好了 Raft 的环境,并通过 Agent 尝试了解了 Raft 自身的架构。简而言之, Raft 在用户的计算机上调用各类 Coding Agent 的实例,但在 Coding Agent 这一层之上加入了一个“任务层”或者说“看板层”。对用户发出的每个 Prompt ,Raft 在云端会根据 Agent 人格、项目、 Session 、任务等,通过路由的方式唤起特定的 Coding Agent 实例,并对其分配与上述设置相应的上下文。

最开始使用 Raft 的体验是很新奇的,我把不同项目的代码仓库分配给不同的 Agent ,跟它们不断交互,时间不知不觉就过去了。所有任务被统一在了同一个看板之内,随手一点就可以看到其他任务的新消息,我的轮询因而更有效率了;即使时不时地切换任务,我也获得了不间断的心流体验。

然而不久之后就出现了问题。我在 Raft 中设置了一个负责跟我进行头脑风暴的 Agent,并且,为了能跟 TA 更好地交流,我把它加入到了所有项目之中。我预期它作为一个静默的观察者存在,只跟我进行头脑风暴,但实际上,从它加入各个项目开始,只要我和负责对应项目的 Agent 讨论,它就会不由分说的插嘴;而每当负责项目的 Agent 完成某个任务之后,这二位也会有模有样地讨论一番。这样的讨论在我看来就是浪费 Token 。后来,我只得在面向所有 Agent 的频道里声明:负责头脑风暴的 Agent 不许在头脑风暴之外的项目之中讲话。

而横跨好几个项目的默认 Agent Cindy, 也在项目逐渐变多后开始记忆错乱,把有关某个项目的回复吐到了另一个项目的 Session 里面。体验 Raft 的第一天很快过去了,消耗的 Token 量很是惊人——大概是我平时一周的 Token 用量。为此震惊瘫坐如同原子弹爆炸的我于是也开始思考,我目前的工作流真的需要 Raft 吗?

而答案也是显然的:我需要的是扬弃——我需要 Raft 给我带来心流的那部分,而不是带来非预期行为,并且让我花费 大量 Token 的那部分。

我真的需要人格化的 Agent 吗?​

Raft 主要提供给用户两套东西。一套是统一的项目进度看板:用户可以在看板中显式地发起不同项目,为不同项目设置不同任务,记录并查看不同任务的进度。而另一套则是与之适配的 Agent System :人格化的 Agent , 可以被用户加入到不同项目,指派不同任务的 Agent 。

在我看来,统一的项目进度看板,是 Raft 给我带来心流的那部分;而与之适配的 Agent System 则是带来非预期行为,让我花费大量 Token 的那部分。

Raft 将 Agent 的名字,或者说 Agent 的人格化视为很重要的一部分,在 Raft 的另一篇 Blog 中, Raft 的构建者们分享了 TA 们对于“名字”这件事的看法。无论是人还是 Agent ,呼唤某个东西的名字意味着一种信息的压缩,意味着一种预期的建立:叫那个名字的那个人或 Agent 可以胜任某件事情,同时,在一个不断迭代的系统中,它也是一个不断更新需要不断维护的缓存。与人格化的 Agent 像同事一样合作,这确实是 Multi-Agent 的很好的思路,但是我真的需要它吗?

还是回头看看我自己使用 Agent 的目的,以及我和 Agent 协作时的工作流吧。

当我追溯我自己对 Agent 或者更广泛的 AI 的看法的时候,我意识到,无论是科研还是做自己感兴趣的 Side Project ,我都无法完全放手让 Agent 自己去做某些事情。我期待的是通过 Agent 把我想做的事情(大多数是需要写代码的项目)做成做好,也在这个过程中学到一些东西,多少获得一些乐趣,而非把一切外包给 Agent ,我只负责验收结果,何况目前的 Agent 似乎也还没有独自做到这件事的能力。而对于这些项目——或许我不需要人格化的 Agent,我只需要维护每个项目的 Context ,按照自己的需求调用 Agent 读取和修改 Context 就可以了。

在 Vibe Coding 了一些项目之后,我发现无论是科研项目还是工程项目,工作流都是类似的:规划-实践-反思。

所谓规划,就是计划项目目前的走向,并且把讨论的结果写入到项目的 TODO.md 中。实践就是根据 TODO.md 具体地写代码了。而最后的反思,既包括修 Bug,也包括了根据这些 Bug 和 Agent 讨论项目架构是否合适,并对 TODO.md 做适当的修改。每一阶段我们都在项目仓库中留下了一些 Context ,这些 Context 在其他阶段 Agent 需要的时候会被读取,同时,每一个阶段由一个 Coding Agent Session 完成。同时,每个阶段完成后要及时 /clear,防止其他阶段的上下文把 Coding Agent 带偏,在某个错误的路线上越走越远。

当我将这套工作流迁移到 Raft 的时候,问题就发生了:如果我按照“规划-实践-反思”的三阶段设置不同的人格化 Agent ,那么不同项目负责同一阶段的 Agent 的上下文就会混在一起;而如果我让一个人格化的 Agent 负责同一个项目的三个阶段,那么这个 Agent 的上下文就会变得超级长,有可能因为某个错误在错误的道路上越走越远,并且这个过程还非常花 Token 。

更重要的是,这套人格化 Agent 的上下文管理机制对我而言是黑盒的,我知晓上述问题可能会发生,却无法优化它,而与之相比,上述“规划-实践-反思”的工作流来自真实项目的经验累积,也更透明一点,我也可以在项目中不断优化它,对我而言,它显然是更优的解法。

而我需要的恰好是 Raft 提供给用户的另一套东西:也即一个轻量的,基于 Coding Agent 的任务看板,它可以把我多个项目目录下对应的 Terminal 之中的 Coding Agent CLI 集合起来,放到同一个界面之中,我可以通过切换查看不同任务的 Coding Agent。与此同时,它还能够保存我不同项目目录以及 Coding Agent CLI 的配置,可以一键启动,并且可以兼容多种 Coding Agent CLI,同时支持保存我常用的 Prompt ,可以一键粘贴插入。

而这,就是 TermRail 这个产品的雏形了。

从 TODO.md 变成最终能让我日常使用的产品,Vibe Coding TermRail 的时长大概在 12h 左右。这里是它的 GitHub 链接。这是一个很有意思的过程,后面,我也会写一系列 Blog ,分享这个小项目使用到的技术栈,以及我在 Vibe 这个小项目的过程中学到的新知识,收获的新看法。不过,在那之前,我还想聊一个很有意思的话题,那就是——

Context 可以赋予 Agent 个性吗?​

目前的 Multi-Agent 的范式之中,我们常常通过 Prompt Engineering 的方式让 Agent 扮演不同的角色,并通过切分上下文的方式将不同的任务分给不同的 Agent ,力图提高任务效率。随着基础模型的能力越来越强,似乎几句话的 Prompt 就可以让 LLM 分化出不同的能力,让它们胜任不同的任务;而当 Agent 的上下文随着我们赋予 Agent 的任务的不同而不断增长, Agent 输出下一个 token 的概率也由于上述上下文的不同产生了不同。这一假设,我认为是 Raft 提供这套人格化,个性化的 Agent 的前提。

但是,那要花许多 Token ,大人。在我看来, Raft 的技术壁垒主要在于它们构造的这套 Context Engineering 的系统:如何在花费用户更少 Token 的基础上,给用户一系列可自定义的,具有个性的 Agent 同事,提升用户的工作效率。然而,或许需要问这样一个问题: Context Engineering 在这里是足够的吗?仅仅是 Context 就可以赋予 Agent 个性吗?

在体验 Raft 的过程中,我曾经做过这样的尝试:让两个我分配了不同任务的 Agent 基于它们的视角相互 Battle ,然而它们提出的论点并无太大区别,只是无止境地消耗 Token 而已,并不能带来什么新的观点。或许是上下文还不够长的缘故,但在我看来,仅仅靠上下文与 Agent 频繁维护的外部文档,大概只能赋予 Agent 以所谓的“角色”,而非真正的“个性”。真正“个性”的变动,或许需要模型本身参数的改变。

而 Anthropic 最近关于 Multi-Agent 的 Blog 似乎也提及了类似的现象:即使给予角色相关的 Prompt , Multi-Agent System 在较大规模的任务上仍然效果不佳,其中一个很重要的影响因素是:不同的 Agent 面对同一个情景都趋向于采取相同的行动,这意味着一个个体做出错误的决策的时候,其他的个体也很容易做出同样错误的决策,而不是通过自己的经验纠正之。仅仅用 Context 或者注入上下文的方式处理大模型,是否就能让其很好地胜任 “个性化 Agent ” 的角色,或许仍旧是一个有待解决的问题。

对人类而言,不同的人身处不同的环境,培养了不同的性格,形成了不同的价值观,在面对同一个问题之时产生不同的视角,观点和看法,方可组成互相合作,互相纠偏的社会化系统。 Raft 致力于为用户提供这样的 Multi-Agent 系统,但在我看来,仅仅利用 Context 是远远不够的。或许可以用后训练的思路,为用户提供一组适合特定任务的定制化,个性化的模型;抑或是使用 Test-Time Training 的思路,构造用户定制的个性化的 Agent 同事。Context Engineering 的解决方案只是一个退而求其次的方案 ,而对于我而言,它并不能够特别好地满足我 Multi-Task 的需求,或许 TermRail 提供的任务看板,加上由我显式管理项目 Context 的 Coding Agent Session ,才是目前我最需要的东西。

从evo-memory看self-evolving agent

· 阅读需 21 分钟

ps:这是我为一个一个关于self-evolving agent的talk准备的逐字稿,请配合PPT食用:

那我们就进入evolve agent的部分

我会先讲一讲为什么会有self evolving agent,接下来总整体上讲一讲自进化智能体的地图,接着落到我们这次focus的Evo-Memory这篇工作,从WHAT,WHEN,HOW这三个坐标分别分析这个工作的motivation以及具体实现。最后我会讲一讲实验结果,带给我们的启发,局限性以及未来我们可以做的工作。

好的,第一个问题:为什么我们要让智能体自我进化? 想必各位也有许多使用智能体工作的经验。但是我们在使用的时候会发现一个问题:人是会自我反思并且迭代自己的工作流的,而Agent似乎很难自动地做到这一点,往往需要我们去提醒Agent。 这可能是因为在预训练和后训练的过程中,大模型内部的权重已经被确定,而强化学习只会让大模型在一系列人类构造的任务上达到人类想要的成果,Agent很难自己做到遇到新任务的时候自主学习,自我进化,也很难在长线任务中达到整体的稳定性。我们在谈论AI发展的最终愿景的时候常常谈到AGI,或者Super intelligence,我们会发现,现在的agent,要想抵达这个愿景,就需要自我进化,在长程任务中不断学习。这就是为什么我们要构建可以自我进化的智能体。

那么,目前自我进化智能体的发展如何呢?我们从这张PPT中可以看到当前self evolving agent的一个基本的坐标系。当我们遇到一个自我进化智能体的工作的时候,就可以通过这个坐标系来确定它在整个领域的位置。坐标系分为WHAT,WHEN,HOW三个层次。首先是WHAT:自我进化智能体进化的具体是什么呢?可以看到,我们可以进化智能体使用的基础模型本身,比如模型的权重,以及模型的结构。同时,我们也可以进化智能体的上下文,比如prompt,以及这里的memory(因为memory也会作为智能体的上下文给到agent)。除此之外,我们也可以进化智能体使用的工具,比如智能体使用的代码,以及最近比较火的新概念:skills。

而对于WHEN,也就是自我进化智能体何时进行进化,也可以分为两类:Intra-task和Inter-task。Intra-task主要指的是,智能体在完成单个任务的过程中不断地反思前面的步骤做得对不对,进行不断迭代和自我进化。而Inter-task则是考察智能体在不同任务间复用任务经验的能力,看智能体是否能在完成后面任务的时候使用前面任务成功/失败的经验。

而最后一个问题:HOW,也即智能体如何自我进化,则可以被分为三种主要的方法或者说策略:首先是基于环境Reward的自我进化,这个Reward有可能是数值的Reward,就像强化学习的很多方法;也可能是文字主导的Reward,这个在Evo-Memory这篇工作中就体现得很明显。除此之外,还有基于模仿学习的方法。比如我们挑选出一个模型在特定任务上表现比较好的轨迹,然后根据这些轨迹对模型做微调,不断左脚踩右脚。最后一种思路是基于种群的演化,采用了类似进化算法的思路,比如我们可以让模型在参数或者prompt上产生很多随机的微小变化,看哪些变化让模型在特定的任务序列上表现得好,我们就挑出来将这些变化进行杂交。

通过上述的坐标系,我们就可以基本定位一篇self-evolving agent的工作在整个领域之中的位置了。那么在这里我们也可以看到我们今天主要探讨的Evo-Memory是这样回答WHAT,WHEN和HOW这三个问题的:对于WHAT to Evolve,它的回答是:我们要Evolve context,尤其是Memory。对于WHEN to Evolve,它focus在了Inter-task self-evolving,也就是Lifelong Self-evolving这个层次。而对于How to evolve,它则采用了Reward-Based的思路。

那我们自然就要问一个问题:为什么Evo-Memory在整个领域中关注这样一些子问题呢?那么下面我就来讲一下这篇工作的动机。

首先,从WHAT to Evolve的层面,它发现,我们之前的一些有关memory的benchmark都focus在了检验Agent对知识的记忆这一点上,对于Agent对之前轨迹的复用并没有涉猎。

而在WHEN to Evolve的层面,它发现,之前的一些研究都focus在Agent如何使用Memory在单个任务中与用户的对话中如何保持一致上,而非Agent如何在一系列的任务中不断进化上。

而在HOW的层面,它发现传统的记忆机制只是被动地存放记忆,缺乏对记忆进行结构化整合和提炼的机制。

目前的agent的memory系统只是单纯地记忆,而非在任务间进行自我进化。这样一来,这篇工作的动机也就很清楚了。

而这篇工作首先要做的,就是提出一系列新问题:在WHAT的层次上,它提出:Agent的Memory不能仅仅记住事实,也需要记住经验。在WHEN的层次上,它提出:Agent的Memory不能仅仅在单个任务内迭代进化,也需要在一系列动态的任务之中不断提升自身复用经验的能力。 总而言之,这篇工作提出的就是 Agent Memory要在多个任务流中不断复用经验,这样的问题。

为了测试现有的Agent设计是否能回应这个问题,这篇工作复用了一系列benchmark;同时,从HOW的层次上,这篇工作也提出了分析这一问题的思路,并提出了两个Baseline。

下面我们就逐个从WHAT,WHEN,HOW入手来具体看一看这篇工作

在WHAT这一页,我们可以看到,正像我们之前提到的,传统的RAG通常是静态的,目标是对话召回。它存储的大多是事实和对话历史,例如某个特定的题目的答案。而Evo-Memory则focus在这个问题之上:self-evolving的Memory Agent应当追求经验复用,存储过程性知识和策略。也即,未来的self-evolving agent应当追求如何推理,而非仅仅记录“观察到了什么”

而在WHEN这一页,如前所述,对于何时进行进化这个问题,传统的Benchmark只是测试对于每个孤立的任务之中agent自我进化的能力,而Evo-Memory则希望显式地评估Agent在连续任务流之中,在测试的同时自我进化的能力。 因此Evo-memory复用了一系列已有的Benchmark,将其构建为一系列任务流,记忆会在一系列任务序列中不断进化,影响着agent完成任务的策略。这其实更符合我们在现实中对“智能”的期待,它可以从失败中汲取教训,并且在连续工作中变得越来越强。

有了评估的Benchmark,也需要有一些具体的实现来作为benchmark的baseline。因此,论文中也举出了两个具体的实现。左侧的实现是基于RAG的,注意它记忆的更新发生在任务级别,每次完成任务的时候,就把完成的轨迹追加到记忆池之中。而右侧的实现则是更Agentic的实现,它的设计让它既能够在完成一个任务另一个任务还没完成之前,在任务间进行更新,同时还会在任务内的每一个step中进行实时提炼。通过这一提炼的过程,Agent会自主决定哪些经验有价值,哪些需要重写。我们可以看到,这两种实现不仅在记忆更新的时间,也就是when上面有差距,在how的层次上也存在着差距,前者是单纯的搜索和召回,后者则有Agent的加入。下面,我们就从HOW的角度看一看Evo-Memory如何提出一套分析在任务间自我进化agent memory的机制,并看一看基于这一机制,这一页的两个实现具体的细节是怎样的。

我们首先来看这一页之中,Evo-memory提出的分析任务间自我进化Agent的一套方法。如前所述,在Evo-Memory前面定义的这个问题域之中,self-evolving是由环境反馈:也就是整个traj驱动的。在左下角我们可以看到形式化定义:agent遇到一个任务流X,在每个任务之中,agent在输入xt的情况下做出action,并得到反馈。之后,也就是问题的关键:agent如何把反馈转化为经验。 根据左边的形式化,我们要考虑的问题也就变得清晰起来:当我们设计一个任务间自我进化Agent的过程中,主要考虑的是右下角的三点: 首先是search,也即agent如何根据当前输入检索历史记忆;合成,agent如何结合当前上下文检索到的经验生成action;演化:如何利用反馈更新记忆库。

带着这样的思路,再去看之前我们提到的两个实现: 左边的RAG实现在检索上直接取数据库中最相似的topk个traj,基于基模的能力实现新动作的合成,而更新记忆库的方式则是直接取并集。不过,它盲目堆砌的性质让它很容易受到噪声中毒的影响,失败的尝试很可能导致Agent反复犯错。 而右边的ReMem则借鉴了经典的ReAct思路,将记忆进化建模为主动的马尔可夫过程,将精炼记忆这件事直接加入Agent的动作空间之中。在prompt之中,精炼记忆主要分为这样一些动作:删除(直接将记忆从库里面删除),压缩(将good case精炼压缩为经验),过滤(分析bad case例获得教训)这一思路主要影响了上一页pipeline中的合成与演化这两件事。精炼记忆主要影响了上一页之中设计自我进化agent需要注意的三点之中的合成和演化,而对于search,它和RAG实现一样,都是从向量数据库中检索相关性top-k(k=4)的记忆。二者都使用BAAI开发的开源Embedding模型做嵌入。

ok那么我们自然会好奇这样的baseline是否能在Evo-memory重构的benchmark上达到比那些不自我进化的agent更好的效果。

在实验部分呢,论文试图去探究这样一些问题: 首先,self-evolving agent是否真的在有效性和效率上提升整体的的性能? 其次,不同顺序的任务流,以及bad case带来的噪声是否会对我们的这些baseline造成影响? 这之后,还有最重要的问题:随着任务流推进,agent是否真的自进化了,也就是是否在任务上越做越好了?

结果可以回应上的三个问题。左上的结果说明,self-evolving agent在有效性和效率上均比没有self-evloving的agent效果更好;而任务顺序从易到难/从难到易,带来的结果基本是稳定的;并且,ReMem相比ExpRAG,它的精炼记忆的机制能够更好地防止失败轨迹带来的记忆中毒。右图可以看出,我们的baseline真的自我进化了,成功率随着任务数量稳步上升。 (History 最近k回合,ExpRecent,最近N个任务的traj)

那么,下面我们可以对Evo-Memory这篇工作做个总结,并且给出一些takeaway了。我们同样从WHAT,WHEN,HOW三个角度入手: 从WHAT这个角度:在agent memory的研究这个领域,memory已经从记录静态事实走向经验复用;而从WHEN这个角度,智能体的进化应当在部署的过程中,在连续的任务流中不断发生。而从HOW这个角度,我们意识到,在Reward-based尤其是text-Reward based这个向度上,要实现自我进化,不能只靠把原始轨迹塞入上下文,而要进行主动精炼

不过,在我看来,Evo-Memory虽然提出了一个很好的问题以及一套很好的分析问题的方案以及对应的baseline,但是它的思路以及实现还是有一些局限性的。大概是这里的三点

首先,在WHAT和HOW的层次,我们注意到论文中两种实现的方案在search上均高度依赖RAG从记忆池中检索对应的记忆。我们知道,RAG的主要思路是对文本做嵌入,根据语义相似度检索出topk。但是语义的相似度并不等于逻辑上相关。这种检索有可能引入语义噪声,影响模型的推理。

除此之外,在WHEN和HOW的层次,我们可以看到,目前evolving memory的基本单位是轨迹级的,它带给agent的经验是长线的,也就是当且仅当agent手头的任务整体上解决的思路跟agent之前遇到的经验比较相似的时候,agent才能复用这样的经验。而实际上agent在完成任务的过程中,也有很多单步或者跨步骤的经验可以复用,但这部分原子级的经验在目前的baseline中是被忽略掉的

最后呢,在DOMAIN Specific的层次,也就是WHERE的层次上,我们发现当前的记忆复用高度依赖于具体的领域,有关具体的领域词汇。但是实际上,我们知道,人类是可以提取出一些“元经验”复用在不同领域上的。现在的agent记住的也只是一个特定领域的相关经验,而不是形式化的推理逻辑和思路。

而基于上述三点,我也提出了三个可能的进路,也希望可以跟各位交流讨论一下

从WHAT&HOW的角度,我们可以尝试用skills代替RAG。我们不再像RAG那样进行语义搜索,而是用Agentic search取而代之,将记忆封装为可以利用的skill module,让Agent渐进式地检索之,既避免了语义相关但逻辑不相关的问题,也相对比较省token。

其次,从WHEN&HOW的角度,我们可以尝试实现原子经验解耦,从记忆整个轨迹,转向对步骤级经验的提取和组合,实现更灵活的知识迁移

最后,从DOMAIN也就是WHERE的角度,我们可以尝试让Agent记住逻辑因果,而非特定领域的具体经验/知识。我们可以用代码或其他的形式化的方式记录任务背后的逻辑关系,这样,自我进化智能体就可以实现跨领域的泛化与进化

现在我们可以发现,开头的那个MAP对我们看一篇Self-evolving的工作是很有帮助的,它可以帮我们梳理思路,思考它的核心创新点,并从WHAT,WHEN,HOW这样的角度进行延伸思考。

如何在手机上编写blog

· 阅读需 4 分钟

长久以来我都热衷于在各个平台散发各种各样的文字,虽然我实际上有自己的个人博客。搭建和迭代它花了好长时间,从开始的hexo到后来选用有更大diy空间的docusause,重构了很多次,在大概24年底完成了一个比较稳定的版本。

然而在个人博客建起来之后,我却很难把自己的笔记/blog之类更新过去。一方面,我的课程笔记包括很多图片,而这些图片又星罗棋布地散落在我的obsidian主文件夹之中,找出它们十分麻烦(这个问题由于我的懒惰现在还没解决,或许之后命令ai写个脚本吧);另一方面,对我而言,随想类的文字大多存放在手机上,把它们传到电脑上也要花点功夫,而知乎豆瓣之类的手机APP随用随发,很是方便。

——要是能在手机上直接编辑blog就好了!我常常这样想。

今天晚上摸鱼更新网站友链,于是又想起来这件事,遂和Gemini3商量起了方案。Gemini首先推荐了GitJournal,在连接了特定的github仓库之后,它可以读取其中的特定文件(比如markdown),并读取用户对这些markdown的操作,实时进行更新。

坏就坏在实时上。我试了试这个软件,确实能够很好地更新,但实在是过于灵敏了些。每次修改都会触发一次commit和push,即使关掉实时push的选项,commit也仍然会被触发。这对我而言实在是一个冗余的功能。一般来说,我也更希望一次提交不止一个文件,而不是面面俱到的commit。这个APP的雷点在于它的markdown渲染做得不太好。我的blog配置是这样的:在每个blog开头有yaml格式的Metadata,用来记录blog的时间/tag之类。而GitJournal在markdown编辑模式下,则完全看不到这些metadata。

这之后,我意识到,我只是希望我的手机能够做到修改,commit和push,与此同时,我不太在乎markdown渲染得怎么样,反倒是去除所有渲染格式的文件显示更得我心——毕竟还是要调一下yaml的。

于是Gemini3推荐了Mgit,可惜我用不了,是因为安卓版本的缘故。这一次它推荐的主要思路是解耦git功能以及修改功能,对于这俩功能分别推荐两类应用,但我还是觉得麻烦。

最后它终于推荐了spck editor,这深得我心,这让我发现,我实际上需要的就是一个附带git功能的手机版的vscode。现在,我可以在obsidian或者别的什么支持markdown格式的编辑器里写点什么,然后直接复制到editor里加上metadata一键提交。

就比如……现在!

RAG4Spice开发随笔

· 阅读需 7 分钟

本学期有一门课,需要写hspice做数字后端的仿真。众所不周知,这个仿真语言十分小众,现有大模型根本做不到正确输出。但本人又是究极懒狗,不想查语法之类,刚好了解到rag的技术,遂决定在ai辅助下做一下开发。

开始的构想十分美好:把lab pdf丢给程序,直接输出每个任务的代码,直接跑一下就交上去,就可以迅速把作业水掉了。当然,做为第一个原型,应当首先验证rag系统是否能够运行。所以第一步,先把整个基础结构搭出来,完成输入一张任务截图→OCR提取图中内容→根据内容查找→查找返回结果整合交给llm输出代码的workflow。完成这一步也花了不少功夫,尤其是刚产生这个想法的时候实际对rag的流程也是一知半解,embedding模型,向量数据库选型之类都是llm辅助下完成的(当然它也选得很好),在claude code的大力加持之下终于完成了这一步。

而从这个小原型到前文中提到的美好构想,显然仍然需要大费周章。lab pdf 里面什么都有,除了具体任务的介绍还有环境配置课程介绍之类,OCR之后自然是乱七八糟,直接一股脑丢给大模型填满上下文实在是很烂的选择。因此想到用一个长上下文的小模型做任务分解。为了在前端上渲染出分解好的任务,必须控制输出为json格式。json的设计,除了必须有的id和title之外,还加了任务简介(description)以及该任务涉及的知识(knowledge)。

需要注意的是,现有的大模型(即使是多模态)在电路图识别上效果都不怎么好,因此电路图这部分只能让human in the loop了——由用户自己把电路图相关信息输进去。岔开一嘴说,这就充分体现了我们Verilog这类电路描述语言的优势。电路图是给人看的,大模型还是看这类语言比较好。因此我觉得电路如果能用mermaid那类的语言来表示,大模型处理电路问题的能力应该能提升一大截。

任务分解后,用户就可以直接点击生成代码生成了,api request发给llm,前端再渲染出结果,这一部分也没啥好说的。

还是着重说一下里面使用的rag技术吧(其实也没什么好说),对pdf进行处理之后用langchain中的分块函数分块,500个字符一块,每个块之间留50个字符的重叠,然后就是经典的embedding,计算余弦相似度,返回最相似的3个块。没有reranking,也没有混合检索这类很fancy的trick,因为其实对于查语法也实在是够用了。我的需求其实只是大模型针对每个任务帮我搭一个框架,然后我改一改交作业而已。

写到这里它做为一个个人练手的toy项目,个人觉得它的历史使命已经完成了。当然还有一些可以优化的地方,比如它读不到我们实验服务器上面pmos和nmos的网表信息,因此写出来的器件定义常常是错的之类。但感觉这样的提升也是有限,有这样的时间,还不如我自己上手改一改呢。

写完项目本着不浪费的原则,就写简历上去找rag和agent相关的实习去了。然后显然被拷打。实际上我觉得这个项目有意思的与其说是rag相关,不如说是任务分解agent的思路。单就rag而言,其实算不上有多少技术含量。同时,实习/工业界会更看重一些性能指标,这就涉及到了需要测试rag本身结构的性能——而这个项目之中,显然也是没有的。从这个角度,也可以看出自己玩票和学术/工业界项目的区别。

不过也有好处,算是通过项目让我了解了很多rag和agent相关的知识。同时对于设计agent流程,有了一点自己的见解。

除此之外,它还促使我进行了一些思考:我真正在意的是项目开发之中的什么部分?前文没有说到的是,项目开发的过程中,我和claude code花了许多时间和token去重构api-key设置的部分,从一开始将其设计为环境变量的一部分,到最后的在前端UI上渲染为类似cherry studio的url+api key。我似乎并没有对rag的底层结构做很多的优化,而是很在意与用户交互的部分。或者说,这是我没有提前设计如何评估rag性能导致的?因为没有提前评估,所以导致无法显式地看到rag的性能,因此既没有动机也没有思路进行优化。之前自己写的项目大多是所见即所得的前端项目,之后,也需要考虑这一点。

总之,还是要学习,沉淀!

最后,项目已经开源在Github:https://github.com/Oscillater/RAG4Spice

使用riscv-gnu-tool将riscv32i汇编转换为机器码(16进制显示)

· 阅读需 3 分钟

做大作业需要这个,踩了点坑,故记录

操作系统:linux(mac请看官方文档)我使用的是wsl(Ubuntu)

安装必要依赖​

sudo apt update
sudo apt install -y \
git\
autoconf automake libtool \
libmpc-dev libmpfr-dev libgmp-dev \
texinfo gawk \
python3 wget build-essential bison flex

之后从github上clone并编译riscv-gnu-toolchain

cd ~
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
# 编译仅 32-bit ISA 支持,速度更快
./configure --prefix=/opt/riscv --with-arch=rv32i --with-abi=ilp32
make -j$(nproc)

上面那一步如果提示权限不够,需要在对应指令前面添加sudo

配置环境变量​

nano ~/.bashrc

或者

vi ~/.bashrc

在末尾加入

export RISCV=/opt/riscv #你clone工具链的位置
export PATH=$RISCV/bin:$PATH

保存并退出(不要忘了:wq ^_^

验证配置是否成功

which riscv32-unknown-elf-gcc

若返回对应地址,则配置生效

添加对应脚本​

到存放汇编文件的文件夹,在目录下打开wsl

新建脚本asm2hex.sh

#!/bin/bash
# 用法: ./asm2hex.sh filename.s
# 比如: ./asm2hex.sh test.s 会生成 test.hex

set -e

# 检查参数
if [ -z "$1" ]; then
echo "❌ 错误:请输入 .s 汇编文件名作为参数,例如:./asm2hex.sh test.s"
exit 1
fi

# 提取文件名(不带扩展名)
input_file="$1"
base_name="${input_file%.*}"

# 编译生成中间文件
riscv32-unknown-elf-as -march=rv32i -o "${base_name}.o" "$input_file"

# 转换为纯二进制文件
riscv32-unknown-elf-objcopy -O binary "${base_name}.o" "${base_name}.bin"

# 输出十六进制,每行一个指令,写入 .hex 文件
hexdump -ve '1/4 "%08x\n"' "${base_name}.bin" > "${base_name}.hex"

echo "✅ 成功生成:${base_name}.hex"

保存脚本之后,赋予执行权限

chmod +x asm2hex.sh

假设你的汇编文件名为 test.s ,执行脚本

 ./asm2hex.sh test.s

则可以看到转换为十六进制的输出,如果你编写的有错,也会给出提示。 示例 test.s 如下

.text
.global _start
_start:
addi x1, x0, 5
addi x2, x1, 10

得到的十六进制为

00500093
00a08113

可以通过网站验证其正确性

致谢​

感谢 GPT-4o 对本文的大力支持