数据截至 (上游 commit fa8930ebe72a)
第 3 章:自建 agent —— 让它自己长出函数
前两章建好了「函数即数据 + 即时 exec」的底座。本章是项目卖点:既然函数是数据,LLM 就能生成新函数存回库。看
process_user_input和self_build怎么把一句话需求,变成库里几个新函数并跑出结果。这些都在
babyagi/functionz/packs/drafts/,作者明确标注是实验性 draft,生成的代码「minimal and may need improvement」。
3.1 核心直觉:复用优先,不够才造
自建的中心思想不是「每次都让 LLM 写代码」,而是先翻库里有没有现成的,没有才生成——而且生成时鼓励拆成小而可复用的函数。这样库会越用越「肥」,复用率越来越高。
用户一句话需求
│
▼
┌──────────────────────┐ 有现成的?
│ check_existing_functions│──── 是 ──► 直接用那个函数
└──────────┬───────────┘
│ 否
▼
┌──────────────────────┐
│ break_down_task │ LLM 把任务拆成若干小函数(名/描述/参数/依赖)
└──────────┬───────────┘
▼
┌────────────────── ────┐ 逐个函数:
│ generate_functions │ ├ find_similar_function(向量查重)
│ │ └ 没有 → create_function → LLM 写代码 → 存回库
└──────────┬───────────┘
▼
┌──────────────────────┐
│ extract_function_params│ LLM 从原始需求里抽出调用参数
└──────────┬───────────┘
▼
run_final_function → 输出
这条主线全在 process_user_input(code_writing_functions.py:514)。
3.2 主流程:process_user_input
# babyagi/functionz/packs/drafts/code_writing_functions.py:514
def process_user_input(user_input):
result = check_existing_functions(user_input) # 1. 先查现成
if result['function_found']:
function_name = result['function_name']
else:
function_breakdown = break_down_task(user_input) # 2. 拆任务
context = {'user_input': user_input, 'function_breakdown': function_breakdown}
generate_functions(function_breakdown, context) # 3. 逐个生成并存库
function_name = function_breakdown[0]['name'] # 假设主函数是第一个
parameters = extract_function_parameters(user_input, function_name) # 4. 抽参
return run_final_function(function_name, **parameters) # 5. 执行
注意第 3 步后的一句强假设:function_name = function_breakdown[0]['name']——它默认 LLM 拆出来的第一个函数就是入口函数。这是个脆弱约定,LLM 拆错顺序就会调错入口。
3.3 关键步骤拆解
(1) check_existing_functions:LLM 当「检索器」
把库里所有函数的 name + description 喂给 LLM,问「有没有哪个完美满足需求」,要求返回 JSON。解析失败就 while True 无限重试:
# babyagi/functionz/packs/drafts/code_writing_functions.py:59
response = gpt_call(prompt)
try:
result = json.loads(response)
if 'function_found' in result and ...:
return result
except Exception:
continue # 解析失败就一直重试(没有上限)
这个
while True无限重试模式贯穿整个 draft pack(break_down_task、decide_imports_and_apis、extract_function_parameters都是)。好处是对 LLM 偶发的格式错误鲁棒;坏处是LLM 持续返回坏 JSON 时会卡死。self_build.py里的generate_queries改良了这点——见下文。
(2) break_down_task:把任务拆成「微服务式」小函数
prompt 明确要求 LLM 输出一个函数列表,每项含 name / description / input_parameters / output_parameters / dependencies / imports / code(占位),并反复强调「每个函数尽量小、可复用、参数化而非写死」:
# 提示词要点(code_writing_functions.py:84-92,真实 prompt 节选)
# - Each function should be as small as possible and do one thing well.
# - Use existing functions where possible (gpt_call, find_similar_function...).
# - Every sub function ... designed to be reusable by turning things into parameters.
这一步只产出骨架(code 字段是占位),真正的代码在 create_function 里生成。
(3) generate_functions → create_function:写代码并存回库
对拆出的每个函数,先用