Coverage for merco/tools/__init__.py: 32%
31 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
1"""工具系统 — 自动发现、toolset 过滤、动态描述"""
3import importlib.util
4import logging
5import os
6from pathlib import Path
8from .registry import ToolRegistry, tool_registry
9from .base import BaseTool
11logger = logging.getLogger("merco.tools")
13# 内置工具模块列表
14_BUILTIN_MODULES = [
15 "merco.tools.file_tools",
16 "merco.tools.bash_tools",
17 "merco.tools.web_tools",
18 "merco.tools.task_tools",
19 "merco.tools.mcp_tools",
20 "merco.tools.skill_tools",
21 "merco.tools.edit",
22]
25def discover_tools(tool_paths: list[str] | None = None):
26 """导入内置工具 + 扫描外部路径,触发各模块的自注册。
28 内置工具:导入 _BUILTIN_MODULES 列表中的所有模块。
29 外部工具:扫描 tool_paths 中的每个目录,importlib 动态加载 *.py 文件。
31 每个工具模块在 import 时会调用 tool_registry.register() 完成注册。
32 外部工具与内置工具写法完全一致:继承 BaseTool,末尾调用 register()。
33 """
34 # 1. 内置工具
35 for mod_name in _BUILTIN_MODULES:
36 try:
37 importlib.import_module(mod_name)
38 except Exception:
39 logger.warning("加载内置工具模块失败: %s", mod_name, exc_info=True)
41 # 2. 外部工具路径
42 if tool_paths:
43 for base_path in tool_paths:
44 expanded = os.path.expanduser(base_path)
45 if not os.path.isdir(expanded):
46 logger.debug("工具路径不存在,跳过: %s", expanded)
47 continue
49 for pyfile in sorted(Path(expanded).glob("*.py")):
50 mod_name = f"_ext_tool_{pyfile.stem}"
51 try:
52 spec = importlib.util.spec_from_file_location(mod_name, str(pyfile))
53 if spec and spec.loader:
54 module = importlib.util.module_from_spec(spec)
55 spec.loader.exec_module(module)
56 logger.debug("已加载外部工具: %s", pyfile)
57 except Exception:
58 logger.warning("加载外部工具失败: %s", pyfile, exc_info=True)
61__all__ = ["ToolRegistry", "BaseTool", "tool_registry", "discover_tools"]