跳转至

工具设置

工具就是一个普通 Python 函数:签名给出参数结构,docstring 给出说明,框架据此生成发给模型的 schema。 本文是工具注册与元数据的接口说明;内置工具清单只是参照,不是接口的一部分。

函数即工具

from xun import ToolBox

def multiply(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

toolbox = ToolBox().register(multiply)     # 返回自身,可链式
# 也可以当装饰器用:@toolbox.tool
要素 规则
工具名 默认取函数名,tool_attr(name=…) 可改
描述 整个 docstring(去首尾空白),写清楚约束与返回格式,不要依赖截断
参数 由类型注解构建 pydantic 参数模型,extra="forbid",调用时按 strict=True 校验
无注解参数 有默认值则用默认值的类型,否则退化为 Any——所以务必写注解
返回值 必须 JSON 可序列化(str / int / float / bool / None / dict / list),返回 None 会被转成 ""
异常 一律由 except_safe 包成 Result.Err(ErrorInfo),堆栈给开发者,给模型的是 str(exception)——所以异常文案要写给模型看
同步执行 工具是同步函数,异步函数会返回协程对象而无法序列化;长任务请并发或后台化
flowchart LR
    M["模型"] -->|"工具调用 JSON"| V["参数模型校验<br/>strict + forbid"]
    V --> I["注入 ToolCallContext"]
    I --> F["你的函数"]
    F --> R["Result.Ok / Err"]
    R --> C["会话历史 + 事件"]

tool_attr

from xun import tool_attr

@tool_attr(name="MultiplyTool")
def multiply(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b
参数 默认 作用
name None 对外工具名;函数自身名字不变
required_capabilities [] 需要的模型能力(目前只有 "vision");模型能力不含它时该工具不进入工具列表
override False 允许与已有工具重名替换

重名规则:默认同名即 ValueError: Conflict tool name: x;新工具带 override=True 才替换, 已存在的 override 工具也不会被无名工具覆盖。 确认、沙箱、并发等运行期策略不属于 tool_attr:它们由工具自己调用显示层或策略对象实现, 见下文与 显示扩展。

工具执行上下文

函数本身没有状态,需要上下文时声明一个 ToolCallContext 参数即可,框架按注解识别并注入—— 该参数不会出现在发给模型的 schema 里。

from xun import ToolCallContext as Context
from pathlib import Path

def workdir_name(ctx: Context[dict]):
    ctx.value["tool_call_time"] = "12:00:00"
    return Path(ctx.agent.workspace.workdir).name

ans = setup_agent(tools=[workdir_name], display=NullDisplay()) \
        .instruct("你的工作目录叫什么?").execute(context={"tool_call_time": "?"})
# ans 返回后,context 字典已被工具改写
成员 说明
ctx.agent 正在执行该工具的 Agent[Init]——workspace、config、display、hooks、state、cancel_event 都在它身上
ctx.tool_name 实际被调用的工具名
ctx.value execute(context=…) 传入的任意对象,工具内修改调用方可见

识别规则:按注解匹配(ToolCallContext 或 ToolCallContext[T]),与参数名无关,一个函数只注入一个。

工具里最常用的几个透入口:

ctx.agent.workspace.workdir                     # 工作目录
ctx.agent.workspace.resolve(p)                  # 路径解析 + 作用域校验
ctx.agent.get_confirm("确认覆盖?")              # 走显示层问人
ctx.agent.check_cancel()                        # 长循环里的取消检查点
ctx.agent.display_event(HTMLInfoEvent(html=…))  # 往界面推自定义内容

ToolBox 接口

方法 说明
register(*funcs) 注册普通函数(自动包 except_safe),返回自身
tool(func) 装饰器形式的 register
register_raw(*functions) 注册已构建好的 Function,可带现成 JSON Schema(动态工具用)
pop(name) 移除一个工具
disable(*names) / enable(*names) 按名字或 glob 停用 / 启用,is_disabled(name) 查询
list_tools(capabilities=None, include_disabled=False) 工具定义列表,按能力过滤
list_tools_json(capabilities=None) OpenAI tools 数组
with_defaults(*sets) 装载内置工具集,参数见下表
with_subagent_provider(getter=None) 注册子智能体工具,见 子智能体派生
disable_subagent() / enable_subagent() 关掉 / 打开派生工具(用于限制递归深度)
clone() 浅拷贝,Agent.inherit 用

被停用的工具既不进入 schema,被调用也会失败。

内置工具集

with_defaults() 不带参数时装载全部八个集合,共 33 个工具;也可以只装其中几个: toolbox.with_defaults("fs", "cmd")。

集合 工具
system system_info, datetime, sleep
framework ask_preference, extract_compacted_tool_result
fs list_dir, read_file, file_info, temp_dir, write_file, mkdir, move, copy, delete, request_image, glob, grep
patch apply_patch
cmd bash, bash_spawn, bash_wait, bash_running, bash_kill
search web_search
browser browser_page, browser_resize, browser_snapshot, browser_interact, browser_evaluate, browser_logs, browser_screenshot
diagnostic check_syntax, check_lint

加上 setup_agent(default_tools=True) 或 with_subagent_provider() 时,再多出四个派生工具 (agent_run / agent_spawn / agent_wait / agent_list)。

作用域与依赖

文件系统工具的路径都相对工作目录解析,越界会被拒绝;写操作按下面的规则确认。 browser 需要 playwright install;apply_patch 需要安装 Git;check_lint 需要 mypy。

路径与写权限

Workspace.resolve() 把路径归类为「工作目录内」或「临时目录内」,两者之外直接抛错。 写权限只在会破坏既有内容的操作上生效:

情况 行为
新建文件 / 新建目录 直接允许
临时目录内的覆盖、移动、复制覆盖、删除 直接允许
命中写允许列表 允许,不询问
其他:覆盖已存在文件、移动源文件、复制到有同名目标的目录、删除、补丁改动已有文件 询问:授权该路径 / 本次允许 / 拒绝

只有用户选择「授权该路径」才会写进允许列表——自动确认放行不留任何持久授权。 允许列表是当前智能体的运行时状态(存在 agent.state 里,进程重启即清空), 用 /policy path_allowlist* 查看与增删。

内置命令的风险评估

bash 工具先用 shlex 做结构化解析,按 ; && || | 切成若干命令段,逐段判定。 只要有一段命中下列原因,就需要走确认:

原因 说明
命令段不在允许列表 整段命中(含多段前缀)才算通过
用了需要确认的 shell 操作符 重定向与后台运行(>、>>、<、& 等);>/dev/null、2>&1 这类安全形式例外
路径式命令 写的是 ./bin/tool、/usr/bin/tool 而不是裸命令名
反引号或换行拼出来的命令 `…` 与多行命令
$(…) 命令替换 会递归判定括号里的命令是否允许

需要确认时,先交给一个风险评估子智能体判定(只读工具、最多 3 次工具调用、不装载扩展):

判定 效果
allow 直接执行,不打扰用户
unsure 弹确认,默认项为「允许」
reject 弹确认,默认项为「拒绝」;自动确认模式下即直接失败

确认框可以选「Other」填写拒绝理由,理由与风险判定原因会一并回传给智能体, 标题按判定结果区分「Command Confirmation」与「Dangerous Command Confirmation」。

默认允许列表只含只读与系统信息查询类命令(ls、cat、head、tail、grep、nl、stat、df、du、 ps、top、uname、dmesg、journalctl、nvidia-smi、ping、ss 等),以及 python -m unittest / python -m pytest 与 git status / log / diff / show。 带空格的条目按词边界前缀匹配,所以 git diff --cached 命中 git diff。 curl、wget、sed 这类既能外联又能改文件的命令不在默认列表里,请走确认或自行加入:

/policy cmd_allowlist_add "python -m ruff"

这是尽力而为,不是沙箱

cmd_policy.py 的模块注释写得很直白:shell 执行几乎不可能只靠解析做到安全, 这些判定是最佳努力。真正需要隔离时请用 xunc 容器。

后台命令

长时间运行的命令交给 bash_spawn 启动,返回任务 ID 与两个日志文件路径(stdout / stderr), 之后用 bash_wait(可传轮询超时)、bash_running、bash_kill 管理; 结果里带耗时与输出大小。任务表存在智能体状态里,因此派生的子智能体共享同一批任务, 父智能体终结时会收尾。