数据截至 (上游 commit fa8930ebe72a)
第 4 章:巧妙之处、边界与代码地图
收尾章:先讲值得带走的设计精华,再诚实列出真实的坑(包括一个没实现的函数),最后给横向对比和可 grep 的代码地图。
4.1 巧妙之处(可借鉴的技术)
(1) 「函数即数据」+ 版本化:让程序能改自己
把函数体抽成字符串存库(registration.py:24)、用 is_active 位做版本切换(local_db.py:98),是整个项目的支点。因为代码变成了可增删改查的数据,LLM 生成的新代码才能无缝成为系统的一部分——这是 self-building 能成立的根本前提。
(2) 模块级 __getattr__ 门面糖
babyagi.anything() / func.anything() 自动去库里找同名函数执行(__init__.py:112、framework.py:26)。用户感觉在调普通 Python 函数,实则每次都从数据库取码 exec。用语言机制把「数据库查询」伪装成「属性访问」,极大降低了使用心智负担。
(3) 运行即装依赖:生成的函数能立刻跑
依赖(第三方库)在执行时缺了就当场 pip install(execution.py:15),依赖函数递归 exec 进同一作用域(execution.py:37)。这让 LLM 刚生成、声明了 imports=["requests"] 的新函数无需任何手动安装步骤就能首次运行。
(4) 调用劫持 + 防环:统一的依赖装配与日志
依赖函数被包成 wrapper 回调 execute()(execution.py:50),使整棵调用树都享有依赖装配和日志;visited(依赖环)和 executed_functions(触发器递归)两道防线(execution.py:37、:221)防住了自引用结构的死循环。
(5) 触发器实现「自我维护」闭环
ai_description_generator 声明 triggers=["function_added_or_updated"](ai_functions.py:32):每当有函数被加/改,自动用 LLM 补描述。新生成的函数会被系统自动「整理归档」,这是 self-building 系统保持可检索的关键。
4.2 边界与局限(诚实)
★ 真实的坑:add_trigger 根本没实现
add_trigger_wrapper(default_functions.py:111)→ func.add_trigger(framework.py:108)→ db.add_trigger(db_router.py:289)→ 最终调 self.db.add_trigger(...),但 LocalDB 里压根没有 add_trigger 方法(grep local_db.py 命中 0 次)。所以运行时调用 add_trigger_wrapper 会抛 AttributeError。
实际可用的触发器只能通过注册时的 triggers=[...] 字段声明(存进 FunctionVersion.triggers JSON,由 get_triggers_for_function 读取,db_router.py:293)。这是文档/API 与实现脱节的典型 draft 缺陷。
exec 无沙箱:任意代码执行
执行引擎对函数代码直接 exec(code, local_scope)(execution.py:122),且会自动 pip install 任意包名(execution.py:19)。库里的代码(尤其 LLM 生成的)以完整进程权限运行,没有任何隔离。配合「LLM 写代码存库」,这是明显的安全面——作者也明说非生产用途。
「第一个函数即入口」的强假设
process_user_input 默认 function_breakdown[0] 是入口(code_writing_functions.py:528)。LLM 拆解顺序一旦不把主函数放第一个,就会调错函数。
draft pack 的无限重试
check_existing_functions / break_down_task 等用 while True 解析 LLM 输出(code_writing_functions.py:69 等),LLM 持续返回坏 JSON 会卡死。只有 self_build.py 的 generate_queries 用了有上限重试。
输出参数靠静态猜测
parse_function_parameters 只扫第一个 return、按 AST 节点类型猜类型(registration.py:64),复杂返回一律 Any,且只看第一个 return 语句——参数元数据不可全信。
单进程单库,无并发设计
全局一个 _func_instance + 一个 SQLite 文件(funztionz.db),scoped_session(local_db.py:16)。没有面向多 worker/高并发的设计,定位是单机实验。
4.3 横向对比(同 shelf 兄弟项目)
| 维度 | BabyAGI (functionz) | 经典 ReAct/工具型 agent | 多 agent 编排框架 |
|---|---|---|---|
| 核心抽象 | 函数即数据库行,运行时 exec | 工具是固定注册的 Python 回调 | agent + 消息/角色 |
| 能否自造能力 | 能:LLM 写新函数存回库 | 一般不能(工具集预定义) | 一般不能 |
| 能力持久化 | 持久(进 SQLite,跨会话留存) | 进程内,重启即失 | 多为进程内 |
| 版本/回滚 | 内建(FunctionVersion + is_active) | 无 | 无 |
| 安全隔离 | 无(裸 exec + 自动 pip) | 取决于工具实现 | 取决于实现 |
| 成熟度 | 实验 / draft | 多为成熟库 | 多为成熟库 |
一句话定位: 兄弟项目大多在「用固定工具集把任务做好」,BabyAGI 赌的是另一条路——让 agent 持久地、可版本化地往自己身上加工具。代价是牺牲了安全隔离和工程成熟度。
注:本表对「兄弟项目」的描述是该类框架的一般取舍,具体子库以各自 doc 为准。
4.4 代码地图(导航索引)
按符号名可 grep 定位,行号 as-of fa8930eb:
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 包入口 / 单例 / 动态属性 | babyagi/__init__.py | _func_instance、create_app、register_function、__getattr__ |
| 门面类 / 接好三件套 | babyagi/functionz/core/framework.py | Functionz、Functionz.__init__、Functionz.__getattr__ |
| 执行引擎主流程 | babyagi/functionz/core/execution.py | FunctionExecutor.execute |
| 依赖递归装配 | babyagi/functionz/core/execution.py | _resolve_dependencies、_create_function_wrapper |
| 自动 pip 安装 | babyagi/functionz/core/execution.py | _install_external_dependency |
| 触发器执行 + 防递归 | babyagi/functionz/core/execution.py | _execute_triggered_functions、_prepare_trigger_arguments |
| 注册:抽代码 + AST 解析 | babyagi/functionz/core/registration.py | register_function、parse_function_parameters |
| 注册查重 | babyagi/functionz/core/registration.py | function_has_no_changes |
| ORM 5 张表 | babyagi/functionz/db/models.py | Function、FunctionVersion、Import、Log、SecretKey |
| 版本化落库 | babyagi/functionz/db/local_db.py | LocalDB.add_or_update_function |
| ORM 拍平成 dict | babyagi/functionz/db/db_router.py | DBRouter.get_function、get_all_functions |
| 触发器读取(注意 add_trigger 缺失) | babyagi/functionz/db/db_router.py | get_triggers_for_function、add_trigger(调用了不存在的 LocalDB.add_trigger) |
| 密钥加密 | babyagi/functionz/db/models.py | SecretKey.value、get_or_create_key |
| 库操作函数包 | babyagi/functionz/packs/default/default_functions.py | add_new_function、execute_function_wrapper、function_added_or_updated |
| LLM 调用 / 向量检索 | babyagi/functionz/packs/default/ai_functions.py | gpt_call、find_similar_function、ai_description_generator |
| 函数调用式 chat | babyagi/functionz/packs/default/function_calling_chat.py | chat_with_functions |
| 自建主流程 | babyagi/functionz/packs/drafts/code_writing_functions.py | process_user_input、break_down_task、create_function、generate_function_code |
| 批量自建 | babyagi/functionz/packs/drafts/self_build.py | self_build、generate_queries |
4.5 一句话收束
BabyAGI(functionz)把一个大胆的想法做成了能跑的最小原型:把函数当数据存、运行时 exec 复活、让 LLM 写新函数存回同一个库,于是 agent 能持久地、可版本化地给自己加能力。它在「自我扩展」这条轴上走得很前,但用裸 exec、无沙箱、draft 级容错换来这份简洁——正如作者所言,这是用来启发讨论的实验,不是生产系统。