组件自扩展
Agent 的能力不是写死的。遇到没有现成工具的任务,它可以现场写一个组件、落盘、3 秒后自己用上——这就是自扩展闭环。
闭环流程
一次自扩展通常走完这六步。任何一步失败都要停下来修,不要叠加着往下走。
- 任务进来,现有工具都不合适
- 用 component.dir() 确认真实写入路径(pip 安装与绿色版不同,不要猜)
- 取模板或直接 generate,把骨架写到 components/ 下
- 补全 _cmd_ 方法的实现与 docstring
- 等 3 秒,或手动 reload(),确认出现在工具列表里
- 立刻用真实输入调一次验证,通过才算完成
component 工具命令
内置组件 component 是自扩展的入口,11 个子命令覆盖生成到排障。
| 命令 | 用途 |
|---|---|
template(style) | 取模板代码:minimal / full / example / background |
generate(name, description, commands) | 生成单文件组件并写入 components/ |
generate_package(name, description, commands, modules) | 生成包组件(文件夹 + modules),适合复杂功能 |
list / info(name) | 列出已注册组件,可按名搜索、分页 |
reload() | 手动触发一次全量扫描重载 |
dir() | 返回组件的真实目录路径 |
sandbox_list / sandbox_add / sandbox_remove | 查看 / 加入 / 移出免沙箱白名单,改完立即生效 |
cleanup(dry_run, force) | 清理加载错误、失效白名单条目、残留沙箱进程 |
install(package, upgrade) | 从 PyPI 安装组件包(cellium-comp-*) |
生成与生效
# 生成骨架
component.generate("hash_tool", "File hash calculator",
commands='[{"name":"md5","desc":"Calculate MD5 hash"},{"name":"sha256","desc":"Calculate SHA256 hash"}]')
# 文件落盘 → 3 秒内被 watcher 捕获 → 自动加载 → 立刻可调用
hash_tool.md5(input_data="/etc/hostname")让 LLM 自己查用法
组件工具的 description 会随命令表变化。拿不准某个命令的参数时,先调一次 help 或 info,比反复试错快。
热插拔监控
ComponentWatcher 每 3 秒拍一次快照(路径 → mtime),只处理顶层 .py 文件。
| 事件 | 动作 |
|---|---|
| 新文件出现 | 实例化、注册、包装成工具、写入配置 |
| mtime 变化 | 卸载旧实例后重新加载,on_load 重新执行 |
| 文件被删除 | 从内存与工具注册表移除,配置同步清理 |
注意:包组件(文件夹)只按目录内所有 .py 的最大 mtime 判断,watcher 本身只扫顶层 .py。改包内部文件后需要 component.reload() 才会生效。
组件规范
照这个骨架写,参数 schema 和工具描述都能自动生成正确。
from typing import Any, Dict
from app.core.interface.base_cell import BaseCell
class HashTool(BaseCell):
"""文件哈希计算器。支持 md5 / sha256。"""
@property
def cell_name(self) -> str:
return "hash_tool"
def _cmd_md5(self, input_data: str = "", **kwargs) -> Dict[str, Any]:
"""计算文件 MD5
Args:
input_data: 文件路径
**kwargs: session_id / platform_context 由框架注入
Returns:
{"success": bool, "md5": str}
"""
import hashlib
h = hashlib.md5()
with open(input_data, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return {"success": True, "md5": h.hexdigest()}- cell_name 用 @property 返回,全小写、全局唯一,它就是工具名
- 命令方法必须 _cmd_ 前缀,docstring 第一行是描述,Args: 段是参数说明
- 必须返回 Dict(不要返回裸字符串),错误也返回 error 字段而不是抛异常
- 框架会注入 session_id / platform_context 到 kwargs,需要时直接取
保留工具名
shell— ShellToolmemory— MemoryToolfile— FileTool
沙箱陷阱
默认子进程执行,很多「本地能跑、组件里不行」都是这个原因。
- 子进程拿不到主进程的内存状态,跨命令共享数据要落文件或进白名单
- 进程空闲 300 秒被回收,模块级变量会丢;需要常驻状态就别依赖全局变量
- 单次命令超过 120 秒直接判超时,长任务拆成多步或放后台
目录结构
components/
├── __init__.py
├── _example_component.py # 官方模板(以 _ 开头,不参与加载)
├── component_builder.py # component 工具本体
├── scheduler.py # 定时任务
├── skill_manager.py # Skill 索引
├── skill_installer.py # Skill 安装
├── sub_agent.py # 子 Agent 并行
├── web_fetch.py / web_search.py
├── qq_files.py / telegram_files.py / feishu_files.py / weixin_files.py
├── skills/ # Skill 包目录
└── my_tool.py # Agent 自己生成的组件为什么能立刻用上
AgentLoop 不在启动时固化工具列表,而是每轮对话都向 ComponentToolRegistry 重新读取。所以组件加载完成的下一轮,LLM 的可用工具里就有它——不需要重启进程。
Key Point
自扩展的边界:能生成组件,但不要自动 pip 安装依赖、不要改内置组件文件、不要在没有验证的情况下宣称完成。生成 → 生效 → 真实调用验证,三步都过了才算成功。
