跳转到主要内容
工具就是 agent 可以执行的动作。运行时,Engine 会把 Decision 里的工具调用分发给 ToolRegistry,由它查找并执行对应 callable。

@tool 装饰器

使用 @tool 把任意 callable 标记成 QitOS 工具。这个装饰器只会附加元数据,不会改变函数行为,所以在测试里依旧可以像普通函数一样直接调用。
from qitos import tool
from qitos.core.tool import ToolPermission

@tool(
    name="read_file",
    description="Read the contents of a file at the given path.",
    timeout_s=10.0,
    permissions=ToolPermission(filesystem_read=True),
)
def read_file(path: str) -> str:
    with open(path, "r") as f:
        return f.read()

@tool 参数

参数类型说明
namestr | NoneDecision.actions 中使用的工具名。默认是函数 __name__
descriptionstr | None展示给 LLM 的工具说明。默认回退到 docstring。
timeout_sfloat | None单次调用超时时间,单位秒。None 表示不限时。
max_retriesint失败时重试次数。默认 0
permissionsToolPermission | None声明该工具需要的系统能力。
required_opslist[str] | None环境层面的低级操作能力标识。

ToolPermission

ToolPermission 描述工具允许做什么。Engine 可以在 preflight 阶段利用这些信息检查环境能力。
from qitos.core.tool import ToolPermission

ToolPermission(
    filesystem_read=True,
    filesystem_write=False,
    network=True,
    command=False,
)
四个字段默认都是 False

ToolRegistry

ToolRegistry 是 Engine 分发 action 时查询的集合。通常在 AgentModule 构造函数里创建并传入。
from qitos import ToolRegistry

registry = ToolRegistry()

注册单个工具

registry.register() 注册一个 callable 或 BaseTool 实例:
@tool(name="add")
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

registry.register(add)
也可以在注册时覆写名称或元数据:
from qitos.core.tool import ToolMeta

registry.register(add, name="math.add")
registry.register(some_func, meta=ToolMeta(description="Custom description"))
同一个 registry 内工具名必须唯一。重复注册同名工具会抛出 ValueError

include 扫描模块或对象

registry.include(obj) 会扫描 obj 上所有公开且可调用的属性,并注册其中带有 @tool 元数据的成员:
class MyTools:
    @tool(name="summarize")
    def summarize(self, text: str) -> str:
        ...

    @tool(name="translate")
    def translate(self, text: str, lang: str) -> str:
        ...

tools = MyTools()
registry.include(tools)
当你把一组相关工具封装成类方法时,这通常是最方便的注册方式。

注册 toolset

Toolset 指的是任何拥有 tools() 方法、并返回 callable 或 BaseTool 列表的对象。使用 register_toolset() 注册它:
from qitos.kit import CodingToolSet

registry.register_toolset(
    CodingToolSet(
        workspace_root="/tmp/work",
        include_notebook=False,
        enable_lsp=False,
        enable_tasks=False,
        enable_web=False,
        expose_modern_names=False,
    )
)
来自 toolset 的工具会自动带 namespace:toolset_name.tool_name。你也可以显式指定 namespace:
registry.register_toolset(CodingToolSet(workspace_root="/tmp/work"), namespace="coding")

BaseToolFunctionTool

如果工具需要维护共享状态或生命周期逻辑,可以直接继承 BaseTool
from qitos.core.tool import BaseTool, ToolSpec, ToolPermission


class DatabaseTool(BaseTool):
    """Query a SQLite database."""

    def __init__(self, db_path: str):
        self.db_path = db_path
        super().__init__(
            ToolSpec(
                name="query_db",
                description="Run a SQL query and return rows as a list.",
                parameters={"sql": {"type": "string", "description": "SQL statement"}},
                required=["sql"],
                permissions=ToolPermission(filesystem_read=True),
            )
        )

    def run(self, sql: str) -> list:
        import sqlite3
        with sqlite3.connect(self.db_path) as conn:
            return conn.execute(sql).fetchall()


registry.register(DatabaseTool(db_path="data.db"))
当你向 register() 传入普通 callable 时,QitOS 内部会自动把它包装成 FunctionTool。大多数情况下你无需显式实例化它。
对于实际编码 agent,通常优先使用 CodingToolSetqitos.kit.toolset 中的 preset builders,而不是手工把文件、shell、web 工具逐个注册。

把 registry 传给 AgentModule

把准备好的 ToolRegistry 传入 AgentModule.__init__()
from qitos import AgentModule, ToolRegistry, tool


class SearchAgent(AgentModule[MyState, dict, Action]):
    def __init__(self):
        registry = ToolRegistry()
        registry.register(web_search)
        registry.register(read_file)
        super().__init__(tool_registry=registry)
Engine 会读取 agent.tool_registry 并据此创建 ActionExecutor。你也可以调用 registry.get_tool_descriptions() 得到格式化工具说明,用于插入 system prompt:
def build_system_prompt(self, state: MyState) -> str | None:
    tools_text = self.tool_registry.get_tool_descriptions()
    return f"You have access to these tools:\n\n{tools_text}"

完整示例

from qitos import tool, ToolPermission

@tool(
    name="read_file",
    timeout_s=5.0,
    permissions=ToolPermission(filesystem_read=True),
)
def read_file(path: str) -> str:
    """Read the contents of a file."""
    with open(path) as f:
        return f.read()


@tool(
    name="write_file",
    timeout_s=5.0,
    permissions=ToolPermission(filesystem_write=True),
)
def write_file(path: str, content: str) -> str:
    """Write content to a file."""
    with open(path, "w") as f:
        f.write(content)
    return f"Written {len(content)} bytes to {path}"