# 工具调用原理

## 一、工具注册体系

### 两种 Skill 类型

| 类型 | 目录 | 注册方式 | 调用方式 |
|------|------|----------|----------|
| 工具型 | `plugins/<name>/SKILL.md` + `tool.py` | `tool.py` 中定义 schema + handler | LLM function call |
| 指令型 | `plugins/<name>/SKILL.md` 或 `skills/<name>/SKILL.md` | `SKILL.md` 注入摘要到 system prompt | LLM 按指令用已有工具操作 |

### 工具型 Skill 注册

```python
# plugins/xxx/tool.py
SCHEMAS = [
    {
        "type": "function",
        "function": {
            "name": "tool_name",
            "description": "工具描述",
            "parameters": {
                "type": "object",
                "properties": {
                    "param1": {"type": "string", "description": "..."}
                },
                "required": ["param1"]
            }
        }
    }
]

def tool_name(param1: str) -> str:
    # 执行逻辑
    return "结果文本"

def register():
    for schema in SCHEMAS:
        register_tool(schema, tool_name)
```

### Schema 加载

```python
# skills/__init__.py + run/tool.py
def load_tool_schemas(disabled_skills=None) -> list[dict]:
    """从 TOOL_REGISTRY 返回已注册 schema，并按 disabled_skills 过滤。"""
```

启动或刷新时会调用 `skills.register_all()`，由各插件的 `register()` 调用 `run.tool.register_tool(schema, handler, meta)` 写入全局 `TOOL_REGISTRY`。

## 二、工具调用流程

### LLM 返回 tool_calls

```json
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "read_file",
        "arguments": "{\"path\": \"config/config_soul.md\"}"
      }
    }
  ]
}
```

### ToolRunner 执行

```python
# run/tool.py
class ToolRunner:
    def execute(self, response) -> tuple[list[dict], list[dict]]:
        # 解析 response.tool_calls
        # 检查 deny / enabled / disabled_skills / 调用次数上限
        # 从 TOOL_REGISTRY 找到 handler
        # 使用 ThreadPoolExecutor 执行 handler(**args)
        # 按 tool_timeout 控制超时；meta.skip_tool_timeout=true 时跳过全局超时
        # 写工具日志，返回 tool result 消息和前端展示详情
```

### 完整调用循环（run_chat_turn）

```python
# run/engine.py
def run_chat_turn(chat, tool_runner, provider, tools):
    while tool_round < MAX_TOOL_ROUNDS:  # 来自 config_core.json 的 tool.tool_max_per_type
        response = provider.respond(chat.build_messages(), tools=tools)
        if response.text:
            yield {"type": "text", "content": response.text}
        if response.tool_calls:
            results, details = tool_runner.execute(response)
            chat.add_tool_results(results)
            for d in details:
                yield {"type": "tool_call", "name": d["name"], "success": d["success"]}
        else:
            break
```

## 三、上下文绑定机制

每次对话开始前，`AgentService.chat()` 或 `_run_turn()` 会调用：
```python
ai_tool.set_auto_improve_context(provider=provider, chat=chat, user_name=username)
tp_tool.set_task_plan_context(provider=provider, chat=chat, user_name=username)
set_multimodal_context(provider=provider, chat=chat, user_name=username)
```

## 四、当前工具边界

| 旧插件 | 当前替代 |
|--------|----------|
| `file_search` | `file.search_files` |
| `video_download` | `download_anything.download_video` |
| `web_content_fetcher` | `network.web_read` |

重点工具：

| Skill | 主要工具 |
|-------|----------|
| `file` | 文件读写、范围读取、目录树、搜索、复制、移动、创建目录、删除文件 |
| `network` | `http_get`, `http_post`, `web_read` |
| `download_anything` | `inspect_download_url`, `download_direct_file`, `download_video`, `list_downloads` |
| `tavily_search` | `tavily_search`, `tavily_extract`, `tavily_crawl`, `tavily_map`, `tavily_research` |
| `pdf_tools` | PDF 信息、提取、拆分、合并、旋转、盖字、水印、预览、OCR、脱敏、视觉对比 |

能用专用 Skill 或工具完成的，不优先使用 shell。

## 五、工具调用限制

- 每轮工具调用循环上限来自 `config/config_core.json` 的 `tool.tool_max_per_type`，默认 **80**
- 单工具调用上限来自 `tool.tool_max_per_tool`，默认 **80**
- 单次工具执行超时优先级：用户 `config.json` 的 `tool.tool_timeout` > 全局 `config_core.json` > 默认 **120 秒**
- 个别工具可通过注册 meta 跳过全局超时，例如 `time.sleep`
- 工具结果过大时自动截断
- 超过限制时 yield `{"type": "max_rounds"}` 或 `{"type": "error"}`
