massgen 0.1.0a2__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.

Files changed (111) 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 +8 -1
  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 +31 -0
  9. massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +282 -11
  10. massgen/backend/chat_completions.py +182 -92
  11. massgen/backend/claude.py +115 -18
  12. massgen/backend/claude_code.py +378 -14
  13. massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
  14. massgen/backend/gemini.py +1275 -1607
  15. massgen/backend/gemini_mcp_manager.py +545 -0
  16. massgen/backend/gemini_trackers.py +344 -0
  17. massgen/backend/gemini_utils.py +43 -0
  18. massgen/backend/response.py +129 -70
  19. massgen/cli.py +643 -132
  20. massgen/config_builder.py +381 -32
  21. massgen/configs/README.md +111 -80
  22. massgen/configs/basic/multi/three_agents_default.yaml +1 -1
  23. massgen/configs/basic/single/single_agent.yaml +1 -1
  24. massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
  25. massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
  26. massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
  27. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
  28. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
  29. massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
  30. massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
  31. massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
  32. massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
  33. massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
  34. massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
  35. massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
  36. massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
  37. massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
  38. massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
  39. massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
  40. massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
  41. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
  42. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
  43. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
  44. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
  45. massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
  46. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
  47. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
  48. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
  49. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
  50. massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
  51. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
  52. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
  53. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
  54. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
  55. massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
  56. massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
  57. massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
  58. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
  59. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
  60. massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
  61. massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
  62. massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
  63. massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
  64. massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
  65. massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
  66. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
  67. massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
  68. massgen/formatter/_chat_completions_formatter.py +104 -0
  69. massgen/formatter/_claude_formatter.py +120 -0
  70. massgen/formatter/_gemini_formatter.py +448 -0
  71. massgen/formatter/_response_formatter.py +88 -0
  72. massgen/frontend/coordination_ui.py +4 -2
  73. massgen/logger_config.py +35 -3
  74. massgen/message_templates.py +56 -6
  75. massgen/orchestrator.py +179 -10
  76. massgen/stream_chunk/base.py +3 -0
  77. massgen/tests/custom_tools_example.py +392 -0
  78. massgen/tests/mcp_test_server.py +17 -7
  79. massgen/tests/test_config_builder.py +423 -0
  80. massgen/tests/test_custom_tools.py +401 -0
  81. massgen/tests/test_tools.py +127 -0
  82. massgen/tool/README.md +935 -0
  83. massgen/tool/__init__.py +39 -0
  84. massgen/tool/_async_helpers.py +70 -0
  85. massgen/tool/_basic/__init__.py +8 -0
  86. massgen/tool/_basic/_two_num_tool.py +24 -0
  87. massgen/tool/_code_executors/__init__.py +10 -0
  88. massgen/tool/_code_executors/_python_executor.py +74 -0
  89. massgen/tool/_code_executors/_shell_executor.py +61 -0
  90. massgen/tool/_exceptions.py +39 -0
  91. massgen/tool/_file_handlers/__init__.py +10 -0
  92. massgen/tool/_file_handlers/_file_operations.py +218 -0
  93. massgen/tool/_manager.py +634 -0
  94. massgen/tool/_registered_tool.py +88 -0
  95. massgen/tool/_result.py +66 -0
  96. massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
  97. massgen/tool/docs/builtin_tools.md +681 -0
  98. massgen/tool/docs/exceptions.md +794 -0
  99. massgen/tool/docs/execution_results.md +691 -0
  100. massgen/tool/docs/manager.md +887 -0
  101. massgen/tool/docs/workflow_toolkits.md +529 -0
  102. massgen/tool/workflow_toolkits/__init__.py +57 -0
  103. massgen/tool/workflow_toolkits/base.py +55 -0
  104. massgen/tool/workflow_toolkits/new_answer.py +126 -0
  105. massgen/tool/workflow_toolkits/vote.py +167 -0
  106. {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/METADATA +89 -131
  107. {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/RECORD +111 -36
  108. {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/WHEEL +0 -0
  109. {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/entry_points.txt +0 -0
  110. {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/licenses/LICENSE +0 -0
  111. {massgen-0.1.0a2.dist-info → massgen-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,794 @@
1
+ # Exception Handling Documentation
2
+
3
+ ## Overview
4
+
5
+ The MassGen Tool System provides a hierarchy of custom exceptions for handling errors in tool operations. These exceptions provide clear, specific error information to help with debugging and error recovery.
6
+
7
+ **What are Tool Exceptions?**
8
+ Exceptions are Python's way of signaling that something went wrong. The Tool System defines specific exception types for different error scenarios, making it easier to understand and handle problems.
9
+
10
+ **Why use custom exceptions?**
11
+ Instead of generic errors, custom exceptions provide:
12
+ - **Specific Error Types**: Know exactly what went wrong
13
+ - **Structured Information**: Access to error details programmatically
14
+ - **Better Error Messages**: Clear, actionable error descriptions
15
+ - **Easier Debugging**: Specific exceptions help identify issues quickly
16
+
17
+ ## Exception Hierarchy
18
+
19
+ ```
20
+ Exception
21
+ └── ToolException (base)
22
+ ├── InvalidToolArgumentsException
23
+ ├── ToolNotFoundException
24
+ ├── ToolExecutionException
25
+ └── CategoryNotFoundException
26
+ ```
27
+
28
+ All tool exceptions inherit from `ToolException`, which inherits from Python's built-in `Exception` class.
29
+
30
+ ## Exception Classes
31
+
32
+ ### ToolException
33
+
34
+ **What it is**: Base exception class for all tool-related errors.
35
+
36
+ **Why use it**: Catch all tool system errors with a single except clause.
37
+
38
+ **Location**: `massgen.tool._exceptions`
39
+
40
+ ```python
41
+ from massgen.tool._exceptions import ToolException
42
+
43
+ class ToolException(Exception):
44
+ """Base exception for tool-related errors."""
45
+ ```
46
+
47
+ **Usage**:
48
+
49
+ ```python
50
+ from massgen.tool._exceptions import ToolException
51
+
52
+ try:
53
+ # Any tool operation
54
+ manager.add_tool_function(func=my_tool)
55
+ await manager.execute_tool(tool_request)
56
+ except ToolException as e:
57
+ # Catches all tool-specific errors
58
+ print(f"Tool system error: {e}")
59
+ except Exception as e:
60
+ # Catches other errors
61
+ print(f"General error: {e}")
62
+ ```
63
+
64
+ **When to use**:
65
+ - When you want to catch any tool-related error
66
+ - When specific error type doesn't matter
67
+ - For logging all tool errors uniformly
68
+
69
+ ---
70
+
71
+ ### InvalidToolArgumentsException
72
+
73
+ **What it is**: Raised when a tool receives invalid or malformed arguments.
74
+
75
+ **Why it happens**:
76
+ - Arguments don't match tool's parameter schema
77
+ - Required parameters are missing
78
+ - Parameter types are incorrect
79
+ - Parameter values are invalid
80
+
81
+ **Location**: `massgen.tool._exceptions`
82
+
83
+ ```python
84
+ class InvalidToolArgumentsException(ToolException):
85
+ """Raised when tool receives invalid arguments."""
86
+
87
+ def __init__(self, error_msg: str):
88
+ self.error_msg = error_msg
89
+ super().__init__(self.error_msg)
90
+ ```
91
+
92
+ **Attributes**:
93
+ - `error_msg`: Detailed description of what's wrong with the arguments
94
+
95
+ **Examples**:
96
+
97
+ ```python
98
+ from massgen.tool._exceptions import InvalidToolArgumentsException
99
+
100
+ # Missing required parameter
101
+ try:
102
+ await my_tool() # Missing required 'name' parameter
103
+ except InvalidToolArgumentsException as e:
104
+ print(f"Invalid arguments: {e.error_msg}")
105
+ # Output: "Missing required parameter: name"
106
+
107
+ # Wrong type
108
+ try:
109
+ await my_tool(name=123) # Expected string, got int
110
+ except InvalidToolArgumentsException as e:
111
+ print(f"Type error: {e.error_msg}")
112
+ # Output: "Parameter 'name' must be string, got int"
113
+
114
+ # Invalid value
115
+ try:
116
+ await my_tool(age=-5) # Age can't be negative
117
+ except InvalidToolArgumentsException as e:
118
+ print(f"Value error: {e.error_msg}")
119
+ # Output: "Parameter 'age' must be positive"
120
+ ```
121
+
122
+ **How to handle**:
123
+
124
+ ```python
125
+ async def safe_tool_call(tool_func, **kwargs):
126
+ """Safely call a tool with argument validation."""
127
+ try:
128
+ result = await tool_func(**kwargs)
129
+ return result
130
+ except InvalidToolArgumentsException as e:
131
+ print(f"❌ Argument validation failed: {e.error_msg}")
132
+ print(f"💡 Check parameter names, types, and values")
133
+ # Return error result instead of crashing
134
+ return ExecutionResult(
135
+ output_blocks=[TextContent(data=f"Error: {e.error_msg}")]
136
+ )
137
+ ```
138
+
139
+ ---
140
+
141
+ ### ToolNotFoundException
142
+
143
+ **What it is**: Raised when attempting to access a tool that doesn't exist in the registry.
144
+
145
+ **Why it happens**:
146
+ - Tool was never registered
147
+ - Tool name is misspelled
148
+ - Tool was deleted
149
+ - Wrong category (disabled or non-existent)
150
+
151
+ **Location**: `massgen.tool._exceptions`
152
+
153
+ ```python
154
+ class ToolNotFoundException(ToolException):
155
+ """Raised when requested tool is not found."""
156
+
157
+ def __init__(self, tool_name: str):
158
+ self.tool_name = tool_name
159
+ super().__init__(f"Tool '{tool_name}' not found in registry")
160
+ ```
161
+
162
+ **Attributes**:
163
+ - `tool_name`: The name of the tool that wasn't found
164
+
165
+ **Examples**:
166
+
167
+ ```python
168
+ from massgen.tool._exceptions import ToolNotFoundException
169
+
170
+ # Tool never registered
171
+ try:
172
+ manager.delete_tool_function("nonexistent_tool")
173
+ # Note: delete_tool_function doesn't raise this exception
174
+ # It's raised by other operations
175
+ except ToolNotFoundException as e:
176
+ print(f"Tool '{e.tool_name}' doesn't exist")
177
+
178
+ # Misspelled name
179
+ try:
180
+ manager.apply_extension_model("custom_tool__my_too", MyModel)
181
+ # Misspelled: "my_too" instead of "my_tool"
182
+ except ToolNotFoundException as e:
183
+ print(f"Did you mean 'custom_tool__my_tool'?")
184
+
185
+ # Tool in disabled category
186
+ manager.setup_category("experimental", "Experimental tools", enabled=False)
187
+ manager.add_tool_function(func=experimental_tool, category="experimental")
188
+
189
+ # Tool exists but category is disabled
190
+ schemas = manager.fetch_tool_schemas()
191
+ # experimental_tool won't be in schemas
192
+ ```
193
+
194
+ **How to handle**:
195
+
196
+ ```python
197
+ def find_tool_or_suggest(manager: ToolManager, tool_name: str):
198
+ """Find tool or suggest alternatives."""
199
+ try:
200
+ # Try to find tool
201
+ if tool_name in manager.registered_tools:
202
+ return manager.registered_tools[tool_name]
203
+ else:
204
+ raise ToolNotFoundException(tool_name)
205
+ except ToolNotFoundException as e:
206
+ # Suggest similar tools
207
+ all_tools = list(manager.registered_tools.keys())
208
+ similar = [t for t in all_tools if e.tool_name.lower() in t.lower()]
209
+
210
+ if similar:
211
+ print(f"❌ Tool '{e.tool_name}' not found")
212
+ print(f"💡 Did you mean one of these?")
213
+ for tool in similar:
214
+ print(f" - {tool}")
215
+ else:
216
+ print(f"❌ No tools matching '{e.tool_name}' found")
217
+ print(f"📋 Available tools: {all_tools}")
218
+ ```
219
+
220
+ ---
221
+
222
+ ### ToolExecutionException
223
+
224
+ **What it is**: Raised when a tool's execution fails due to runtime errors.
225
+
226
+ **Why it happens**:
227
+ - Tool code raises an exception
228
+ - External dependency fails
229
+ - Resource unavailable (file, network, etc.)
230
+ - Timeout or cancellation
231
+
232
+ **Location**: `massgen.tool._exceptions`
233
+
234
+ ```python
235
+ class ToolExecutionException(ToolException):
236
+ """Raised when tool execution fails."""
237
+
238
+ def __init__(self, tool_name: str, error_details: str):
239
+ self.tool_name = tool_name
240
+ self.error_details = error_details
241
+ super().__init__(f"Tool '{tool_name}' execution failed: {error_details}")
242
+ ```
243
+
244
+ **Attributes**:
245
+ - `tool_name`: Name of the tool that failed
246
+ - `error_details`: Description of what went wrong
247
+
248
+ **Examples**:
249
+
250
+ ```python
251
+ from massgen.tool._exceptions import ToolExecutionException
252
+
253
+ # File operation failure
254
+ try:
255
+ result = await read_file_content("/nonexistent/file.txt")
256
+ except ToolExecutionException as e:
257
+ print(f"Tool: {e.tool_name}")
258
+ print(f"Error: {e.error_details}")
259
+ # Output:
260
+ # Tool: read_file_content
261
+ # Error: File not found: /nonexistent/file.txt
262
+
263
+ # Network failure
264
+ try:
265
+ result = await fetch_api_data(url="https://invalid-url-xyz.com")
266
+ except ToolExecutionException as e:
267
+ print(f"API call failed: {e.error_details}")
268
+ # Output: API call failed: Connection timeout
269
+
270
+ # Code execution failure
271
+ try:
272
+ result = await run_python_script("import nonexistent_module")
273
+ except ToolExecutionException as e:
274
+ print(f"Script failed: {e.error_details}")
275
+ # Output: Script failed: ModuleNotFoundError: No module named 'nonexistent_module'
276
+ ```
277
+
278
+ **How to handle**:
279
+
280
+ ```python
281
+ async def execute_with_retry(manager, tool_request, max_retries=3):
282
+ """Execute tool with automatic retry on failure."""
283
+ for attempt in range(max_retries):
284
+ try:
285
+ result = await manager.execute_tool(tool_request)
286
+ return result
287
+
288
+ except ToolExecutionException as e:
289
+ if attempt < max_retries - 1:
290
+ print(f"⚠️ Attempt {attempt + 1} failed: {e.error_details}")
291
+ print(f"🔄 Retrying...")
292
+ await asyncio.sleep(2 ** attempt) # Exponential backoff
293
+ else:
294
+ print(f"❌ All {max_retries} attempts failed")
295
+ print(f"Tool: {e.tool_name}")
296
+ print(f"Error: {e.error_details}")
297
+ raise # Re-raise after final attempt
298
+ ```
299
+
300
+ ---
301
+
302
+ ### CategoryNotFoundException
303
+
304
+ **What it is**: Raised when attempting to access a tool category that doesn't exist.
305
+
306
+ **Why it happens**:
307
+ - Category was never created
308
+ - Category name is misspelled
309
+ - Category was deleted
310
+ - Trying to use reserved name "default" incorrectly
311
+
312
+ **Location**: `massgen.tool._exceptions`
313
+
314
+ ```python
315
+ class CategoryNotFoundException(ToolException):
316
+ """Raised when tool category is not found."""
317
+
318
+ def __init__(self, category_name: str):
319
+ self.category_name = category_name
320
+ super().__init__(f"Category '{category_name}' not found")
321
+ ```
322
+
323
+ **Attributes**:
324
+ - `category_name`: Name of the category that wasn't found
325
+
326
+ **Examples**:
327
+
328
+ ```python
329
+ from massgen.tool._exceptions import CategoryNotFoundException
330
+
331
+ # Add tool to non-existent category
332
+ try:
333
+ manager.add_tool_function(func=my_tool, category="nonexistent")
334
+ except ValueError as e: # Note: This actually raises ValueError, not CategoryNotFoundException
335
+ print(f"Category error: {e}")
336
+ # Output: Category 'nonexistent' not found
337
+
338
+ # Note: CategoryNotFoundException is defined but not currently used
339
+ # The actual implementation raises ValueError instead
340
+ # This may change in future versions
341
+
342
+ # Proper category usage
343
+ manager.setup_category("my_category", "Description", enabled=True)
344
+ manager.add_tool_function(func=my_tool, category="my_category") # ✅ Works
345
+ ```
346
+
347
+ **How to handle**:
348
+
349
+ ```python
350
+ def ensure_category(manager: ToolManager, category_name: str):
351
+ """Ensure category exists, create if not."""
352
+ if category_name in manager.tool_categories:
353
+ print(f"✅ Category '{category_name}' already exists")
354
+ else:
355
+ try:
356
+ manager.setup_category(
357
+ category_name=category_name,
358
+ description=f"Auto-created category: {category_name}",
359
+ enabled=True
360
+ )
361
+ print(f"✅ Created category '{category_name}'")
362
+ except Exception as e:
363
+ print(f"❌ Failed to create category: {e}")
364
+ ```
365
+
366
+ ## Error Handling Patterns
367
+
368
+ ### Pattern 1: Specific Exception Handling
369
+
370
+ Handle each exception type differently:
371
+
372
+ ```python
373
+ from massgen.tool._exceptions import (
374
+ InvalidToolArgumentsException,
375
+ ToolNotFoundException,
376
+ ToolExecutionException,
377
+ CategoryNotFoundException
378
+ )
379
+
380
+ try:
381
+ manager.add_tool_function(func=my_tool, category="custom")
382
+ result = await manager.execute_tool(tool_request)
383
+
384
+ except InvalidToolArgumentsException as e:
385
+ # Fix arguments and retry
386
+ print(f"Invalid arguments: {e.error_msg}")
387
+ # Prompt user for correct arguments
388
+ corrected_args = get_user_input()
389
+ result = await manager.execute_tool({
390
+ "name": tool_request["name"],
391
+ "input": corrected_args
392
+ })
393
+
394
+ except ToolNotFoundException as e:
395
+ # Suggest alternatives
396
+ print(f"Tool '{e.tool_name}' not found")
397
+ similar_tools = find_similar_tools(e.tool_name)
398
+ print(f"Similar tools: {similar_tools}")
399
+
400
+ except ToolExecutionException as e:
401
+ # Log error and return safe result
402
+ logger.error(f"Tool {e.tool_name} failed: {e.error_details}")
403
+ result = ExecutionResult(
404
+ output_blocks=[TextContent(data=f"Error: {e.error_details}")]
405
+ )
406
+
407
+ except CategoryNotFoundException as e:
408
+ # Create category and retry
409
+ print(f"Category '{e.category_name}' not found, creating...")
410
+ manager.setup_category(e.category_name, "Auto-created", enabled=True)
411
+ manager.add_tool_function(func=my_tool, category=e.category_name)
412
+ ```
413
+
414
+ ### Pattern 2: Catch-All with Logging
415
+
416
+ Log all errors uniformly:
417
+
418
+ ```python
419
+ import logging
420
+ from massgen.tool._exceptions import ToolException
421
+
422
+ logger = logging.getLogger(__name__)
423
+
424
+ try:
425
+ result = await manager.execute_tool(tool_request)
426
+
427
+ except ToolException as e:
428
+ # Log with full context
429
+ logger.error(
430
+ "Tool operation failed",
431
+ exc_info=True,
432
+ extra={
433
+ "tool_request": tool_request,
434
+ "exception_type": type(e).__name__,
435
+ "exception_message": str(e)
436
+ }
437
+ )
438
+ # Return error result
439
+ result = ExecutionResult(
440
+ output_blocks=[TextContent(data=f"Error: {str(e)}")]
441
+ )
442
+ ```
443
+
444
+ ### Pattern 3: Retry with Backoff
445
+
446
+ Automatically retry failed operations:
447
+
448
+ ```python
449
+ import asyncio
450
+ from massgen.tool._exceptions import ToolExecutionException
451
+
452
+ async def execute_with_backoff(manager, tool_request, max_retries=3):
453
+ """Execute with exponential backoff on failure."""
454
+ for attempt in range(max_retries):
455
+ try:
456
+ result = await manager.execute_tool(tool_request)
457
+ return result
458
+
459
+ except ToolExecutionException as e:
460
+ wait_time = 2 ** attempt # 1s, 2s, 4s...
461
+
462
+ if attempt < max_retries - 1:
463
+ logger.warning(
464
+ f"Tool execution failed (attempt {attempt + 1}/{max_retries}): {e.error_details}"
465
+ )
466
+ logger.info(f"Retrying in {wait_time} seconds...")
467
+ await asyncio.sleep(wait_time)
468
+ else:
469
+ logger.error(f"Tool execution failed after {max_retries} attempts")
470
+ raise
471
+ ```
472
+
473
+ ### Pattern 4: Graceful Degradation
474
+
475
+ Provide fallback behavior:
476
+
477
+ ```python
478
+ from massgen.tool._exceptions import ToolNotFoundException
479
+
480
+ async def execute_with_fallback(manager, primary_tool, fallback_tool, args):
481
+ """Try primary tool, fall back to alternative if not found."""
482
+ try:
483
+ # Try primary tool
484
+ result = await manager.execute_tool({
485
+ "name": primary_tool,
486
+ "input": args
487
+ })
488
+ return result
489
+
490
+ except ToolNotFoundException:
491
+ logger.warning(f"Primary tool '{primary_tool}' not found, using fallback")
492
+
493
+ # Try fallback
494
+ try:
495
+ result = await manager.execute_tool({
496
+ "name": fallback_tool,
497
+ "input": args
498
+ })
499
+ return result
500
+
501
+ except ToolNotFoundException:
502
+ logger.error(f"Neither '{primary_tool}' nor '{fallback_tool}' available")
503
+ # Return error result
504
+ return ExecutionResult(
505
+ output_blocks=[TextContent(data="No suitable tool available")]
506
+ )
507
+ ```
508
+
509
+ ### Pattern 5: Validation Before Execution
510
+
511
+ Prevent errors proactively:
512
+
513
+ ```python
514
+ from massgen.tool._exceptions import InvalidToolArgumentsException
515
+
516
+ def validate_tool_request(manager, tool_request):
517
+ """Validate tool request before execution."""
518
+ tool_name = tool_request.get("name")
519
+ tool_input = tool_request.get("input", {})
520
+
521
+ # Check tool exists
522
+ if tool_name not in manager.registered_tools:
523
+ raise ToolNotFoundException(tool_name)
524
+
525
+ # Get tool schema
526
+ tool_entry = manager.registered_tools[tool_name]
527
+ schema = tool_entry.schema_def["function"]["parameters"]
528
+
529
+ # Check required parameters
530
+ required = schema.get("required", [])
531
+ missing = [p for p in required if p not in tool_input]
532
+
533
+ if missing:
534
+ raise InvalidToolArgumentsException(
535
+ f"Missing required parameters: {', '.join(missing)}"
536
+ )
537
+
538
+ # Check parameter types
539
+ properties = schema.get("properties", {})
540
+ for param_name, param_value in tool_input.items():
541
+ if param_name in properties:
542
+ expected_type = properties[param_name].get("type")
543
+ actual_type = type(param_value).__name__
544
+
545
+ # Simple type checking (can be more sophisticated)
546
+ if expected_type == "string" and not isinstance(param_value, str):
547
+ raise InvalidToolArgumentsException(
548
+ f"Parameter '{param_name}' must be string, got {actual_type}"
549
+ )
550
+
551
+ return True
552
+
553
+ # Usage
554
+ try:
555
+ validate_tool_request(manager, tool_request)
556
+ result = await manager.execute_tool(tool_request)
557
+ except ToolException as e:
558
+ print(f"Validation or execution failed: {e}")
559
+ ```
560
+
561
+ ## Best Practices
562
+
563
+ ### 1. Be Specific
564
+
565
+ Catch specific exceptions when you can handle them differently:
566
+
567
+ ```python
568
+ # ❌ Too broad
569
+ try:
570
+ result = await manager.execute_tool(tool_request)
571
+ except Exception as e:
572
+ print("Something went wrong")
573
+
574
+ # ✅ Specific handling
575
+ try:
576
+ result = await manager.execute_tool(tool_request)
577
+ except InvalidToolArgumentsException as e:
578
+ fix_arguments(e.error_msg)
579
+ except ToolNotFoundException as e:
580
+ suggest_alternatives(e.tool_name)
581
+ except ToolExecutionException as e:
582
+ log_and_retry(e.tool_name, e.error_details)
583
+ ```
584
+
585
+ ### 2. Preserve Stack Traces
586
+
587
+ Use `exc_info=True` for debugging:
588
+
589
+ ```python
590
+ import logging
591
+
592
+ try:
593
+ result = await manager.execute_tool(tool_request)
594
+ except ToolException as e:
595
+ # Logs full stack trace
596
+ logger.error("Tool execution failed", exc_info=True)
597
+ ```
598
+
599
+ ### 3. Provide Context
600
+
601
+ Add context to error messages:
602
+
603
+ ```python
604
+ try:
605
+ result = await manager.execute_tool(tool_request)
606
+ except ToolExecutionException as e:
607
+ # Add context
608
+ enhanced_message = (
609
+ f"Failed to execute tool for task '{task_id}'\n"
610
+ f"Tool: {e.tool_name}\n"
611
+ f"Error: {e.error_details}\n"
612
+ f"Request: {tool_request}"
613
+ )
614
+ logger.error(enhanced_message)
615
+ ```
616
+
617
+ ### 4. Don't Swallow Exceptions
618
+
619
+ Always handle or re-raise:
620
+
621
+ ```python
622
+ # ❌ Silently ignores errors
623
+ try:
624
+ result = await manager.execute_tool(tool_request)
625
+ except ToolException:
626
+ pass # Bad! Error is lost
627
+
628
+ # ✅ Log and handle
629
+ try:
630
+ result = await manager.execute_tool(tool_request)
631
+ except ToolException as e:
632
+ logger.error(f"Tool failed: {e}")
633
+ result = ExecutionResult(
634
+ output_blocks=[TextContent(data=f"Error: {e}")]
635
+ )
636
+ ```
637
+
638
+ ### 5. Use Finally for Cleanup
639
+
640
+ Ensure resources are cleaned up:
641
+
642
+ ```python
643
+ resource = None
644
+ try:
645
+ resource = acquire_resource()
646
+ result = await manager.execute_tool(tool_request)
647
+ except ToolException as e:
648
+ logger.error(f"Tool failed: {e}")
649
+ raise
650
+ finally:
651
+ if resource:
652
+ resource.cleanup()
653
+ ```
654
+
655
+ ## Complete Example
656
+
657
+ ```python
658
+ import logging
659
+ import asyncio
660
+ from typing import Optional
661
+ from massgen.tool import ToolManager, ExecutionResult, TextContent
662
+ from massgen.tool._exceptions import (
663
+ ToolException,
664
+ InvalidToolArgumentsException,
665
+ ToolNotFoundException,
666
+ ToolExecutionException,
667
+ CategoryNotFoundException
668
+ )
669
+
670
+ logger = logging.getLogger(__name__)
671
+
672
+ class RobustToolExecutor:
673
+ """Robust tool executor with comprehensive error handling."""
674
+
675
+ def __init__(self, manager: ToolManager):
676
+ self.manager = manager
677
+ self.retry_count = 3
678
+ self.retry_delay = 1.0
679
+
680
+ async def execute(
681
+ self,
682
+ tool_request: dict,
683
+ fallback_result: Optional[ExecutionResult] = None
684
+ ) -> ExecutionResult:
685
+ """Execute tool with robust error handling."""
686
+
687
+ # Validate request
688
+ if not self._validate_request(tool_request):
689
+ return ExecutionResult(
690
+ output_blocks=[TextContent(data="Invalid tool request format")]
691
+ )
692
+
693
+ # Try execution with retry
694
+ for attempt in range(self.retry_count):
695
+ try:
696
+ result = await self._execute_with_timeout(tool_request)
697
+ logger.info(f"Tool execution succeeded on attempt {attempt + 1}")
698
+ return result
699
+
700
+ except InvalidToolArgumentsException as e:
701
+ logger.error(f"Invalid arguments: {e.error_msg}")
702
+ return ExecutionResult(
703
+ output_blocks=[TextContent(data=f"Argument error: {e.error_msg}")]
704
+ )
705
+
706
+ except ToolNotFoundException as e:
707
+ logger.error(f"Tool not found: {e.tool_name}")
708
+ alternatives = self._find_similar_tools(e.tool_name)
709
+ msg = f"Tool '{e.tool_name}' not found."
710
+ if alternatives:
711
+ msg += f" Similar tools: {', '.join(alternatives)}"
712
+ return ExecutionResult(
713
+ output_blocks=[TextContent(data=msg)]
714
+ )
715
+
716
+ except ToolExecutionException as e:
717
+ if attempt < self.retry_count - 1:
718
+ logger.warning(
719
+ f"Execution failed (attempt {attempt + 1}): {e.error_details}"
720
+ )
721
+ await asyncio.sleep(self.retry_delay * (2 ** attempt))
722
+ else:
723
+ logger.error(f"Execution failed after {self.retry_count} attempts")
724
+ if fallback_result:
725
+ logger.info("Using fallback result")
726
+ return fallback_result
727
+ return ExecutionResult(
728
+ output_blocks=[TextContent(data=f"Execution error: {e.error_details}")]
729
+ )
730
+
731
+ except Exception as e:
732
+ logger.exception("Unexpected error during tool execution")
733
+ return ExecutionResult(
734
+ output_blocks=[TextContent(data=f"Unexpected error: {str(e)}")]
735
+ )
736
+
737
+ async def _execute_with_timeout(
738
+ self,
739
+ tool_request: dict,
740
+ timeout: float = 30.0
741
+ ) -> ExecutionResult:
742
+ """Execute with timeout."""
743
+ try:
744
+ result = await asyncio.wait_for(
745
+ self.manager.execute_tool(tool_request).__anext__(),
746
+ timeout=timeout
747
+ )
748
+ return result
749
+ except asyncio.TimeoutError:
750
+ raise ToolExecutionException(
751
+ tool_request["name"],
752
+ f"Execution timed out after {timeout} seconds"
753
+ )
754
+
755
+ def _validate_request(self, tool_request: dict) -> bool:
756
+ """Validate tool request structure."""
757
+ return (
758
+ isinstance(tool_request, dict) and
759
+ "name" in tool_request and
760
+ isinstance(tool_request.get("input", {}), dict)
761
+ )
762
+
763
+ def _find_similar_tools(self, tool_name: str) -> list:
764
+ """Find similar tool names."""
765
+ all_tools = list(self.manager.registered_tools.keys())
766
+ similar = [
767
+ t for t in all_tools
768
+ if tool_name.lower() in t.lower() or t.lower() in tool_name.lower()
769
+ ]
770
+ return similar[:3] # Return top 3
771
+
772
+ # Usage
773
+ async def main():
774
+ manager = ToolManager()
775
+ executor = RobustToolExecutor(manager)
776
+
777
+ # Execute with full error handling
778
+ result = await executor.execute({
779
+ "name": "my_tool",
780
+ "input": {"arg1": "value1"}
781
+ })
782
+
783
+ print(result.output_blocks[0].data)
784
+
785
+ if __name__ == "__main__":
786
+ asyncio.run(main())
787
+ ```
788
+
789
+ ---
790
+
791
+ For more information, see:
792
+ - [ToolManager Documentation](manager.md)
793
+ - [ExecutionResult Documentation](execution_results.md)
794
+ - [Built-in Tools Guide](builtin_tools.md)