Day 10 / 共 20 天 · 第 2 周 Block 系统(收官)

SDK 与自定义 Block

第2周收官。开发者如何用 SDK 写一个新 Block、用 ProviderBuilder 声明第三方集成、AutoRegistry 自动注册。看"声明式"如何让扩展变简单。

📍 你在整门课的位置 · 第 2 周 Block 深入(收官)
D9 Block 成本 D10 SDK D11 Graph 模型深入 D12 ExecutionManager
💡 今天的类比世界观:写块上架 = "报名 + 自动登记花名册" SDK = 一站式报名入口(一个地方把要用的东西都拿到);写块骨架 = 填一张标准报名表(继承 Block、声明输入输出);ProviderBuilder = 公司信息填一次,旗下多个块共享AutoRegistry = 自动把你登记进花名册,不用你手动去某处 import 报到。今天都用"报名登记"来想。

👶 小白:我写完一个新 Block,要去哪个文件里手动"注册"它,平台才认识?

👨‍🏫 老师:哪都不用去改。只要你的类继承了 Block 并放进 blocks 目录,AutoRegistry 会在启动时自动扫描、发现、登记它——就像报名表交上去,花名册自动多了你一行。这就是"声明式"的威力:你只声明"我是一个什么样的块",繁琐的注册接线由框架代劳。回味 Day 02 的"自动发现注册",正是同一招。

L01

SDK 一站式入口

写 Block 的开发者用 backend/sdk/sdk/__init__.py 是统一入口,一行 import 就能拿到一切:

from backend.sdk import *
# 导出:Block、BlockSchemaInput/Output、SchemaField、
#       各种 Credentials、ProviderBuilder、cost、AutoRegistry ...
读法:SDK 把写 Block 需要的所有东西集中 re-export——开发者不用到处 import,一句 from backend.sdk import * 齐活。SDK 目录只有 4 个文件:builder.pyprovider.pyregistry.pycost_integration.py——精简。
为什么要专门的 SDK 入口? Block 的相关类散落在 data/model.pyblocks/_base.py 等多处。如果每个 Block 作者都要记住"SchemaField 在哪、Credentials 在哪",很烦。SDK 把这些集中成一个"给 Block 作者的工具箱"——降低写块的门槛,让社区更容易贡献。好的 SDK = 降低扩展门槛。
L02

写一个块的最小骨架

StoreValueBlockblocks/basic.py:70)是入门模板,套路(Day 07 总结过):

from backend.sdk import Block, BlockSchemaInput, BlockSchemaOutput, SchemaField, BlockCategory

class MyBlock(Block):
    class Input(BlockSchemaInput):
        text: str = SchemaField(description="...")
    class Output(BlockSchemaOutput):
        result: str = SchemaField(description="...")
    def __init__(self):
        super().__init__(id="<新 UUID>", description=..., categories={BlockCategory.BASIC},
                         input_schema=MyBlock.Input, output_schema=MyBlock.Output,
                         test_input={"text": "hi"}, test_output=[("result", "HI")])
    async def run(self, input_data: Input, **kwargs) -> BlockOutput:
        yield "result", input_data.text.upper()
读法:写块 = 定义 Input/Output + __init__ 声明元数据 + run。放进 blocks/ 目录就自动被发现(Day 02)。生成一个新 UUID 当 id(永久身份,Day 06),配上 test_input/output 自带测试。
L03

ProviderBuilder:声明第三方集成

🤔 痛点:接一个新服务,要改凭证系统、成本系统、OAuth、环境变量……几十处,太容易漏 传统做法是"命令式"——你得亲手把新服务登记进平台的每一个子系统。少接一处,凭证就注入不进来、成本就漏算。第三方贡献者望而却步。
💡 本质:把"接入动作"变成"一段声明",让平台自己去对号入座 你只用链式 builder 描述"这个服务叫什么、要什么凭证、怎么计费",.build() 后交给 AutoRegistry——它自动把这段声明注入平台各子系统。你声明"是什么",平台负责"怎么接"。这就是声明式扩展。

对接外部服务(含 API key / OAuth / 计费),用 ProviderBuilder 流式 API(sdk/builder.py:27)。真实例子(blocks/exa/_config.py):

exa = (
    ProviderBuilder("exa")
    .with_description("Neural web search")
    .with_api_key("EXA_API_KEY", "Exa API Key")     # 声明需要 API key
    .with_webhook_manager(ExaWebhookManager)
    .with_base_cost(100, BlockCostType.COST_USD)     # 声明基础成本
    .build()
)
读法:一个链式 builder 声明"这个服务(exa):叫什么、需要什么凭证、基础成本多少、webhook 怎么管"——一次配好整个 provider。还支持 .with_oauth(...)(自动推导 {NAME}_CLIENT_ID/SECRET 环境变量)。
builder 模式的好处 对接一个新服务涉及一堆配置(凭证类型、环境变量名、成本、webhook)。链式 builder 让你用可读的、声明式的方式一次配好,比散落各处的配置清晰得多。.with_xxx().with_yyy().build() 读起来就像在描述"这个服务有这些特性"。和 crewAI Flow 的装饰器、eino 的 Chain builder 一样——流式/声明式 API 让配置意图一目了然。
L04

一次配置,多块共享

配好 provider 后,该服务的每个 Block 用 exa.credentials_field(...) 声明凭证字段(blocks/exa/search.py:49),run 签名带 credentialssearch.py:135):

class Input(BlockSchemaInput):
    credentials = exa.credentials_field(...)   # 复用 provider 配置
async def run(self, input_data, *, credentials: APIKeyCredentials, **kwargs):
    api_key = credentials.api_key.get_secret_value()   # 拿到真 key
📝 具体例子:一次声明,45 个块白嫖配置 exa/_config.pyProviderBuilder("exa")…build() 只写一次(配好凭证类型、环境变量 EXA_API_KEY、基础成本)。
然后 exa/search.pyexa/contents.pyexa/find_similar.py…共 ~45 个块,每个只写一行 credentials = exa.credentials_field(...) 就复用了这份配置。改 Exa 的成本?只动 _config.py 一处,45 个块同时生效。
_config.py 注释说明:"一个 provider 配置一次,~45 个兄弟 block 共享"。Exa 有 45 个 Block(搜索、抓取、相似查找…),它们共享同一份 provider 配置(凭证、成本规则)——不用每个块重复声明。这就是 ProviderBuilder 的价值:DRY(不重复自己)——服务级配置写一次,块级复用。
L05

AutoRegistry:自动注册中枢

AutoRegistrysdk/registry.py:49)是中央注册表(线程安全),保存所有 SDK 注册的 provider、默认凭证、OAuth handler、webhook manager、成本配置、API key 映射。

你的声明 ProviderBuilder.build() AutoRegistry 中央注册表 + patch register 凭证系统 成本系统 OAuth 系统 webhook / 环境变量 patch_integrations() 猴子补丁·零侵入注入
声明式扩展全链路:你 build() 一段声明 → AutoRegistry 收下 → patch_integrations() 把它悄悄注入平台既有的凭证/成本/OAuth/webhook 各系统。作者不碰平台核心一行代码。
"猴子补丁"注入是什么魔法? patch_integrations()registry.py:166)用猴子补丁(monkey patch)把 SDK 注册的东西注入到平台既有的集成点。意思是:你用 ProviderBuilder 声明了一个新服务,AutoRegistry 会"悄悄地"把它接进平台的凭证系统、成本系统、OAuth 系统——作者只需"声明",平台自动"感知",不用改平台核心代码。这让"加一个新的第三方集成"变成纯声明式的、零侵入的操作。约定优于配置 + 自动注册——扩展性拉满。
L06

声明式的威力(回味)

整个 Block/SDK 系统贯穿一个理念:声明,而非命令。你只需声明:"这个块有这些输入输出""这个服务需要这种凭证""这个块这样计费"——平台自动把它接入发现、注册、凭证、成本、UI 各个系统。

对比命令式:如果没有这套声明式机制,加一个块你得手动:注册到块列表、写前端表单、接入凭证系统、配成本表、写数据库迁移……几十步、易出错。声明式让这一切自动化——你专注"块干什么",平台处理"块怎么被集成"。这是 AutoGPT Platform 能有 300+ 块、社区能持续贡献的根本原因。好的扩展机制 = 让贡献者只关心业务、不关心管道。
L07

前端画布怎么映射到后端

回扣 Day 03:前端可视化画布和后端 Graph 模型一一对应——画布上一个方块 = 一个 Node(绑 block_id);连线 = 一条 Link;整个画布 = 一个 Graph

你的块怎么出现在画布上? 你写好一个 Block(有 Input/Output schema)→ 自动发现注册(Day 02)→ 同步到 DB(Day 06)→ 前端从 API 拿到块清单,用 Block 的 jsonschema()(Day 02)自动渲染出"这个块长什么样:有哪些输入框、哪些输出引脚"。用户拖它到画布 = 创建一个 Node,连线 = 创建 Link。从你写的 Python 类,到用户能在画布上拖拽的可视化积木——这条链完全自动。你只写后端块定义,前端 UI 白送。
L08

🎓 第 2 周收官 + 动手

第 2 周(Day 06-10)你已吃透 Block 系统

  • Day 06 Block 基类:ABC+泛型、_execute 生命周期、类型分类
  • Day 07 真实 Block:纯计算/IO/状态/AI 四种范例
  • Day 08 凭证:引用vs实体、discriminator、加锁刷新
  • Day 09 成本:六种类型、静态/动态、cost_filter
  • Day 10 SDK:ProviderBuilder、AutoRegistry、声明式扩展

你已理解"积木怎么定义、怎么被集成"。下周(Day 11-15)进入 执行引擎——图模型、manager、数据流、定时、WebSocket。

✋ 动手

P=autogpt_platform/backend/backend
sed -n '120,171p' $P/sdk/__init__.py                 # SDK 导出
sed -n '27,90p' $P/sdk/builder.py | head -40         # ProviderBuilder
sed -n '49,180p' $P/sdk/registry.py | head -50       # AutoRegistry
cat $P/blocks/exa/_config.py                          # provider 实战
下周预告 · Day 11:进入执行引擎——Graph 模型(Node/Link)深入:三层 Graph 模型、计算字段、校验、版本/fork、starting_nodes,为读执行引擎打基础。
← Day 09 成本 Day 11 · Graph 模型 →