tooluniverse 1.0.4__py3-none-any.whl → 1.0.6__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 (57) hide show
  1. tooluniverse/__init__.py +56 -5
  2. tooluniverse/agentic_tool.py +90 -14
  3. tooluniverse/arxiv_tool.py +113 -0
  4. tooluniverse/biorxiv_tool.py +97 -0
  5. tooluniverse/core_tool.py +153 -0
  6. tooluniverse/crossref_tool.py +73 -0
  7. tooluniverse/data/agentic_tools.json +2 -2
  8. tooluniverse/data/arxiv_tools.json +87 -0
  9. tooluniverse/data/biorxiv_tools.json +70 -0
  10. tooluniverse/data/core_tools.json +105 -0
  11. tooluniverse/data/crossref_tools.json +70 -0
  12. tooluniverse/data/dblp_tools.json +73 -0
  13. tooluniverse/data/doaj_tools.json +94 -0
  14. tooluniverse/data/fatcat_tools.json +72 -0
  15. tooluniverse/data/hal_tools.json +70 -0
  16. tooluniverse/data/medrxiv_tools.json +70 -0
  17. tooluniverse/data/odphp_tools.json +354 -0
  18. tooluniverse/data/openaire_tools.json +85 -0
  19. tooluniverse/data/osf_preprints_tools.json +77 -0
  20. tooluniverse/data/pmc_tools.json +109 -0
  21. tooluniverse/data/pubmed_tools.json +65 -0
  22. tooluniverse/data/unpaywall_tools.json +86 -0
  23. tooluniverse/data/wikidata_sparql_tools.json +42 -0
  24. tooluniverse/data/zenodo_tools.json +82 -0
  25. tooluniverse/dblp_tool.py +62 -0
  26. tooluniverse/default_config.py +18 -0
  27. tooluniverse/doaj_tool.py +124 -0
  28. tooluniverse/execute_function.py +70 -9
  29. tooluniverse/fatcat_tool.py +66 -0
  30. tooluniverse/hal_tool.py +77 -0
  31. tooluniverse/llm_clients.py +487 -0
  32. tooluniverse/mcp_tool_registry.py +3 -3
  33. tooluniverse/medrxiv_tool.py +97 -0
  34. tooluniverse/odphp_tool.py +226 -0
  35. tooluniverse/openaire_tool.py +145 -0
  36. tooluniverse/osf_preprints_tool.py +67 -0
  37. tooluniverse/pmc_tool.py +181 -0
  38. tooluniverse/pubmed_tool.py +110 -0
  39. tooluniverse/remote/boltz/boltz_mcp_server.py +2 -2
  40. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +2 -2
  41. tooluniverse/smcp.py +313 -191
  42. tooluniverse/smcp_server.py +4 -7
  43. tooluniverse/test/test_claude_sdk.py +93 -0
  44. tooluniverse/test/test_odphp_tool.py +166 -0
  45. tooluniverse/test/test_openrouter_client.py +288 -0
  46. tooluniverse/test/test_stdio_hooks.py +1 -1
  47. tooluniverse/test/test_tool_finder.py +1 -1
  48. tooluniverse/unpaywall_tool.py +63 -0
  49. tooluniverse/wikidata_sparql_tool.py +61 -0
  50. tooluniverse/zenodo_tool.py +74 -0
  51. {tooluniverse-1.0.4.dist-info → tooluniverse-1.0.6.dist-info}/METADATA +101 -74
  52. {tooluniverse-1.0.4.dist-info → tooluniverse-1.0.6.dist-info}/RECORD +56 -19
  53. {tooluniverse-1.0.4.dist-info → tooluniverse-1.0.6.dist-info}/entry_points.txt +1 -0
  54. tooluniverse-1.0.6.dist-info/licenses/LICENSE +201 -0
  55. tooluniverse-1.0.4.dist-info/licenses/LICENSE +0 -21
  56. {tooluniverse-1.0.4.dist-info → tooluniverse-1.0.6.dist-info}/WHEEL +0 -0
  57. {tooluniverse-1.0.4.dist-info → tooluniverse-1.0.6.dist-info}/top_level.txt +0 -0
@@ -25,6 +25,7 @@ Constants:
25
25
  """
26
26
 
27
27
  import copy
28
+ import inspect
28
29
  import json
29
30
  import random
30
31
  import string
@@ -1638,7 +1639,14 @@ class ToolUniverse:
1638
1639
  """
1639
1640
  return "".join(random.choices(string.ascii_letters + string.digits, k=9))
1640
1641
 
1641
- def run(self, fcall_str, return_message=False, verbose=True, format="llama"):
1642
+ def run(
1643
+ self,
1644
+ fcall_str,
1645
+ return_message=False,
1646
+ verbose=True,
1647
+ format="llama",
1648
+ stream_callback=None,
1649
+ ):
1642
1650
  """
1643
1651
  Execute function calls from input string or data.
1644
1652
 
@@ -1671,7 +1679,9 @@ class ToolUniverse:
1671
1679
  # return the function call+result message with call id.
1672
1680
  call_results = []
1673
1681
  for i in range(len(function_call_json)):
1674
- call_result = self.run_one_function(function_call_json[i])
1682
+ call_result = self.run_one_function(
1683
+ function_call_json[i], stream_callback=stream_callback
1684
+ )
1675
1685
  call_id = self.call_id_gen()
1676
1686
  function_call_json[i]["call_id"] = call_id
1677
1687
  call_results.append(
@@ -1691,12 +1701,14 @@ class ToolUniverse:
1691
1701
  ] + call_results
1692
1702
  return revised_messages
1693
1703
  else:
1694
- return self.run_one_function(function_call_json)
1704
+ return self.run_one_function(
1705
+ function_call_json, stream_callback=stream_callback
1706
+ )
1695
1707
  else:
1696
1708
  error("Not a function call")
1697
1709
  return None
1698
1710
 
1699
- def run_one_function(self, function_call_json):
1711
+ def run_one_function(self, function_call_json, stream_callback=None):
1700
1712
  """
1701
1713
  Execute a single function call.
1702
1714
 
@@ -1719,8 +1731,13 @@ class ToolUniverse:
1719
1731
  arguments = function_call_json["arguments"]
1720
1732
 
1721
1733
  # Execute the tool
1734
+ tool_instance = None
1735
+ tool_arguments = arguments
1722
1736
  if function_name in self.callable_functions:
1723
- result = self.callable_functions[function_name].run(arguments)
1737
+ tool_instance = self.callable_functions[function_name]
1738
+ result, tool_arguments = self._execute_tool_with_stream(
1739
+ tool_instance, arguments, stream_callback
1740
+ )
1724
1741
  else:
1725
1742
  if function_name in self.all_tool_dict:
1726
1743
  self.logger.debug(
@@ -1729,7 +1746,10 @@ class ToolUniverse:
1729
1746
  tool = self.init_tool(
1730
1747
  self.all_tool_dict[function_name], add_to_cache=True
1731
1748
  )
1732
- result = tool.run(arguments)
1749
+ tool_instance = tool
1750
+ result, tool_arguments = self._execute_tool_with_stream(
1751
+ tool_instance, arguments, stream_callback
1752
+ )
1733
1753
  else:
1734
1754
  return f"Tool '{function_name}' not found"
1735
1755
 
@@ -1738,17 +1758,58 @@ class ToolUniverse:
1738
1758
  context = {
1739
1759
  "tool_name": function_name,
1740
1760
  "tool_type": (
1741
- tool.__class__.__name__ if "tool" in locals() else "unknown"
1761
+ tool_instance.__class__.__name__
1762
+ if tool_instance is not None
1763
+ else "unknown"
1742
1764
  ),
1743
1765
  "execution_time": time.time(),
1744
- "arguments": arguments,
1766
+ "arguments": tool_arguments,
1745
1767
  }
1746
1768
  result = self.hook_manager.apply_hooks(
1747
- result, function_name, arguments, context
1769
+ result, function_name, tool_arguments, context
1748
1770
  )
1749
1771
 
1750
1772
  return result
1751
1773
 
1774
+ def _execute_tool_with_stream(self, tool_instance, arguments, stream_callback):
1775
+ """Invoke a tool, forwarding stream callbacks when supported."""
1776
+
1777
+ tool_arguments = arguments
1778
+ stream_flag_key = (
1779
+ getattr(tool_instance, "STREAM_FLAG_KEY", None)
1780
+ if stream_callback
1781
+ else None
1782
+ )
1783
+
1784
+ if isinstance(arguments, dict):
1785
+ tool_arguments = dict(arguments)
1786
+
1787
+ if (
1788
+ stream_callback
1789
+ and stream_flag_key
1790
+ and stream_flag_key not in tool_arguments
1791
+ ):
1792
+ tool_arguments[stream_flag_key] = True
1793
+
1794
+ if stream_callback is None:
1795
+ return tool_instance.run(tool_arguments), tool_arguments
1796
+
1797
+ try:
1798
+ signature = inspect.signature(tool_instance.run)
1799
+ if "stream_callback" in signature.parameters:
1800
+ return (
1801
+ tool_instance.run(
1802
+ tool_arguments, stream_callback=stream_callback
1803
+ ),
1804
+ tool_arguments,
1805
+ )
1806
+ except (ValueError, TypeError):
1807
+ # If inspection fails, fall back to best-effort execution
1808
+ pass
1809
+
1810
+ # Tool doesn't support streaming yet; execute normally
1811
+ return tool_instance.run(tool_arguments), tool_arguments
1812
+
1752
1813
  def toggle_hooks(self, enabled: bool):
1753
1814
  """
1754
1815
  Enable or disable output hooks globally.
@@ -0,0 +1,66 @@
1
+ import requests
2
+ from .base_tool import BaseTool
3
+ from .tool_registry import register_tool
4
+
5
+
6
+ @register_tool("FatcatScholarTool")
7
+ class FatcatScholarTool(BaseTool):
8
+ """
9
+ Search Internet Archive Scholar via Fatcat releases search.
10
+
11
+ Parameters (arguments):
12
+ query (str): Query string
13
+ max_results (int): Max results (default 10, max 100)
14
+ """
15
+
16
+ def __init__(self, tool_config):
17
+ super().__init__(tool_config)
18
+ self.base_url = "https://api.fatcat.wiki/v0/release/search"
19
+
20
+ def run(self, arguments=None):
21
+ arguments = arguments or {}
22
+ query = arguments.get("query")
23
+ max_results = int(arguments.get("max_results", 10))
24
+
25
+ if not query:
26
+ return {"error": "`query` parameter is required."}
27
+
28
+ params = {
29
+ "q": query,
30
+ "size": max(1, min(max_results, 100)),
31
+ }
32
+ try:
33
+ resp = requests.get(self.base_url, params=params, timeout=20)
34
+ resp.raise_for_status()
35
+ data = resp.json()
36
+ except requests.RequestException as e:
37
+ return {
38
+ "error": "Network/API error calling Fatcat",
39
+ "reason": str(e),
40
+ }
41
+ except ValueError:
42
+ return {"error": "Failed to decode Fatcat response as JSON"}
43
+
44
+ results = []
45
+ for r in data.get("hits", {}).get("hits", []):
46
+ src = r.get("_source", {})
47
+ title = src.get("title")
48
+ year = src.get("release_year")
49
+ doi = src.get("doi")
50
+ authors = src.get("contrib_names", [])
51
+ url = None
52
+ if src.get("wikidata_qid"):
53
+ url = f"https://fatcat.wiki/release/{src.get('ident') or ''}"
54
+ results.append(
55
+ {
56
+ "title": title,
57
+ "authors": authors,
58
+ "year": year,
59
+ "doi": doi,
60
+ "url": url,
61
+ "source": "Fatcat/IA Scholar",
62
+ }
63
+ )
64
+
65
+ return results
66
+
@@ -0,0 +1,77 @@
1
+ import requests
2
+ from .base_tool import BaseTool
3
+ from .tool_registry import register_tool
4
+
5
+
6
+ @register_tool("HALTool")
7
+ class HALTool(BaseTool):
8
+ """
9
+ Search the French HAL open archive via its public API.
10
+
11
+ Arguments:
12
+ query (str): Search term (Lucene syntax)
13
+ max_results (int): Max results to return (default 10, max 100)
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ tool_config,
19
+ base_url="https://api.archives-ouvertes.fr/search/",
20
+ ):
21
+ super().__init__(tool_config)
22
+ self.base_url = base_url.rstrip("/") + "/"
23
+
24
+ def run(self, arguments=None):
25
+ arguments = arguments or {}
26
+ query = arguments.get("query")
27
+ max_results = int(arguments.get("max_results", 10))
28
+ if not query:
29
+ return {"error": "`query` parameter is required."}
30
+ return self._search(query, max_results)
31
+
32
+ def _search(self, query, max_results):
33
+ params = {
34
+ "q": query,
35
+ "wt": "json",
36
+ "rows": max(1, min(max_results, 100)),
37
+ "fl": (
38
+ "title_s,authFullName_s,producedDateY_i,uri_s,doiId_s,"
39
+ "docType_s,abstract_s,source_s"
40
+ ),
41
+ }
42
+ try:
43
+ resp = requests.get(f"{self.base_url}", params=params, timeout=20)
44
+ resp.raise_for_status()
45
+ data = resp.json()
46
+ except requests.RequestException as e:
47
+ return {"error": "Network/API error calling HAL", "reason": str(e)}
48
+ except ValueError:
49
+ return {"error": "Failed to decode HAL response as JSON"}
50
+
51
+ docs = data.get("response", {}).get("docs", [])
52
+ results = []
53
+ for d in docs:
54
+ title = (d.get("title_s") or [None])
55
+ if isinstance(title, list):
56
+ title = title[0]
57
+ authors = d.get("authFullName_s") or []
58
+ year = d.get("producedDateY_i")
59
+ doi = d.get("doiId_s")
60
+ url = d.get("uri_s")
61
+ abstract = d.get("abstract_s")
62
+ if isinstance(abstract, list):
63
+ abstract = abstract[0]
64
+ source = d.get("source_s")
65
+ results.append(
66
+ {
67
+ "title": title,
68
+ "authors": authors,
69
+ "year": year,
70
+ "doi": doi,
71
+ "url": url,
72
+ "abstract": abstract,
73
+ "source": source,
74
+ }
75
+ )
76
+
77
+ return results