tooluniverse 0.2.0__py3-none-any.whl → 1.0.1__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.

Files changed (186) hide show
  1. tooluniverse/__init__.py +340 -4
  2. tooluniverse/admetai_tool.py +84 -0
  3. tooluniverse/agentic_tool.py +563 -0
  4. tooluniverse/alphafold_tool.py +96 -0
  5. tooluniverse/base_tool.py +129 -6
  6. tooluniverse/boltz_tool.py +207 -0
  7. tooluniverse/chem_tool.py +192 -0
  8. tooluniverse/compose_scripts/__init__.py +1 -0
  9. tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
  10. tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
  11. tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
  12. tooluniverse/compose_scripts/literature_tool.py +34 -0
  13. tooluniverse/compose_scripts/output_summarizer.py +279 -0
  14. tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
  15. tooluniverse/compose_scripts/tool_discover.py +705 -0
  16. tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
  17. tooluniverse/compose_tool.py +371 -0
  18. tooluniverse/ctg_tool.py +1002 -0
  19. tooluniverse/custom_tool.py +81 -0
  20. tooluniverse/dailymed_tool.py +108 -0
  21. tooluniverse/data/admetai_tools.json +155 -0
  22. tooluniverse/data/adverse_event_tools.json +108 -0
  23. tooluniverse/data/agentic_tools.json +1156 -0
  24. tooluniverse/data/alphafold_tools.json +87 -0
  25. tooluniverse/data/boltz_tools.json +9 -0
  26. tooluniverse/data/chembl_tools.json +16 -0
  27. tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
  28. tooluniverse/data/compose_tools.json +202 -0
  29. tooluniverse/data/dailymed_tools.json +70 -0
  30. tooluniverse/data/dataset_tools.json +646 -0
  31. tooluniverse/data/disease_target_score_tools.json +712 -0
  32. tooluniverse/data/efo_tools.json +17 -0
  33. tooluniverse/data/embedding_tools.json +319 -0
  34. tooluniverse/data/enrichr_tools.json +31 -0
  35. tooluniverse/data/europe_pmc_tools.json +22 -0
  36. tooluniverse/data/expert_feedback_tools.json +10 -0
  37. tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
  38. tooluniverse/data/fda_drug_labeling_tools.json +1 -1
  39. tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
  40. tooluniverse/data/finder_tools.json +209 -0
  41. tooluniverse/data/gene_ontology_tools.json +113 -0
  42. tooluniverse/data/gwas_tools.json +1082 -0
  43. tooluniverse/data/hpa_tools.json +333 -0
  44. tooluniverse/data/humanbase_tools.json +47 -0
  45. tooluniverse/data/idmap_tools.json +74 -0
  46. tooluniverse/data/mcp_client_tools_example.json +113 -0
  47. tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
  48. tooluniverse/data/medlineplus_tools.json +141 -0
  49. tooluniverse/data/monarch_tools.json +1 -1
  50. tooluniverse/data/openalex_tools.json +36 -0
  51. tooluniverse/data/opentarget_tools.json +1 -1
  52. tooluniverse/data/output_summarization_tools.json +101 -0
  53. tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
  54. tooluniverse/data/packages/categorized_tools.txt +206 -0
  55. tooluniverse/data/packages/cheminformatics_tools.json +347 -0
  56. tooluniverse/data/packages/earth_sciences_tools.json +74 -0
  57. tooluniverse/data/packages/genomics_tools.json +776 -0
  58. tooluniverse/data/packages/image_processing_tools.json +38 -0
  59. tooluniverse/data/packages/machine_learning_tools.json +789 -0
  60. tooluniverse/data/packages/neuroscience_tools.json +62 -0
  61. tooluniverse/data/packages/original_tools.txt +0 -0
  62. tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
  63. tooluniverse/data/packages/scientific_computing_tools.json +560 -0
  64. tooluniverse/data/packages/single_cell_tools.json +453 -0
  65. tooluniverse/data/packages/structural_biology_tools.json +396 -0
  66. tooluniverse/data/packages/visualization_tools.json +399 -0
  67. tooluniverse/data/pubchem_tools.json +215 -0
  68. tooluniverse/data/pubtator_tools.json +68 -0
  69. tooluniverse/data/rcsb_pdb_tools.json +1332 -0
  70. tooluniverse/data/reactome_tools.json +19 -0
  71. tooluniverse/data/semantic_scholar_tools.json +26 -0
  72. tooluniverse/data/special_tools.json +2 -25
  73. tooluniverse/data/tool_composition_tools.json +88 -0
  74. tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
  75. tooluniverse/data/txagent_client_tools.json +9 -0
  76. tooluniverse/data/uniprot_tools.json +211 -0
  77. tooluniverse/data/url_fetch_tools.json +94 -0
  78. tooluniverse/data/uspto_downloader_tools.json +9 -0
  79. tooluniverse/data/uspto_tools.json +811 -0
  80. tooluniverse/data/xml_tools.json +3275 -0
  81. tooluniverse/dataset_tool.py +296 -0
  82. tooluniverse/default_config.py +165 -0
  83. tooluniverse/efo_tool.py +42 -0
  84. tooluniverse/embedding_database.py +630 -0
  85. tooluniverse/embedding_sync.py +396 -0
  86. tooluniverse/enrichr_tool.py +266 -0
  87. tooluniverse/europe_pmc_tool.py +52 -0
  88. tooluniverse/execute_function.py +1775 -95
  89. tooluniverse/extended_hooks.py +444 -0
  90. tooluniverse/gene_ontology_tool.py +194 -0
  91. tooluniverse/graphql_tool.py +158 -36
  92. tooluniverse/gwas_tool.py +358 -0
  93. tooluniverse/hpa_tool.py +1645 -0
  94. tooluniverse/humanbase_tool.py +389 -0
  95. tooluniverse/logging_config.py +254 -0
  96. tooluniverse/mcp_client_tool.py +764 -0
  97. tooluniverse/mcp_integration.py +413 -0
  98. tooluniverse/mcp_tool_registry.py +925 -0
  99. tooluniverse/medlineplus_tool.py +337 -0
  100. tooluniverse/openalex_tool.py +228 -0
  101. tooluniverse/openfda_adv_tool.py +283 -0
  102. tooluniverse/openfda_tool.py +393 -160
  103. tooluniverse/output_hook.py +1122 -0
  104. tooluniverse/package_tool.py +195 -0
  105. tooluniverse/pubchem_tool.py +158 -0
  106. tooluniverse/pubtator_tool.py +168 -0
  107. tooluniverse/rcsb_pdb_tool.py +38 -0
  108. tooluniverse/reactome_tool.py +108 -0
  109. tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
  110. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
  111. tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
  112. tooluniverse/remote/expert_feedback/simple_test.py +23 -0
  113. tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
  114. tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
  115. tooluniverse/remote/immune_compass/compass_tool.py +327 -0
  116. tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
  117. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
  118. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
  119. tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
  120. tooluniverse/remote_tool.py +99 -0
  121. tooluniverse/restful_tool.py +53 -30
  122. tooluniverse/scripts/generate_tool_graph.py +408 -0
  123. tooluniverse/scripts/visualize_tool_graph.py +829 -0
  124. tooluniverse/semantic_scholar_tool.py +62 -0
  125. tooluniverse/smcp.py +2452 -0
  126. tooluniverse/smcp_server.py +975 -0
  127. tooluniverse/test/mcp_server_test.py +0 -0
  128. tooluniverse/test/test_admetai_tool.py +370 -0
  129. tooluniverse/test/test_agentic_tool.py +129 -0
  130. tooluniverse/test/test_alphafold_tool.py +71 -0
  131. tooluniverse/test/test_chem_tool.py +37 -0
  132. tooluniverse/test/test_compose_lieraturereview.py +63 -0
  133. tooluniverse/test/test_compose_tool.py +448 -0
  134. tooluniverse/test/test_dailymed.py +69 -0
  135. tooluniverse/test/test_dataset_tool.py +200 -0
  136. tooluniverse/test/test_disease_target_score.py +56 -0
  137. tooluniverse/test/test_drugbank_filter_examples.py +179 -0
  138. tooluniverse/test/test_efo.py +31 -0
  139. tooluniverse/test/test_enrichr_tool.py +21 -0
  140. tooluniverse/test/test_europe_pmc_tool.py +20 -0
  141. tooluniverse/test/test_fda_adv.py +95 -0
  142. tooluniverse/test/test_fda_drug_labeling.py +91 -0
  143. tooluniverse/test/test_gene_ontology_tools.py +66 -0
  144. tooluniverse/test/test_gwas_tool.py +139 -0
  145. tooluniverse/test/test_hpa.py +625 -0
  146. tooluniverse/test/test_humanbase_tool.py +20 -0
  147. tooluniverse/test/test_idmap_tools.py +61 -0
  148. tooluniverse/test/test_mcp_server.py +211 -0
  149. tooluniverse/test/test_mcp_tool.py +247 -0
  150. tooluniverse/test/test_medlineplus.py +220 -0
  151. tooluniverse/test/test_openalex_tool.py +32 -0
  152. tooluniverse/test/test_opentargets.py +28 -0
  153. tooluniverse/test/test_pubchem_tool.py +116 -0
  154. tooluniverse/test/test_pubtator_tool.py +37 -0
  155. tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
  156. tooluniverse/test/test_reactome.py +54 -0
  157. tooluniverse/test/test_semantic_scholar_tool.py +24 -0
  158. tooluniverse/test/test_software_tools.py +147 -0
  159. tooluniverse/test/test_tool_description_optimizer.py +49 -0
  160. tooluniverse/test/test_tool_finder.py +26 -0
  161. tooluniverse/test/test_tool_finder_llm.py +252 -0
  162. tooluniverse/test/test_tools_find.py +195 -0
  163. tooluniverse/test/test_uniprot_tools.py +74 -0
  164. tooluniverse/test/test_uspto_tool.py +72 -0
  165. tooluniverse/test/test_xml_tool.py +113 -0
  166. tooluniverse/tool_finder_embedding.py +267 -0
  167. tooluniverse/tool_finder_keyword.py +693 -0
  168. tooluniverse/tool_finder_llm.py +699 -0
  169. tooluniverse/tool_graph_web_ui.py +955 -0
  170. tooluniverse/tool_registry.py +416 -0
  171. tooluniverse/uniprot_tool.py +155 -0
  172. tooluniverse/url_tool.py +253 -0
  173. tooluniverse/uspto_tool.py +240 -0
  174. tooluniverse/utils.py +369 -41
  175. tooluniverse/xml_tool.py +369 -0
  176. tooluniverse-1.0.1.dist-info/METADATA +387 -0
  177. tooluniverse-1.0.1.dist-info/RECORD +182 -0
  178. tooluniverse-1.0.1.dist-info/entry_points.txt +9 -0
  179. tooluniverse/generate_mcp_tools.py +0 -113
  180. tooluniverse/mcp_server.py +0 -3340
  181. tooluniverse-0.2.0.dist-info/METADATA +0 -139
  182. tooluniverse-0.2.0.dist-info/RECORD +0 -21
  183. tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
  184. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/WHEEL +0 -0
  185. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/licenses/LICENSE +0 -0
  186. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/top_level.txt +0 -0
tooluniverse/utils.py CHANGED
@@ -1,5 +1,64 @@
1
1
  import yaml
2
2
  import json
3
+ import re
4
+ import hashlib
5
+ import os
6
+ from typing import Dict, Any, Union, List
7
+ from huggingface_hub import hf_hub_download
8
+ from pydantic._internal._model_construction import ModelMetaclass
9
+
10
+
11
+ def download_from_hf(tool_config):
12
+ # Extract dataset configuration
13
+ hf_parameters = tool_config.get("hf_dataset_path")
14
+ relative_local_path = hf_parameters.get("save_to_local_dir")
15
+
16
+ # Compute absolute path to save locally
17
+ if not os.path.isabs(relative_local_path):
18
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
19
+ absolute_local_dir = os.path.join(project_root, relative_local_path)
20
+ else:
21
+ absolute_local_dir = relative_local_path
22
+
23
+ # Ensure the directory exists
24
+ os.makedirs(absolute_local_dir, exist_ok=True)
25
+
26
+ # Download the CSV from Hugging Face Hub
27
+ try:
28
+ # Prepare download arguments
29
+ download_args = {
30
+ "repo_id": hf_parameters.get("repo_id"),
31
+ "filename": hf_parameters.get("path_in_repo"),
32
+ "repo_type": "dataset",
33
+ "local_dir": absolute_local_dir,
34
+ }
35
+
36
+ # Only add token if it's not None and not empty
37
+ token = hf_parameters.get("token")
38
+ if token is not None and token.strip():
39
+ download_args["token"] = token
40
+ else:
41
+ download_args["token"] = False
42
+
43
+ downloaded_path = hf_hub_download(**download_args)
44
+
45
+ # The downloaded file path is returned by hf_hub_download
46
+ result = {"success": True, "local_path": downloaded_path}
47
+ except Exception as e:
48
+ result = {"success": False, "error": str(e)}
49
+
50
+ return result
51
+
52
+
53
+ def get_md5(input_str):
54
+ # Create an MD5 hash object
55
+ md5_hash = hashlib.md5()
56
+
57
+ # Encode the string and update the hash object
58
+ md5_hash.update(input_str.encode("utf-8"))
59
+
60
+ # Return the hexadecimal MD5 digest
61
+ return md5_hash.hexdigest()
3
62
 
4
63
 
5
64
  def yaml_to_dict(yaml_file_path):
@@ -13,7 +72,7 @@ def yaml_to_dict(yaml_file_path):
13
72
  dict: Dictionary representation of the YAML file content.
14
73
  """
15
74
  try:
16
- with open(yaml_file_path, 'r') as file:
75
+ with open(yaml_file_path, "r") as file:
17
76
  yaml_dict = yaml.safe_load(file)
18
77
  return yaml_dict
19
78
  except FileNotFoundError:
@@ -32,7 +91,7 @@ def read_json_list(file_path):
32
91
  Returns:
33
92
  list: A list of dictionaries containing the JSON objects.
34
93
  """
35
- with open(file_path, 'r') as file:
94
+ with open(file_path, "r") as file:
36
95
  data = json.load(file)
37
96
  return data
38
97
 
@@ -45,18 +104,30 @@ def evaluate_function_call(tool_definition, function_call):
45
104
  "number": float,
46
105
  "boolean": bool,
47
106
  "array": list,
48
- "object": dict
107
+ "object": dict,
108
+ "pydantic": ModelMetaclass,
49
109
  }
50
110
 
51
111
  # Check if the function name matches
52
112
  if tool_definition["name"] != function_call["name"]:
53
113
  return False, "Function name does not match."
54
114
 
115
+ # Handle the case where properties is None (no arguments expected)
116
+ if tool_definition["parameter"]["properties"] is None:
117
+ # If properties is None, the function should not have any arguments
118
+ if function_call.get("arguments") and len(function_call["arguments"]) > 0:
119
+ return False, "This function does not accept any arguments."
120
+ return True, "Function call is valid."
121
+
55
122
  # Check if all required parameters are present
56
- required_params = [key for key, value in tool_definition["parameter"]
57
- ["properties"].items() if value.get("required", False)]
123
+ required_params = [
124
+ key
125
+ for key, value in tool_definition["parameter"]["properties"].items()
126
+ if value.get("required", False)
127
+ ]
58
128
  missing_params = [
59
- param for param in required_params if param not in function_call["arguments"]]
129
+ param for param in required_params if param not in function_call["arguments"]
130
+ ]
60
131
  if missing_params:
61
132
  return False, f"Missing required parameters: {missing_params}"
62
133
 
@@ -69,12 +140,37 @@ def evaluate_function_call(tool_definition, function_call):
69
140
  if param not in valid_params:
70
141
  invalid_params.append(param)
71
142
  else:
72
- expected_type = valid_params[param]["type"]
143
+ param_schema = valid_params[param]
144
+
145
+ # Handle both simple and complex parameter schemas
146
+ expected_type = None
147
+
148
+ # Case 1: Simple schema with direct "type" field
149
+ if "type" in param_schema:
150
+ expected_type = param_schema["type"]
151
+
152
+ # Case 2: Complex schema with "anyOf" (common in MCP tools)
153
+ elif "anyOf" in param_schema:
154
+ # Extract the primary type from anyOf, ignoring null types
155
+ for type_option in param_schema["anyOf"]:
156
+ if type_option.get("type") and type_option["type"] != "null":
157
+ expected_type = type_option["type"]
158
+ break
159
+
160
+ # If we still don't have a type, skip validation for this parameter
161
+ if not expected_type:
162
+ continue
163
+
73
164
  if expected_type not in type_map:
74
165
  return False, f"Unsupported parameter type: {expected_type}"
75
- if not isinstance(value, type_map[expected_type]):
76
- type_mismatches.append(
77
- (param, expected_type, type(value).__name__))
166
+
167
+ # Special handling for 'number' type which should accept both int and float
168
+ if expected_type == "number":
169
+ if not isinstance(value, (int, float)):
170
+ type_mismatches.append((param, expected_type, type(value).__name__))
171
+ else:
172
+ if not isinstance(value, type_map[expected_type]):
173
+ type_mismatches.append((param, expected_type, type(value).__name__))
78
174
 
79
175
  if invalid_params:
80
176
  return False, f"Invalid parameters provided: {invalid_params}"
@@ -84,6 +180,7 @@ def evaluate_function_call(tool_definition, function_call):
84
180
 
85
181
  return True, "Function call is valid."
86
182
 
183
+
87
184
  def evaluate_function_call_from_toolbox(toolbox, function_call):
88
185
  tool_name = function_call["name"]
89
186
  this_tool_dec = toolbox.get_one_tool_by_one_name(tool_name)
@@ -91,9 +188,11 @@ def evaluate_function_call_from_toolbox(toolbox, function_call):
91
188
  return False, "Tool not found."
92
189
  results, results_message = evaluate_function_call(this_tool_dec, function_call)
93
190
  return results, results_message
94
-
95
191
 
96
- def compare_function_calls(pred_function_call, gt_function_call, compare_arguments=True, compare_value=True):
192
+
193
+ def compare_function_calls(
194
+ pred_function_call, gt_function_call, compare_arguments=True, compare_value=True
195
+ ):
97
196
  # Extracting the name and arguments from the predicted function call
98
197
  pred_name = pred_function_call["name"]
99
198
  pred_arguments = pred_function_call["arguments"]
@@ -111,7 +210,10 @@ def compare_function_calls(pred_function_call, gt_function_call, compare_argumen
111
210
  if set(pred_arguments.keys()) != set(gt_arguments.keys()):
112
211
  missing_in_pred = set(gt_arguments.keys()) - set(pred_arguments.keys())
113
212
  missing_in_gt = set(pred_arguments.keys()) - set(gt_arguments.keys())
114
- return False, f"Argument keys do not match. Missing in predicted: {missing_in_pred}, Missing in ground truth: {missing_in_gt}"
213
+ return (
214
+ False,
215
+ f"Argument keys do not match. Missing in predicted: {missing_in_pred}, Missing in ground truth: {missing_in_gt}",
216
+ )
115
217
  if compare_value:
116
218
  # Compare argument values
117
219
  mismatched_values = []
@@ -125,12 +227,12 @@ def compare_function_calls(pred_function_call, gt_function_call, compare_argumen
125
227
  return True, "Function calls match."
126
228
 
127
229
 
128
- def extract_function_call_json(lst, return_message=False, verbose=True):
230
+ def extract_function_call_json(lst, return_message=False, verbose=True, format="llama"):
129
231
  if type(lst) is dict:
130
232
  if return_message:
131
233
  return lst, ""
132
234
  return lst
133
- result_str = ''.join(lst)
235
+ result_str = "".join(lst)
134
236
  if verbose:
135
237
  print("\033[1;34mPossible LLM outputs for function call:\033[0m", result_str)
136
238
  try:
@@ -140,33 +242,259 @@ def extract_function_call_json(lst, return_message=False, verbose=True):
140
242
  return function_call_json
141
243
  except json.JSONDecodeError:
142
244
  try:
143
- index_start = result_str.find(
144
- '[TOOL_CALLS]')
145
- index_end = result_str.find('</s>')
146
- if index_end == -1:
147
- index_end = result_str.find('<|eom_id|>')
148
- if index_end == -1:
149
- function_call_str = result_str[index_start+ len('[TOOL_CALLS]'):]
150
- else:
151
- function_call_str = result_str[index_start+ len('[TOOL_CALLS]'):index_end]
152
- # print("function_call_str", function_call_str)
153
- function_call_json = json.loads(function_call_str.strip())
245
+ if format == "llama":
246
+ index_start = result_str.find("[TOOL_CALLS]")
247
+ index_end = result_str.find("</s>")
248
+ if index_end == -1:
249
+ index_end = result_str.find("<|eom_id|>")
250
+ if index_end == -1:
251
+ function_call_str = result_str[index_start + len("[TOOL_CALLS]") :]
252
+ else:
253
+ function_call_str = result_str[
254
+ index_start + len("[TOOL_CALLS]") : index_end
255
+ ]
256
+ # print("function_call_str", function_call_str)
257
+ function_call_json = json.loads(function_call_str.strip())
258
+ elif format == "qwen":
259
+ index_start = result_str.find("<tool_call>")
260
+ function_call_str = result_str[index_start:]
261
+
262
+ pattern = re.compile(r"<tool_call>(.*?)</tool_call>", re.DOTALL)
263
+ matches = pattern.findall(function_call_str)
264
+ function_call_json = []
265
+
266
+ for match in matches:
267
+ # Clean up the JSON string
268
+ json_str = match.strip()
269
+ data = json.loads(json_str)
270
+ function_call_json.append(data)
271
+
154
272
  if return_message:
155
273
  message = result_str[:index_start]
156
274
  return function_call_json, message
157
275
  return function_call_json
158
- except json.JSONDecodeError:
159
- try:
160
- print("Multiple function calls not implemented for 'llama' format.")
161
- index_start = result_str.find(
162
- '<functioncall>') + len('<functioncall>')
163
- index_end = result_str.find('</functioncall>')
164
- function_call_str = result_str[index_start:index_end]
165
- # function_call_str = function_call_str.replace("'", '"')
166
- function_call_json = json.loads(function_call_str.strip())
167
- return function_call_json
168
- except json.JSONDecodeError as e:
169
- print("Not a function call:", e)
170
- if return_message:
171
- return None, result_str
172
- return None
276
+
277
+ except json.JSONDecodeError as e:
278
+ print("Not a function call:", e)
279
+ if return_message:
280
+ return None, result_str
281
+ return None
282
+
283
+
284
+ def validate_query(query: Dict[str, Any]) -> bool:
285
+ """
286
+ Validate a query dictionary for required fields and structure.
287
+
288
+ Args:
289
+ query (Dict[str, Any]): The query dictionary to validate
290
+
291
+ Returns:
292
+ bool: True if query is valid, False otherwise
293
+ """
294
+ if not isinstance(query, dict):
295
+ return False
296
+
297
+ # Check for basic required fields (customize based on your needs)
298
+ required_fields = ["query", "parameters"]
299
+ for field in required_fields:
300
+ if field not in query:
301
+ return False
302
+
303
+ # Additional validation logic can be added here
304
+ return True
305
+
306
+
307
+ def normalize_gene_symbol(gene_symbol: str) -> str:
308
+ """
309
+ Normalize a gene symbol to standard format.
310
+
311
+ Args:
312
+ gene_symbol (str): The gene symbol to normalize
313
+
314
+ Returns:
315
+ str: Normalized gene symbol
316
+ """
317
+ if not isinstance(gene_symbol, str):
318
+ return str(gene_symbol)
319
+
320
+ # Convert to uppercase and strip whitespace
321
+ normalized = gene_symbol.strip().upper()
322
+
323
+ # Remove common prefixes/suffixes if needed
324
+ # This is a basic implementation - customize as needed
325
+ normalized = re.sub(r"^GENE[-_]?", "", normalized)
326
+ normalized = re.sub(r"[-_]?GENE$", "", normalized)
327
+
328
+ return normalized
329
+
330
+
331
+ def format_api_response(
332
+ response_data: Any, format_type: str = "json"
333
+ ) -> Union[str, Dict[str, Any], List[Any]]:
334
+ """
335
+ Format API response data into a standardized format.
336
+
337
+ Args:
338
+ response_data (Any): The response data to format
339
+ format_type (str): The desired output format ('json', 'pretty', 'minimal')
340
+
341
+ Returns:
342
+ Union[str, Dict[str, Any]]: Formatted response
343
+ """
344
+ if format_type == "json":
345
+ if isinstance(response_data, (dict, list)):
346
+ return response_data
347
+ else:
348
+ return {"data": response_data, "status": "success"}
349
+
350
+ elif format_type == "pretty":
351
+ if isinstance(response_data, dict):
352
+ return json.dumps(response_data, indent=2, ensure_ascii=False)
353
+ else:
354
+ return json.dumps(
355
+ {"data": response_data, "status": "success"},
356
+ indent=2,
357
+ ensure_ascii=False,
358
+ )
359
+
360
+ elif format_type == "minimal":
361
+ if isinstance(response_data, dict) and "data" in response_data:
362
+ return response_data["data"]
363
+ else:
364
+ return response_data
365
+
366
+ else:
367
+ return response_data
368
+
369
+
370
+ def validate_hook_config(config: Dict[str, Any]) -> bool:
371
+ """
372
+ Validate hook configuration for correctness and completeness.
373
+
374
+ This function checks that the hook configuration contains all required
375
+ fields and that the structure is valid for the hook system.
376
+
377
+ Args:
378
+ config (Dict[str, Any]): Hook configuration to validate
379
+
380
+ Returns:
381
+ bool: True if configuration is valid, False otherwise
382
+ """
383
+ try:
384
+ # Check for required top-level fields
385
+ if not isinstance(config, dict):
386
+ return False
387
+
388
+ # Validate global settings if present
389
+ if "global_settings" in config:
390
+ global_settings = config["global_settings"]
391
+ if not isinstance(global_settings, dict):
392
+ return False
393
+
394
+ # Validate hooks array
395
+ if "hooks" in config:
396
+ hooks = config["hooks"]
397
+ if not isinstance(hooks, list):
398
+ return False
399
+
400
+ for hook in hooks:
401
+ if not validate_hook_conditions(hook.get("conditions", {})):
402
+ return False
403
+
404
+ # Validate tool-specific hooks
405
+ if "tool_specific_hooks" in config:
406
+ tool_hooks = config["tool_specific_hooks"]
407
+ if not isinstance(tool_hooks, dict):
408
+ return False
409
+
410
+ for _tool_name, tool_config in tool_hooks.items():
411
+ if not isinstance(tool_config, dict):
412
+ return False
413
+ if "hooks" in tool_config:
414
+ for hook in tool_config["hooks"]:
415
+ if not validate_hook_conditions(hook.get("conditions", {})):
416
+ return False
417
+
418
+ # Validate category hooks
419
+ if "category_hooks" in config:
420
+ category_hooks = config["category_hooks"]
421
+ if not isinstance(category_hooks, dict):
422
+ return False
423
+
424
+ for _category_name, category_config in category_hooks.items():
425
+ if not isinstance(category_config, dict):
426
+ return False
427
+ if "hooks" in category_config:
428
+ for hook in category_config["hooks"]:
429
+ if not validate_hook_conditions(hook.get("conditions", {})):
430
+ return False
431
+
432
+ return True
433
+
434
+ except Exception:
435
+ return False
436
+
437
+
438
+ def validate_hook_conditions(conditions: Dict[str, Any]) -> bool:
439
+ """
440
+ Validate hook trigger conditions.
441
+
442
+ This function checks that the hook conditions are properly structured
443
+ and contain valid operators and thresholds.
444
+
445
+ Args:
446
+ conditions (Dict[str, Any]): Hook conditions to validate
447
+
448
+ Returns:
449
+ bool: True if conditions are valid, False otherwise
450
+ """
451
+ try:
452
+ if not isinstance(conditions, dict):
453
+ return False
454
+
455
+ # Validate output length conditions
456
+ if "output_length" in conditions:
457
+ length_condition = conditions["output_length"]
458
+ if not isinstance(length_condition, dict):
459
+ return False
460
+
461
+ # Check for required fields
462
+ if "threshold" not in length_condition:
463
+ return False
464
+ if "operator" not in length_condition:
465
+ return False
466
+
467
+ # Validate threshold is numeric
468
+ threshold = length_condition["threshold"]
469
+ if not isinstance(threshold, (int, float)) or threshold < 0:
470
+ return False
471
+
472
+ # Validate operator
473
+ operator = length_condition["operator"]
474
+ if operator not in [">", ">=", "<", "<="]:
475
+ return False
476
+
477
+ # Validate content type conditions
478
+ if "content_type" in conditions:
479
+ content_type = conditions["content_type"]
480
+ if not isinstance(content_type, str):
481
+ return False
482
+ if content_type not in ["json", "text", "xml", "csv"]:
483
+ return False
484
+
485
+ # Validate tool type conditions
486
+ if "tool_type" in conditions:
487
+ tool_type = conditions["tool_type"]
488
+ if not isinstance(tool_type, str):
489
+ return False
490
+
491
+ # Validate tool name conditions
492
+ if "tool_name" in conditions:
493
+ tool_name = conditions["tool_name"]
494
+ if not isinstance(tool_name, str):
495
+ return False
496
+
497
+ return True
498
+
499
+ except Exception:
500
+ return False