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.
- autourgos_toolbox-3.0.0/LICENSE +21 -0
- autourgos_toolbox-3.0.0/PKG-INFO +204 -0
- autourgos_toolbox-3.0.0/README.md +180 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox/__init__.py +34 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox/base.py +130 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox/middleware.py +344 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox/py.typed +0 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox.egg-info/PKG-INFO +204 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox.egg-info/SOURCES.txt +13 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox.egg-info/dependency_links.txt +1 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox.egg-info/requires.txt +1 -0
- autourgos_toolbox-3.0.0/autourgos_toolbox.egg-info/top_level.txt +1 -0
- autourgos_toolbox-3.0.0/pyproject.toml +38 -0
- autourgos_toolbox-3.0.0/setup.cfg +4 -0
- autourgos_toolbox-3.0.0/tests/test_toolbox_middleware.py +268 -0
|
@@ -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)
|