工具系统¶
函数即工具¶
把普通函数交给 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 工具,并在注册阶段打印一次跳过提示。