跳转至

工具系统

函数即工具

把普通函数交给 ToolBox,框架自动推导名称、描述与参数 JSON Schema:

from xun import ToolBox

def list_dir(path: str, details: bool = False) -> dict:
    """List the contents of a directory.
    Returns names of files and directories; with details also sizes."""
    ...

toolbox = ToolBox().register(list_dir)

推导规则:

项目 规则
工具名 @tool_attr(name=...) 优先,否则用函数名
描述 整个 docstring 原文(不做 Google/reST 解析),因此参数含义要在 docstring 里写清楚
参数模型 pydantic.create_model(),extra="forbid":模型多传字段会直接校验失败
参数类型 取类型注解;无注解但有非 None 默认值时用 type(default);否则 Any
必填判定 无默认值即必填;Optional[int] = None 为可空且非必填;Literal[...] 变成枚举
校验 调用时 model_validate(..., strict=True)——不做隐式转换,"3" 传给 int 会被拒绝;例外是 list / tuple 参数:模型把数组写成字符串('["a", "b"]' 或 'a, b')时会自动解析,Optional[list[...]] 同样适用,而联合类型里含 str 成员时保持原样;逗号写法的元素仍按字符串解析,元素是数值时请用 JSON 数组写法

self/cls 与 ToolCallContext 参数会被自动排除在 schema 之外。

ToolBox API

方法 说明
register(*funcs) 注册若干函数(自动套上 except_safe);重名时按 override 决定胜负,否则 ValueError: Conflict tool name
tool(func) 装饰器形式的 register
with_defaults(*groups) 注册内置工具组;不传参数表示注册全部 8 组
with_subagent_provider(agent_getter=None) 注册 agent_run / agent_run_parallel;agent_getter 接收 AgentGetterParam 返回未初始化的子智能体
disable(*names) / enable(*names) 支持 fnmatch 通配(browser_*);禁用不等于删除,list_tools() 会过滤掉
disable_subagent() / enable_subagent() 只关掉两个子智能体工具
list_tools(model_capabilities=None) 过滤掉禁用项与「模型能力不满足」的工具
list_tools_json(model_capabilities=None) 交给模型的 tools 数组
call_tool(agent, tool_name, arguments, context) 统一调用入口
clone() 复制工具表与禁用集合这两个容器;Function 对象本身仍与源工具箱共享(不是深拷贝)

内置工具组名:system、framework、fs、patch、cmd、search、browser、diagnostic。逐项清单见 内置工具参考。

tool_attr

from xun import tool_attr

@tool_attr(name="MultiplyTool")
def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b
属性 类型 默认 作用
name str \| None None 覆盖暴露给模型的工具名(内置工具大量使用,如 system_time → datetime)
required_capabilities Sequence[ModelCapabilityType] [] 目前仅 "vision";模型能力不包含时该工具不会出现在请求里
override bool False 允许静默替换同名工具(扩展里覆盖内置工具时用)

tool_attr 只贴元数据,不做注册;注册仍走 ToolBox.register()。调试重名覆盖时设 _XUN_INFO_TOOL_OVERRIDE=1 可打印覆盖信息。

上下文注入

工具若要访问智能体本身或调用方传入的数据,只需声明一个 ToolCallContext 参数:

from xun import ToolCallContext as Context
import datetime

def stamp(context: Context[dict]) -> str:
    """Record the call time into the shared context."""
    context.value["tool_call_time"] = datetime.datetime.now().isoformat()
    return context.value["tool_call_time"]

agent.instruct("Call your only tool.").execute(context={"tool_call_time": "?"})
访问器 内容
ctx.agent 发起调用的智能体(Agent[Agent.T.Init])
ctx.tool_name 注册名(可能与 Python 函数名不同)
ctx.value Agent.execute(context=...) 传入的任意值,逐层透传

ToolCallContext[T] 是泛型,T 即 value 的类型;字段私有,只读属性访问。

返回值与异常

ToolBox.register() 会给每个工具套上 except_safe:

  • 返回普通值 → 自动包装 Result.Ok(value)
  • 抛异常 → Result.Err(ErrorInfo(error=str(exc), details=repr(exc)))
  • KeyboardInterrupt / CancelledError 原样上抛

因此工具里 return 或 raise 即可,不需要手工构造 Result。序列化走 model_dump / to_json / value_json;序列化失败不会上抛,工具结果变成 [Error] Failed to serialize tool result: ... 文本。

图片

图片不能作为工具返回值直接进入上下文。defer_tool_image(ctx, image, msg="") 会注册一次性 after_execution_step 钩子,在本轮工具结果之后追加一条带图(可带一句说明)的用户消息(request_image、browser_screenshot 就是这么做的)。约束:PNG/JPEG/WebP/GIF,单张不超过 10 MB;request_image 还会在裁剪后把长边压到 1000px,并在说明文字里写回最终尺寸与来源尺寸。

路径

所有路径参数经 Workspace.resolve() 校验:相对路径基于 workdir,允许 workdir 与惰性临时目录两处(临时目录只有在被真正创建之后才会被识别为工作区的一部分);越界报 Path ... is not within the agent's workspace。返回值里的路径通常给绝对路径(例外:glob、grep 返回相对搜索根的路径,list_dir 只返回条目名,apply_patch 的摘要相对 patch 目录)。

权限与确认策略

需要人批准的调用统一走 agent.get_choice() / agent.get_confirm(),返回 ChoiceOutcome[T](choice + source 是 "user" 还是 "auto")。

ChoiceOutcome 没有真值

直接 if agent.get_confirm(...) 会抛 TypeError。请写 .choice:if agent.get_confirm("Proceed?").choice: ...

场景 行为
写已存在的文件、copy 覆盖、delete、move 判定看目标路径,move 看源路径(移动等价于删源);落在惰性临时目录内的写入直接放行;其余弹出「写权限」三选一:允许并授权 / 仅本次允许 / 拒绝;选「授权」才写入写允许列表。apply_patch 与 mkdir 只做工作区路径校验,不走这条确认
bash 命令 先查命令允许列表;不在列表或含危险操作符时,交给一个「命令风险评估」子智能体(只读取工具)判断 allow/unsure/reject,再决定是否弹确认
config.auto_confirm 为真 直接返回默认选项并标记 source="auto",不会写入任何允许列表
ask_preference 工具 显式跳过自动确认,始终询问

内置命令允许列表覆盖常见的诊断与文本处理命令(ls、cat、git status、git diff、python -m pytest 等,其中 curl、wget、sed、journalctl 等并非只读),采用按 token 前缀匹配,因此 git diff --cached 命中 git diff,而 git statusx 不命中。bash 把命令切成段并检查:可执行文件是否在允许列表、是否使用非常规路径的可执行文件、反引号替换、换行拼接、$(...) 子替换等;shell 操作符里只有 ;、&&、||、|、(、) 自动允许,其余(& 后台、>、>>、<、<<、>&、<&)都要确认;重定向仅 > /dev/null、1>/dev/null、2>/dev/null、2>&1 这几种安全形式免确认。

允许列表按智能体实例保存在 state["__builtin_tool_policy"],可用 /policy 系列命令查看与增删,见 xun · 终端会话。

Windows

cmd 组在 os.name == "nt" 时不注册 bash 工具,并在注册阶段打印一次跳过提示。