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,691 @@
1
+ # ExecutionResult Documentation
2
+
3
+ ## Overview
4
+
5
+ `ExecutionResult` is the standardized container for all tool execution outputs in the MassGen Tool System. It provides a structured way to return results from tools, supporting text, images, audio, streaming outputs, and metadata.
6
+
7
+ ## Core Concepts
8
+
9
+ **What is ExecutionResult?**
10
+ Think of ExecutionResult as a package that tools send back to agents. Like a delivery package, it contains:
11
+ - The main content (text, images, audio)
12
+ - Metadata (extra information about the result)
13
+ - Status flags (is it streaming? is it final? was it interrupted?)
14
+ - A unique ID for tracking
15
+
16
+ **Why use ExecutionResult?**
17
+ Instead of returning raw strings or objects, ExecutionResult provides:
18
+ - **Type Safety**: Structured format prevents errors
19
+ - **Multimodal Support**: Can include text, images, and audio together
20
+ - **Streaming**: Support for progressive results
21
+ - **Metadata**: Extra information without cluttering main content
22
+ - **Tracking**: Unique IDs for debugging and logging
23
+
24
+ ## Class Definition
25
+
26
+ ```python
27
+ from dataclasses import dataclass, field
28
+ from datetime import datetime
29
+ from typing import List, Optional, Union
30
+
31
+ @dataclass
32
+ class ExecutionResult:
33
+ """Result container for tool execution outputs."""
34
+
35
+ output_blocks: List[Union[TextContent, ImageContent, AudioContent]]
36
+ """The execution output blocks from the tool."""
37
+
38
+ meta_info: Optional[dict] = None
39
+ """Additional metadata accessible within the system."""
40
+
41
+ is_streaming: bool = False
42
+ """Indicates if the output is being streamed."""
43
+
44
+ is_final: bool = True
45
+ """Indicates if this is the final result in a stream."""
46
+
47
+ was_interrupted: bool = False
48
+ """Indicates if the execution was interrupted."""
49
+
50
+ result_id: str = field(default_factory=_generate_id)
51
+ """Unique identifier for this result."""
52
+ ```
53
+
54
+ ## Content Block Types
55
+
56
+ ### TextContent
57
+
58
+ **What it does**: Wraps text output from tools. This is the most common content type for returning strings, error messages, or formatted text.
59
+
60
+ **When to use it**: For any text-based output - results, logs, error messages, JSON strings, etc.
61
+
62
+ ```python
63
+ from massgen.tool import TextContent
64
+
65
+ # Simple text
66
+ text = TextContent(data="Hello, World!")
67
+
68
+ # Formatted output
69
+ text = TextContent(data="""
70
+ Results:
71
+ - Item 1: Success
72
+ - Item 2: Failed
73
+ - Item 3: Pending
74
+ """)
75
+
76
+ # JSON output
77
+ import json
78
+ data_dict = {"status": "ok", "count": 42}
79
+ text = TextContent(data=json.dumps(data_dict, indent=2))
80
+
81
+ # Error message
82
+ text = TextContent(data="Error: File not found at /path/to/file")
83
+
84
+ # Large text
85
+ with open("report.txt") as f:
86
+ text = TextContent(data=f.read())
87
+ ```
88
+
89
+ ### ImageContent
90
+
91
+ **What it does**: Wraps image data (typically base64-encoded) from tools that generate or process images.
92
+
93
+ **When to use it**: For image generation, image processing results, charts, visualizations, or any visual output.
94
+
95
+ ```python
96
+ from massgen.tool import ImageContent
97
+ import base64
98
+
99
+ # From file
100
+ with open("chart.png", "rb") as f:
101
+ image_data = base64.b64encode(f.read()).decode()
102
+ image = ImageContent(data=image_data)
103
+
104
+ # From generation API
105
+ # Assume dalle.generate() returns base64 image
106
+ image_data = await dalle.generate("A sunset over mountains")
107
+ image = ImageContent(data=image_data)
108
+
109
+ # Multiple images
110
+ images = [
111
+ ImageContent(data=encode_image("image1.png")),
112
+ ImageContent(data=encode_image("image2.png")),
113
+ ImageContent(data=encode_image("image3.png"))
114
+ ]
115
+ ```
116
+
117
+ ### AudioContent
118
+
119
+ **What it does**: Wraps audio data (typically base64-encoded) from tools that generate or process audio.
120
+
121
+ **When to use it**: For text-to-speech, audio generation, audio processing, or any audio output.
122
+
123
+ ```python
124
+ from massgen.tool import AudioContent
125
+ import base64
126
+
127
+ # From file
128
+ with open("speech.mp3", "rb") as f:
129
+ audio_data = base64.b64encode(f.read()).decode()
130
+ audio = AudioContent(data=audio_data)
131
+
132
+ # From TTS API
133
+ audio_data = await tts.synthesize("Hello, World!")
134
+ audio = AudioContent(data=audio_data)
135
+
136
+ # Podcast episode
137
+ with open("episode_1.mp3", "rb") as f:
138
+ audio = AudioContent(data=base64.b64encode(f.read()).decode())
139
+ ```
140
+
141
+ ## Creating ExecutionResults
142
+
143
+ ### Simple Text Result
144
+
145
+ ```python
146
+ from massgen.tool import ExecutionResult, TextContent
147
+
148
+ # Basic success result
149
+ result = ExecutionResult(
150
+ output_blocks=[TextContent(data="Operation completed successfully")]
151
+ )
152
+
153
+ # With metadata
154
+ result = ExecutionResult(
155
+ output_blocks=[TextContent(data="Processed 100 items")],
156
+ meta_info={"processed_count": 100, "duration_ms": 1250}
157
+ )
158
+ ```
159
+
160
+ ### Multimodal Result
161
+
162
+ ```python
163
+ from massgen.tool import ExecutionResult, TextContent, ImageContent
164
+
165
+ # Text + Image
166
+ result = ExecutionResult(
167
+ output_blocks=[
168
+ TextContent(data="Generated chart for dataset:"),
169
+ ImageContent(data=chart_image_base64),
170
+ TextContent(data="Analysis complete. See chart above.")
171
+ ],
172
+ meta_info={"chart_type": "bar", "data_points": 20}
173
+ )
174
+
175
+ # Multiple images with descriptions
176
+ result = ExecutionResult(
177
+ output_blocks=[
178
+ TextContent(data="Comparison results:"),
179
+ TextContent(data="Original image:"),
180
+ ImageContent(data=original_image),
181
+ TextContent(data="Processed image:"),
182
+ ImageContent(data=processed_image),
183
+ TextContent(data="Difference highlighted in red")
184
+ ]
185
+ )
186
+ ```
187
+
188
+ ### Error Result
189
+
190
+ ```python
191
+ # Simple error
192
+ error_result = ExecutionResult(
193
+ output_blocks=[TextContent(data="Error: File not found")]
194
+ )
195
+
196
+ # Detailed error with metadata
197
+ error_result = ExecutionResult(
198
+ output_blocks=[
199
+ TextContent(data="Error: Connection timeout"),
200
+ TextContent(data="Failed to connect to server after 3 attempts")
201
+ ],
202
+ meta_info={
203
+ "error_type": "ConnectionTimeout",
204
+ "attempts": 3,
205
+ "timeout_seconds": 30
206
+ }
207
+ )
208
+
209
+ # Exception formatting
210
+ try:
211
+ risky_operation()
212
+ except Exception as e:
213
+ error_result = ExecutionResult(
214
+ output_blocks=[
215
+ TextContent(data=f"Error: {type(e).__name__}"),
216
+ TextContent(data=f"Details: {str(e)}")
217
+ ],
218
+ meta_info={"traceback": traceback.format_exc()}
219
+ )
220
+ ```
221
+
222
+ ## Streaming Results
223
+
224
+ ### Basic Streaming
225
+
226
+ **What it does**: Allows tools to send results progressively rather than all at once. Useful for long-running operations where you want to show progress.
227
+
228
+ **Why use it**: Users see progress immediately instead of waiting for completion. Better user experience for slow operations.
229
+
230
+ ```python
231
+ from typing import AsyncGenerator
232
+ from massgen.tool import ExecutionResult, TextContent
233
+ import asyncio
234
+
235
+ async def streaming_tool() -> AsyncGenerator[ExecutionResult, None]:
236
+ """Tool that streams progress updates."""
237
+
238
+ # Start
239
+ yield ExecutionResult(
240
+ output_blocks=[TextContent(data="Starting process...")],
241
+ is_streaming=True,
242
+ is_final=False
243
+ )
244
+
245
+ # Progress updates
246
+ for i in range(1, 6):
247
+ await asyncio.sleep(1) # Simulate work
248
+ yield ExecutionResult(
249
+ output_blocks=[TextContent(data=f"Processing step {i}/5...")],
250
+ is_streaming=True,
251
+ is_final=False
252
+ )
253
+
254
+ # Final result
255
+ yield ExecutionResult(
256
+ output_blocks=[TextContent(data="Process complete!")],
257
+ is_streaming=True,
258
+ is_final=True # Mark as final
259
+ )
260
+
261
+ # Usage
262
+ async for result in streaming_tool():
263
+ print(result.output_blocks[0].data)
264
+ if result.is_final:
265
+ print("Done!")
266
+ ```
267
+
268
+ ### Streaming with Accumulation
269
+
270
+ ```python
271
+ async def accumulating_stream() -> AsyncGenerator[ExecutionResult, None]:
272
+ """Stream results that build on each other."""
273
+
274
+ accumulated = []
275
+
276
+ for i in range(5):
277
+ await asyncio.sleep(0.5)
278
+ accumulated.append(f"Item {i+1}")
279
+
280
+ # Send accumulated results each time
281
+ yield ExecutionResult(
282
+ output_blocks=[
283
+ TextContent(data=f"Found {len(accumulated)} items so far:"),
284
+ TextContent(data="\n".join(accumulated))
285
+ ],
286
+ is_streaming=True,
287
+ is_final=(i == 4),
288
+ meta_info={"total_items": len(accumulated)}
289
+ )
290
+ ```
291
+
292
+ ### Streaming with Status
293
+
294
+ ```python
295
+ async def status_streaming() -> AsyncGenerator[ExecutionResult, None]:
296
+ """Stream with different status updates."""
297
+
298
+ phases = [
299
+ ("Initializing", 2),
300
+ ("Processing", 3),
301
+ ("Validating", 2),
302
+ ("Finalizing", 1)
303
+ ]
304
+
305
+ for phase_name, duration in phases:
306
+ yield ExecutionResult(
307
+ output_blocks=[TextContent(data=f"Phase: {phase_name}")],
308
+ is_streaming=True,
309
+ is_final=False,
310
+ meta_info={"current_phase": phase_name}
311
+ )
312
+
313
+ await asyncio.sleep(duration)
314
+
315
+ # Final
316
+ yield ExecutionResult(
317
+ output_blocks=[TextContent(data="All phases complete")],
318
+ is_streaming=True,
319
+ is_final=True,
320
+ meta_info={"total_phases": len(phases)}
321
+ )
322
+ ```
323
+
324
+ ## Handling Interruption
325
+
326
+ **What it does**: The `was_interrupted` flag indicates that execution was cancelled or stopped early.
327
+
328
+ **Why use it**: Allows tools to signal that they didn't complete normally, helping agents understand partial results.
329
+
330
+ ```python
331
+ async def interruptible_tool() -> AsyncGenerator[ExecutionResult, None]:
332
+ """Tool that can be cancelled."""
333
+
334
+ try:
335
+ for i in range(100):
336
+ await asyncio.sleep(0.1)
337
+
338
+ yield ExecutionResult(
339
+ output_blocks=[TextContent(data=f"Progress: {i}%")],
340
+ is_streaming=True,
341
+ is_final=False
342
+ )
343
+
344
+ except asyncio.CancelledError:
345
+ # Handle cancellation gracefully
346
+ yield ExecutionResult(
347
+ output_blocks=[
348
+ TextContent(data="<system>Operation was cancelled</system>")
349
+ ],
350
+ is_streaming=True,
351
+ is_final=True,
352
+ was_interrupted=True # Mark as interrupted
353
+ )
354
+ raise # Re-raise to propagate cancellation
355
+
356
+ # Usage
357
+ import asyncio
358
+
359
+ async def run_with_timeout():
360
+ gen = interruptible_tool()
361
+ try:
362
+ async for result in gen:
363
+ print(result.output_blocks[0].data)
364
+ # Simulate timeout after 3 results
365
+ if "Progress: 2%" in result.output_blocks[0].data:
366
+ raise asyncio.TimeoutError()
367
+ except asyncio.TimeoutError:
368
+ # Generator will send interrupted result
369
+ pass
370
+ ```
371
+
372
+ ## Metadata Usage
373
+
374
+ **What it does**: `meta_info` stores additional data that shouldn't be shown directly to users but is useful for logging, debugging, or system processing.
375
+
376
+ **Why use it**: Separate user-facing content from system-level information. Keep output clean while preserving detailed data.
377
+
378
+ ### Common Metadata Patterns
379
+
380
+ ```python
381
+ # Execution metrics
382
+ result = ExecutionResult(
383
+ output_blocks=[TextContent(data="Analysis complete")],
384
+ meta_info={
385
+ "execution_time_ms": 1234,
386
+ "memory_used_mb": 45,
387
+ "api_calls": 3
388
+ }
389
+ )
390
+
391
+ # Data provenance
392
+ result = ExecutionResult(
393
+ output_blocks=[TextContent(data="Data retrieved")],
394
+ meta_info={
395
+ "source": "database",
396
+ "query": "SELECT * FROM users",
397
+ "row_count": 100,
398
+ "timestamp": datetime.now().isoformat()
399
+ }
400
+ )
401
+
402
+ # Model information
403
+ result = ExecutionResult(
404
+ output_blocks=[ImageContent(data=image_data)],
405
+ meta_info={
406
+ "model": "dall-e-3",
407
+ "prompt": "a sunset",
408
+ "size": "1024x1024",
409
+ "revised_prompt": "a beautiful sunset over mountains"
410
+ }
411
+ )
412
+
413
+ # Error details
414
+ result = ExecutionResult(
415
+ output_blocks=[TextContent(data="Error: Request failed")],
416
+ meta_info={
417
+ "error_code": "TIMEOUT",
418
+ "retry_count": 3,
419
+ "last_error": "Connection timeout after 30s",
420
+ "failed_at": datetime.now().isoformat()
421
+ }
422
+ )
423
+
424
+ # Processing statistics
425
+ result = ExecutionResult(
426
+ output_blocks=[TextContent(data="Processed dataset")],
427
+ meta_info={
428
+ "input_rows": 1000,
429
+ "output_rows": 950,
430
+ "filtered_rows": 50,
431
+ "null_values_removed": 25,
432
+ "duplicates_removed": 25,
433
+ "processing_stages": ["clean", "filter", "deduplicate"]
434
+ }
435
+ )
436
+ ```
437
+
438
+ ## Result ID Tracking
439
+
440
+ **What it does**: Each ExecutionResult gets a unique ID automatically generated from timestamp and random components.
441
+
442
+ **Why use it**: Track results through the system, correlate logs, debug issues, and identify specific execution instances.
443
+
444
+ ```python
445
+ # Automatic ID generation
446
+ result1 = ExecutionResult(output_blocks=[TextContent(data="Result 1")])
447
+ result2 = ExecutionResult(output_blocks=[TextContent(data="Result 2")])
448
+
449
+ print(f"Result 1 ID: {result1.result_id}")
450
+ print(f"Result 2 ID: {result2.result_id}")
451
+ # Output:
452
+ # Result 1 ID: 20240315_143022_123456
453
+ # Result 2 ID: 20240315_143022_123457
454
+
455
+ # Use for logging
456
+ import logging
457
+
458
+ logger = logging.getLogger(__name__)
459
+
460
+ async def logged_tool() -> ExecutionResult:
461
+ result = ExecutionResult(
462
+ output_blocks=[TextContent(data="Operation complete")]
463
+ )
464
+
465
+ logger.info(f"Tool execution complete - ID: {result.result_id}")
466
+ logger.debug(f"Result details: {result.output_blocks[0].data}")
467
+
468
+ return result
469
+
470
+ # Use for tracking
471
+ execution_history = {}
472
+
473
+ async def tracked_tool() -> ExecutionResult:
474
+ result = ExecutionResult(
475
+ output_blocks=[TextContent(data="Processed")],
476
+ meta_info={"processed_items": 100}
477
+ )
478
+
479
+ # Store in history
480
+ execution_history[result.result_id] = {
481
+ "timestamp": datetime.now(),
482
+ "output": result.output_blocks[0].data,
483
+ "metadata": result.meta_info
484
+ }
485
+
486
+ return result
487
+ ```
488
+
489
+ ## Complete Examples
490
+
491
+ ### Example 1: Data Processing Tool
492
+
493
+ ```python
494
+ from massgen.tool import ExecutionResult, TextContent
495
+ import json
496
+
497
+ async def analyze_csv(file_path: str, column: str) -> ExecutionResult:
498
+ """Analyze a CSV file column.
499
+
500
+ Args:
501
+ file_path: Path to CSV file
502
+ column: Column to analyze
503
+
504
+ Returns:
505
+ ExecutionResult with statistics
506
+ """
507
+ # Load data (simplified)
508
+ import pandas as pd
509
+ df = pd.read_csv(file_path)
510
+
511
+ # Calculate statistics
512
+ stats = {
513
+ "mean": df[column].mean(),
514
+ "median": df[column].median(),
515
+ "std": df[column].std(),
516
+ "min": df[column].min(),
517
+ "max": df[column].max()
518
+ }
519
+
520
+ # Format output
521
+ output = f"Statistics for column '{column}':\n"
522
+ output += json.dumps(stats, indent=2)
523
+
524
+ return ExecutionResult(
525
+ output_blocks=[TextContent(data=output)],
526
+ meta_info={
527
+ "file": file_path,
528
+ "column": column,
529
+ "row_count": len(df),
530
+ "null_count": df[column].isnull().sum()
531
+ }
532
+ )
533
+ ```
534
+
535
+ ### Example 2: Image Generation Tool
536
+
537
+ ```python
538
+ from massgen.tool import ExecutionResult, TextContent, ImageContent
539
+ import base64
540
+
541
+ async def generate_chart(
542
+ data: list,
543
+ chart_type: str = "bar"
544
+ ) -> ExecutionResult:
545
+ """Generate a chart from data.
546
+
547
+ Args:
548
+ data: Data points for chart
549
+ chart_type: Type of chart (bar, line, pie)
550
+
551
+ Returns:
552
+ ExecutionResult with chart image
553
+ """
554
+ # Generate chart (simplified)
555
+ import matplotlib.pyplot as plt
556
+ import io
557
+
558
+ fig, ax = plt.subplots()
559
+
560
+ if chart_type == "bar":
561
+ ax.bar(range(len(data)), data)
562
+ elif chart_type == "line":
563
+ ax.plot(range(len(data)), data)
564
+
565
+ # Save to bytes
566
+ buf = io.BytesIO()
567
+ plt.savefig(buf, format='png')
568
+ buf.seek(0)
569
+ image_data = base64.b64encode(buf.read()).decode()
570
+
571
+ plt.close()
572
+
573
+ return ExecutionResult(
574
+ output_blocks=[
575
+ TextContent(data=f"Generated {chart_type} chart with {len(data)} points"),
576
+ ImageContent(data=image_data)
577
+ ],
578
+ meta_info={
579
+ "chart_type": chart_type,
580
+ "data_points": len(data),
581
+ "min_value": min(data),
582
+ "max_value": max(data)
583
+ }
584
+ )
585
+ ```
586
+
587
+ ### Example 3: Streaming File Processor
588
+
589
+ ```python
590
+ from typing import AsyncGenerator
591
+ from massgen.tool import ExecutionResult, TextContent
592
+ import asyncio
593
+
594
+ async def process_large_file(
595
+ file_path: str,
596
+ operation: str
597
+ ) -> AsyncGenerator[ExecutionResult, None]:
598
+ """Process a large file with progress updates.
599
+
600
+ Args:
601
+ file_path: Path to file
602
+ operation: Operation to perform (count, validate, transform)
603
+
604
+ Yields:
605
+ ExecutionResult objects with progress
606
+ """
607
+ # Open file
608
+ with open(file_path, 'r') as f:
609
+ lines = f.readlines()
610
+ total = len(lines)
611
+
612
+ # Initial status
613
+ yield ExecutionResult(
614
+ output_blocks=[TextContent(data=f"Processing {total} lines...")],
615
+ is_streaming=True,
616
+ is_final=False
617
+ )
618
+
619
+ # Process in batches
620
+ batch_size = 1000
621
+ processed = 0
622
+
623
+ for i in range(0, total, batch_size):
624
+ batch = lines[i:i+batch_size]
625
+
626
+ # Simulate processing
627
+ await asyncio.sleep(0.1)
628
+ processed += len(batch)
629
+
630
+ # Progress update
631
+ progress = int((processed / total) * 100)
632
+ yield ExecutionResult(
633
+ output_blocks=[
634
+ TextContent(data=f"Progress: {processed}/{total} lines ({progress}%)")
635
+ ],
636
+ is_streaming=True,
637
+ is_final=False,
638
+ meta_info={"processed_lines": processed, "total_lines": total}
639
+ )
640
+
641
+ # Final result
642
+ yield ExecutionResult(
643
+ output_blocks=[
644
+ TextContent(data=f"Processing complete! Processed {total} lines")
645
+ ],
646
+ is_streaming=True,
647
+ is_final=True,
648
+ meta_info={
649
+ "operation": operation,
650
+ "total_lines": total,
651
+ "file_path": file_path
652
+ }
653
+ )
654
+ ```
655
+
656
+ ## Best Practices
657
+
658
+ ### Content Organization
659
+
660
+ 1. **Use Multiple Blocks**: Separate different types of content
661
+ 2. **Clear Descriptions**: Precede images/audio with descriptive text
662
+ 3. **Structured Output**: Use formatting for readability
663
+ 4. **Error Context**: Include context with error messages
664
+
665
+ ### Metadata Guidelines
666
+
667
+ 1. **Don't Duplicate**: Don't repeat output_blocks content in meta_info
668
+ 2. **Use for System Data**: Metrics, timestamps, internal IDs
669
+ 3. **Keep JSON-Serializable**: Only basic types (str, int, float, bool, list, dict)
670
+ 4. **Meaningful Keys**: Use clear, descriptive key names
671
+
672
+ ### Streaming Best Practices
673
+
674
+ 1. **Regular Updates**: Send updates frequently enough to show progress
675
+ 2. **Mark Final**: Always set `is_final=True` on last result
676
+ 3. **Handle Cancellation**: Use try/except for asyncio.CancelledError
677
+ 4. **Meaningful Progress**: Show actual progress, not just "working..."
678
+
679
+ ### Error Handling
680
+
681
+ 1. **Structured Errors**: Use consistent error message format
682
+ 2. **Include Details**: Provide context for debugging
683
+ 3. **Use Metadata**: Store stack traces and error codes in meta_info
684
+ 4. **User-Friendly**: Main message should be clear to non-technical users
685
+
686
+ ---
687
+
688
+ For more information, see:
689
+ - [ToolManager Documentation](manager.md)
690
+ - [Built-in Tools Guide](builtin_tools.md)
691
+ - [Workflow Toolkits](workflow_toolkits.md)