tooluniverse 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tooluniverse might be problematic. Click here for more details.

@@ -81,31 +81,37 @@ def compose(arguments: Dict[str, Any], tooluniverse, call_tool) -> Dict[str, Any
81
81
  else:
82
82
  print(f"❌ Chunk {i+1} summarization failed")
83
83
 
84
- # Step 3: Merge summaries
84
+ # Step 3: Merge summaries (or gracefully fall back)
85
85
  if chunk_summaries:
86
86
  final_summary = _merge_summaries(
87
87
  chunk_summaries, query_context, tool_name, max_summary_length, call_tool
88
88
  )
89
+ print(
90
+ f"✅ Summarization completed. Final length: {len(final_summary)} characters"
91
+ )
92
+ return {
93
+ "success": True,
94
+ "original_length": len(tool_output),
95
+ "summary_length": len(final_summary),
96
+ "chunks_processed": len(chunks),
97
+ "summary": final_summary,
98
+ "tool_name": tool_name,
99
+ }
89
100
  else:
90
- final_summary = "Unable to generate summary due to processing errors."
101
+ # Treat as a non-fatal failure so upstream falls back to original output
91
102
  print("❌ No chunk summaries were generated. This usually indicates:")
92
103
  print(" 1. ToolOutputSummarizer tool is not available")
93
104
  print(" 2. The output_summarization tools are not loaded")
94
105
  print(" 3. There was an error in the summarization process")
95
106
  print(" Please check that the SMCP server is started with hooks enabled.")
96
-
97
- print(
98
- f" Summarization completed. Final length: {len(final_summary)} characters"
99
- )
100
-
101
- return {
102
- "success": True,
103
- "original_length": len(tool_output),
104
- "summary_length": len(final_summary),
105
- "chunks_processed": len(chunks),
106
- "summary": final_summary,
107
- "tool_name": tool_name,
108
- }
107
+ return {
108
+ "success": False,
109
+ "error": "No chunk summaries generated",
110
+ "original_length": len(tool_output),
111
+ "chunks_processed": len(chunks),
112
+ "original_output": tool_output,
113
+ "tool_name": tool_name,
114
+ }
109
115
 
110
116
  except Exception as e:
111
117
  error_msg = f"Error in output summarization: {str(e)}"
@@ -17,6 +17,7 @@ def compose(arguments, tooluniverse, call_tool):
17
17
  """
18
18
  import json
19
19
  import warnings
20
+ import uuid
20
21
  from collections import Counter
21
22
 
22
23
  def _parse_agent_output(output, tool_name="Unknown Tool"):
@@ -365,5 +366,10 @@ def compose(arguments, tooluniverse, call_tool):
365
366
 
366
367
  except Exception as e:
367
368
  print(f"An error occurred during single-occurrence tag removal: {e}")
369
+
370
+ # Step 6: Manually set the UUID 'id' field to ensure true randomness
371
+ for tool_metadata in all_tool_metadata:
372
+ if 'error' not in tool_metadata:
373
+ tool_metadata['id'] = str(uuid.uuid4())
368
374
 
369
375
  return all_tool_metadata
@@ -1162,7 +1162,7 @@
1162
1162
  "type": "AgenticTool",
1163
1163
  "name": "ToolMetadataGenerator",
1164
1164
  "description": "Generates a JSON structure with the metadata of a tool in ToolUniverse, given the JSON configuration of the tool.",
1165
- "prompt": "You are an expert in processing ToolUniverse tool configurations. Your task is to extract and generate key metadata from a given tool's JSON configuration and return it as a new, structured JSON object.\n\n**Input Tool Configuration:**\n```json\n{tool_config}\n```\n\n**Tool Type Mappings (for simplifying toolType):**\n```json\n{tool_type_mappings}\n```\n\n**Instructions:**\nFrom the input configuration, generate a new JSON object with the specified structure. All fields enclosed in '<','>' are placeholders for instructions; you should generate a specific value for the tool based on its configuration. Fields not in brackets should use the default values provided.\n\n**Output JSON Structure:**\n```json\n{\n \"id\": \"<generate a new uuid>\",\n \"name\": \"<extract from tool_config.name>\",\n \"description\": \"<extract and tool_config.description and slightly summarize it if it is too long>\",\n \"detailed_description\": \"<extract from tool_config.description>\",\n \"toolType\": \"<if tool_config.type or tool_config.name appears in tool_type_mappings dict in one of the lists (among the dict's values), extract the corresponding key and set it as the simplified toolType. otherwise, set toolType to be 'API' (the default)>\",\n \"tags\": [],\n \"category\": \"<extract from tool_config.type>\",\n \"lab\": \"Zitnik Lab\",\n \"source\": \"<extract the name of the database, package, model, or write 'Agentic'>\",\n \"version\": \"v1.0.0\",\n \"reviewed\": true,\n \"isValidated\": true,\n \"usageStats\": \"100+ uses\",\n \"capabilities\": [\n \"<list capabilities strictly derivable from tool_config>\"\n ],\n \"limitations\": [\n \"May require refinement\"\n ],\n \"parameters\": {<for each parameter key include an object with type and description>},\n \"inputSchema\": <echo tool_config.parameter exactly>,\n \"exampleInput\": <JSON object with example values for each parameter>,\n \"apiEndpoints\": [\n {\n \"method\": \"MCP\",\n \"url\": \"https://tooluniversemcpserver.onrender.com/mcp/\"\n }\n ]\n}\n```\n\nReturn ONLY the final JSON object with no extra commentary.",
1165
+ "prompt": "You are an expert in processing ToolUniverse tool configurations. Your task is to extract and generate key metadata from a given tool's JSON configuration and return it as a new, structured JSON object.\n\n**Input Tool Configuration:**\n```json\n{tool_config}\n```\n\n**Tool Type Mappings (for simplifying toolType):**\n```json\n{tool_type_mappings}\n```\n\n**Instructions:**\nFrom the input configuration, generate a new JSON object with the specified structure. All fields enclosed in '<','>' are placeholders for instructions; you should generate a specific value for the tool based on its configuration. Fields not in brackets should use the default values provided.\n\n**Output JSON Structure:**\n```json\n{\n \"id\": \"<generate a new uuid>\",\n \"name\": \"<extract from tool_config.name>\",\n \"description\": \"<extract and tool_config.description and slightly summarize it if it is too long>\",\n \"detailed_description\": \"<extract from tool_config.description>\",\n \"toolType\": \"<if tool_config.type or tool_config.name appears in tool_type_mappings dict in one of the lists (among the dict's values), extract the corresponding key and set it as the simplified toolType. otherwise, set toolType to be 'API' (the default)>\",\n \"tags\": [],\n \"category\": \"<extract from tool_config.type>\",\n \"lab\": \"Zitnik Lab\",\n \"source\": \"<extract the name of the database, package, model, or write 'Agentic'>\",\n \"version\": \"v1.0.0\",\n \"reviewed\": true,\n \"isValidated\": true,\n \"usageStats\": \"100+ uses\",\n \"capabilities\": [\n \"<list capabilities strictly derivable from tool_config>\"\n ],\n \"limitations\": [\n \"None for now\"\n ],\n \"parameters\": {<for each parameter key include an object with type and description>},\n \"inputSchema\": <echo tool_config.parameter exactly>,\n \"exampleInput\": <JSON object with example values for each parameter>,\n \"apiEndpoints\": [\n {\n \"method\": \"MCP\",\n \"url\": \"https://tooluniversemcpserver.onrender.com/mcp/\"\n }\n ]\n}\n```\n\nReturn ONLY the final JSON object with no extra commentary.",
1166
1166
  "input_arguments": [
1167
1167
  "tool_config",
1168
1168
  "tool_type_mappings"
@@ -73,7 +73,7 @@
73
73
  "type": "integer",
74
74
  "description": "Size of each chunk for processing",
75
75
  "required": false,
76
- "default": 2000
76
+ "default": 30000
77
77
  },
78
78
  "focus_areas": {
79
79
  "type": "string",
@@ -85,7 +85,7 @@
85
85
  "type": "integer",
86
86
  "description": "Maximum length of final summary",
87
87
  "required": false,
88
- "default": 3000
88
+ "default": 10000
89
89
  }
90
90
  },
91
91
  "required": ["tool_output", "query_context", "tool_name"]
@@ -1000,9 +1000,9 @@ class ToolUniverse:
1000
1000
  """
1001
1001
  return copy.deepcopy(self.all_tools)
1002
1002
 
1003
- def list_built_in_tools(self, mode="config"):
1003
+ def list_built_in_tools(self, mode="config", scan_all=False):
1004
1004
  """
1005
- List all built-in tool categories and their statistics with two different modes.
1005
+ List all built-in tool categories and their statistics with different modes.
1006
1006
 
1007
1007
  This method provides a comprehensive overview of all available tools in the ToolUniverse,
1008
1008
  organized by categories. It reads directly from the default tool files to gather statistics,
@@ -1012,39 +1012,62 @@ class ToolUniverse:
1012
1012
  mode (str, optional): Organization mode for tools. Defaults to 'config'.
1013
1013
  - 'config': Organize by config file categories (original behavior)
1014
1014
  - 'type': Organize by tool types (implementation classes)
1015
+ - 'list_name': Return a list of all tool names
1016
+ - 'list_spec': Return a list of all tool specifications
1017
+ scan_all (bool, optional): Whether to scan all JSON files in data directory recursively.
1018
+ If True, scans all JSON files in data/ and its subdirectories.
1019
+ If False (default), uses predefined tool file mappings.
1015
1020
 
1016
1021
  Returns:
1017
- dict: A dictionary containing tool statistics with the following structure:
1018
-
1019
- {
1020
- 'categories': {
1021
- 'category_name': {
1022
- 'count': int, # Number of tools in this category
1023
- 'tools': list # List of tool names (only when mode='type')
1024
- },
1025
- ...
1026
- },
1027
- 'total_categories': int, # Total number of tool categories
1028
- 'total_tools': int, # Total number of unique tools
1029
- 'mode': str, # The mode used for organization
1030
- 'summary': str # Human-readable summary of statistics
1031
- }
1022
+ dict or list:
1023
+ - For 'config' and 'type' modes: A dictionary containing tool statistics
1024
+ - For 'list_name' mode: A list of all tool names
1025
+ - For 'list_spec' mode: A list of all tool specifications
1032
1026
 
1033
1027
  Example:
1034
1028
  >>> tool_universe = ToolUniverse()
1035
- >>> # Group by config file categories
1029
+ >>> # Group by config file categories (predefined files only)
1036
1030
  >>> stats = tool_universe.list_built_in_tools(mode='config')
1037
- >>> # Group by tool types
1038
- >>> stats = tool_universe.list_built_in_tools(mode='type')
1031
+ >>> # Scan all JSON files in data directory recursively
1032
+ >>> stats = tool_universe.list_built_in_tools(mode='config', scan_all=True)
1033
+ >>> # Get all tool names from all JSON files
1034
+ >>> tool_names = tool_universe.list_built_in_tools(mode='list_name', scan_all=True)
1039
1035
 
1040
1036
  Note:
1041
1037
  - This method reads directly from tool files and works without calling load_tools()
1042
1038
  - Tools are deduplicated across categories, so the same tool won't be counted multiple times
1043
- - The summary is automatically printed to console when this method is called
1039
+ - The summary is automatically printed to console when this method is called (except for list_name and list_spec modes)
1040
+ - When scan_all=True, all JSON files in data/ and subdirectories are scanned
1044
1041
  """
1045
- if mode not in ["config", "type"]:
1046
- raise ValueError("Mode must be either 'config' or 'type'")
1042
+ if mode not in ["config", "type", "list_name", "list_spec"]:
1043
+ raise ValueError(
1044
+ "Mode must be one of: 'config', 'type', 'list_name', 'list_spec'"
1045
+ )
1047
1046
 
1047
+ # For list_name and list_spec modes, we can return early with just the data
1048
+ if mode in ["list_name", "list_spec"]:
1049
+ all_tools = []
1050
+ all_tool_names = set() # For deduplication across categories
1051
+
1052
+ if scan_all:
1053
+ # Scan all JSON files in data directory recursively
1054
+ all_tools, all_tool_names = self._scan_all_json_files()
1055
+ else:
1056
+ # Use predefined tool files (original behavior)
1057
+ all_tools, all_tool_names = self._scan_predefined_files()
1058
+
1059
+ # Deduplicate tools by name
1060
+ unique_tools = {}
1061
+ for tool in all_tools:
1062
+ if tool["name"] not in unique_tools:
1063
+ unique_tools[tool["name"]] = tool
1064
+
1065
+ if mode == "list_name":
1066
+ return sorted(list(unique_tools.keys()))
1067
+ elif mode == "list_spec":
1068
+ return list(unique_tools.values())
1069
+
1070
+ # Original logic for config and type modes
1048
1071
  result = {
1049
1072
  "categories": {},
1050
1073
  "total_categories": 0,
@@ -1053,58 +1076,43 @@ class ToolUniverse:
1053
1076
  "summary": "",
1054
1077
  }
1055
1078
 
1056
- all_tool_names = set() # For deduplication across categories
1057
- all_tools = [] # Store all tools for type-based grouping
1058
-
1059
- # Read tools from each category file
1060
- for category, file_path in self.tool_files.items():
1061
- try:
1062
- # Read the JSON file for this category
1063
- tools_in_category = read_json_list(file_path)
1064
- all_tools.extend(tools_in_category)
1079
+ if scan_all:
1080
+ # Scan all JSON files in data directory recursively
1081
+ all_tools, all_tool_names = self._scan_all_json_files()
1065
1082
 
1066
- if mode == "config":
1067
- tool_names = [tool["name"] for tool in tools_in_category]
1068
- result["categories"][category] = {"count": len(tool_names)}
1083
+ # For config mode with scan_all, organize by file names
1084
+ if mode == "config":
1085
+ file_tools_map = {}
1086
+ for tool in all_tools:
1087
+ # Get the source file for this tool (we need to track this)
1088
+ # For now, we'll organize by tool type as a fallback
1089
+ tool_type = tool.get("type", "Unknown")
1090
+ if tool_type not in file_tools_map:
1091
+ file_tools_map[tool_type] = []
1092
+ file_tools_map[tool_type].append(tool)
1093
+
1094
+ for category, tools in file_tools_map.items():
1095
+ result["categories"][category] = {"count": len(tools)}
1096
+ else:
1097
+ # Use predefined tool files (original behavior)
1098
+ all_tools, all_tool_names = self._scan_predefined_files()
1069
1099
 
1070
- # Add to global set for deduplication
1071
- all_tool_names.update([tool["name"] for tool in tools_in_category])
1100
+ # Read tools from each category file
1101
+ for category, file_path in self.tool_files.items():
1102
+ try:
1103
+ # Read the JSON file for this category
1104
+ tools_in_category = read_json_list(file_path)
1072
1105
 
1073
- except Exception as e:
1074
- warning(
1075
- f"Warning: Could not read tools from {category} ({file_path}): {e}"
1076
- )
1077
- if mode == "config":
1078
- result["categories"][category] = {"count": 0}
1106
+ if mode == "config":
1107
+ tool_names = [tool["name"] for tool in tools_in_category]
1108
+ result["categories"][category] = {"count": len(tool_names)}
1079
1109
 
1080
- # Also include remote tools for listing purposes (not auto-loaded elsewhere)
1081
- try:
1082
- remote_dir = os.path.join(current_dir, "data", "remote_tools")
1083
- if os.path.isdir(remote_dir):
1084
- remote_tools = []
1085
- for fname in os.listdir(remote_dir):
1086
- if not fname.lower().endswith(".json"):
1087
- continue
1088
- fpath = os.path.join(remote_dir, fname)
1089
- try:
1090
- tools_in_file = read_json_list(fpath)
1091
- if isinstance(tools_in_file, dict):
1092
- tools_in_file = list(tools_in_file.values())
1093
- if isinstance(tools_in_file, list):
1094
- remote_tools.extend(tools_in_file)
1095
- except Exception as e:
1096
- warning(
1097
- f"Warning: Could not read remote tools from {fpath}: {e}"
1098
- )
1099
- if remote_tools:
1100
- all_tools.extend(remote_tools)
1101
- all_tool_names.update([tool["name"] for tool in remote_tools])
1110
+ except Exception as e:
1111
+ warning(
1112
+ f"Warning: Could not read tools from {category} ({file_path}): {e}"
1113
+ )
1102
1114
  if mode == "config":
1103
- result["categories"]["remote_tools"] = {
1104
- "count": len(remote_tools)
1105
- }
1106
- except Exception as e:
1107
- warning(f"Warning: Failed to scan remote tools directory: {e}")
1115
+ result["categories"][category] = {"count": 0}
1108
1116
 
1109
1117
  # If mode is 'type', organize by tool types instead
1110
1118
  if mode == "type":
@@ -1202,6 +1210,113 @@ class ToolUniverse:
1202
1210
 
1203
1211
  return result
1204
1212
 
1213
+ def _scan_predefined_files(self):
1214
+ """
1215
+ Scan predefined tool files (original behavior).
1216
+
1217
+ Returns:
1218
+ tuple: (all_tools, all_tool_names) where all_tools is a list of tool configs
1219
+ and all_tool_names is a set of tool names for deduplication
1220
+ """
1221
+ all_tools = []
1222
+ all_tool_names = set()
1223
+
1224
+ # Read tools from each category file
1225
+ for category, file_path in self.tool_files.items():
1226
+ try:
1227
+ # Read the JSON file for this category
1228
+ tools_in_category = read_json_list(file_path)
1229
+ all_tools.extend(tools_in_category)
1230
+ all_tool_names.update([tool["name"] for tool in tools_in_category])
1231
+ except Exception as e:
1232
+ warning(
1233
+ f"Warning: Could not read tools from {category} ({file_path}): {e}"
1234
+ )
1235
+
1236
+ # Also include remote tools
1237
+ try:
1238
+ remote_dir = os.path.join(current_dir, "data", "remote_tools")
1239
+ if os.path.isdir(remote_dir):
1240
+ remote_tools = []
1241
+ for fname in os.listdir(remote_dir):
1242
+ if not fname.lower().endswith(".json"):
1243
+ continue
1244
+ fpath = os.path.join(remote_dir, fname)
1245
+ try:
1246
+ tools_in_file = read_json_list(fpath)
1247
+ if isinstance(tools_in_file, dict):
1248
+ tools_in_file = list(tools_in_file.values())
1249
+ if isinstance(tools_in_file, list):
1250
+ remote_tools.extend(tools_in_file)
1251
+ except Exception as e:
1252
+ warning(
1253
+ f"Warning: Could not read remote tools from {fpath}: {e}"
1254
+ )
1255
+ if remote_tools:
1256
+ all_tools.extend(remote_tools)
1257
+ all_tool_names.update([tool["name"] for tool in remote_tools])
1258
+ except Exception as e:
1259
+ warning(f"Warning: Failed to scan remote tools directory: {e}")
1260
+
1261
+ return all_tools, all_tool_names
1262
+
1263
+ def _scan_all_json_files(self):
1264
+ """
1265
+ Recursively scan all JSON files in the data directory and its subdirectories.
1266
+
1267
+ Returns:
1268
+ tuple: (all_tools, all_tool_names) where all_tools is a list of tool configs
1269
+ and all_tool_names is a set of tool names for deduplication
1270
+ """
1271
+ all_tools = []
1272
+ all_tool_names = set()
1273
+
1274
+ # Get the data directory path
1275
+ data_dir = os.path.join(current_dir, "data")
1276
+
1277
+ if not os.path.exists(data_dir):
1278
+ warning(f"Warning: Data directory not found: {data_dir}")
1279
+ return all_tools, all_tool_names
1280
+
1281
+ # Recursively find all JSON files
1282
+ json_files = []
1283
+ for root, _dirs, files in os.walk(data_dir):
1284
+ for file in files:
1285
+ if file.lower().endswith(".json"):
1286
+ json_files.append(os.path.join(root, file))
1287
+
1288
+ self.logger.debug(f"Found {len(json_files)} JSON files to scan")
1289
+
1290
+ # Read tools from each JSON file
1291
+ for json_file in json_files:
1292
+ try:
1293
+ tools_in_file = read_json_list(json_file)
1294
+
1295
+ # Handle different data formats
1296
+ if isinstance(tools_in_file, dict):
1297
+ # Convert dict of tools to list of tools
1298
+ tools_in_file = list(tools_in_file.values())
1299
+ elif not isinstance(tools_in_file, list):
1300
+ # Skip files that don't contain tool configurations
1301
+ continue
1302
+
1303
+ # Add tools to our collection
1304
+ for tool in tools_in_file:
1305
+ if isinstance(tool, dict) and "name" in tool:
1306
+ all_tools.append(tool)
1307
+ all_tool_names.add(tool["name"])
1308
+
1309
+ self.logger.debug(f"Loaded {len(tools_in_file)} tools from {json_file}")
1310
+
1311
+ except Exception as e:
1312
+ warning(f"Warning: Could not read tools from {json_file}: {e}")
1313
+ continue
1314
+
1315
+ self.logger.info(
1316
+ f"Scanned {len(json_files)} JSON files, found {len(all_tools)} tools"
1317
+ )
1318
+ return all_tools, all_tool_names
1319
+
1205
1320
  def refresh_tool_name_desc(
1206
1321
  self,
1207
1322
  enable_full_desc=False,