工具设置¶
工具就是一个普通 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 管理;
结果里带耗时与输出大小。任务表存在智能体状态里,因此派生的子智能体共享同一批任务,
父智能体终结时会收尾。