> ## Documentation Index
> Fetch the complete documentation index at: https://qitor.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools 与 Registry

> 理解 QitOS 中的工具、工具集以及 ToolRegistry 如何组合成智能体能力表面。

工具就是智能体可以执行的动作。运行时，Engine 会把 `Decision` 里的动作（标准化工具调用）分发给 `ToolRegistry`，由它查找并执行对应的可调用对象。

## `@tool` 装饰器

使用 `@tool` 把任意可调用对象标记成 QitOS 工具。这个装饰器只会附加元数据，不会改变函数行为，测试里依旧可以像普通函数一样直接调用。

```python theme={null}
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` 参数

| 参数             | 类型                       | 说明                                             |
| -------------- | ------------------------ | ---------------------------------------------- |
| `name`         | `str \| None`            | 在 `Decision.actions` 中使用的工具名。默认是函数 `__name__`。 |
| `description`  | `str \| None`            | 展示给大模型的工具说明。默认回退到文档字符串。                        |
| `timeout_s`    | `float \| None`          | 单次调用超时时间，单位秒。`None` 表示不限时。                     |
| `max_retries`  | `int`                    | 失败时重试次数。默认 `0`。                                |
| `permissions`  | `ToolPermission \| None` | 声明该工具需要的系统能力。                                  |
| `required_ops` | `list[str] \| None`      | 环境层面的低级操作能力标识。                                 |

### `ToolPermission`

`ToolPermission` 描述工具允许做什么。Engine 可以在预检阶段利用这些信息检查环境能力。

```python theme={null}
from qitos.core.tool import ToolPermission

ToolPermission(
    filesystem_read=True,
    filesystem_write=False,
    network=True,
    command=False,
)
```

四个字段默认都是 `False`。

***

## ToolRegistry

`ToolRegistry` 是 Engine 分发动作时查询的注册表。通常在 `AgentModule` 构造函数里创建并传入。

```python theme={null}
from qitos import ToolRegistry

registry = ToolRegistry()
```

### 注册单个工具

用 `registry.register()` 注册一个可调用对象或 `BaseTool` 实例：

```python theme={null}
@tool(name="add")
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

registry.register(add)
```

也可以在注册时覆写名称或元数据：

```python theme={null}
from qitos.core.tool import ToolMeta

registry.register(add, name="math.add")
registry.register(some_func, meta=ToolMeta(description="Custom description"))
```

<Warning>
  同一个注册表内工具名必须唯一。重复注册同名工具会抛出 `ValueError`。
</Warning>

### 用 `include` 扫描模块或对象

`registry.include(obj)` 会扫描 `obj` 上所有公开且可调用的属性，并注册其中带有 `@tool` 元数据的成员：

```python theme={null}
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）指的是任何拥有 `tools()` 方法、并返回可调用对象或 `BaseTool` 列表的对象。使用 `register_toolset()` 注册它：

```python theme={null}
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_name.tool_name`。你也可以显式指定命名空间：

```python theme={null}
registry.register_toolset(CodingToolSet(workspace_root="/tmp/work"), namespace="coding")
```

***

## `BaseTool` 与 `FunctionTool`

如果工具需要维护共享状态或生命周期逻辑，可以直接继承 `BaseTool`：

```python theme={null}
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()` 传入普通可调用对象时，QitOS 内部会自动把它包装成 `FunctionTool`。大多数情况下你无需显式实例化它。

<Note>
  对于实际编码智能体，通常优先使用 `CodingToolSet` 或 `qitos.kit.toolset` 中的预设构建器，而不是手工把文件、Shell、Web 工具逐个注册。
</Note>

***

## 把注册表传给 AgentModule

把准备好的 `ToolRegistry` 传入 `AgentModule.__init__()`：

```python theme={null}
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()` 得到格式化工具说明，用于插入系统提示词：

```python theme={null}
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}"
```

***

## 完整示例

<CodeGroup>
  ```python tools.py theme={null}
  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}"
  ```

  ```python agent.py theme={null}
  from dataclasses import dataclass
  from typing import Any

  from qitos import AgentModule, StateSchema, ToolRegistry

  from .tools import read_file, write_file


  @dataclass
  class EditorState(StateSchema):
      files_read: list[str] = None

      def __post_init__(self):
          if self.files_read is None:
              self.files_read = []


  class EditorAgent(AgentModule[EditorState, dict[str, Any], Any]):
      def __init__(self, llm):
          registry = ToolRegistry()
          registry.register(read_file)
          registry.register(write_file)
          super().__init__(tool_registry=registry, llm=llm)

      def init_state(self, task: str, **kwargs: Any) -> EditorState:
          return EditorState(task=task, max_steps=int(kwargs.get("max_steps", 10)))

      def build_system_prompt(self, state: EditorState) -> str | None:
          return (
              "You are a file editor.\n\n"
              f"Tools:\n{self.tool_registry.get_tool_descriptions()}"
          )

      def reduce(self, state: EditorState, observation: dict, decision) -> EditorState:
          for action in decision.actions:
              if action.name == "read_file":
                  state.files_read.append(action.args.get("path", ""))
          if decision.final_answer:
              state.final_result = decision.final_answer
          return state
  ```
</CodeGroup>
