跳转至

扩展系统

把 Python 文件放进 $XUN_HOME/extensions/,每个智能体在 initialize() 时都会重放它们的副作用:注册工具、挂钩子、加命令、改配置。无需注册表,也无需在调用方接线。

扩展是可信代码

扩展在导入期与初始化期以完整进程权限运行,性质等同 shell 的 rc 文件。只放你自己信任的代码。

两种形态

形态 路径 说明
包 extensions/{name}/setup_extension.py 入口文件名固定;可以相对导入同目录的兄弟模块
扁平 extensions/{name}.py 零仪式;不支持相对导入
  • 目录里没有 setup_extension.py 会告警并跳过;两种形态同名时包优先,扁平文件被屏蔽。
  • 以 . 或 __ 开头的条目被忽略;非 .py 文件(例如目录里的 README.md)静默跳过。
  • 应用顺序按名称排序,两种形态统一排序,结果确定。

入口函数

模块必须提供可调用的 setup_extension(ctx):

"""Log every tool call."""          # docstring 首行 = 扩展描述,/extensions 会显示
from xun import ExtensionContext

def setup_extension(ctx: ExtensionContext) -> None:
    ctx.agent.hooks.before_tool_call.add(lambda args: print(args.tool_calls))

ExtensionContext 暴露的公开 API 只有两项:

成员 说明
ctx.agent 正在初始化的智能体(Agent[Agent.T.Uninit])
ctx.name 扩展名(目录名或文件名)

一切效果都通过 ctx.agent 完成:toolbox.register(...)、hooks.*.add(...)、command.register(...)、config 就地改、state、system(...)、display、workspace。

加载时序

flowchart TD
  A["Agent.initialize()"] --> B{"启用扩展?"}
  B -->|否| Z["跳过"]
  B -->|是| C["按名称顺序扫描扩展"]
  C --> D["导入模块"]
  D --> E["执行 setup_extension"]
  E --> F["继续初始化"]

关键语义:

  • 模块每个进程只导入一次(模块级副作用只跑一次,模块状态保留),setup_extension 则每个智能体各跑一次——包括子智能体与摘要/风险评估这类内部辅助智能体。
  • 扩展在模型名自动探测之前执行,因此改 config.model.name 会生效。
  • 导入失败或 setup_extension 抛异常只打印 Extension warning:,不阻断启动,也不影响其它扩展;但同一个 setup 内后续语句会随异常中断。
  • 已加载的模块不会卸载(子智能体重放依赖它们)。
  • 关闭方式:config.enable_extensions = false(配置或扩展里改),或 Agent.config.enable_extensions = False 后再 initialize()。

查看已加载扩展

>>> /extensions
 shown: Show me.
 web_display_log: Console log for web display

仓库内示例

"""Console log for web display."""
from xun import ExtensionContext, WebDisplay

def setup_extension(ctx: ExtensionContext) -> None:
    if not isinstance(ctx.agent.display, WebDisplay):
        return
    agent_id = f"{ctx.agent.name} ({ctx.agent.identifier})"
    ctx.agent.hooks.before_display_error.add(
        lambda args: print(f"{agent_id} Error: {args.message}")
    )

先看显示层类型再挂钩子,是避免在无关智能体上产生副作用的常用写法。

"""Override web_search with an external Web Search tool."""
from xun import ExtensionContext, tool_attr

try:
    from some_sdk import Client
    _client = Client()
except Exception as exc:                       # 依赖或密钥缺失时整体退化为不生效
    _client = None
    print(f"extension disabled: {exc}")

def setup_extension(ctx: ExtensionContext) -> None:
    if _client is None:
        return

    @tool_attr(name="web_search", override=True)
    def my_search(query: str, limit: int = 5) -> list[dict]:
        """Search the web."""
        return _client.search(query, count=limit)

    ctx.agent.toolbox.register(my_search)

override=True 让同名工具静默替换内置实现;把可选依赖的探测放在模块级,setup 里只判断是否可用。

与其它机制的关系

想做的事 更合适的机制
只给某个智能体加东西 直接调用 agent.toolbox.register() / agent.hooks.*.add()
给所有智能体加东西、跨项目共享 扩展(本文)
只改配置 配置文件;或扩展里改 ctx.agent.config
换系统提示词 agent.system(),或 setup_agent(default_system_prompt=False)