autourgos-toolbox 3.0.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jitin Kumar Sengar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: autourgos-toolbox
3
+ Version: 3.0.0
4
+ Summary: Dynamic lazy-loading toolbox middleware for Autourgos agents — keeps context windows clean by loading tools only when needed.
5
+ Author-email: Jitin Kumar Sengar <devxjitin@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/devxjitin/autourgos-toolbox
8
+ Project-URL: Repository, https://github.com/devxjitin/autourgos-toolbox
9
+ Project-URL: Issues, https://github.com/devxjitin/autourgos-toolbox/issues
10
+ Keywords: autourgos,agent,middleware,toolbox,llm,tools,lazy-loading
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: autourgos-react-agent>=1.6.0
23
+ Dynamic: license-file
24
+
25
+ # autourgos-toolbox
26
+
27
+ Dynamic lazy-loading toolbox middleware for [Autourgos](https://github.com/devxjitin) agents.
28
+
29
+ Keeps the agent's context window clean by only showing toolbox names and descriptions upfront. The agent loads the tools it actually needs at runtime by calling `expose_toolbox(name)`.
30
+
31
+ ---
32
+
33
+ ## Why use this?
34
+
35
+ Real-world agents often need dozens of tools — GitHub, databases, web search, file system, APIs. Dumping all of them into the prompt at once:
36
+
37
+ - Wastes tokens on tools the agent will never use for a given task
38
+ - Confuses the LLM with too many choices
39
+ - Can hit the context window limit before the task even starts
40
+
41
+ `ToolboxMiddleware` solves this by grouping tools into named **toolboxes** and only showing the catalog upfront. The agent picks what it needs and loads it on demand.
42
+
43
+ ---
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install autourgos-toolbox
49
+ ```
50
+
51
+ Depends on `autourgos-react-agent`. Works with any Autourgos agent.
52
+
53
+ ---
54
+
55
+ ## Quick Start
56
+
57
+ ```python
58
+ from autourgos_toolbox import Toolbox, ToolboxMiddleware
59
+ from autourgos_react_agent import ReactAgent
60
+
61
+ # Define toolboxes
62
+ web_box = Toolbox(
63
+ name="web",
64
+ description="Web search and page scraping tools.",
65
+ tools=[web_search, scrape_url],
66
+ )
67
+ db_box = Toolbox(
68
+ name="database",
69
+ description="SQL query tools for the production database.",
70
+ tools=[run_query, list_tables, describe_table],
71
+ )
72
+
73
+ # Attach middleware
74
+ middleware = ToolboxMiddleware(toolboxes=[web_box, db_box])
75
+ agent = ReactAgent(llm=my_llm, middleware=[middleware])
76
+
77
+ result = agent.invoke("Find the latest Python release and log it to the database")
78
+ print(result)
79
+ ```
80
+
81
+ When used with a verbose `ReactAgent` (`ReactAgent(..., verbose=True)`), this
82
+ middleware narrates its own actions into the same trace as the agent's
83
+ Thought/Action/Observation output, for example:
84
+
85
+ ```
86
+ [Toolbox] Exposed toolbox 'web' to agent.
87
+ ```
88
+
89
+ The agent sees this in its prompt at the start:
90
+
91
+ ```
92
+ ## Dynamic Toolboxes
93
+ Available Toolboxes:
94
+ - **web**: Web search and page scraping tools.
95
+ - **database**: SQL query tools for the production database.
96
+ ```
97
+
98
+ When it needs the web tools, it calls:
99
+ ```json
100
+ {"action": "expose_toolbox", "action_input": {"toolbox_name": "web"}}
101
+ ```
102
+
103
+ And immediately all web tools are registered and their schemas injected into the prompt.
104
+
105
+ ---
106
+
107
+ ## How it works
108
+
109
+ 1. **on_agent_start** — middleware injects `expose_toolbox` and `expose_tool` meta-tools plus a toolbox catalog into the agent's system prompt. No actual tools are loaded yet.
110
+ 2. **Agent calls expose_toolbox** — all tools in that toolbox are registered on the agent and their schemas are appended to the prompt.
111
+ 3. **Agent uses the tools** — now fully loaded and callable.
112
+ 4. **on_agent_end / on_agent_error** — agent is fully restored to its original state (tools + prompt) so the next run starts clean.
113
+
114
+ ---
115
+
116
+ ## Define toolboxes
117
+
118
+ ### As a list of Toolbox objects
119
+
120
+ ```python
121
+ from autourgos_toolbox import Toolbox, ToolboxMiddleware
122
+
123
+ github_box = Toolbox(
124
+ name="github",
125
+ description="Tools for reading and writing GitHub issues and PRs.",
126
+ tools=[search_issues, create_pr, list_repos],
127
+ )
128
+
129
+ middleware = ToolboxMiddleware(toolboxes=[github_box])
130
+ ```
131
+
132
+ ### As a dict
133
+
134
+ ```python
135
+ middleware = ToolboxMiddleware(toolboxes={
136
+ "github": {
137
+ "description": "Tools for reading and writing GitHub issues and PRs.",
138
+ "tools": [search_issues, create_pr, list_repos],
139
+ },
140
+ "slack": {
141
+ "description": "Tools for sending and reading Slack messages.",
142
+ "tools": [send_message, read_channel],
143
+ },
144
+ })
145
+ ```
146
+
147
+ ### Add toolboxes dynamically
148
+
149
+ ```python
150
+ middleware = ToolboxMiddleware()
151
+ middleware.add_toolbox("github", "GitHub tools.", [search_issues, create_pr])
152
+ middleware.add_toolbox("slack", "Slack tools.", [send_message, read_channel])
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Using StructuredTool
158
+
159
+ Tools can be plain callables or `StructuredTool` instances. `StructuredTool` auto-infers the JSON schema from type annotations and docstrings:
160
+
161
+ ```python
162
+ from autourgos_toolbox import StructuredTool
163
+
164
+ def search_issues(query: str, repo: str) -> str:
165
+ """Search GitHub issues.
166
+
167
+ Args:
168
+ query: Search keywords.
169
+ repo: Repository name in owner/repo format.
170
+ """
171
+ ...
172
+
173
+ tool = StructuredTool.from_function(search_issues)
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Combine with other middleware
179
+
180
+ ```python
181
+ from autourgos_toolbox import ToolboxMiddleware, Toolbox
182
+ from autourgos_history import AgentHistoryMiddleware
183
+ from autourgos_summarizer import AutoSummarizeMiddleware
184
+
185
+ middleware = [
186
+ ToolboxMiddleware(toolboxes=[web_box, db_box]),
187
+ AutoSummarizeMiddleware(summarize_every=5),
188
+ AgentHistoryMiddleware(),
189
+ ]
190
+ agent = ReactAgent(llm=my_llm, middleware=middleware)
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Requirements
196
+
197
+ - Python 3.9+
198
+ - Any Autourgos agent with `add_tools()`, `agent.tools`, and `agent.system_prompt` / `agent.prompt_template`
199
+
200
+ ---
201
+
202
+ ## License
203
+
204
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,180 @@
1
+ # autourgos-toolbox
2
+
3
+ Dynamic lazy-loading toolbox middleware for [Autourgos](https://github.com/devxjitin) agents.
4
+
5
+ Keeps the agent's context window clean by only showing toolbox names and descriptions upfront. The agent loads the tools it actually needs at runtime by calling `expose_toolbox(name)`.
6
+
7
+ ---
8
+
9
+ ## Why use this?
10
+
11
+ Real-world agents often need dozens of tools — GitHub, databases, web search, file system, APIs. Dumping all of them into the prompt at once:
12
+
13
+ - Wastes tokens on tools the agent will never use for a given task
14
+ - Confuses the LLM with too many choices
15
+ - Can hit the context window limit before the task even starts
16
+
17
+ `ToolboxMiddleware` solves this by grouping tools into named **toolboxes** and only showing the catalog upfront. The agent picks what it needs and loads it on demand.
18
+
19
+ ---
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install autourgos-toolbox
25
+ ```
26
+
27
+ Depends on `autourgos-react-agent`. Works with any Autourgos agent.
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ from autourgos_toolbox import Toolbox, ToolboxMiddleware
35
+ from autourgos_react_agent import ReactAgent
36
+
37
+ # Define toolboxes
38
+ web_box = Toolbox(
39
+ name="web",
40
+ description="Web search and page scraping tools.",
41
+ tools=[web_search, scrape_url],
42
+ )
43
+ db_box = Toolbox(
44
+ name="database",
45
+ description="SQL query tools for the production database.",
46
+ tools=[run_query, list_tables, describe_table],
47
+ )
48
+
49
+ # Attach middleware
50
+ middleware = ToolboxMiddleware(toolboxes=[web_box, db_box])
51
+ agent = ReactAgent(llm=my_llm, middleware=[middleware])
52
+
53
+ result = agent.invoke("Find the latest Python release and log it to the database")
54
+ print(result)
55
+ ```
56
+
57
+ When used with a verbose `ReactAgent` (`ReactAgent(..., verbose=True)`), this
58
+ middleware narrates its own actions into the same trace as the agent's
59
+ Thought/Action/Observation output, for example:
60
+
61
+ ```
62
+ [Toolbox] Exposed toolbox 'web' to agent.
63
+ ```
64
+
65
+ The agent sees this in its prompt at the start:
66
+
67
+ ```
68
+ ## Dynamic Toolboxes
69
+ Available Toolboxes:
70
+ - **web**: Web search and page scraping tools.
71
+ - **database**: SQL query tools for the production database.
72
+ ```
73
+
74
+ When it needs the web tools, it calls:
75
+ ```json
76
+ {"action": "expose_toolbox", "action_input": {"toolbox_name": "web"}}
77
+ ```
78
+
79
+ And immediately all web tools are registered and their schemas injected into the prompt.
80
+
81
+ ---
82
+
83
+ ## How it works
84
+
85
+ 1. **on_agent_start** — middleware injects `expose_toolbox` and `expose_tool` meta-tools plus a toolbox catalog into the agent's system prompt. No actual tools are loaded yet.
86
+ 2. **Agent calls expose_toolbox** — all tools in that toolbox are registered on the agent and their schemas are appended to the prompt.
87
+ 3. **Agent uses the tools** — now fully loaded and callable.
88
+ 4. **on_agent_end / on_agent_error** — agent is fully restored to its original state (tools + prompt) so the next run starts clean.
89
+
90
+ ---
91
+
92
+ ## Define toolboxes
93
+
94
+ ### As a list of Toolbox objects
95
+
96
+ ```python
97
+ from autourgos_toolbox import Toolbox, ToolboxMiddleware
98
+
99
+ github_box = Toolbox(
100
+ name="github",
101
+ description="Tools for reading and writing GitHub issues and PRs.",
102
+ tools=[search_issues, create_pr, list_repos],
103
+ )
104
+
105
+ middleware = ToolboxMiddleware(toolboxes=[github_box])
106
+ ```
107
+
108
+ ### As a dict
109
+
110
+ ```python
111
+ middleware = ToolboxMiddleware(toolboxes={
112
+ "github": {
113
+ "description": "Tools for reading and writing GitHub issues and PRs.",
114
+ "tools": [search_issues, create_pr, list_repos],
115
+ },
116
+ "slack": {
117
+ "description": "Tools for sending and reading Slack messages.",
118
+ "tools": [send_message, read_channel],
119
+ },
120
+ })
121
+ ```
122
+
123
+ ### Add toolboxes dynamically
124
+
125
+ ```python
126
+ middleware = ToolboxMiddleware()
127
+ middleware.add_toolbox("github", "GitHub tools.", [search_issues, create_pr])
128
+ middleware.add_toolbox("slack", "Slack tools.", [send_message, read_channel])
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Using StructuredTool
134
+
135
+ Tools can be plain callables or `StructuredTool` instances. `StructuredTool` auto-infers the JSON schema from type annotations and docstrings:
136
+
137
+ ```python
138
+ from autourgos_toolbox import StructuredTool
139
+
140
+ def search_issues(query: str, repo: str) -> str:
141
+ """Search GitHub issues.
142
+
143
+ Args:
144
+ query: Search keywords.
145
+ repo: Repository name in owner/repo format.
146
+ """
147
+ ...
148
+
149
+ tool = StructuredTool.from_function(search_issues)
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Combine with other middleware
155
+
156
+ ```python
157
+ from autourgos_toolbox import ToolboxMiddleware, Toolbox
158
+ from autourgos_history import AgentHistoryMiddleware
159
+ from autourgos_summarizer import AutoSummarizeMiddleware
160
+
161
+ middleware = [
162
+ ToolboxMiddleware(toolboxes=[web_box, db_box]),
163
+ AutoSummarizeMiddleware(summarize_every=5),
164
+ AgentHistoryMiddleware(),
165
+ ]
166
+ agent = ReactAgent(llm=my_llm, middleware=middleware)
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Requirements
172
+
173
+ - Python 3.9+
174
+ - Any Autourgos agent with `add_tools()`, `agent.tools`, and `agent.system_prompt` / `agent.prompt_template`
175
+
176
+ ---
177
+
178
+ ## License
179
+
180
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,34 @@
1
+ """
2
+ autourgos-toolbox — Dynamic lazy-loading toolbox middleware for Autourgos agents.
3
+
4
+ Keeps context windows clean by only showing toolbox names upfront.
5
+ The agent calls `expose_toolbox(name)` at runtime to load the tools it needs.
6
+
7
+ Quick start::
8
+
9
+ from autourgos_toolbox import Toolbox, ToolboxMiddleware
10
+
11
+ web_box = Toolbox("web", "Web search tools.", [search, scrape])
12
+ db_box = Toolbox("database", "SQL tools.", [run_query, list_tables])
13
+
14
+ middleware = ToolboxMiddleware(toolboxes=[web_box, db_box])
15
+ agent = ReactAgent(llm=my_llm, middleware=[middleware])
16
+ """
17
+
18
+ from .base import CallbackHandler, StructuredTool, build_tool_list, register_tool
19
+ from .middleware import Toolbox, ToolboxMiddleware
20
+
21
+ try:
22
+ from importlib.metadata import version as _meta_version
23
+ __version__ = _meta_version("autourgos-toolbox")
24
+ except Exception:
25
+ __version__ = "2.1.0"
26
+
27
+ __all__ = [
28
+ "Toolbox",
29
+ "ToolboxMiddleware",
30
+ "StructuredTool",
31
+ "CallbackHandler",
32
+ "build_tool_list",
33
+ "register_tool",
34
+ ]
@@ -0,0 +1,130 @@
1
+ """
2
+ base.py — Base classes for autourgos-toolbox.
3
+
4
+ CallbackHandler is re-exported from autourgos-react-agent, the package that
5
+ owns this interface, to avoid divergent duplicate copies. StructuredTool,
6
+ register_tool, and build_tool_list are toolbox-specific (they operate on a
7
+ dict-based tool registry, unlike react-agent's own build_tool_list which
8
+ operates on a plain list of tool dicts) and remain defined locally here.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import inspect
13
+ import json
14
+ from typing import Any, Callable, Dict, List, Optional
15
+
16
+ from autourgos_react_agent import CallbackHandler
17
+
18
+ __all__ = ["CallbackHandler", "StructuredTool", "register_tool", "build_tool_list"]
19
+
20
+
21
+ # ── StructuredTool ─────────────────────────────────────────────────────────────
22
+
23
+ class StructuredTool:
24
+ """
25
+ A callable tool with a name, description, and auto-inferred JSON schema
26
+ built from the function's type annotations and docstring.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ name: str,
32
+ description: str,
33
+ func: Callable,
34
+ args_schema: Optional[Dict[str, Any]] = None,
35
+ ) -> None:
36
+ self.name = name
37
+ self.description = description
38
+ self.func = func
39
+ self.args_schema = args_schema or self._infer_schema(func)
40
+
41
+ @classmethod
42
+ def from_function(
43
+ cls,
44
+ func: Callable,
45
+ name: Optional[str] = None,
46
+ description: Optional[str] = None,
47
+ ) -> "StructuredTool":
48
+ tool_name = name or func.__name__
49
+ tool_desc = description or (inspect.getdoc(func) or "")
50
+ return cls(name=tool_name, description=tool_desc, func=func)
51
+
52
+ @staticmethod
53
+ def _infer_schema(func: Callable) -> Dict[str, Any]:
54
+ sig = inspect.signature(func)
55
+ hints = {}
56
+ try:
57
+ hints = func.__annotations__
58
+ except Exception:
59
+ pass
60
+
61
+ _PY_TO_JSON = {
62
+ "str": "string", "int": "integer", "float": "number",
63
+ "bool": "boolean", "list": "array", "dict": "object",
64
+ }
65
+
66
+ properties: Dict[str, Any] = {}
67
+ required: List[str] = []
68
+
69
+ for param_name, param in sig.parameters.items():
70
+ if param_name in ("self", "return"):
71
+ continue
72
+ hint = hints.get(param_name)
73
+ type_name = "string"
74
+ if hint is not None:
75
+ raw = getattr(hint, "__name__", str(hint))
76
+ type_name = _PY_TO_JSON.get(raw, "string")
77
+
78
+ # parse inline description from docstring "param: description" lines
79
+ doc = inspect.getdoc(func) or ""
80
+ pdesc = ""
81
+ for line in doc.splitlines():
82
+ line = line.strip()
83
+ if line.startswith(f"{param_name}:") or line.startswith(f"{param_name} :"):
84
+ pdesc = line.split(":", 1)[-1].strip()
85
+ break
86
+
87
+ properties[param_name] = {"type": type_name, "description": pdesc}
88
+ if param.default is inspect.Parameter.empty:
89
+ required.append(param_name)
90
+
91
+ return {
92
+ "type": "object",
93
+ "properties": properties,
94
+ "required": required,
95
+ }
96
+
97
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
98
+ return self.func(*args, **kwargs)
99
+
100
+
101
+ # ── runtime helpers ────────────────────────────────────────────────────────────
102
+
103
+ def register_tool(registry: Dict[str, Dict[str, Any]], tool: Any) -> None:
104
+ """Register a tool (StructuredTool or plain callable) into a dict registry."""
105
+ if isinstance(tool, StructuredTool):
106
+ registry[tool.name] = {
107
+ "description": tool.description,
108
+ "parameters": tool.args_schema,
109
+ "func": tool.func,
110
+ }
111
+ elif callable(tool):
112
+ name = getattr(tool, "__name__", str(tool))
113
+ doc = inspect.getdoc(tool) or ""
114
+ registry[name] = {
115
+ "description": doc,
116
+ "parameters": StructuredTool._infer_schema(tool),
117
+ "func": tool,
118
+ }
119
+ elif isinstance(tool, dict) and "name" in tool:
120
+ registry[tool["name"]] = tool
121
+
122
+
123
+ def build_tool_list(tools: Dict[str, Dict[str, Any]]) -> str:
124
+ """Format a tool registry into a prompt-ready string."""
125
+ lines: List[str] = []
126
+ for name, info in tools.items():
127
+ desc = info.get("description", "")
128
+ params = info.get("parameters", {})
129
+ lines.append(f"Tool: {name}\nDescription: {desc}\nParameters: {json.dumps(params, indent=2)}\n")
130
+ return "\n".join(lines)