massgen 0.1.0a3__py3-none-any.whl → 0.1.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 massgen might be problematic. Click here for more details.
- massgen/__init__.py +1 -1
- massgen/agent_config.py +17 -0
- massgen/api_params_handler/_api_params_handler_base.py +1 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +8 -1
- massgen/api_params_handler/_claude_api_params_handler.py +8 -1
- massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
- massgen/api_params_handler/_response_api_params_handler.py +8 -1
- massgen/backend/base.py +31 -0
- massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +282 -11
- massgen/backend/chat_completions.py +182 -92
- massgen/backend/claude.py +115 -18
- massgen/backend/claude_code.py +378 -14
- massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
- massgen/backend/gemini.py +1275 -1607
- massgen/backend/gemini_mcp_manager.py +545 -0
- massgen/backend/gemini_trackers.py +344 -0
- massgen/backend/gemini_utils.py +43 -0
- massgen/backend/response.py +129 -70
- massgen/cli.py +577 -110
- massgen/config_builder.py +376 -27
- massgen/configs/README.md +111 -80
- massgen/configs/basic/multi/three_agents_default.yaml +1 -1
- massgen/configs/basic/single/single_agent.yaml +1 -1
- massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
- massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
- massgen/formatter/_chat_completions_formatter.py +104 -0
- massgen/formatter/_claude_formatter.py +120 -0
- massgen/formatter/_gemini_formatter.py +448 -0
- massgen/formatter/_response_formatter.py +88 -0
- massgen/frontend/coordination_ui.py +4 -2
- massgen/logger_config.py +35 -3
- massgen/message_templates.py +56 -6
- massgen/orchestrator.py +179 -10
- massgen/stream_chunk/base.py +3 -0
- massgen/tests/custom_tools_example.py +392 -0
- massgen/tests/mcp_test_server.py +17 -7
- massgen/tests/test_config_builder.py +423 -0
- massgen/tests/test_custom_tools.py +401 -0
- massgen/tests/test_tools.py +127 -0
- massgen/tool/README.md +935 -0
- massgen/tool/__init__.py +39 -0
- massgen/tool/_async_helpers.py +70 -0
- massgen/tool/_basic/__init__.py +8 -0
- massgen/tool/_basic/_two_num_tool.py +24 -0
- massgen/tool/_code_executors/__init__.py +10 -0
- massgen/tool/_code_executors/_python_executor.py +74 -0
- massgen/tool/_code_executors/_shell_executor.py +61 -0
- massgen/tool/_exceptions.py +39 -0
- massgen/tool/_file_handlers/__init__.py +10 -0
- massgen/tool/_file_handlers/_file_operations.py +218 -0
- massgen/tool/_manager.py +634 -0
- massgen/tool/_registered_tool.py +88 -0
- massgen/tool/_result.py +66 -0
- massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
- massgen/tool/docs/builtin_tools.md +681 -0
- massgen/tool/docs/exceptions.md +794 -0
- massgen/tool/docs/execution_results.md +691 -0
- massgen/tool/docs/manager.md +887 -0
- massgen/tool/docs/workflow_toolkits.md +529 -0
- massgen/tool/workflow_toolkits/__init__.py +57 -0
- massgen/tool/workflow_toolkits/base.py +55 -0
- massgen/tool/workflow_toolkits/new_answer.py +126 -0
- massgen/tool/workflow_toolkits/vote.py +167 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/METADATA +89 -131
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/RECORD +111 -36
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/WHEEL +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
# ToolManager Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `ToolManager` class is the central component of the MassGen Tool System. It handles all aspects of tool lifecycle management including registration, schema generation, category management, and execution orchestration.
|
|
6
|
+
|
|
7
|
+
## Class Overview
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from massgen.tool import ToolManager
|
|
11
|
+
|
|
12
|
+
class ToolManager:
|
|
13
|
+
"""Manager class for tool registration and execution.
|
|
14
|
+
|
|
15
|
+
Provides methods for:
|
|
16
|
+
- Tool registration: add_tool_function
|
|
17
|
+
- Tool removal: delete_tool_function
|
|
18
|
+
- Category management: setup_category, modify_categories, delete_categories
|
|
19
|
+
- Schema retrieval: fetch_tool_schemas
|
|
20
|
+
- Tool execution: execute_tool
|
|
21
|
+
"""
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Initialization
|
|
25
|
+
|
|
26
|
+
### \_\_init\_\_()
|
|
27
|
+
|
|
28
|
+
**What it does**: Creates a new ToolManager instance with empty tool and category registries.
|
|
29
|
+
|
|
30
|
+
**Why you need it**: The ToolManager is your entry point to the entire tool system. You need an instance to register tools, manage categories, and execute tools.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from massgen.tool import ToolManager
|
|
34
|
+
|
|
35
|
+
# Create a new tool manager
|
|
36
|
+
manager = ToolManager()
|
|
37
|
+
|
|
38
|
+
# Ready to register tools and categories
|
|
39
|
+
manager.setup_category("utilities", "Utility tools", enabled=True)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Internal State**:
|
|
43
|
+
- `registered_tools`: Dictionary mapping tool names to RegisteredToolEntry objects
|
|
44
|
+
- `tool_categories`: Dictionary mapping category names to ToolCategory objects
|
|
45
|
+
|
|
46
|
+
## Category Management
|
|
47
|
+
|
|
48
|
+
### setup_category()
|
|
49
|
+
|
|
50
|
+
**What it does**: Creates a new category for organizing related tools. Categories can be enabled or disabled as a group, making it easy to control which tools are available.
|
|
51
|
+
|
|
52
|
+
**Why you need it**: As you add more tools, organizing them into categories makes management easier. You can enable/disable entire groups of tools based on the task at hand.
|
|
53
|
+
|
|
54
|
+
**Parameters**:
|
|
55
|
+
- `category_name` (required): Unique identifier for the category
|
|
56
|
+
- `description` (required): Human-readable description of what tools in this category do
|
|
57
|
+
- `enabled` (optional): Whether tools in this category are initially active (default: False)
|
|
58
|
+
- `usage_hints` (optional): Guidelines for when to use these tools
|
|
59
|
+
|
|
60
|
+
**Returns**: None (raises ValueError if category already exists or name is "default")
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Basic category creation
|
|
64
|
+
manager.setup_category(
|
|
65
|
+
category_name="data_analysis",
|
|
66
|
+
description="Tools for analyzing and processing data",
|
|
67
|
+
enabled=True
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Category with usage hints
|
|
71
|
+
manager.setup_category(
|
|
72
|
+
category_name="visualization",
|
|
73
|
+
description="Chart and graph generation tools",
|
|
74
|
+
enabled=False, # Disabled by default
|
|
75
|
+
usage_hints="""
|
|
76
|
+
Use these tools when you need to:
|
|
77
|
+
- Create charts from data
|
|
78
|
+
- Generate visual reports
|
|
79
|
+
- Export images for presentations
|
|
80
|
+
"""
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Error: Reserved name
|
|
84
|
+
try:
|
|
85
|
+
manager.setup_category("default", "Default category", enabled=True)
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
print(f"Error: {e}") # Cannot use reserved name 'default'
|
|
88
|
+
|
|
89
|
+
# Error: Duplicate category
|
|
90
|
+
manager.setup_category("math", "Math tools", enabled=True)
|
|
91
|
+
try:
|
|
92
|
+
manager.setup_category("math", "Duplicate", enabled=True)
|
|
93
|
+
except ValueError as e:
|
|
94
|
+
print(f"Error: {e}") # Category 'math' already exists
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### modify_categories()
|
|
98
|
+
|
|
99
|
+
**What it does**: Enable or disable entire categories of tools at once. This is useful for dynamically controlling which tools are available based on context or user permissions.
|
|
100
|
+
|
|
101
|
+
**Why you need it**: Different tasks require different tools. Instead of managing tools individually, you can enable/disable entire categories to match your current needs.
|
|
102
|
+
|
|
103
|
+
**Parameters**:
|
|
104
|
+
- `category_list` (required): List of category names to modify
|
|
105
|
+
- `enabled` (required): True to enable, False to disable
|
|
106
|
+
|
|
107
|
+
**Returns**: None
|
|
108
|
+
|
|
109
|
+
**Note**: The "default" category cannot be disabled and will be silently ignored.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# Enable multiple categories
|
|
113
|
+
manager.modify_categories(
|
|
114
|
+
category_list=["data_analysis", "visualization"],
|
|
115
|
+
enabled=True
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Disable a category
|
|
119
|
+
manager.modify_categories(
|
|
120
|
+
category_list=["admin_tools"],
|
|
121
|
+
enabled=False
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Trying to disable default category (silently ignored)
|
|
125
|
+
manager.modify_categories(["default"], enabled=False)
|
|
126
|
+
# Default category remains active
|
|
127
|
+
|
|
128
|
+
# Enable non-existent category (silently ignored)
|
|
129
|
+
manager.modify_categories(["nonexistent"], enabled=True)
|
|
130
|
+
# No error, but nothing happens
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### delete_categories()
|
|
134
|
+
|
|
135
|
+
**What it does**: Permanently removes categories and all tools registered in those categories. This is destructive and cannot be undone.
|
|
136
|
+
|
|
137
|
+
**Why you need it**: When you no longer need a set of tools, you can clean up by removing the entire category. This keeps your tool registry organized.
|
|
138
|
+
|
|
139
|
+
**Parameters**:
|
|
140
|
+
- `category_list` (required): List of category names to delete (or single string)
|
|
141
|
+
|
|
142
|
+
**Returns**: None (raises ValueError if trying to delete "default")
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# Delete single category
|
|
146
|
+
manager.delete_categories("temporary_tools")
|
|
147
|
+
|
|
148
|
+
# Delete multiple categories
|
|
149
|
+
manager.delete_categories(["experiment", "debug_utils"])
|
|
150
|
+
|
|
151
|
+
# Alternative: pass string instead of list
|
|
152
|
+
manager.delete_categories("old_category")
|
|
153
|
+
|
|
154
|
+
# Error: Cannot delete default category
|
|
155
|
+
try:
|
|
156
|
+
manager.delete_categories("default")
|
|
157
|
+
except ValueError as e:
|
|
158
|
+
print(f"Error: {e}") # Cannot remove the default category
|
|
159
|
+
|
|
160
|
+
# Tools in deleted categories are also removed
|
|
161
|
+
manager.setup_category("temp", "Temporary", enabled=True)
|
|
162
|
+
manager.add_tool_function(func=my_tool, category="temp")
|
|
163
|
+
manager.delete_categories("temp")
|
|
164
|
+
# my_tool is now gone from registered_tools
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### fetch_category_hints()
|
|
168
|
+
|
|
169
|
+
**What it does**: Collects usage hints from all enabled categories and formats them as a markdown string. This is useful for providing context-aware guidance to AI agents.
|
|
170
|
+
|
|
171
|
+
**Why you need it**: When tools are organized with usage hints, agents can make better decisions about which tools to use. This method aggregates all relevant hints.
|
|
172
|
+
|
|
173
|
+
**Returns**: String containing formatted hints from all enabled categories
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Set up categories with hints
|
|
177
|
+
manager.setup_category(
|
|
178
|
+
"file_ops",
|
|
179
|
+
"File operations",
|
|
180
|
+
enabled=True,
|
|
181
|
+
usage_hints="Use for reading, writing, and managing files"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
manager.setup_category(
|
|
185
|
+
"network",
|
|
186
|
+
"Network tools",
|
|
187
|
+
enabled=True,
|
|
188
|
+
usage_hints="Use for HTTP requests and API calls"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Get combined hints
|
|
192
|
+
hints = manager.fetch_category_hints()
|
|
193
|
+
print(hints)
|
|
194
|
+
# Output:
|
|
195
|
+
# ## file_ops Tools
|
|
196
|
+
# Use for reading, writing, and managing files
|
|
197
|
+
#
|
|
198
|
+
# ## network Tools
|
|
199
|
+
# Use for HTTP requests and API calls
|
|
200
|
+
|
|
201
|
+
# Disabled categories are not included
|
|
202
|
+
manager.modify_categories(["network"], enabled=False)
|
|
203
|
+
hints = manager.fetch_category_hints()
|
|
204
|
+
# Only file_ops hints are included
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Tool Registration
|
|
208
|
+
|
|
209
|
+
### add_tool_function()
|
|
210
|
+
|
|
211
|
+
**What it does**: Registers a Python function as a tool that AI agents can use. This is the main method for adding capabilities to your system. It automatically generates JSON schemas from function signatures and docstrings.
|
|
212
|
+
|
|
213
|
+
**Why you need it**: This is how you add new capabilities to your agents. Any Python function can become a tool with automatic schema generation, type checking, and documentation.
|
|
214
|
+
|
|
215
|
+
**Parameters**:
|
|
216
|
+
- `path` (optional): Path to Python file or module name. If None, use `func` parameter
|
|
217
|
+
- `func` (required if path is None): Function to register (callable or function name string)
|
|
218
|
+
- `category` (optional): Category name (default: "default")
|
|
219
|
+
- `preset_args` (optional): Arguments to preset and hide from schema
|
|
220
|
+
- `description` (optional): Override description from docstring
|
|
221
|
+
- `tool_schema` (optional): Manually provide JSON schema instead of auto-generation
|
|
222
|
+
- `include_full_desc` (optional): Include long description from docstring (default: True)
|
|
223
|
+
- `allow_var_args` (optional): Include *args in schema (default: False)
|
|
224
|
+
- `allow_var_kwargs` (optional): Include **kwargs in schema (default: False)
|
|
225
|
+
- `post_processor` (optional): Function to transform results before returning
|
|
226
|
+
|
|
227
|
+
**Returns**: None (raises ValueError on errors)
|
|
228
|
+
|
|
229
|
+
**Important**: Custom tools (category != "builtin") are automatically prefixed with `custom_tool__`
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
# Example 1: Register a simple function
|
|
233
|
+
async def greet(name: str, greeting: str = "Hello") -> ExecutionResult:
|
|
234
|
+
"""Greet someone by name.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
name: Person's name
|
|
238
|
+
greeting: Greeting phrase
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
ExecutionResult with greeting message
|
|
242
|
+
"""
|
|
243
|
+
return ExecutionResult(
|
|
244
|
+
output_blocks=[TextContent(data=f"{greeting}, {name}!")]
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
manager.add_tool_function(func=greet)
|
|
248
|
+
# Registered as "custom_tool__greet"
|
|
249
|
+
|
|
250
|
+
# Example 2: Load from file path
|
|
251
|
+
manager.add_tool_function(
|
|
252
|
+
path="my_tools/analyzer.py",
|
|
253
|
+
func="analyze_data",
|
|
254
|
+
category="analysis"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Example 3: Load from module
|
|
258
|
+
manager.add_tool_function(
|
|
259
|
+
path="massgen.tool._code_executors",
|
|
260
|
+
func="run_python_script",
|
|
261
|
+
category="execution"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Example 4: Load built-in by name
|
|
265
|
+
manager.add_tool_function(func="run_python_script")
|
|
266
|
+
|
|
267
|
+
# Example 5: With preset arguments
|
|
268
|
+
manager.add_tool_function(
|
|
269
|
+
func=api_call,
|
|
270
|
+
preset_args={
|
|
271
|
+
"api_key": os.getenv("API_KEY"), # Hidden from schema
|
|
272
|
+
"timeout": 30,
|
|
273
|
+
"base_url": "https://api.example.com"
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Example 6: Custom schema
|
|
278
|
+
custom_schema = {
|
|
279
|
+
"type": "function",
|
|
280
|
+
"function": {
|
|
281
|
+
"name": "my_tool",
|
|
282
|
+
"description": "Custom tool",
|
|
283
|
+
"parameters": {
|
|
284
|
+
"type": "object",
|
|
285
|
+
"properties": {
|
|
286
|
+
"input": {"type": "string"}
|
|
287
|
+
},
|
|
288
|
+
"required": ["input"]
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
manager.add_tool_function(
|
|
294
|
+
func=my_function,
|
|
295
|
+
tool_schema=custom_schema
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Example 7: With post-processor
|
|
299
|
+
def redact_sensitive_data(request: dict, result: ExecutionResult) -> ExecutionResult:
|
|
300
|
+
"""Remove API keys and tokens from output."""
|
|
301
|
+
for block in result.output_blocks:
|
|
302
|
+
if isinstance(block, TextContent):
|
|
303
|
+
block.data = re.sub(r'token=\w+', 'token=***', block.data)
|
|
304
|
+
return result
|
|
305
|
+
|
|
306
|
+
manager.add_tool_function(
|
|
307
|
+
func=fetch_data,
|
|
308
|
+
post_processor=redact_sensitive_data
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Example 8: Override description
|
|
312
|
+
manager.add_tool_function(
|
|
313
|
+
func=complex_function,
|
|
314
|
+
description="Simplified description for agents"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Example 9: Allow **kwargs
|
|
318
|
+
def flexible_function(**options):
|
|
319
|
+
"""Function with flexible options."""
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
manager.add_tool_function(
|
|
323
|
+
func=flexible_function,
|
|
324
|
+
allow_var_kwargs=True # Include **options in schema
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Error handling
|
|
328
|
+
try:
|
|
329
|
+
manager.add_tool_function(func=my_tool) # Registers successfully
|
|
330
|
+
manager.add_tool_function(func=my_tool) # Error: already registered
|
|
331
|
+
except ValueError as e:
|
|
332
|
+
print(f"Error: {e}") # Tool 'custom_tool__my_tool' is already registered
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
manager.add_tool_function(func=some_function, category="nonexistent")
|
|
336
|
+
except ValueError as e:
|
|
337
|
+
print(f"Error: {e}") # Category 'nonexistent' not found
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Schema Generation Details**:
|
|
341
|
+
|
|
342
|
+
The tool automatically extracts:
|
|
343
|
+
- Function name → `tool_name`
|
|
344
|
+
- Docstring short description → `description`
|
|
345
|
+
- Docstring long description → extended `description` (if `include_full_desc=True`)
|
|
346
|
+
- Parameter type hints → parameter types in schema
|
|
347
|
+
- Parameter docstrings → parameter descriptions
|
|
348
|
+
- Default values → optional parameters in schema
|
|
349
|
+
|
|
350
|
+
Example:
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from typing import List, Optional
|
|
354
|
+
|
|
355
|
+
async def analyze_sentiment(
|
|
356
|
+
text: str,
|
|
357
|
+
language: str = "en",
|
|
358
|
+
include_score: bool = True,
|
|
359
|
+
keywords: Optional[List[str]] = None
|
|
360
|
+
) -> ExecutionResult:
|
|
361
|
+
"""Analyze text sentiment.
|
|
362
|
+
|
|
363
|
+
Performs sentiment analysis on the provided text,
|
|
364
|
+
returning positive/negative/neutral classification.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
text: Text to analyze
|
|
368
|
+
language: Language code (ISO 639-1)
|
|
369
|
+
include_score: Whether to include confidence score
|
|
370
|
+
keywords: Optional keywords to focus on
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
ExecutionResult with sentiment classification
|
|
374
|
+
"""
|
|
375
|
+
# Implementation
|
|
376
|
+
...
|
|
377
|
+
|
|
378
|
+
# Generated schema:
|
|
379
|
+
{
|
|
380
|
+
"type": "function",
|
|
381
|
+
"function": {
|
|
382
|
+
"name": "custom_tool__analyze_sentiment",
|
|
383
|
+
"description": "Analyze text sentiment.\n\nPerforms sentiment analysis...",
|
|
384
|
+
"parameters": {
|
|
385
|
+
"type": "object",
|
|
386
|
+
"properties": {
|
|
387
|
+
"text": {
|
|
388
|
+
"type": "string",
|
|
389
|
+
"description": "Text to analyze"
|
|
390
|
+
},
|
|
391
|
+
"language": {
|
|
392
|
+
"type": "string",
|
|
393
|
+
"description": "Language code (ISO 639-1)",
|
|
394
|
+
"default": "en"
|
|
395
|
+
},
|
|
396
|
+
"include_score": {
|
|
397
|
+
"type": "boolean",
|
|
398
|
+
"description": "Whether to include confidence score",
|
|
399
|
+
"default": true
|
|
400
|
+
},
|
|
401
|
+
"keywords": {
|
|
402
|
+
"type": "array",
|
|
403
|
+
"items": {"type": "string"},
|
|
404
|
+
"description": "Optional keywords to focus on"
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
"required": ["text"]
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### delete_tool_function()
|
|
414
|
+
|
|
415
|
+
**What it does**: Removes a registered tool from the system. The tool will no longer appear in schemas or be available for execution.
|
|
416
|
+
|
|
417
|
+
**Why you need it**: When tools become outdated or are no longer needed, you can remove them to keep the tool registry clean and prevent agents from using deprecated functionality.
|
|
418
|
+
|
|
419
|
+
**Parameters**:
|
|
420
|
+
- `tool_name` (required): Exact name of the tool to remove (including any prefixes)
|
|
421
|
+
|
|
422
|
+
**Returns**: None (silently succeeds even if tool doesn't exist)
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
# Remove a custom tool
|
|
426
|
+
manager.add_tool_function(func=my_tool)
|
|
427
|
+
manager.delete_tool_function("custom_tool__my_tool")
|
|
428
|
+
|
|
429
|
+
# Remove a built-in tool
|
|
430
|
+
manager.add_tool_function(func="run_shell_script")
|
|
431
|
+
manager.delete_tool_function("run_shell_script")
|
|
432
|
+
|
|
433
|
+
# No error if tool doesn't exist
|
|
434
|
+
manager.delete_tool_function("nonexistent_tool") # Silently succeeds
|
|
435
|
+
|
|
436
|
+
# Verify removal
|
|
437
|
+
schemas = manager.fetch_tool_schemas()
|
|
438
|
+
tool_names = [s['function']['name'] for s in schemas]
|
|
439
|
+
assert "custom_tool__my_tool" not in tool_names
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Schema Management
|
|
443
|
+
|
|
444
|
+
### fetch_tool_schemas()
|
|
445
|
+
|
|
446
|
+
**What it does**: Generates JSON schemas for all active tools. Returns only tools in enabled categories plus any tools in the "default" category.
|
|
447
|
+
|
|
448
|
+
**Why you need it**: AI agents need schemas to understand what tools are available and how to use them. This method provides the schemas in a format compatible with function calling APIs.
|
|
449
|
+
|
|
450
|
+
**Returns**: List of dictionaries, each containing a JSON schema for one tool
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
# Get all active tool schemas
|
|
454
|
+
schemas = manager.fetch_tool_schemas()
|
|
455
|
+
|
|
456
|
+
# Print tool names
|
|
457
|
+
for schema in schemas:
|
|
458
|
+
print(f"Tool: {schema['function']['name']}")
|
|
459
|
+
print(f"Description: {schema['function'].get('description', 'N/A')}")
|
|
460
|
+
|
|
461
|
+
# Example output format
|
|
462
|
+
[
|
|
463
|
+
{
|
|
464
|
+
"type": "function",
|
|
465
|
+
"function": {
|
|
466
|
+
"name": "custom_tool__process_data",
|
|
467
|
+
"description": "Process and transform data",
|
|
468
|
+
"parameters": {
|
|
469
|
+
"type": "object",
|
|
470
|
+
"properties": {
|
|
471
|
+
"data": {"type": "array", "description": "Input data"},
|
|
472
|
+
"operation": {"type": "string", "description": "Operation to perform"}
|
|
473
|
+
},
|
|
474
|
+
"required": ["data", "operation"]
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
# ... more tool schemas
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
# Only enabled categories are included
|
|
482
|
+
manager.setup_category("tools", "My tools", enabled=False)
|
|
483
|
+
manager.add_tool_function(func=my_tool, category="tools")
|
|
484
|
+
|
|
485
|
+
schemas = manager.fetch_tool_schemas()
|
|
486
|
+
# my_tool is NOT in schemas (category disabled)
|
|
487
|
+
|
|
488
|
+
manager.modify_categories(["tools"], enabled=True)
|
|
489
|
+
schemas = manager.fetch_tool_schemas()
|
|
490
|
+
# my_tool IS in schemas (category enabled)
|
|
491
|
+
|
|
492
|
+
# Default category always included
|
|
493
|
+
manager.add_tool_function(func=essential_tool, category="default")
|
|
494
|
+
schemas = manager.fetch_tool_schemas()
|
|
495
|
+
# essential_tool is ALWAYS in schemas
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### apply_extension_model()
|
|
499
|
+
|
|
500
|
+
**What it does**: Extends a tool's schema by merging in additional parameters from a Pydantic model. This allows you to dynamically add new parameters without modifying the original function.
|
|
501
|
+
|
|
502
|
+
**Why you need it**: Sometimes you need to add configuration options or advanced parameters to existing tools without changing their code. Extension models provide a clean way to augment tool schemas.
|
|
503
|
+
|
|
504
|
+
**Parameters**:
|
|
505
|
+
- `tool_name` (required): Name of the tool to extend
|
|
506
|
+
- `model_class` (required): Pydantic BaseModel class with additional parameters
|
|
507
|
+
|
|
508
|
+
**Returns**: None (raises ValueError if tool not found or model is invalid)
|
|
509
|
+
|
|
510
|
+
**Important**: Extension properties cannot conflict with existing tool parameters
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
from pydantic import BaseModel, Field
|
|
514
|
+
|
|
515
|
+
# Define extension model
|
|
516
|
+
class AdvancedOptions(BaseModel):
|
|
517
|
+
"""Advanced configuration options."""
|
|
518
|
+
|
|
519
|
+
verbose: bool = Field(
|
|
520
|
+
default=False,
|
|
521
|
+
description="Enable verbose logging"
|
|
522
|
+
)
|
|
523
|
+
max_retries: int = Field(
|
|
524
|
+
default=3,
|
|
525
|
+
description="Maximum number of retry attempts"
|
|
526
|
+
)
|
|
527
|
+
timeout: float = Field(
|
|
528
|
+
default=30.0,
|
|
529
|
+
description="Timeout in seconds"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Register base tool
|
|
533
|
+
async def fetch_data(url: str) -> ExecutionResult:
|
|
534
|
+
"""Fetch data from URL.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
url: URL to fetch from
|
|
538
|
+
"""
|
|
539
|
+
# Implementation
|
|
540
|
+
...
|
|
541
|
+
|
|
542
|
+
manager.add_tool_function(func=fetch_data)
|
|
543
|
+
|
|
544
|
+
# Extend with advanced options
|
|
545
|
+
manager.apply_extension_model(
|
|
546
|
+
tool_name="custom_tool__fetch_data",
|
|
547
|
+
model_class=AdvancedOptions
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# Schema now includes verbose, max_retries, timeout
|
|
551
|
+
schemas = manager.fetch_tool_schemas()
|
|
552
|
+
# Parameters: url, verbose, max_retries, timeout
|
|
553
|
+
|
|
554
|
+
# Error: Property conflict
|
|
555
|
+
class ConflictingModel(BaseModel):
|
|
556
|
+
url: str = Field(description="Conflicts with existing 'url' parameter")
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
manager.apply_extension_model("custom_tool__fetch_data", ConflictingModel)
|
|
560
|
+
except ValueError as e:
|
|
561
|
+
print(f"Error: {e}") # Property 'url' conflicts with existing schema
|
|
562
|
+
|
|
563
|
+
# Error: Tool not found
|
|
564
|
+
try:
|
|
565
|
+
manager.apply_extension_model("nonexistent_tool", AdvancedOptions)
|
|
566
|
+
except ValueError as e:
|
|
567
|
+
print(f"Error: {e}") # Tool 'nonexistent_tool' not found
|
|
568
|
+
|
|
569
|
+
# Error: Not a BaseModel
|
|
570
|
+
try:
|
|
571
|
+
manager.apply_extension_model("custom_tool__fetch_data", dict)
|
|
572
|
+
except TypeError as e:
|
|
573
|
+
print(f"Error: {e}") # Extension model must be a Pydantic BaseModel
|
|
574
|
+
|
|
575
|
+
# Remove extension
|
|
576
|
+
manager.apply_extension_model("custom_tool__fetch_data", None)
|
|
577
|
+
# Schema reverts to original (only url parameter)
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Extension Model Requirements**:
|
|
581
|
+
- Must inherit from `pydantic.BaseModel`
|
|
582
|
+
- Properties cannot duplicate existing tool parameters
|
|
583
|
+
- Field descriptions are merged into schema
|
|
584
|
+
- Default values are respected
|
|
585
|
+
- Type annotations are converted to JSON schema types
|
|
586
|
+
|
|
587
|
+
## Tool Execution
|
|
588
|
+
|
|
589
|
+
### execute_tool()
|
|
590
|
+
|
|
591
|
+
**What it does**: Executes a registered tool asynchronously and streams results. Handles different return types (ExecutionResult, generators, async generators) and applies post-processing if configured.
|
|
592
|
+
|
|
593
|
+
**Why you need it**: This is how you actually run tools. It provides a uniform interface regardless of tool implementation details, handles errors gracefully, and supports streaming results.
|
|
594
|
+
|
|
595
|
+
**Parameters**:
|
|
596
|
+
- `tool_request` (required): Dictionary with `name` (tool name) and `input` (arguments dict)
|
|
597
|
+
|
|
598
|
+
**Yields**: `ExecutionResult` objects (may yield multiple results for streaming tools)
|
|
599
|
+
|
|
600
|
+
**Error Handling**: Errors are returned as ExecutionResult, not raised as exceptions
|
|
601
|
+
|
|
602
|
+
```python
|
|
603
|
+
# Example 1: Simple tool execution
|
|
604
|
+
tool_request = {
|
|
605
|
+
"name": "custom_tool__calculate_sum",
|
|
606
|
+
"input": {"a": 5, "b": 3}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async for result in manager.execute_tool(tool_request):
|
|
610
|
+
print(result.output_blocks[0].data)
|
|
611
|
+
# Output: "Sum: 8"
|
|
612
|
+
|
|
613
|
+
# Example 2: Streaming tool
|
|
614
|
+
async for result in manager.execute_tool({
|
|
615
|
+
"name": "custom_tool__process_batch",
|
|
616
|
+
"input": {"items": [1, 2, 3, 4, 5]}
|
|
617
|
+
}):
|
|
618
|
+
if not result.is_final:
|
|
619
|
+
print(f"Progress: {result.output_blocks[0].data}")
|
|
620
|
+
else:
|
|
621
|
+
print(f"Complete: {result.output_blocks[0].data}")
|
|
622
|
+
|
|
623
|
+
# Example 3: Error handling (tool not found)
|
|
624
|
+
async for result in manager.execute_tool({
|
|
625
|
+
"name": "nonexistent_tool",
|
|
626
|
+
"input": {}
|
|
627
|
+
}):
|
|
628
|
+
print(result.output_blocks[0].data)
|
|
629
|
+
# Output: "ToolNotFound: No tool named 'nonexistent_tool' exists"
|
|
630
|
+
|
|
631
|
+
# Example 4: With preset arguments
|
|
632
|
+
manager.add_tool_function(
|
|
633
|
+
func=api_call,
|
|
634
|
+
preset_args={"api_key": "secret", "timeout": 30}
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
# Preset args are automatically merged
|
|
638
|
+
async for result in manager.execute_tool({
|
|
639
|
+
"name": "custom_tool__api_call",
|
|
640
|
+
"input": {"endpoint": "/users"} # api_key and timeout added automatically
|
|
641
|
+
}):
|
|
642
|
+
print(result.output_blocks[0].data)
|
|
643
|
+
|
|
644
|
+
# Example 5: Post-processing
|
|
645
|
+
def log_execution(request: dict, result: ExecutionResult) -> ExecutionResult:
|
|
646
|
+
print(f"Tool {request['name']} executed")
|
|
647
|
+
return result
|
|
648
|
+
|
|
649
|
+
manager.add_tool_function(func=my_tool, post_processor=log_execution)
|
|
650
|
+
|
|
651
|
+
async for result in manager.execute_tool({
|
|
652
|
+
"name": "custom_tool__my_tool",
|
|
653
|
+
"input": {}
|
|
654
|
+
}):
|
|
655
|
+
# log_execution is called before result is yielded
|
|
656
|
+
pass
|
|
657
|
+
|
|
658
|
+
# Example 6: Cancellation handling
|
|
659
|
+
import asyncio
|
|
660
|
+
|
|
661
|
+
async def execute_with_timeout():
|
|
662
|
+
try:
|
|
663
|
+
task = asyncio.create_task(
|
|
664
|
+
manager.execute_tool({"name": "long_running_tool", "input": {}}).__anext__()
|
|
665
|
+
)
|
|
666
|
+
result = await asyncio.wait_for(task, timeout=5.0)
|
|
667
|
+
except asyncio.TimeoutError:
|
|
668
|
+
task.cancel()
|
|
669
|
+
print("Tool execution cancelled")
|
|
670
|
+
|
|
671
|
+
# Example 7: Collect all results
|
|
672
|
+
results = []
|
|
673
|
+
async for result in manager.execute_tool(tool_request):
|
|
674
|
+
results.append(result)
|
|
675
|
+
|
|
676
|
+
final_result = results[-1] # Last result
|
|
677
|
+
all_outputs = [r.output_blocks for r in results] # All outputs
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Execution Flow**:
|
|
681
|
+
|
|
682
|
+
1. **Tool Lookup**: Find tool in `registered_tools`
|
|
683
|
+
2. **Argument Merging**: Combine `preset_params` with `input`
|
|
684
|
+
3. **Function Execution**:
|
|
685
|
+
- Async functions: `await function(**args)`
|
|
686
|
+
- Sync functions: `function(**args)`
|
|
687
|
+
4. **Result Wrapping**:
|
|
688
|
+
- `AsyncGenerator`: Stream results directly
|
|
689
|
+
- `Generator`: Convert to async generator
|
|
690
|
+
- `ExecutionResult`: Wrap in async generator
|
|
691
|
+
5. **Post-Processing**: Apply post_processor if configured
|
|
692
|
+
6. **Error Handling**: Catch exceptions and return as ExecutionResult
|
|
693
|
+
|
|
694
|
+
**Supported Return Types**:
|
|
695
|
+
|
|
696
|
+
```python
|
|
697
|
+
# Type 1: Direct ExecutionResult
|
|
698
|
+
async def tool1() -> ExecutionResult:
|
|
699
|
+
return ExecutionResult(output_blocks=[...])
|
|
700
|
+
|
|
701
|
+
# Type 2: Sync Generator
|
|
702
|
+
def tool2() -> Generator[ExecutionResult, None, None]:
|
|
703
|
+
yield ExecutionResult(...)
|
|
704
|
+
yield ExecutionResult(...)
|
|
705
|
+
|
|
706
|
+
# Type 3: Async Generator (streaming)
|
|
707
|
+
async def tool3() -> AsyncGenerator[ExecutionResult, None]:
|
|
708
|
+
yield ExecutionResult(...)
|
|
709
|
+
await asyncio.sleep(1)
|
|
710
|
+
yield ExecutionResult(...)
|
|
711
|
+
|
|
712
|
+
# Invalid: Other types raise TypeError
|
|
713
|
+
async def invalid_tool() -> str:
|
|
714
|
+
return "not an ExecutionResult" # TypeError!
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
## Utility Methods
|
|
718
|
+
|
|
719
|
+
### reset_state()
|
|
720
|
+
|
|
721
|
+
**What it does**: Clears all registered tools and categories, resetting the manager to its initial state.
|
|
722
|
+
|
|
723
|
+
**Why you need it**: Useful for testing, or when you need to completely reconfigure the tool system.
|
|
724
|
+
|
|
725
|
+
**Returns**: None
|
|
726
|
+
|
|
727
|
+
```python
|
|
728
|
+
# Set up tools and categories
|
|
729
|
+
manager.setup_category("tools", "My tools", enabled=True)
|
|
730
|
+
manager.add_tool_function(func=tool1)
|
|
731
|
+
manager.add_tool_function(func=tool2)
|
|
732
|
+
|
|
733
|
+
# Clear everything
|
|
734
|
+
manager.reset_state()
|
|
735
|
+
|
|
736
|
+
# Manager is now empty
|
|
737
|
+
assert len(manager.registered_tools) == 0
|
|
738
|
+
assert len(manager.tool_categories) == 0
|
|
739
|
+
|
|
740
|
+
# Need to re-register everything
|
|
741
|
+
manager.setup_category("tools", "My tools", enabled=True)
|
|
742
|
+
manager.add_tool_function(func=tool1)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
## Internal Methods
|
|
746
|
+
|
|
747
|
+
These methods are used internally but can be useful for advanced use cases.
|
|
748
|
+
|
|
749
|
+
### \_load_builtin_function()
|
|
750
|
+
|
|
751
|
+
**What it does**: Searches for a function in the built-in tool modules (`_basic`, `_code_executors`, `_file_handlers`, `_multimedia_processors`, `workflow_toolkits`).
|
|
752
|
+
|
|
753
|
+
**Returns**: Function object if found, None otherwise
|
|
754
|
+
|
|
755
|
+
### \_load_function_from_path()
|
|
756
|
+
|
|
757
|
+
**What it does**: Loads a function from a file path or module name. Handles absolute paths, relative paths, and module imports.
|
|
758
|
+
|
|
759
|
+
**Returns**: Function object if found, None otherwise
|
|
760
|
+
|
|
761
|
+
### \_extract_tool_schema()
|
|
762
|
+
|
|
763
|
+
**What it does**: Generates JSON schema from a function's signature and docstring using introspection.
|
|
764
|
+
|
|
765
|
+
**Parameters**:
|
|
766
|
+
- `func`: Function to analyze
|
|
767
|
+
- `include_full`: Include long description from docstring
|
|
768
|
+
- `include_varargs`: Include *args in schema
|
|
769
|
+
- `include_varkwargs`: Include **kwargs in schema
|
|
770
|
+
|
|
771
|
+
**Returns**: Dictionary containing JSON schema
|
|
772
|
+
|
|
773
|
+
## Complete Example
|
|
774
|
+
|
|
775
|
+
```python
|
|
776
|
+
from massgen.tool import ToolManager, ExecutionResult, TextContent
|
|
777
|
+
from pydantic import BaseModel, Field
|
|
778
|
+
import asyncio
|
|
779
|
+
|
|
780
|
+
# Initialize manager
|
|
781
|
+
manager = ToolManager()
|
|
782
|
+
|
|
783
|
+
# Create categories
|
|
784
|
+
manager.setup_category(
|
|
785
|
+
"data",
|
|
786
|
+
"Data processing tools",
|
|
787
|
+
enabled=True,
|
|
788
|
+
usage_hints="Use for data transformation and analysis"
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
manager.setup_category(
|
|
792
|
+
"export",
|
|
793
|
+
"Export and formatting tools",
|
|
794
|
+
enabled=False # Disabled by default
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
# Define tools
|
|
798
|
+
async def clean_data(data: list, remove_nulls: bool = True) -> ExecutionResult:
|
|
799
|
+
"""Clean and prepare data for analysis.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
data: Raw data list
|
|
803
|
+
remove_nulls: Whether to remove null values
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
ExecutionResult with cleaned data
|
|
807
|
+
"""
|
|
808
|
+
cleaned = [x for x in data if x is not None] if remove_nulls else data
|
|
809
|
+
return ExecutionResult(
|
|
810
|
+
output_blocks=[TextContent(data=f"Cleaned {len(cleaned)} items")],
|
|
811
|
+
meta_info={"original_count": len(data), "cleaned_count": len(cleaned)}
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
# Register tools
|
|
815
|
+
manager.add_tool_function(func=clean_data, category="data")
|
|
816
|
+
|
|
817
|
+
# Extend with options
|
|
818
|
+
class CleaningOptions(BaseModel):
|
|
819
|
+
deduplicate: bool = Field(False, description="Remove duplicate values")
|
|
820
|
+
sort: bool = Field(False, description="Sort the data")
|
|
821
|
+
|
|
822
|
+
manager.apply_extension_model("custom_tool__clean_data", CleaningOptions)
|
|
823
|
+
|
|
824
|
+
# Get schemas
|
|
825
|
+
schemas = manager.fetch_tool_schemas()
|
|
826
|
+
for schema in schemas:
|
|
827
|
+
print(f"\nTool: {schema['function']['name']}")
|
|
828
|
+
print(f"Parameters: {list(schema['function']['parameters']['properties'].keys())}")
|
|
829
|
+
|
|
830
|
+
# Execute tool
|
|
831
|
+
async def main():
|
|
832
|
+
result_iterator = manager.execute_tool({
|
|
833
|
+
"name": "custom_tool__clean_data",
|
|
834
|
+
"input": {
|
|
835
|
+
"data": [1, None, 2, None, 3],
|
|
836
|
+
"remove_nulls": True,
|
|
837
|
+
"deduplicate": False,
|
|
838
|
+
"sort": True
|
|
839
|
+
}
|
|
840
|
+
})
|
|
841
|
+
|
|
842
|
+
async for result in result_iterator:
|
|
843
|
+
print(result.output_blocks[0].data)
|
|
844
|
+
print(f"Metadata: {result.meta_info}")
|
|
845
|
+
|
|
846
|
+
asyncio.run(main())
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
## Best Practices
|
|
850
|
+
|
|
851
|
+
### Tool Registration
|
|
852
|
+
|
|
853
|
+
1. **Use Categories**: Organize tools into logical categories
|
|
854
|
+
2. **Provide Descriptions**: Write clear docstrings for automatic schema generation
|
|
855
|
+
3. **Type Everything**: Use type hints for all parameters
|
|
856
|
+
4. **Preset Sensitive Data**: Use `preset_args` for API keys and credentials
|
|
857
|
+
5. **Enable Selectively**: Start with categories disabled, enable as needed
|
|
858
|
+
|
|
859
|
+
### Schema Management
|
|
860
|
+
|
|
861
|
+
1. **Validate Schemas**: Check generated schemas match expectations
|
|
862
|
+
2. **Extend Carefully**: Ensure extension models don't conflict
|
|
863
|
+
3. **Document Parameters**: Use detailed parameter descriptions in docstrings
|
|
864
|
+
4. **Version Tools**: Consider versioning when changing tool signatures
|
|
865
|
+
|
|
866
|
+
### Execution
|
|
867
|
+
|
|
868
|
+
1. **Handle Errors**: Check result content for error messages
|
|
869
|
+
2. **Stream Long Operations**: Use generators for long-running tasks
|
|
870
|
+
3. **Clean Up Resources**: Use try/finally for resource cleanup
|
|
871
|
+
4. **Timeout Operations**: Set reasonable timeouts for all tools
|
|
872
|
+
5. **Post-Process Carefully**: Ensure post-processors don't break results
|
|
873
|
+
|
|
874
|
+
### Performance
|
|
875
|
+
|
|
876
|
+
1. **Lazy Load**: Only load tools when needed
|
|
877
|
+
2. **Cache Schemas**: Fetch schemas once and reuse
|
|
878
|
+
3. **Async Everything**: Use async functions for I/O operations
|
|
879
|
+
4. **Batch Operations**: Group related tool calls when possible
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
For more information, see:
|
|
884
|
+
- [ExecutionResult Documentation](execution_results.md)
|
|
885
|
+
- [Built-in Tools Guide](builtin_tools.md)
|
|
886
|
+
- [Workflow Toolkits](workflow_toolkits.md)
|
|
887
|
+
- [Exception Handling](exceptions.md)
|