将本地工具贡献至 ToolUniverse

本指南介绍了如何将本地 Python 工具贡献到 ToolUniverse 仓库。本地工具在 ToolUniverse 进程中运行,并可供所有用户使用。

备注

关键区别:向代码库贡献内容相比于本地使用工具需要额外的步骤。最关键的步骤是修改 __init__.py 文件中的 4 个特定位置。

备注

For Local Development Only: If you just want to use a tool locally without contributing to the repository, see the single-file example in examples/my_new_tool/single_file_example.py. This approach doesn’t require modifying any core ToolUniverse files (__init__.py, default_config.py, or data/ directory). The complete working examples are available in examples/my_new_tool/ directory with a README explaining both approaches.

快速概览

贡献本地工具的10个步骤:

  1. 环境设置 - 分叉、克隆、安装依赖项

  2. 创建工具文件 - 位于``src/tooluniverse/``中的Python类

  3. 注册工具 - 使用 @register_tool('Type') 装饰器

  4. 创建配置 - JSON 文件位于 data/xxx_tools.json

  5. (Optional) Add unit tests

  6. Done! Tool is auto-discovered.

  7. 代码质量 - 提交前钩子(自动)

  8. 文档 - 文档字符串和示例

  9. 创建示例 - 在``examples/``中创建工作示例

  10. 提交 PR - 请遵循贡献指南

分步指南

步骤 1:环境设置

# Fork the repository on GitHub first
git clone https://github.com/yourusername/ToolUniverse.git
cd ToolUniverse

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"

# Install pre-commit hooks
./setup_precommit.sh

步骤 2:创建工具文件

在``src/tooluniverse/xxx_tool.py``中创建您的工具文件:

from tooluniverse.tool_registry import register_tool
from tooluniverse.base_tool import BaseTool
from typing import Dict, Any

@register_tool('MyNewTool')  # Note: No config here for contributions
class MyNewTool(BaseTool):
    """My new tool for ToolUniverse."""

    def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
        """Execute the tool."""
        # Your tool logic here
        input_value = arguments.get('input', '')
        return {
            "result": input_value.upper(),
            "success": True
        }

    def validate_input(self, **kwargs) -> None:
        """Validate input parameters."""
        input_val = kwargs.get('input')
        if not input_val:
            raise ValueError("Input is required")

步骤 3:注册工具

@register_tool('MyNewTool') 装饰器用于注册您的工具类。请注意,对于贡献部分,我们不会在装饰器中包含配置——配置应放在单独的 JSON 文件中。

步骤 4:创建配置文件

创建或编辑 src/tooluniverse/data/xxx_tools.json:

[
  {
    "name": "my_new_tool",
    "type": "MyNewTool",
    "description": "Convert text to uppercase",
    "parameter": {
      "type": "object",
      "properties": {
        "input": {
          "type": "string",
          "description": "Text to convert to uppercase"
        }
      },
      "required": ["input"]
    },
    "examples": [
      {
        "description": "Convert text to uppercase",
        "arguments": {"input": "hello world"}
      }
    ],
    "tags": ["text", "utility"],
    "author": "Your Name <your.email@example.com>",
    "version": "1.0.0"
  }
]

Step 5: No Modifications Needed in __init__.py!

With the new automated discovery system, you do NOT need to modify `src/tooluniverse/__init__.py`.

The system will automatically find your tool class if it is decorated with @register_tool and located inside the src/tooluniverse package tree.

Verification:

# Test that your tool can be imported immediately
from tooluniverse import MyNewTool
print(MyNewTool)  # Should show the class or lazy proxy

步骤 6:编写测试

在``tests/unit/test_my_new_tool.py``中创建测试:

import pytest
from tooluniverse.my_new_tool import MyNewTool

class TestMyNewTool:
    def setup_method(self):
        self.tool = MyNewTool()

    def test_success(self):
        """Test successful execution."""
        result = self.tool.run({"input": "hello"})
        assert result["success"] is True
        assert result["result"] == "HELLO"

    def test_validation(self):
        """Test input validation."""
        with pytest.raises(ValueError):
            self.tool.validate_input(input="")

    def test_empty_input(self):
        """Test empty input handling."""
        result = self.tool.run({"input": ""})
        assert result["success"] is True
        assert result["result"] == ""

运行带覆盖率的测试:.. code-block:: bash

pytest tests/unit/test_my_new_tool.py –cov=tooluniverse –cov-report=html

第七步:代码质量检查(自动)

在提交代码时,预提交钩子会自动运行:

git add .
git commit -m "feat: add MyNewTool"
# Pre-commit will run: Black, Flake8, Ruff, etc.
# If checks fail, fix the issues and commit again

步骤 8:文档编制

为您的工具类添加全面的文档字符串:

class MyNewTool(BaseTool):
    """
    Convert text to uppercase.

    This tool takes a string input and returns it converted to uppercase.
    Useful for text processing workflows.

    Args:
        input (str): The text to convert to uppercase

    Returns:
        dict: Result dictionary with 'result' and 'success' keys

    Example:
        >>> tool = MyNewTool()
        >>> result = tool.run({"input": "hello"})
        >>> print(result["result"])
        HELLO
    """

请提供需要翻译的具体文本内容,我将为您进行翻译。

步骤 9:创建示例

Create examples/my_new_tool/my_new_tool_example.py:

"""Example usage of MyNewTool.

This example follows the documentation pattern for contributing tools to the
repository. It demonstrates the multi-file structure:
- my_new_tool.py: Tool class definition
- my_new_tool_tools.json: Tool configuration
- my_new_tool_example.py: Example usage

Note: In a real contribution, these files would be placed in:
- src/tooluniverse/my_new_tool.py
- src/tooluniverse/data/my_new_tool_tools.json
- examples/my_new_tool_example.py

And you would need to modify __init__.py in 4 locations.
"""

import os
import sys

# Import the tool class to register it
from my_new_tool import MyNewTool  # noqa: E402, F401

from tooluniverse import ToolUniverse  # noqa: E402


def main():
    # Initialize ToolUniverse
    tu = ToolUniverse()

    # Load tools with the config file
    # In a real contribution, this would be in default_tool_files
    current_dir = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(current_dir, 'my_new_tool_tools.json')
    tu.load_tools(tool_config_files={"my_new_tool": config_path})

    # Use the tool
    result = tu.run({
        "name": "my_new_tool",
        "arguments": {"input": "hello world"}
    })

    print(f"Result: {result}")

    # Test with different inputs
    test_inputs = ["hello", "world", "python"]
    for text in test_inputs:
        result = tu.run({
            "name": "my_new_tool",
            "arguments": {"input": text}
        })
        print(f"'{text}' -> '{result.get('result', 'ERROR')}'")

if __name__ == "__main__":
    main()

Note: A complete working example can be found in examples/my_new_tool/ directory, which includes both the multi-file structure (for contributions) and a single-file example (for local development). See examples/my_new_tool/README.md for details.

第10步:提交拉取请求

# Create feature branch
git checkout -b feature/add-my-new-tool

# Add all files
git add src/tooluniverse/my_new_tool.py
git add src/tooluniverse/data/xxx_tools.json
git add src/tooluniverse/__init__.py
git add tests/unit/test_my_new_tool.py
git add examples/my_new_tool_example.py

# Commit with descriptive message
git commit -m "feat: add MyNewTool for text processing

- Implement MyNewTool class with uppercase conversion
- Add comprehensive unit tests with >95% coverage
- Include usage examples and documentation
- Support input validation and error handling

Closes #[issue-number]"

# Push and create PR
git push origin feature/add-my-new-tool

PR 模板: .. code-block:: markdown

## 描述

此PR新增了MyNewTool,一款用于文本处理的本地新工具。

## 修改内容

  • 工具实施:完成 MyNewTool 类

  • 测试:单元测试覆盖率超过 95%

  • 文档:详尽的文档字符串和示例

  • 配置:位于 data/xxx_tools.json 的 JSON 配置

  • 集成:在 4 个位置修改了 __init__.py

## 测试

`bash pytest tests/unit/test_my_new_tool.py --cov=tooluniverse python examples/my_new_tool/my_new_tool_example.py `

## 检查清单

  • [x] 测试在本地通过

  • [x] 代码遵循项目风格指南

  • [x] 文档已完成

  • [x] __init__.py 已在所有 4 个位置进行了修改

  • [x] 示例按预期运行

常见错误

❌ Most Common: Missing @register_tool decorator - Tool won’t be discovered if not decorated - Solution: Add @register_tool("MyTool")

❌ Config in wrong place - Don’t put config in @register_tool() decorator (for contributions) - Put it in data/my_new_tool_tools.json instead - Note: For local development only, you CAN put config in the decorator (see examples/my_new_tool/single_file_example.py)

❌ 文件位置错误 - 工具文件必须位于 src/tooluniverse/ - 不应放在您的项目目录中

❌ 缺少测试 - 覆盖率必须 >90% - 测试成功和错误情况

❌ 导入错误 - 检查模块名称是否与文件名称匹配 - 检查类名称是否完全一致(区分大小写)

故障排除

ImportError: 无法导入名称 ‘MyNewTool’ .. code-block:: python

# 检查工具是否在 __all__ 列表中 from tooluniverse import __all__ print(“MyNewTool” in __all__) # 应为 True

# 检查是否存在导入语句 # 查找:from .my_new_tool import MyNewTool

AttributeError: 模块 ‘tooluniverse’ 没有属性 ‘MyNewTool’ - 请确认工具名称是否在 __all__ 列表中 - 检查工具名称是否与类名完全匹配

使用 ToolUniverse 时未找到工具 .. code-block:: python

# 验证工具是否正确加载 from tooluniverse import ToolUniverse tu = ToolUniverse() tu.load_tools()

# Check if tool is in the loaded tools print(“my_new_tool” in tu.all_tool_dict) # Should be True

下一步

在成功提交您的本地工具后:

小技巧

成功提示:从简单的工具开始,进行充分测试,如果遇到问题,可以在 GitHub 讨论中寻求帮助!