Day 12 / 共 20 天 · 第 3 周 工具/记忆/资源

tool_manager 与工具市场

工具怎么从 GitHub 下载、DB 登记、运行时反射加载、注入依赖?今天串起"工具市场"的完整链路——这是插件化工具的基础。

📍 你在整门课的位置
第1周 核心概念 第2周 Agent执行 D11 工具体系深入 D12 工具市场 D13-15 记忆/资源/模型 第4周 平台/异步/生态
L01

工具市场链路

🤔 昨天工具类都写在代码里,为什么还要一个"市场"和一套加载机制? 因为如果每个工具都得写死在核心代码里、启动时显式 import,那"加一个新工具"就要改核心代码、重新部署。想让别人一键装工具、社区繁荣,就必须让工具能运行时动态加载——这正是今天要拆的机制。
💡 一句话本质 工具市场 = "把工具的代码位置存进数据库,运行时靠反射动态加载 + 依赖注入"。核心一句:DB 里的 Tool 记录不存逻辑、只存"代码在哪"(folder/file/class),框架按这个地址用 importlib 找到类、实例化、塞进依赖。核心代码一行不改,就能扩展任意多工具——这就是插件化。
市场下载GitHub zipball 落本地目录marketplace_tools DB 登记Tool 表 反射加载ToolBuilder 注入依赖llm/resource Agent 使用
"工具市场"是什么? 像手机的应用商店——SuperAGI 有个工具市场,你能浏览、安装第三方工具(发 Slack、操作 Jira、查天气…),不用自己写。整条链路:从 GitHub 下载工具代码 → 放到本地目录 → 在数据库登记这个工具 → 智能体运行时动态加载它 → 注入依赖 → 用。今天逐段看。
L02

tool_manager 下载

tool_manager.py 负责从 GitHub 下载工具:

# tool_manager.py
# load_tools_config (:101) 读 superagi/tools.json 里登记的工具 URL
# download_and_extract_tools (:121):市场工具解压到 tools/marketplace_tools
#                                    外部工具解压到 tools/external_tools/
# download_marketplace_tool / download_tool (:17):通过 GitHub zipball API 下载解压
# load_marketplace_tools (:108):从官方仓库 TransformerOptimus/SuperAGI-Tools 拉列表
读法:工具代码托管在 GitHub,tool_manager.py 用 GitHub 的 zipball API 下载、解压到本地目录。Day 04 的 entrypoint.sh 启动时就跑 python tool_manager.py 先把工具下载好。三个目录(tools/external_tools/marketplace_tools)就是工具代码的存放处。
L03

Tool 表存"代码定位"

Tool 模型(models/tool.py:8)——关键:它存的是"代码在哪"而非逻辑本身:

class Tool(DBBaseModel):
    name = Column(String)
    folder_name = Column(String)   # 工具代码所在目录
    class_name = Column(String)    # 工具类名
    file_name = Column(String)     # 工具文件名
    toolkit_id = Column(Integer)   # 归属哪个 Toolkit
读法:数据库里的 Tool 记录不存工具逻辑,只存"这个工具的代码在 folder_name/file_name 里的 class_name 类"。就像一个"地址簿"——记着"这个工具的代码放在哪、类叫什么"。运行时靠这个定位信息去动态加载(L04)。
📝 一条真实的 Tool 记录长这样
Tool(
  name       = "DuckDuckGoSearch",
  folder_name= "duck_duck_go",              # 目录
  file_name  = "duck_duck_go_search.py",    # 文件
  class_name = "DuckDuckGoSearchTool",      # 类名
  toolkit_id = 3,                            # 属于哪个工具箱
)
看这条记录里没有任何搜索逻辑——只有"去 duck_duck_go/duck_duck_go_search.pyDuckDuckGoSearchTool 这个类"。L04 就照这个地址把类"取"出来。
⚠️ 小白常误以为 "工具装进了数据库"——DB 里存着工具的代码。其实恰恰相反:代码永远在磁盘的 .py 文件里(L02 下载来的),DB 只存一张"地址条"。就像应用商店的"应用详情页"不含程序本体、只写着下载地址——真正的程序在你手机的存储里。
💡 生活类比(应用商店世界观) Tool 表就像应用商店里每个 app 的"货架标签":标签上写着 app 名、开发者、安装包放在哪个仓库货位(folder/file/class)——标签本身不是 app。商店靠标签找到安装包,SuperAGI 靠 Tool 记录找到工具类。
L04

ToolBuilder 反射加载

ToolBuilder.build_toolagent/tool_builder.py:50)用 Python 反射动态导入并实例化工具类:

module_name = ".".join([...tools_dir..., tool.folder_name, file_name])
module = importlib.import_module(module_name)      # 动态导入模块
obj_class = getattr(module, tool.class_name)       # 按类名取出类
new_object = obj_class()                           # 实例化
new_object.toolkit_config = DBToolkitConfiguration(session=..., toolkit_id=...)
读法:根据 DB 里 Tool 记录的 folder/file/class 名,用 importlib.import_module + getattr 在运行时把工具类找出来、实例化。再把 toolkit_config 换成数据库版(DBToolkitConfiguration,从 DB 读配置并 decrypt_data 解密密钥,Day 11)。
反射加载:从"地址簿"到"能干活的工具实例" DB Tool 记录 folder_name file_name class_name import_module() getattr(class_name) 按地址取出类 obj_class() 实例化空工具 注入依赖 llm / resource toolkit_config → 可用工具 核心代码没有一句 "import DuckDuckGoSearchTool"——全靠 DB 里的地址 + 反射,运行时才把类找出来。
DB 地址 → importlib 取模块 → getattr 取类 → 实例化 → 注入依赖,全在运行时完成。
L05

依赖注入

set_default_params_tooltool_builder.py:84)用一串 hasattr 判断,把运行时依赖注入进工具实例:

if hasattr(tool, 'llm'):
    tool.llm = get_model(model=..., api_key=..., ...)   # 注入 LLM(Day 10 工厂)
if hasattr(tool, 'resource_manager'):
    tool.resource_manager = FileManager(...)            # 注入文件管理器
if hasattr(tool, 'tool_response_manager'): ...
# 还注入 goals / instructions / agent_id / memory 等
读法:工具类里声明可选字段(llm、resource_manager…),框架构建时按 hasattr 判断"这工具需要啥"、按需塞进去。所以 Day 03 的 WriteFileTool.resource_managerThinkingTool.llm 都不是自己创建的,而是被框架注入。
📝 同一个工具,注入不同依赖 → 行为不同 WriteFileTool 声明了字段 resource_manager
• 组织 A 的运行:注入的 FileManager 配了 S3 → 文件写到 A 的 S3 桶;
• 组织 B 的运行:注入的 FileManager 用本地盘 → 文件写到 B 的本地目录。
工具类代码完全一样,只因框架注入了不同的 resource_manager,行为就不同——这就是"解耦"的价值。
👶🧑‍🏫 对话体:为什么工具不自己创建 LLM? 👶 小白:工具里直接写 self.llm = OpenAi(api_key="sk-…") 不是更省事吗?
🧑‍🏫 老师:那这个工具就"焊死"在 OpenAI 上了。换 Google Palm 的组织用不了;而且 api_key 写谁的?每个组织的 key 都不一样。
👶 小白:那工具运行时怎么拿到"该用哪个模型、谁的 key"?
🧑‍🏫 老师:它不拿——它只声明一个空字段 llm,框架在构建时(set_default_params_tool)按当前这次运行的配置把正确的 LLM 实例塞进来。工具永远不用知道答案,答案由框架"喂"。这就是依赖注入。
依赖注入的好处 生活类比(还是应用商店世界观):依赖注入就像手机 app 声明"我需要相机和定位权限"——app 自己不造相机,装好后由系统统一把相机、定位"接"给它;换一台手机(换组织/换 LLM),接的就是那台手机的相机。
工具不自己 new LLM/FileManager——它声明"我需要这些",由框架注入。好处:① 工具与"具体用哪个 LLM、哪个数据库会话"解耦(同一工具能用不同模型);② 好测试(测试时注入假 LLM);③ 工具代码更纯粹(只管业务,不管依赖从哪来)。这和 AutoGPT 执行时注入凭证、eino 的 DI 一个道理——依赖注入是解耦的利器。
L06

为什么用反射

为什么不直接 import 工具类,而要用反射动态加载?

⚡ 错误驱动:如果没有反射,装一个市场工具会发生什么? 设想没有反射:你从市场装了个"发 Slack"工具,代码下载好了,但核心代码里没有 import SlackTool 这一行——框架根本不知道它存在。要用它,你得:改核心源码加 import → 重新构建镜像 → 重启整个平台(所有正在跑的智能体中断)。装一个 app 要重装整个操作系统,商店直接没法成立。反射把这个流程变成:DB 里插一行记录,下一次运行就能用,平台一秒都不用停。
反射 = 插件化的基础 如果工具类都要在代码里显式 import,那加一个新工具就得改核心代码(加一行 import、注册到某个列表)。用反射(importlib + getattr):新工具只需在数据库登记"文件夹/文件/类名",框架运行时就能动态找到并加载——核心代码一行不用改这正是"工具市场/插件化"的技术基础:从市场下载一个工具、登记进 DB,它就能被智能体用,无需重新部署。反射让"运行时动态扩展"成为可能——和 AutoGPT 的 AutoRegistry、Day 02 的自动发现异曲同工。
L07

完整链路串联

把今天和前面串起来——一个工具从市场到被智能体使用:

  1. 下载(tool_manager):从 GitHub 下载工具代码到本地目录。
  2. 登记:在 DB Tool 表记下 folder/file/class 名。
  3. 反射加载(ToolBuilder.build_tool):运行时按 DB 记录动态 import 实例化。
  4. 注入依赖(set_default_params_tool):塞进 llm/resource_manager 等。
  5. 进提示(Day 08):工具的 name/desc/schema 拼进 LLM 提示。
  6. 被调用(Day 06/09):LLM 选中它 → 解析动作 → execute → _execute。
从"市场里的一个工具"到"智能体正在用它",这条链路完全动态、可扩展——这就是 SuperAGI 工具生态繁荣的基础。你(或社区)写个新工具、发到市场、别人一键安装即用,无需改平台代码。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 工具市场的完整链路(下载→登记→加载→注入→使用)?
  • Tool 表为什么存"代码定位"而非逻辑?
  • ToolBuilder 怎么用反射动态加载工具?
  • 依赖注入怎么工作?好处是什么?
  • 为什么用反射?它和"插件化"什么关系?

✋ 动手

P=superagi
grep -n 'def download_and_extract_tools\|def load_marketplace_tools' $P/tool_manager.py
sed -n '8,28p' $P/models/tool.py                       # Tool 表
sed -n '50,123p' $P/agent/tool_builder.py | head -50   # 反射加载 + 依赖注入
明天预告 · Day 13向量记忆 vector store——给智能体"长期记忆":Pinecone/Chroma/Qdrant/Redis、embedding、add/get_matching_text 的 RAG 闭环。
← Day 11 工具深入 Day 13 · 向量记忆 →