# BuiltinTool 使用指南

本文档面向 Turbo Agent 的使用者与业务开发者，介绍如何注册和使用内部工具（`BuiltinTool`）。

如果你只想知道“怎么用”，看这份文档即可；如果你要了解底层设计，请看同目录下的 [tools_utils.md](tools_utils.md)。

## 1. 什么是 BuiltinTool

`BuiltinTool` 是通过本地 Python 代码直接执行的内部工具。

它和 `APITool` 的区别是：

- `APITool` 通过 HTTP 请求调用外部平台。
- `BuiltinTool` 直接调用本地函数、类方法或对象方法。

适合的场景：

- 文件处理
- 本地数据转换
- 规则校验
- 内部系统封装
- 轻量级业务逻辑

## 2. 最简单的注册方式

使用 `builtin_tool` 装饰函数。

```python
from turbo_agent_core.schema import builtin_tool


@builtin_tool(name="sum_numbers")
def sum_numbers(a: int, b: int = 0) -> int:
    """计算两个整数之和。

    Args:
        a: 第一个数字。
        b: 第二个数字。

    Returns:
        返回求和结果。
    """
    return a + b
```

这段代码会自动完成：

1. 注册工具 handler。
2. 从函数签名中提取输入 schema。
3. 从返回值类型中推导输出 schema。
4. 从 docstring 中提取工具描述、参数说明和返回说明。

## 3. 自动推导了什么

对于上面的函数，系统会自动识别：

- 工具名称：`sum_numbers`
- 输入参数：`a`、`b`
- `a` 为必填
- `b` 默认值为 `0`
- 输出类型为 `int`

因此通常不需要手写 `input_schema` 和 `output_schema`。

## 4. 手工补充工具元数据

如果你需要补充平台、权限、标签等信息，可以在注册时直接声明。

```python
from turbo_agent_core.schema import builtin_tool


@builtin_tool(
    name="sum_numbers",
    space_name="finance",
    project_name_id="calc",
    tags=["math", "demo"],
    required_permissions=["tool.sum"],
    execution_hint={"category": "internal"},
)
def sum_numbers(a: int, b: int = 0) -> int:
    """计算两个整数之和。"""
    return a + b
```

常用参数：

- `name`：工具名
- `space_name`：所属空间，默认是 `default`
- `project_name_id`：所属项目
- `preset_kwargs`：预置参数，这些参数会在执行时自动注入，并从 tool 的输入定义中隐藏
- `tags`：标签
- `required_permissions`：所需权限
- `execution_hint`：给上层系统或前端的额外提示

## 5. 预置参数怎么用

如果某些参数不希望暴露给大模型，也不希望出现在导出的 `tool.input` 和 `input_schema` 中，可以用 `preset_kwargs`。

```python
from turbo_agent_core.schema import builtin_tool


@builtin_tool(
    name="render_greeting",
    preset_kwargs={"prefix": "hello "},
)
def render_greeting(name: str, prefix: str) -> str:
    return f"{prefix}{name}"
```

这里 `prefix` 会在执行时自动注入，但不会出现在导出的工具输入定义里。

因此导出后的工具只会暴露：

- `name`

不会暴露：

- `prefix`

这适合：

- 租户 ID、项目 ID、资源类型等系统内部参数
- 固定前缀、固定模板、固定策略开关
- 业务上不希望让大模型感知的内部参数

### 5.1 注册后改写预置参数

注册完成后，也可以继续改写预置参数：

```python
from turbo_agent_core.schema import get_builtin_tool_registry


registry = get_builtin_tool_registry()
registry.set_preset_kwargs(
    "render_greeting",
    {"prefix": "hi "},
    belong_to_project_path="store/characters",
    merge=False,
)
```

改写后：

- 实际执行会使用新的预置值
- `list_tool_defs()` 导出的 `BuiltinTool` 定义仍然不会包含这些预置参数

如果要按作用域查看改写后的定义：

```python
tool_defs = registry.list_tool_defs(belong_to_project_path="store/characters")
```

## 6. 类方式组织工具

如果你有一组相关工具，推荐直接用 `@builtin_tool_provider(...)` 标注类。

```python
from turbo_agent_core.schema import builtin_tool_provider


@builtin_tool_provider(name_prefix="math_")
class MathTools:
    def add(self, a: int, b: int) -> int:
        """加法。"""
        return a + b

    @staticmethod
    def multiply(a: int, b: int) -> int:
        """乘法。"""
        return a * b

    def _debug(self, value: int) -> int:
        return value
```

注册后会导出：

- `math_add`
- `math_multiply`

不会导出：

- `_debug`

规则很简单：只导出不以下划线开头的方法。

## 7. 对象方式组织工具

如果工具依赖某个已实例化对象，也可以直接注册对象。对象场景通常不是装饰器，而是实例化后再注册。

```python
from turbo_agent_core.schema import builtin_tool_provider


class TextTools:
    def __init__(self, prefix: str):
        self.prefix = prefix

    def greet(self, name: str) -> str:
        return f"{self.prefix}{name}"


tools = TextTools(prefix="hello ")
builtin_tool_provider(tools, name_prefix="text_")
```

这样适合提前注入配置、缓存、数据库客户端等依赖。

## 8. 用 toolset 统一组织工具

如果你希望把一批工具统一挂到某个空间、项目或平台下，推荐使用 toolset。

```python
from turbo_agent_core.schema import create_builtin_toolset, get_builtin_tool_registry


toolset = create_builtin_toolset(
    space_name="default",
    project_name_id="demo",
    prefix="demo_",
    tags=["sample"],
)


@toolset.tool()
def greet(name: str) -> str:
    """生成欢迎语。"""
    return f"hello {name}"


@toolset.toolset(name_prefix="calc_")
class CalcTools:
    def minus(self, a: int, b: int) -> int:
        return a - b


registry = get_builtin_tool_registry()
registry.include_toolset(toolset)
```

最终会注册出：

- `demo_greet@default/demo`
- `demo_calc_minus@default/demo`

这种模式特别适合应用启动时集中注册。

## 9. 权限控制怎么用

如果工具有权限要求，可以配置 `required_permissions`。

```python
from turbo_agent_core.schema import builtin_tool


@builtin_tool(
    name="secure_echo",
    required_permissions=["tool.execute"],
)
def secure_echo(text: str) -> str:
    return text
```

运行时需要传入权限信息，例如：

```python
registry.execute(
    "secure_echo",
    {"text": "hello"},
    permissions=["tool.execute"],
)
```

如果缺少权限，会抛出 `PermissionError`。

## 10. 如何拿到运行时上下文

如果你的工具需要知道当前用户、空间、项目、权限等信息，可以声明 `context` 参数。

```python
from turbo_agent_core.schema import BuiltinToolExecutionContext, builtin_tool


@builtin_tool(name="secure_echo")
def secure_echo(text: str, context: BuiltinToolExecutionContext) -> str:
    return f"{context.belong_to_project_path}:{context.user_id}:{text}"
```

系统会自动把执行上下文注入进去，不需要手工拼装。

常用上下文字段：

- `context.user_id`
- `context.username`
- `context.permissions`
- `context.space_name`
- `context.project_name_id`
- `context.belong_to_project_path`
- `context.platform`
- `context.tool`

## 11. 后处理回调怎么用

如果你希望在工具执行完成后做统一处理，例如记录日志、包装结果、脱敏，可以使用 `post_processors`。

```python
from turbo_agent_core.schema import BuiltinToolExecutionContext, builtin_tool


def wrap_result(context: BuiltinToolExecutionContext, result, error):
    if error is not None:
        return None
    return {
        "tool": context.registration.builtin_name,
        "result": result,
    }


@builtin_tool(name="greet", post_processors=[wrap_result])
def greet(name: str) -> str:
    return f"hello {name}"
```

执行结果会被包装成：

```python
{
    "tool": "greet",
    "result": "hello turbo"
}
```

## 12. 如何构造 BuiltinTool 实体

如果你要把注册好的内部工具直接挂到 `Agent.tools` 或 `Character.tools` 下面，应该拿的是 `BuiltinTool` 实体列表，而不是名字列表。

先获取全局注册表：

```python
from turbo_agent_core.schema import get_builtin_tool_registry


registry = get_builtin_tool_registry()
```

### 12.1 获取全部工具定义列表

```python
tool_defs = registry.list_tool_defs()
```

这里返回的是：

```python
List[BuiltinTool]
```

因此可以直接用于：

```python
agent.tools = registry.list_tool_defs()
character.tools = registry.list_tool_defs()
```

如果你只想挂某个空间或项目下的工具，可以直接在注册表层过滤：

```python
tool_defs = registry.list_tool_defs(belong_to_project_path="finance/calc")
```

也支持按空间和项目分别过滤：

```python
tool_defs = registry.list_tool_defs(space_name="store", project_name_id="characters")
```

对应的名字列表也支持相同过滤方式：

```python
tool_names = registry.list_tools(belong_to_project_path="store/characters")
```

### 12.2 获取单个工具定义

如果你已经完成注册，想拿到某一个对应的 `BuiltinTool` 定义对象，可以使用：

```python
from turbo_agent_core.schema import get_builtin_tool_registry


registry = get_builtin_tool_registry()
tool_def = registry.get_tool_def("sum_numbers", belong_to_project_id="finance/calc")
```

拿到的是完整的 `BuiltinTool` 实体，可用于：

- 挂到 `Agent.tools`
- 导出配置
- UI 展示
- 调试检查

### 12.3 为什么还需要 toolset

`toolset` 的作用不是“运行时筛选”，而是“注册时组织”。

它主要解决的是：

- 一批工具共享同一个 `space_name/project_name_id`
- 一批工具共享前缀、标签、权限和后处理器
- 应用启动时可以统一 `include_toolset()` 挂载

现在配合 `list_tool_defs(belong_to_project_path=...)`，就形成了完整闭环：

1. 用 toolset 负责按作用域成组注册
2. 用 `list_tool_defs(...)` 负责按作用域取出可挂载到 `Agent.tools` / `Character.tools` 的工具实体列表

## 13. Agent 中怎么使用

BuiltinTool 注册后，如果你的 `Agent` 挂载了对应的 `BuiltinTool` 定义，就可以和其它工具一样被 Agent 正常调用。

当前 runtime 已经支持把运行时上下文继续透传给 BuiltinTool，因此以下信息都能生效：

- 用户信息
- 权限信息
- 项目作用域
- 后处理回调

## 14. 推荐实践

推荐按以下原则使用：

### 14.1 只有一两个简单工具

直接用函数装饰器。

### 14.2 一组相关工具

用类或对象导出，保持模块结构清晰。

### 14.3 需要统一挂载空间/项目

用 toolset 组织，再统一 `include_toolset()`。

### 14.4 工具有权限要求

优先使用 `required_permissions`，复杂场景再加 `permission_checker`。

### 14.5 需要结果包装或审计

使用 `post_processors`，不要把这些横切逻辑散落在每个 handler 里。

## 15. 当前限制

当前 BuiltinTool 机制已经可用于生产级注册与调用，但仍有几点需要注意：

1. 同步执行不支持异步 handler。
2. 同步执行不支持异步权限校验器。
3. 同步执行不支持异步后处理回调。
4. FrontendTool 的“前端侧注册机制”仍在后续设计中，不在这份文档范围内。

## 16. 最小完整示例

```python
from turbo_agent_core.schema import (
    BuiltinToolExecutionContext,
    create_builtin_toolset,
    get_builtin_tool_registry,
)


toolset = create_builtin_toolset(
    space_name="default",
    project_name_id="demo",
    prefix="demo_",
)


def wrap_result(context: BuiltinToolExecutionContext, result, error):
    if error:
        return None
    return {"tool": context.registration.builtin_name, "result": result}


@toolset.tool(post_processors=[wrap_result], required_permissions=["tool.greet"])
def greet(name: str, context: BuiltinToolExecutionContext) -> str:
    return f"hello {context.user_id}:{name}"


registry = get_builtin_tool_registry()
registry.include_toolset(toolset)


result = registry.execute(
    "demo_greet",
    {"name": "turbo"},
    user_id="u_1",
    permissions=["tool.greet"],
    belong_to_project_path="default/demo",
)

print(result)
```

输出结果：

```python
{'tool': 'demo_greet', 'result': 'hello u_1:turbo'}
```

## 17. 相关文档

- 内部设计说明：[tools_utils.md](tools_utils.md)
- 注册与实现代码：`src/turbo_agent_core/schema/utils/tools.py`，主要入口为 `builtin_tool` / `builtin_tool_provider`
- 工具实体定义：`src/turbo_agent_core/schema/agents.py`