tooluniverse 1.0.7__py3-none-any.whl → 1.0.9__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.
- tooluniverse/__init__.py +37 -14
- tooluniverse/admetai_tool.py +16 -5
- tooluniverse/base_tool.py +36 -0
- tooluniverse/biogrid_tool.py +118 -0
- tooluniverse/build_optimizer.py +87 -0
- tooluniverse/cache/__init__.py +3 -0
- tooluniverse/cache/memory_cache.py +99 -0
- tooluniverse/cache/result_cache_manager.py +235 -0
- tooluniverse/cache/sqlite_backend.py +257 -0
- tooluniverse/clinvar_tool.py +90 -0
- tooluniverse/compose_scripts/output_summarizer.py +87 -33
- tooluniverse/compose_tool.py +2 -2
- tooluniverse/custom_tool.py +28 -0
- tooluniverse/data/adverse_event_tools.json +97 -98
- tooluniverse/data/agentic_tools.json +81 -162
- tooluniverse/data/arxiv_tools.json +1 -4
- tooluniverse/data/compose_tools.json +0 -54
- tooluniverse/data/core_tools.json +1 -4
- tooluniverse/data/dataset_tools.json +7 -7
- tooluniverse/data/doaj_tools.json +1 -3
- tooluniverse/data/drug_discovery_agents.json +282 -0
- tooluniverse/data/europe_pmc_tools.json +1 -2
- tooluniverse/data/genomics_tools.json +174 -0
- tooluniverse/data/geo_tools.json +86 -0
- tooluniverse/data/literature_search_tools.json +15 -35
- tooluniverse/data/markitdown_tools.json +51 -0
- tooluniverse/data/monarch_tools.json +1 -2
- tooluniverse/data/openalex_tools.json +1 -5
- tooluniverse/data/opentarget_tools.json +8 -16
- tooluniverse/data/output_summarization_tools.json +23 -20
- tooluniverse/data/packages/bioinformatics_core_tools.json +2 -2
- tooluniverse/data/packages/cheminformatics_tools.json +1 -1
- tooluniverse/data/packages/genomics_tools.json +1 -1
- tooluniverse/data/packages/single_cell_tools.json +1 -1
- tooluniverse/data/packages/structural_biology_tools.json +1 -1
- tooluniverse/data/pmc_tools.json +1 -4
- tooluniverse/data/ppi_tools.json +139 -0
- tooluniverse/data/pubmed_tools.json +1 -3
- tooluniverse/data/semantic_scholar_tools.json +1 -2
- tooluniverse/data/tool_composition_tools.json +2 -4
- tooluniverse/data/unified_guideline_tools.json +206 -4
- tooluniverse/data/xml_tools.json +15 -15
- tooluniverse/data/zenodo_tools.json +1 -2
- tooluniverse/dbsnp_tool.py +71 -0
- tooluniverse/default_config.py +6 -0
- tooluniverse/ensembl_tool.py +61 -0
- tooluniverse/execute_function.py +235 -76
- tooluniverse/generate_tools.py +303 -20
- tooluniverse/genomics_gene_search_tool.py +56 -0
- tooluniverse/geo_tool.py +116 -0
- tooluniverse/gnomad_tool.py +63 -0
- tooluniverse/logging_config.py +64 -2
- tooluniverse/markitdown_tool.py +159 -0
- tooluniverse/mcp_client_tool.py +10 -5
- tooluniverse/molecule_2d_tool.py +9 -3
- tooluniverse/molecule_3d_tool.py +9 -3
- tooluniverse/output_hook.py +217 -150
- tooluniverse/smcp.py +18 -10
- tooluniverse/smcp_server.py +89 -199
- tooluniverse/string_tool.py +112 -0
- tooluniverse/tools/{MultiAgentLiteratureSearch.py → ADMETAnalyzerAgent.py} +18 -18
- tooluniverse/tools/ArXiv_search_papers.py +3 -3
- tooluniverse/tools/CMA_Guidelines_Search.py +52 -0
- tooluniverse/tools/CORE_search_papers.py +3 -3
- tooluniverse/tools/ClinVar_search_variants.py +52 -0
- tooluniverse/tools/ClinicalTrialDesignAgent.py +63 -0
- tooluniverse/tools/CompoundDiscoveryAgent.py +59 -0
- tooluniverse/tools/DOAJ_search_articles.py +2 -2
- tooluniverse/tools/DiseaseAnalyzerAgent.py +52 -0
- tooluniverse/tools/DrugInteractionAnalyzerAgent.py +52 -0
- tooluniverse/tools/DrugOptimizationAgent.py +63 -0
- tooluniverse/tools/Ensembl_lookup_gene_by_symbol.py +52 -0
- tooluniverse/tools/EuropePMC_search_articles.py +1 -1
- tooluniverse/tools/GIN_Guidelines_Search.py +52 -0
- tooluniverse/tools/GWAS_search_associations_by_gene.py +52 -0
- tooluniverse/tools/LiteratureSynthesisAgent.py +59 -0
- tooluniverse/tools/PMC_search_papers.py +3 -3
- tooluniverse/tools/PubMed_search_articles.py +2 -2
- tooluniverse/tools/SemanticScholar_search_papers.py +1 -1
- tooluniverse/tools/UCSC_get_genes_by_region.py +67 -0
- tooluniverse/tools/Zenodo_search_records.py +1 -1
- tooluniverse/tools/__init__.py +33 -3
- tooluniverse/tools/convert_to_markdown.py +59 -0
- tooluniverse/tools/dbSNP_get_variant_by_rsid.py +46 -0
- tooluniverse/tools/gnomAD_query_variant.py +52 -0
- tooluniverse/tools/openalex_literature_search.py +4 -4
- tooluniverse/ucsc_tool.py +60 -0
- tooluniverse/unified_guideline_tools.py +1175 -57
- tooluniverse/utils.py +51 -4
- tooluniverse/zenodo_tool.py +2 -1
- {tooluniverse-1.0.7.dist-info → tooluniverse-1.0.9.dist-info}/METADATA +10 -3
- {tooluniverse-1.0.7.dist-info → tooluniverse-1.0.9.dist-info}/RECORD +96 -61
- {tooluniverse-1.0.7.dist-info → tooluniverse-1.0.9.dist-info}/entry_points.txt +0 -3
- {tooluniverse-1.0.7.dist-info → tooluniverse-1.0.9.dist-info}/WHEEL +0 -0
- {tooluniverse-1.0.7.dist-info → tooluniverse-1.0.9.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-1.0.7.dist-info → tooluniverse-1.0.9.dist-info}/top_level.txt +0 -0
tooluniverse/execute_function.py
CHANGED
|
@@ -33,6 +33,8 @@ import os
|
|
|
33
33
|
import time
|
|
34
34
|
import hashlib
|
|
35
35
|
import warnings
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from contextlib import nullcontext
|
|
36
38
|
from typing import Any, Dict, List, Optional
|
|
37
39
|
from .utils import read_json_list, evaluate_function_call, extract_function_call_json
|
|
38
40
|
from .exceptions import (
|
|
@@ -58,6 +60,7 @@ from .logging_config import (
|
|
|
58
60
|
error,
|
|
59
61
|
set_log_level,
|
|
60
62
|
)
|
|
63
|
+
from .cache.result_cache_manager import ResultCacheManager
|
|
61
64
|
from .output_hook import HookManager
|
|
62
65
|
from .default_config import default_tool_files, get_default_hook_config
|
|
63
66
|
|
|
@@ -146,7 +149,38 @@ class ToolNamespace:
|
|
|
146
149
|
"""Return a ToolCallable for the requested tool name."""
|
|
147
150
|
if name in self.engine.all_tool_dict:
|
|
148
151
|
return ToolCallable(self.engine, name)
|
|
149
|
-
|
|
152
|
+
|
|
153
|
+
# Attempt a targeted on-demand load for this tool name
|
|
154
|
+
try:
|
|
155
|
+
self.engine.load_tools(include_tools=[name])
|
|
156
|
+
except Exception:
|
|
157
|
+
# Ignore load errors here; we'll surface a clearer error below if still missing
|
|
158
|
+
pass
|
|
159
|
+
if name in self.engine.all_tool_dict:
|
|
160
|
+
return ToolCallable(self.engine, name)
|
|
161
|
+
|
|
162
|
+
# As a fallback, force full discovery once
|
|
163
|
+
try:
|
|
164
|
+
self.engine.force_full_discovery()
|
|
165
|
+
except Exception:
|
|
166
|
+
# Ignore discovery errors; report consolidated reason below
|
|
167
|
+
pass
|
|
168
|
+
if name in self.engine.all_tool_dict:
|
|
169
|
+
return ToolCallable(self.engine, name)
|
|
170
|
+
|
|
171
|
+
# Build a helpful reason summary
|
|
172
|
+
try:
|
|
173
|
+
status = self.engine.get_lazy_loading_status()
|
|
174
|
+
reason = (
|
|
175
|
+
f"after targeted load and full discovery; "
|
|
176
|
+
f"lazy_loading_enabled={status.get('lazy_loading_enabled')}, "
|
|
177
|
+
f"loaded_tools_count={status.get('loaded_tools_count')}, "
|
|
178
|
+
f"immediately_available_tools={status.get('immediately_available_tools')}"
|
|
179
|
+
)
|
|
180
|
+
except Exception:
|
|
181
|
+
reason = "after targeted load and full discovery"
|
|
182
|
+
|
|
183
|
+
raise AttributeError(f"Tool '{name}' not found ({reason})")
|
|
150
184
|
|
|
151
185
|
def __len__(self) -> int:
|
|
152
186
|
"""Return the number of available tools."""
|
|
@@ -260,9 +294,39 @@ class ToolUniverse:
|
|
|
260
294
|
self.hook_manager = None
|
|
261
295
|
self.logger.debug("Output hooks disabled")
|
|
262
296
|
|
|
263
|
-
# Initialize
|
|
264
|
-
|
|
265
|
-
|
|
297
|
+
# Initialize caching configuration
|
|
298
|
+
cache_enabled = os.getenv("TOOLUNIVERSE_CACHE_ENABLED", "true").lower() in (
|
|
299
|
+
"true",
|
|
300
|
+
"1",
|
|
301
|
+
"yes",
|
|
302
|
+
)
|
|
303
|
+
persistence_enabled = os.getenv(
|
|
304
|
+
"TOOLUNIVERSE_CACHE_PERSIST", "true"
|
|
305
|
+
).lower() in ("true", "1", "yes")
|
|
306
|
+
memory_size = int(os.getenv("TOOLUNIVERSE_CACHE_MEMORY_SIZE", "256"))
|
|
307
|
+
default_ttl_env = os.getenv("TOOLUNIVERSE_CACHE_DEFAULT_TTL")
|
|
308
|
+
default_ttl = int(default_ttl_env) if default_ttl_env else None
|
|
309
|
+
singleflight_enabled = os.getenv(
|
|
310
|
+
"TOOLUNIVERSE_CACHE_SINGLEFLIGHT", "true"
|
|
311
|
+
).lower() in ("true", "1", "yes")
|
|
312
|
+
|
|
313
|
+
cache_path = os.getenv("TOOLUNIVERSE_CACHE_PATH")
|
|
314
|
+
if not cache_path and persistence_enabled:
|
|
315
|
+
base_dir = os.getenv("TOOLUNIVERSE_CACHE_DIR")
|
|
316
|
+
if not base_dir:
|
|
317
|
+
base_dir = os.path.join(str(Path.home()), ".tooluniverse")
|
|
318
|
+
os.makedirs(base_dir, exist_ok=True)
|
|
319
|
+
cache_path = os.path.join(base_dir, "cache.sqlite")
|
|
320
|
+
|
|
321
|
+
self.cache_manager = ResultCacheManager(
|
|
322
|
+
memory_size=memory_size,
|
|
323
|
+
persistent_path=cache_path if persistence_enabled else None,
|
|
324
|
+
enabled=cache_enabled,
|
|
325
|
+
persistence_enabled=persistence_enabled,
|
|
326
|
+
singleflight=singleflight_enabled,
|
|
327
|
+
default_ttl=default_ttl,
|
|
328
|
+
)
|
|
329
|
+
|
|
266
330
|
self._strict_validation = os.getenv(
|
|
267
331
|
"TOOLUNIVERSE_STRICT_VALIDATION", "false"
|
|
268
332
|
).lower() in ("true", "1", "yes")
|
|
@@ -1041,9 +1105,14 @@ class ToolUniverse:
|
|
|
1041
1105
|
- When scan_all=True, all JSON files in data/ and subdirectories are scanned
|
|
1042
1106
|
"""
|
|
1043
1107
|
if mode not in ["config", "type", "list_name", "list_spec"]:
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1108
|
+
# Handle invalid modes gracefully
|
|
1109
|
+
if mode is None:
|
|
1110
|
+
mode = "config" # Default to config mode
|
|
1111
|
+
else:
|
|
1112
|
+
# For invalid string modes, return error info instead of raising
|
|
1113
|
+
return {
|
|
1114
|
+
"error": f"Invalid mode '{mode}'. Must be one of: 'config', 'type', 'list_name', 'list_spec'"
|
|
1115
|
+
}
|
|
1047
1116
|
|
|
1048
1117
|
# For list_name and list_spec modes, we can return early with just the data
|
|
1049
1118
|
if mode in ["list_name", "list_spec"]:
|
|
@@ -1693,84 +1762,139 @@ class ToolUniverse:
|
|
|
1693
1762
|
Returns:
|
|
1694
1763
|
str or dict: Result from the tool execution, or error message if validation fails.
|
|
1695
1764
|
"""
|
|
1696
|
-
function_name = function_call_json
|
|
1697
|
-
arguments = function_call_json
|
|
1698
|
-
|
|
1699
|
-
#
|
|
1700
|
-
if
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
if validate:
|
|
1708
|
-
validation_error = self._validate_parameters(function_name, arguments)
|
|
1709
|
-
if validation_error:
|
|
1710
|
-
return self._create_dual_format_error(validation_error)
|
|
1711
|
-
|
|
1712
|
-
# Check function call format (existing validation)
|
|
1713
|
-
check_status, check_message = self.check_function_call(function_call_json)
|
|
1714
|
-
if check_status is False:
|
|
1715
|
-
error_msg = "Invalid function call: " + check_message
|
|
1716
|
-
return self._create_dual_format_error(
|
|
1717
|
-
ToolValidationError(error_msg, details={"check_message": check_message})
|
|
1718
|
-
)
|
|
1765
|
+
function_name = function_call_json.get("name", "")
|
|
1766
|
+
arguments = function_call_json.get("arguments", {})
|
|
1767
|
+
|
|
1768
|
+
# Handle malformed queries gracefully
|
|
1769
|
+
if not function_name:
|
|
1770
|
+
return {"error": "Missing or empty function name"}
|
|
1771
|
+
|
|
1772
|
+
if not isinstance(arguments, dict):
|
|
1773
|
+
return {
|
|
1774
|
+
"error": f"Arguments must be a dictionary, got {type(arguments).__name__}"
|
|
1775
|
+
}
|
|
1719
1776
|
|
|
1720
|
-
# Execute the tool
|
|
1721
1777
|
tool_instance = None
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1778
|
+
cache_namespace = None
|
|
1779
|
+
cache_version = None
|
|
1780
|
+
cache_key = None
|
|
1781
|
+
composed_cache_key = None
|
|
1782
|
+
cache_guard = nullcontext()
|
|
1783
|
+
|
|
1784
|
+
cache_enabled = (
|
|
1785
|
+
use_cache and self.cache_manager is not None and self.cache_manager.enabled
|
|
1786
|
+
)
|
|
1726
1787
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1788
|
+
if cache_enabled:
|
|
1789
|
+
tool_instance = self._get_tool_instance(function_name, cache=True)
|
|
1790
|
+
if tool_instance and tool_instance.supports_caching():
|
|
1791
|
+
cache_namespace = tool_instance.get_cache_namespace()
|
|
1792
|
+
cache_version = tool_instance.get_cache_version()
|
|
1793
|
+
cache_key = self._make_cache_key(function_name, arguments)
|
|
1794
|
+
composed_cache_key = self.cache_manager.compose_key(
|
|
1795
|
+
cache_namespace, cache_version, cache_key
|
|
1730
1796
|
)
|
|
1797
|
+
cached_value = self.cache_manager.get(
|
|
1798
|
+
namespace=cache_namespace,
|
|
1799
|
+
version=cache_version,
|
|
1800
|
+
cache_key=cache_key,
|
|
1801
|
+
)
|
|
1802
|
+
if cached_value is not None:
|
|
1803
|
+
self.logger.debug(f"Cache hit for {function_name}")
|
|
1804
|
+
return cached_value
|
|
1805
|
+
cache_guard = self.cache_manager.singleflight_guard(composed_cache_key)
|
|
1731
1806
|
else:
|
|
1732
|
-
|
|
1807
|
+
cache_enabled = False
|
|
1808
|
+
|
|
1809
|
+
with cache_guard:
|
|
1810
|
+
if cache_enabled:
|
|
1811
|
+
cached_value = self.cache_manager.get(
|
|
1812
|
+
namespace=cache_namespace,
|
|
1813
|
+
version=cache_version,
|
|
1814
|
+
cache_key=cache_key,
|
|
1815
|
+
)
|
|
1816
|
+
if cached_value is not None:
|
|
1817
|
+
self.logger.debug(
|
|
1818
|
+
f"Cache hit for {function_name} (after singleflight wait)"
|
|
1819
|
+
)
|
|
1820
|
+
return cached_value
|
|
1821
|
+
|
|
1822
|
+
# Validate parameters if requested
|
|
1823
|
+
if validate:
|
|
1824
|
+
validation_error = self._validate_parameters(function_name, arguments)
|
|
1825
|
+
if validation_error:
|
|
1826
|
+
return self._create_dual_format_error(validation_error)
|
|
1827
|
+
|
|
1828
|
+
# Check function call format (existing validation)
|
|
1829
|
+
check_status, check_message = self.check_function_call(function_call_json)
|
|
1830
|
+
if check_status is False:
|
|
1831
|
+
error_msg = "Invalid function call: " + check_message
|
|
1733
1832
|
return self._create_dual_format_error(
|
|
1734
|
-
|
|
1735
|
-
error_msg,
|
|
1736
|
-
next_steps=[
|
|
1737
|
-
"Check tool name spelling",
|
|
1738
|
-
"Run tu.tools.refresh()",
|
|
1739
|
-
],
|
|
1833
|
+
ToolValidationError(
|
|
1834
|
+
error_msg, details={"check_message": check_message}
|
|
1740
1835
|
)
|
|
1741
1836
|
)
|
|
1742
|
-
except Exception as e:
|
|
1743
|
-
# Classify and return structured error
|
|
1744
|
-
classified_error = self._classify_exception(e, function_name, arguments)
|
|
1745
|
-
return self._create_dual_format_error(classified_error)
|
|
1746
|
-
|
|
1747
|
-
# Apply output hooks if enabled
|
|
1748
|
-
if self.hook_manager:
|
|
1749
|
-
context = {
|
|
1750
|
-
"tool_name": function_name,
|
|
1751
|
-
"tool_type": (
|
|
1752
|
-
tool_instance.__class__.__name__
|
|
1753
|
-
if tool_instance is not None
|
|
1754
|
-
else "unknown"
|
|
1755
|
-
),
|
|
1756
|
-
"execution_time": time.time(),
|
|
1757
|
-
"arguments": tool_arguments,
|
|
1758
|
-
}
|
|
1759
|
-
result = self.hook_manager.apply_hooks(
|
|
1760
|
-
result, function_name, tool_arguments, context
|
|
1761
|
-
)
|
|
1762
1837
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
if len(self._cache) > self._cache_size:
|
|
1769
|
-
# Remove oldest entries (simple FIFO)
|
|
1770
|
-
oldest_key = next(iter(self._cache))
|
|
1771
|
-
del self._cache[oldest_key]
|
|
1838
|
+
# Execute the tool
|
|
1839
|
+
tool_arguments = arguments
|
|
1840
|
+
try:
|
|
1841
|
+
if tool_instance is None:
|
|
1842
|
+
tool_instance = self._get_tool_instance(function_name, cache=True)
|
|
1772
1843
|
|
|
1773
|
-
|
|
1844
|
+
if tool_instance:
|
|
1845
|
+
result, tool_arguments = self._execute_tool_with_stream(
|
|
1846
|
+
tool_instance, arguments, stream_callback, use_cache, validate
|
|
1847
|
+
)
|
|
1848
|
+
else:
|
|
1849
|
+
error_msg = f"Tool '{function_name}' not found"
|
|
1850
|
+
return self._create_dual_format_error(
|
|
1851
|
+
ToolUnavailableError(
|
|
1852
|
+
error_msg,
|
|
1853
|
+
next_steps=[
|
|
1854
|
+
"Check tool name spelling",
|
|
1855
|
+
"Run tu.tools.refresh()",
|
|
1856
|
+
],
|
|
1857
|
+
)
|
|
1858
|
+
)
|
|
1859
|
+
except Exception as e:
|
|
1860
|
+
# Classify and return structured error
|
|
1861
|
+
classified_error = self._classify_exception(e, function_name, arguments)
|
|
1862
|
+
return self._create_dual_format_error(classified_error)
|
|
1863
|
+
|
|
1864
|
+
# Apply output hooks if enabled
|
|
1865
|
+
if self.hook_manager:
|
|
1866
|
+
context = {
|
|
1867
|
+
"tool_name": function_name,
|
|
1868
|
+
"tool_type": (
|
|
1869
|
+
tool_instance.__class__.__name__
|
|
1870
|
+
if tool_instance is not None
|
|
1871
|
+
else "unknown"
|
|
1872
|
+
),
|
|
1873
|
+
"execution_time": time.time(),
|
|
1874
|
+
"arguments": tool_arguments,
|
|
1875
|
+
}
|
|
1876
|
+
result = self.hook_manager.apply_hooks(
|
|
1877
|
+
result, function_name, tool_arguments, context
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1880
|
+
# Cache result if enabled
|
|
1881
|
+
if cache_enabled and tool_instance and tool_instance.supports_caching():
|
|
1882
|
+
if cache_key is None:
|
|
1883
|
+
cache_key = self._make_cache_key(function_name, arguments)
|
|
1884
|
+
if cache_namespace is None:
|
|
1885
|
+
cache_namespace = tool_instance.get_cache_namespace()
|
|
1886
|
+
if cache_version is None:
|
|
1887
|
+
cache_version = tool_instance.get_cache_version()
|
|
1888
|
+
ttl = tool_instance.get_cache_ttl(result)
|
|
1889
|
+
self.cache_manager.set(
|
|
1890
|
+
namespace=cache_namespace,
|
|
1891
|
+
version=cache_version,
|
|
1892
|
+
cache_key=cache_key,
|
|
1893
|
+
value=result,
|
|
1894
|
+
ttl=ttl,
|
|
1895
|
+
)
|
|
1896
|
+
|
|
1897
|
+
return result
|
|
1774
1898
|
|
|
1775
1899
|
def _execute_tool_with_stream(
|
|
1776
1900
|
self, tool_instance, arguments, stream_callback, use_cache=False, validate=True
|
|
@@ -2037,11 +2161,42 @@ class ToolUniverse:
|
|
|
2037
2161
|
f"Eager loading completed. {len(self.callable_functions)} tools cached."
|
|
2038
2162
|
)
|
|
2039
2163
|
|
|
2164
|
+
@property
|
|
2165
|
+
def _cache(self):
|
|
2166
|
+
"""Access to the internal cache for testing purposes."""
|
|
2167
|
+
if self.cache_manager:
|
|
2168
|
+
return self.cache_manager.memory
|
|
2169
|
+
return {}
|
|
2170
|
+
|
|
2040
2171
|
def clear_cache(self):
|
|
2041
2172
|
"""Clear the result cache."""
|
|
2042
|
-
self.
|
|
2173
|
+
if self.cache_manager:
|
|
2174
|
+
self.cache_manager.clear()
|
|
2043
2175
|
self.logger.info("Result cache cleared")
|
|
2044
2176
|
|
|
2177
|
+
def get_cache_stats(self) -> Dict[str, Any]:
|
|
2178
|
+
"""Return cache statistics."""
|
|
2179
|
+
if not self.cache_manager:
|
|
2180
|
+
return {"enabled": False}
|
|
2181
|
+
return self.cache_manager.stats()
|
|
2182
|
+
|
|
2183
|
+
def dump_cache(self, namespace: Optional[str] = None):
|
|
2184
|
+
"""Iterate over cached entries (persistent layer only)."""
|
|
2185
|
+
if not self.cache_manager:
|
|
2186
|
+
return iter([])
|
|
2187
|
+
return self.cache_manager.dump(namespace=namespace)
|
|
2188
|
+
|
|
2189
|
+
def close(self):
|
|
2190
|
+
"""Release resources."""
|
|
2191
|
+
if self.cache_manager:
|
|
2192
|
+
self.cache_manager.close()
|
|
2193
|
+
|
|
2194
|
+
def __del__(self):
|
|
2195
|
+
try:
|
|
2196
|
+
self.close()
|
|
2197
|
+
except Exception:
|
|
2198
|
+
pass
|
|
2199
|
+
|
|
2045
2200
|
def get_tool_health(self, tool_name: str = None) -> dict:
|
|
2046
2201
|
"""Get health status for tool(s)."""
|
|
2047
2202
|
tool_errors = get_tool_errors()
|
|
@@ -2248,6 +2403,10 @@ class ToolUniverse:
|
|
|
2248
2403
|
self.logger.warning("No tools loaded. Call load_tools() first.")
|
|
2249
2404
|
return []
|
|
2250
2405
|
|
|
2406
|
+
# Handle None or empty pattern
|
|
2407
|
+
if pattern is None or pattern == "":
|
|
2408
|
+
return self.all_tools
|
|
2409
|
+
|
|
2251
2410
|
import re
|
|
2252
2411
|
|
|
2253
2412
|
flags = 0 if case_sensitive else re.IGNORECASE
|