跳转至

扩展机制与加载

本章讲扩展(插件):放进约定目录的文件,在每一个智能体初始化时自动运行,不需要改入口程序。 与之相对的是 API 用法:在调用处 tools=[...] 接线,只影响那一个智能体。

什么时候用扩展,什么时候直接用 API

API 用法 扩展
生效范围 你写代码的那一个智能体 每个 initialize() 的智能体,含派生的子智能体
需要改入口程序 是 否
入口程序能感知它 是 否,它只认目录
典型场景 单次任务、实验、应用内私有工具 团队共用的能力、跨会话常驻的集成
代码位置 你的程序里 $XUN_HOME/extensions/

内置的 MCP、Skills、用量统计都是扩展,而不是核心功能——核心保持无关是刻意的。 例如 MCP 扩展自带标准库实现的客户端,核心不知道 MCP 存在;它需要的只是 ToolBox.register_raw、 ToolBox.pop、ToolBox.is_disabled 与 AgentConfig.extension_settings 这些通用接口。 因此「核心里没有某个集成」不等于做不到,通常只是还没人写成扩展。

扩展不是不可碰的黑盒

扩展能做的,你在同样位置也能做:setup_extension(ctx) 拿到的就是那个尚未初始化的智能体本身。 反过来也成立——需要扩展帮忙时,直接照着某个内置扩展抄结构往往最快。

发现与导入

flowchart TD
    A["$XUN_HOME/extensions/"] --> B{"形态"}
    B -- "<name>.py" --> C["单文件扩展"]
    B -- "<name>/setup_extension.py" --> D["目录扩展<br/>其余文件可自由 import"]
    C --> E["导入 + 读取 extension_attr"]
    D --> E
    E --> F{"版本门"}
    F -- 超范围 --> G["skipped"]
    F -- 兼容 --> H["进入待用列表"]
    H --> I["每次 Agent.initialize()<br/>按名序运行 setup_extension(ctx)"]

规则细节:

  • 扫描按目录项名字排序,进程内每个目录只扫一次并缓存;ExtensionLoader.clear_scan_cache() 可强制重扫。
  • 以 . 或 __ 开头的条目忽略。目录里没有 setup_extension.py 会告警跳过; <name>.py 与 <name>/ 同时存在时,前者被后者遮蔽并告警。
  • 扩展名必须是合法的模块标识符(目录扩展还要能作为 sys.modules 键),否则告警跳过。
  • 入口名固定:模块级可调用对象 setup_extension(ctx),没有它告警跳过。
  • 导入失败 → 状态 failed,只告警;启动继续。

XUN_HOME 未设置时是 ./.xun/extensions/。扩展代码里请走 ctx.data_dir() 与 config.extension_settings,不要把路径写死。

每个智能体重新运行一遍

Agent.initialize() 调用 extension_loader.apply(self),按名序把每个扩展作用到尚未初始化的智能体上。 Agent 的 extension_loader 字段可以注入(测试里给个临时目录的加载器即可)。

情况 行为
config.enable_extensions = false 该智能体完全跳过扩展
setup 抛异常 告警 + 状态 failed,启动继续
工具重名冲突(子智能体常见) 冲突会中止该扩展对子智能体的后续设置,降级为告警;先加上的钩子仍生效
KeyboardInterrupt / CancelledError 原样抛出,不算失败

因为每个智能体都会重放一遍,setup_extension 必须写成幂等: 重复 register 同名工具会抛冲突,昂贵资源(连服务端、读盘)要缓存在模块级。

改完扩展代码

扫描结果按目录缓存,进程启动后改文件不会自动生效——重启,或在测试里调用 clear_scan_cache()。 派生的子智能体同样会重放扩展。

状态与查看

ExtensionLoader.infos() 返回每个扩展的 ExtensionInfo(名字、描述、路径、状态、原因), /extensions 把它渲染成表;Web 端对应 ShowExtensionsEvent。

状态 含义
uninitialized 发现了,但还没跑过 setup
loaded setup 成功跑完
skipped 与当前 xun 版本不兼容,按声明跳过
failed 导入失败或 setup 抛异常,原因见 reason

安全边界

扩展是完整权限的代码

扩展按文件名字典序被导入,导入那一刻它就能执行任意逻辑(不必等 setup_extension)。 它运行在信任边界之内,像 shell 的 rc 文件一样全权,不是沙箱。 需要隔离请用 xunc 容器。config.enable_extensions 只是关掉运行,不是安全边界。

相关文档