massgen 0.1.0a3__py3-none-any.whl → 0.1.2__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.

Files changed (120) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/agent_config.py +17 -0
  3. massgen/api_params_handler/_api_params_handler_base.py +1 -0
  4. massgen/api_params_handler/_chat_completions_api_params_handler.py +15 -2
  5. massgen/api_params_handler/_claude_api_params_handler.py +8 -1
  6. massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
  7. massgen/api_params_handler/_response_api_params_handler.py +8 -1
  8. massgen/backend/base.py +83 -0
  9. massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +286 -15
  10. massgen/backend/capabilities.py +6 -6
  11. massgen/backend/chat_completions.py +200 -103
  12. massgen/backend/claude.py +115 -18
  13. massgen/backend/claude_code.py +378 -14
  14. massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
  15. massgen/backend/gemini.py +1333 -1629
  16. massgen/backend/gemini_mcp_manager.py +545 -0
  17. massgen/backend/gemini_trackers.py +344 -0
  18. massgen/backend/gemini_utils.py +43 -0
  19. massgen/backend/grok.py +39 -6
  20. massgen/backend/response.py +147 -81
  21. massgen/cli.py +605 -110
  22. massgen/config_builder.py +376 -27
  23. massgen/configs/README.md +123 -80
  24. massgen/configs/basic/multi/three_agents_default.yaml +3 -3
  25. massgen/configs/basic/single/single_agent.yaml +1 -1
  26. massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
  27. massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
  28. massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
  29. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
  30. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
  31. massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
  32. massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
  33. massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
  34. massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
  35. massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
  36. massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
  37. massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
  38. massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
  39. massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
  40. massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
  41. massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
  42. massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
  43. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
  44. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
  45. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
  46. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
  47. massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
  48. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
  49. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
  50. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
  51. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
  52. massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
  53. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
  54. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
  55. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
  56. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
  57. massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
  58. massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
  59. massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
  60. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
  61. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
  62. massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
  63. massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
  64. massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
  65. massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
  66. massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
  67. massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
  68. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
  69. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +7 -29
  70. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +5 -6
  71. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +4 -4
  72. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +4 -4
  73. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +2 -2
  74. massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
  75. massgen/formatter/_chat_completions_formatter.py +104 -0
  76. massgen/formatter/_claude_formatter.py +120 -0
  77. massgen/formatter/_gemini_formatter.py +448 -0
  78. massgen/formatter/_response_formatter.py +88 -0
  79. massgen/frontend/coordination_ui.py +4 -2
  80. massgen/logger_config.py +35 -3
  81. massgen/message_templates.py +56 -6
  82. massgen/orchestrator.py +512 -16
  83. massgen/stream_chunk/base.py +3 -0
  84. massgen/tests/custom_tools_example.py +392 -0
  85. massgen/tests/mcp_test_server.py +17 -7
  86. massgen/tests/test_config_builder.py +423 -0
  87. massgen/tests/test_custom_tools.py +401 -0
  88. massgen/tests/test_intelligent_planning_mode.py +643 -0
  89. massgen/tests/test_tools.py +127 -0
  90. massgen/token_manager/token_manager.py +13 -4
  91. massgen/tool/README.md +935 -0
  92. massgen/tool/__init__.py +39 -0
  93. massgen/tool/_async_helpers.py +70 -0
  94. massgen/tool/_basic/__init__.py +8 -0
  95. massgen/tool/_basic/_two_num_tool.py +24 -0
  96. massgen/tool/_code_executors/__init__.py +10 -0
  97. massgen/tool/_code_executors/_python_executor.py +74 -0
  98. massgen/tool/_code_executors/_shell_executor.py +61 -0
  99. massgen/tool/_exceptions.py +39 -0
  100. massgen/tool/_file_handlers/__init__.py +10 -0
  101. massgen/tool/_file_handlers/_file_operations.py +218 -0
  102. massgen/tool/_manager.py +634 -0
  103. massgen/tool/_registered_tool.py +88 -0
  104. massgen/tool/_result.py +66 -0
  105. massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
  106. massgen/tool/docs/builtin_tools.md +681 -0
  107. massgen/tool/docs/exceptions.md +794 -0
  108. massgen/tool/docs/execution_results.md +691 -0
  109. massgen/tool/docs/manager.md +887 -0
  110. massgen/tool/docs/workflow_toolkits.md +529 -0
  111. massgen/tool/workflow_toolkits/__init__.py +57 -0
  112. massgen/tool/workflow_toolkits/base.py +55 -0
  113. massgen/tool/workflow_toolkits/new_answer.py +126 -0
  114. massgen/tool/workflow_toolkits/vote.py +167 -0
  115. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/METADATA +87 -129
  116. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/RECORD +120 -44
  117. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/WHEEL +0 -0
  118. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/entry_points.txt +0 -0
  119. {massgen-0.1.0a3.dist-info → massgen-0.1.2.dist-info}/licenses/LICENSE +0 -0
  120. {massgen-0.1.0a3.dist-info → massgen-0.1.2.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)