3000 行代码怎么长出一棵技能树——GenericAgent 的自进化架构拆解

4 阅读1分钟

上周刷 GitHub Trending,GenericAgent 冲到周榜前五,一周涨了 3500 star。点进去看,README 第一句话就挺狠:

整个仓库从 git init 到每一次 commit,全是 Agent 自己完成的。作者没开过一次终端。

说实话,看到这种话我一般先打个问号。但翻了源码之后,确实有几个设计让我觉得值得拆一拆。

预加载技能 vs 自己长出来的技能

现在市面上的 Agent 框架,大多数走"预加载"路线——框架自带几十上百个工具,用户按需调用。LangChain 这么干,AutoGPT 也这么干。

问题在哪?

工具越多,system prompt 越长,token 消耗越高。一个带 50 个工具描述的 prompt,光工具定义就能吃掉 8000-12000 token。用户说"帮我查个天气",模型也得先读完 50 个工具定义再决定调哪个。

GenericAgent 反过来——核心只有 9 个原子工具。没有"查天气""发邮件""读微信"这些预置能力。它第一次遇到"帮我读微信消息"这个任务时,会自己装依赖、逆向数据库、写读取脚本,然后把整个执行路径固化成一个 skill。下次再问同样的事,直接一行调用。

第一次:安装依赖  逆向DB  写读取脚本  保存skill(约120 token/步 × 15 = 1800 token)
第二次:调用已有skill(约200 token)

这就是它号称"6倍token节省"的来源——不是第一次省,是后面每一次都省。用得越久,积累的 skill 越多,平均 token 消耗持续下降。

100 行 Agent Loop 到底干了什么

我把 agent_loop.py 翻了一遍,核心循环确实就 100 行出头。去掉日志和格式化代码,关键逻辑可以压缩成这样:

def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema, max_turns=40):
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_input}
    ]
    turn = 0
    while turn < max_turns:
        turn += 1
        # 每10轮重置工具描述,防止上下文膨胀
        if turn % 10 == 0:
            client.last_tools = ''
        
        response = client.chat(messages=messages, tools=tools_schema)
        
        if not response.tool_calls:
            break  # 模型没调用工具,任务结束
        
        tool_results = []
        for tc in response.tool_calls:
            tool_name = tc.function.name
            args = json.loads(tc.function.arguments)
            
            # 通过 handler 分发到具体工具
            outcome = handler.dispatch(tool_name, args, response)
            
            if outcome.should_exit:
                return  # 工具主动退出
            if not outcome.next_prompt:
                break   # 当前任务完成
            
            tool_results.append({
                'tool_use_id': tc.id,
                'content': json.dumps(outcome.data, ensure_ascii=False)
            })
        
        # 只传新消息,历史由 Session 管理
        messages = [{"role": "user", "content": next_prompt, "tool_results": tool_results}]

几个值得注意的设计:

每 10 轮重置工具描述。 这个操作很务实。长对话里,工具定义会被反复携带,上下文越来越大。每 10 轮清一次 last_tools,强制模型重新加载工具列表,既控制了 token 消耗,又避免了上下文窗口溢出。

历史消息不在循环里累积。 messages 每轮只放当前轮的用户消息和工具结果,历史对话由外层的 Session 对象管理。这样做的好处是 Agent Loop 本身不关心对话有多长——它只看当前轮需要什么。

handler.dispatch 是个生成器。 工具执行结果通过 yield 流式输出,支持长时间运行的工具(比如跑测试、装依赖)在执行过程中就给前端反馈,而不是等全部跑完再返回。

五层记忆:从规则到经验

GenericAgent 的记忆系统分 5 层,每层解决不同问题:

层级名称存什么类比
L0Meta RulesAgent 的行为规则和系统约束你的本能反应
L1Insight Index极简记忆索引,用于快速路由你的目录和书签
L2Global Facts长期运行积累的稳定知识你的常识库
L3Task Skills可复用的任务执行流程你的肌肉记忆
L4Session Archive已完成任务的精炼记录你的日记

这 5 层不是平等的。L3 是核心——每次完成一个新任务,Agent 会把执行路径结晶成一个 skill 存到 L3。L1 是索引层,记录"什么任务该调哪个 skill",保证下次遇到类似任务时能快速路由,不用重新探索。

用代码说的话,skill 结晶的过程大概是这样的:

# 伪代码:skill 结晶流程
class SkillCrystallizer:
    def crystallize(self, task_description, execution_trace):
        """从执行轨迹中提取可复用的 skill"""
        # 1. 过滤掉失败的步骤和重试
        clean_steps = [s for s in execution_trace if s.status == 'success']
        
        # 2. 提取关键操作序列
        key_ops = self.extract_key_operations(clean_steps)
        
        # 3. 参数化——把具体值替换成变量
        parameterized = self.parameterize(key_ops)
        # 比如把 "pip install mootdx" 变成 "pip install {package}"
        
        # 4. 生成 skill 定义
        skill = {
            "name": self.generate_name(task_description),
            "trigger": task_description,
            "steps": parameterized,
            "dependencies": self.extract_deps(clean_steps),
        }
        
        # 5. 写入 L3 记忆层
        self.memory.write_l3(skill)
        
        # 6. 更新 L1 索引
        self.memory.update_l1_index(skill["name"], skill["trigger"])
        
        return skill

这套设计的好处是 skill 跟用户绑定。同一个 GenericAgent 实例,给不同人用一段时间后,长出来的技能树完全不同。炒股的人有一棵量化筛选的技能树,做自媒体的人有一棵内容发布的技能树。

9 个原子工具够用吗

GenericAgent 只提供 9 个工具:

code_run        执行任意代码
file_read       读文件
file_write      写文件
file_patch      修改文件
web_scan        感知网页内容
web_execute_js  控制浏览器行为
ask_user        人机确认
update_working_checkpoint    持久化上下文
start_long_term_update       写入长期记忆

看起来很少,但仔细想想——code_run 一个工具就能覆盖"装包、写脚本、调 API、控制硬件"这些事。它不是在工具层做扩展,而是在运行时通过 code_run 动态创建新能力,再通过 skill 系统把临时能力固化成永久能力。

这个思路跟 Unix 哲学有点像。Unix 给你 pipeforkexec 几个原语,上层的一切都是组合出来的。GenericAgent 给你 code_runfile_*web_* 几个原语,上层的 skill 都是跑出来的。

说实话,我能想到的局限是——第一次执行复杂任务时,它需要反复试错。比如"帮我读微信消息"这个任务,它得先找到微信数据库在哪、用什么格式存的、怎么解密,中间可能失败好几次。这个过程的 token 消耗不低,用户体验也不太好。但一旦成功一次,后面就是一行调用的事了。

跟其他框架比,它赢在哪

我对比了 GenericAgent 和另一个本周很火的项目 hermes-agent(NousResearch 做的,10万+ star):

GenericAgenthermes-agent
代码量~3000 行53万行
部署pip install + API Key多服务编排
浏览器真实浏览器,保留登录态沙箱/无头浏览器
自进化自主 skill 生长插件生态
记忆5层分级记忆学习循环+用户建模

GenericAgent 赢在极简和自主性。3000 行代码意味着你能在一个下午读完全部源码,理解每个设计决策。而且它的 skill 是完全自主生长的——不需要人写插件、不需要社区贡献,用就行了。

hermes-agent 赢在工程完整度。多平台网关、子 agent 编排、RL 训练支持,这些都是 GenericAgent 目前没有的。

选哪个取决于你想要什么。想理解 Agent 架构原理、想要一个轻量级的个人 Agent,GenericAgent 更合适。想要一个开箱即用的生产级工具,hermes-agent 更成熟。

我的看法

GenericAgent 让我觉得最有意思的不是代码本身,是它背后的理念——Agent 的价值不在于它出厂时带了多少工具,而在于它能不能在使用过程中自己变强。

这跟人学东西其实很像。没人天生会做饭,但你做了几次之后就记住了步骤,下次不用再看菜谱。GenericAgent 的 skill 系统就是这个逻辑——执行过一次的任务,把关键步骤记下来,下次直接用。

当然,目前这类"自进化 Agent"还有不少问题。skill 质量参差不齐(有的执行路径本身不是最优解),skill 之间缺乏组合能力(不能把两个 skill 拼成一个新 skill),记忆层的搜索效率在 skill 数量大了之后也是个问题。

但方向我觉得是对的。未来的 Agent 框架,大概率会从"预装大量工具"走向"少量原语 + 自主进化"。GenericAgent 算是这条路上一个不错的起点。

想试的话,装起来很简单:

git clone https://github.com/lsdefine/GenericAgent.git
cd GenericAgent
pip install streamlit pywebview
cp mykey_template.py mykey.py
# 编辑 mykey.py 填入你的 API Key
python launch.pyw

仓库地址:github.com/lsdefine/Ge…