数据截至 (上游 commit fa8930ebe72a)
第 1 章:三件套与函数存储
本章讲地基:
Functionz怎么把活儿分给两个助手,以及一个函数被登记时,它的「签名、依赖、代码、版本」分别落到数据库哪张表。读完你就知道「函数 = 一行数据」具体长什么样。
1.1 三件套:门面 + 注册器 + 执行器
functionz 的核心只有三个类,职责干净分离:
| 类 | 角色 | 类比 |
|---|---|---|
Functionz | 门面(facade),自己几乎不干活,把方法转发给下面两个和 db | 前台 |
FunctionRegistrar | 「存」:把一个函数解析、查重、写成数据库里的新版本 | 档案管理员 |
FunctionExecutor | 「跑」:从库里取出代码,exec 复活并执行 | 放映员 |
构造时一次性把三者接好,Functionz 持有另外两个的实例:
# babyagi/functionz/core/framework.py:17 — Functionz.__init__
self.db = DBRouter(db_type, **db_kwargs)
self.executor = FunctionExecutor(self) # 跑
self.registrar = FunctionRegistrar(self) # 存
整个进程只有一个 Functionz 实例,在包加载时建好(_func_instance = Functionz(),babyagi/__init__.py:13),所有函数共享同一个库。
门面最妙的一招:__getattr__ 把库里的函数变成「可点出来的属性」
你能写 babyagi.hello_world(),但 babyagi 模块里根本没有 hello_world 这个名字。靠的是 Python 的模块级 __getattr__ 钩子:
# babyagi/__init__.py:112 — 模块级 __getattr__
def __getattr__(name):
if _func_instance.get_function(name): # 库里有这个函数吗?
# 有 → 返回一个执行它的 lambda
return lambda *args, **kwargs: _func_instance.executor.execute(name, *args, **kwargs)
raise AttributeError(...)
Functionz 类自己也有同样套路的 __getattr__(framework.py:26),所以 pack 里能写 func.gpt_call(...) 调到库里的 gpt_call。「点一个不存在的属性 → 去数据库找同名函数 → 找到就执行」,这是整个框架「函数即数据」体验的门面糖。