tool_factory.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import os
  2. import sys
  3. import importlib
  4. import inspect
  5. from pathlib import Path
  6. from typing import List
  7. from langchain.tools import BaseTool
  8. def get_all_tools() -> List[BaseTool]:
  9. """
  10. 自动发现并返回所有工具 - 修复检查逻辑
  11. """
  12. tools = []
  13. print("🛠️ 开始自动发现工具...")
  14. # 获取项目根目录
  15. project_root = Path(__file__).parent.parent
  16. tools_dir = Path(__file__).parent
  17. # 将项目根目录添加到Python路径
  18. if str(project_root) not in sys.path:
  19. sys.path.insert(0, str(project_root))
  20. # 导入配置以检查知识库开关
  21. from config.settings import settings
  22. # 扫描工具文件
  23. tool_files = []
  24. # 模式1: 编译前的.py文件
  25. for file_path in tools_dir.glob("*_tools.py"):
  26. if file_path.is_file():
  27. module_name = file_path.stem
  28. # 如果知识库被禁用,跳过知识库工具
  29. if module_name == "knowledge_tools" and not settings.KNOWLEDGE_BASE_ENABLED:
  30. print("知识库功能已禁用,跳过知识库工具")
  31. continue
  32. tool_files.append(module_name)
  33. print(f"发现工具文件: {module_name}")
  34. # 模式2: 编译后的.pyd文件
  35. for file_path in tools_dir.glob("*_tools.cp*.pyd"):
  36. if file_path.is_file():
  37. # 从文件名中提取模块名,如: ware_tools.cp313-win_amd64.pyd -> ware_tools
  38. module_name = file_path.stem.split(".")[0]
  39. # 如果知识库被禁用,跳过知识库工具
  40. if module_name == "knowledge_tools" and not settings.KNOWLEDGE_BASE_ENABLED:
  41. print("知识库功能已禁用,跳过知识库工具")
  42. continue
  43. if module_name not in tool_files: # 避免重复添加
  44. tool_files.append(module_name)
  45. print(f"发现编译后工具文件: {module_name}")
  46. if not tool_files:
  47. # 如果知识库被禁用,从默认列表中移除知识库工具
  48. default_tools = ["sale_tools", "ware_tools", "price_tools", "money_tools"]
  49. if settings.KNOWLEDGE_BASE_ENABLED:
  50. default_tools.insert(0, "knowledge_tools")
  51. tool_files = default_tools
  52. print("使用默认工具列表")
  53. for module_name in tool_files:
  54. try:
  55. # 导入模块
  56. full_module_path = f"tools.{module_name}"
  57. module = importlib.import_module(full_module_path)
  58. # print(f"加载模块: {module_name}")
  59. # 查找工具 - 使用更全面的方法
  60. tool_count = 0
  61. # 方法1: 检查模块的所有属性
  62. for attr_name in dir(module):
  63. if attr_name.startswith("_"):
  64. continue
  65. attr = getattr(module, attr_name)
  66. # # 详细调试信息
  67. # print(f" 检查 {attr_name}:")
  68. # print(f" 类型: {type(attr)}")
  69. # 检查是否是BaseTool实例
  70. if isinstance(attr, BaseTool):
  71. tools.append(attr)
  72. tool_count += 1
  73. # print(f" 发现BaseTool工具: {getattr(attr, 'name', attr_name)}")
  74. continue
  75. # 检查是否是函数且具有工具属性
  76. if callable(attr):
  77. # print(f" 是否有name属性: {hasattr(attr, 'name')}")
  78. # if hasattr(attr, "name"):
  79. # print(f" name值: {getattr(attr, 'name', '无')}")
  80. # print(f" 是否有description属性: {hasattr(attr, 'description')}")
  81. # if hasattr(attr, "description"):
  82. # print(
  83. # f" description前50字: {getattr(attr, 'description', '')[:50]}"
  84. # )
  85. # 检查是否是工具函数
  86. if is_tool_function(attr):
  87. tools.append(attr)
  88. tool_count += 1
  89. # print(f" 发现工具函数: {attr_name}")
  90. # 方法2: 检查模块的全局变量
  91. print(f" 🔍 检查模块全局变量...")
  92. for name, value in module.__dict__.items():
  93. if name.startswith("_"):
  94. continue
  95. if isinstance(value, BaseTool):
  96. if value not in tools: # 避免重复添加
  97. tools.append(value)
  98. tool_count += 1
  99. # print(
  100. # f" 从全局变量发现BaseTool工具: {getattr(value, 'name', name)}"
  101. # )
  102. if tool_count == 0:
  103. # print(f" 模块 {module_name} 中未发现工具")
  104. # 尝试手动创建工具
  105. manual_tools = create_tools_manually(module_name, module)
  106. if manual_tools:
  107. tools.extend(manual_tools)
  108. tool_count = len(manual_tools)
  109. # print(f" 🔧 手动创建了 {tool_count} 个工具")
  110. else:
  111. print(f" 模块 {module_name} 中发现 {tool_count} 个工具")
  112. except Exception as e:
  113. print(f"加载模块 {module_name} 失败: {e}")
  114. print(f"总共发现 {len(tools)} 个工具")
  115. # # 打印工具详情
  116. # for i, tool in enumerate(tools):
  117. # tool_name = getattr(tool, "name", f"tool_{i+1}")
  118. # tool_desc = getattr(tool, "description", "无描述")
  119. # print(f" {i+1}. {tool_name}: {tool_desc[:50]}...")
  120. return tools
  121. def create_tools_manually(module_name: str, module) -> List[BaseTool]:
  122. """手动创建工具 - 针对@tool装饰器的问题"""
  123. tools = []
  124. if module_name == "knowledge_tools":
  125. # 手动导入并创建知识库工具
  126. try:
  127. from langchain.tools import tool
  128. # 检查模块中是否有工具函数
  129. if hasattr(module, "get_knowledge_list"):
  130. func = getattr(module, "get_knowledge_list")
  131. if callable(func):
  132. # 使用@tool装饰器重新创建工具
  133. tool_instance = tool(func)
  134. tools.append(tool_instance)
  135. print(f" 手动创建工具: get_knowledge_list")
  136. if hasattr(module, "get_knowledge_content"):
  137. func = getattr(module, "get_knowledge_content")
  138. if callable(func):
  139. tool_instance = tool(func)
  140. tools.append(tool_instance)
  141. print(f" 手动创建工具: get_knowledge_content")
  142. except Exception as e:
  143. print(f" 手动创建知识库工具失败: {e}")
  144. elif module_name == "sale_tools":
  145. # 手动创建销售工具
  146. try:
  147. from langchain.tools import tool
  148. if hasattr(module, "get_sale_amt"):
  149. func = getattr(module, "get_sale_amt")
  150. if callable(func):
  151. tool_instance = tool(func)
  152. tools.append(tool_instance)
  153. print(f" 手动创建工具: get_sale_amt")
  154. except Exception as e:
  155. print(f" 手动创建销售工具失败: {e}")
  156. return tools
  157. def is_tool_instance(obj) -> bool:
  158. """检查是否是工具实例"""
  159. if not callable(obj):
  160. return False
  161. # 检查是否是BaseTool实例
  162. if isinstance(obj, BaseTool):
  163. return True
  164. # 检查是否有工具的标准属性
  165. tool_attrs = ["name", "description"]
  166. if all(hasattr(obj, attr) for attr in tool_attrs):
  167. return True
  168. return False
  169. def is_tool_function(obj) -> bool:
  170. """检查是否是工具函数"""
  171. if not callable(obj):
  172. return False
  173. # 检查是否被@tool装饰
  174. if hasattr(obj, "_is_tool") and getattr(obj, "_is_tool", False):
  175. return True
  176. # 检查是否有tool属性
  177. if hasattr(obj, "tool"):
  178. return True
  179. # 检查是否是函数且具有工具属性
  180. if callable(obj) and hasattr(obj, "name") and hasattr(obj, "description"):
  181. return True
  182. return False
  183. # 测试函数
  184. def test_tool_detection():
  185. """测试工具检测"""
  186. print("测试工具检测...")
  187. # 导入一个模块测试
  188. import tools.knowledge_tools as kt
  189. print(f"模块: {kt}")
  190. for attr_name in dir(kt):
  191. if not attr_name.startswith("_"):
  192. attr = getattr(kt, attr_name)
  193. print(f"\n检查 {attr_name}:")
  194. print(f" 类型: {type(attr)}")
  195. print(f" 可调用: {callable(attr)}")
  196. if callable(attr):
  197. print(f" 是否有name属性: {hasattr(attr, 'name')}")
  198. if hasattr(attr, "name"):
  199. print(f" name值: {getattr(attr, 'name', '无')}")
  200. print(f" 是否有description属性: {hasattr(attr, 'description')}")
  201. if hasattr(attr, "description"):
  202. print(
  203. f" description前50字: {getattr(attr, 'description', '')[:50]}"
  204. )
  205. print(f" 是否被@tool装饰: {is_tool_function(attr)}")
  206. print(f" 是否是工具实例: {is_tool_instance(attr)}")