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

1"""工具系统 — 自动发现、toolset 过滤、动态描述""" 

2 

3import importlib.util 

4import logging 

5import os 

6from pathlib import Path 

7 

8from .registry import ToolRegistry, tool_registry 

9from .base import BaseTool 

10 

11logger = logging.getLogger("merco.tools") 

12 

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] 

23 

24 

25def discover_tools(tool_paths: list[str] | None = None): 

26 """导入内置工具 + 扫描外部路径,触发各模块的自注册。 

27 

28 内置工具:导入 _BUILTIN_MODULES 列表中的所有模块。 

29 外部工具:扫描 tool_paths 中的每个目录,importlib 动态加载 *.py 文件。 

30 

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) 

40 

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 

48 

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) 

59 

60 

61__all__ = ["ToolRegistry", "BaseTool", "tool_registry", "discover_tools"]