massgen 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of massgen might be problematic. Click here for more details.
- massgen/__init__.py +1 -1
- massgen/agent_config.py +33 -7
- massgen/api_params_handler/_api_params_handler_base.py +3 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +4 -0
- massgen/api_params_handler/_claude_api_params_handler.py +4 -0
- massgen/api_params_handler/_gemini_api_params_handler.py +4 -0
- massgen/api_params_handler/_response_api_params_handler.py +4 -0
- massgen/backend/azure_openai.py +9 -1
- massgen/backend/base.py +4 -0
- massgen/backend/base_with_custom_tool_and_mcp.py +25 -5
- massgen/backend/claude_code.py +9 -1
- massgen/backend/docs/permissions_and_context_files.md +2 -2
- massgen/backend/gemini.py +35 -6
- massgen/backend/gemini_utils.py +30 -0
- massgen/backend/response.py +2 -0
- massgen/chat_agent.py +9 -3
- massgen/cli.py +291 -43
- massgen/config_builder.py +163 -18
- massgen/configs/README.md +69 -14
- massgen/configs/debug/restart_test_controlled.yaml +60 -0
- massgen/configs/debug/restart_test_controlled_filesystem.yaml +73 -0
- massgen/configs/tools/code-execution/docker_with_sudo.yaml +35 -0
- massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +56 -0
- massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +65 -0
- massgen/configs/tools/custom_tools/computer_use_example.yaml +50 -0
- massgen/configs/tools/custom_tools/crawl4ai_example.yaml +55 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_multi.yaml +61 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_single.yaml +29 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_multi.yaml +51 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_single.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_multi.yaml +55 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_single.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_multi.yaml +47 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_single.yaml +29 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_audio.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_file.yaml +34 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_image.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_video.yaml +34 -0
- massgen/configs/tools/custom_tools/multimodal_tools/youtube_video_analysis.yaml +59 -0
- massgen/docker/README.md +83 -0
- massgen/filesystem_manager/_code_execution_server.py +22 -7
- massgen/filesystem_manager/_docker_manager.py +21 -1
- massgen/filesystem_manager/_filesystem_manager.py +9 -0
- massgen/filesystem_manager/_path_permission_manager.py +148 -0
- massgen/filesystem_manager/_workspace_tools_server.py +0 -997
- massgen/formatter/_gemini_formatter.py +73 -0
- massgen/frontend/coordination_ui.py +175 -257
- massgen/frontend/displays/base_display.py +29 -0
- massgen/frontend/displays/rich_terminal_display.py +155 -9
- massgen/frontend/displays/simple_display.py +21 -0
- massgen/frontend/displays/terminal_display.py +22 -2
- massgen/logger_config.py +50 -6
- massgen/message_templates.py +283 -15
- massgen/orchestrator.py +335 -38
- massgen/tests/test_binary_file_blocking.py +274 -0
- massgen/tests/test_case_studies.md +12 -12
- massgen/tests/test_code_execution.py +178 -0
- massgen/tests/test_multimodal_size_limits.py +407 -0
- massgen/tests/test_orchestration_restart.py +204 -0
- massgen/tool/__init__.py +4 -0
- massgen/tool/_manager.py +7 -2
- massgen/tool/_multimodal_tools/image_to_image_generation.py +293 -0
- massgen/tool/_multimodal_tools/text_to_file_generation.py +455 -0
- massgen/tool/_multimodal_tools/text_to_image_generation.py +222 -0
- massgen/tool/_multimodal_tools/text_to_speech_continue_generation.py +226 -0
- massgen/tool/_multimodal_tools/text_to_speech_transcription_generation.py +217 -0
- massgen/tool/_multimodal_tools/text_to_video_generation.py +223 -0
- massgen/tool/_multimodal_tools/understand_audio.py +211 -0
- massgen/tool/_multimodal_tools/understand_file.py +555 -0
- massgen/tool/_multimodal_tools/understand_image.py +316 -0
- massgen/tool/_multimodal_tools/understand_video.py +340 -0
- massgen/tool/_web_tools/crawl4ai_tool.py +718 -0
- massgen/tool/docs/multimodal_tools.md +1368 -0
- massgen/tool/workflow_toolkits/__init__.py +26 -0
- massgen/tool/workflow_toolkits/post_evaluation.py +216 -0
- massgen/utils.py +1 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/METADATA +101 -69
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/RECORD +82 -46
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/WHEEL +0 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.2.dist-info → massgen-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Create variations based on multiple input images using OpenAI's gpt-4.1 API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Optional
|
|
12
|
+
|
|
13
|
+
from dotenv import load_dotenv
|
|
14
|
+
from openai import OpenAI
|
|
15
|
+
|
|
16
|
+
from massgen.tool._result import ExecutionResult, TextContent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _validate_path_access(path: Path, allowed_paths: Optional[List[Path]] = None) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Validate that a path is within allowed directories.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
path: Path to validate
|
|
25
|
+
allowed_paths: List of allowed base paths (optional)
|
|
26
|
+
agent_cwd: Agent\'s current working directory (automatically injected)
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If path is not within allowed directories
|
|
30
|
+
"""
|
|
31
|
+
if not allowed_paths:
|
|
32
|
+
return # No restrictions
|
|
33
|
+
|
|
34
|
+
for allowed_path in allowed_paths:
|
|
35
|
+
try:
|
|
36
|
+
path.relative_to(allowed_path)
|
|
37
|
+
return # Path is within this allowed directory
|
|
38
|
+
except ValueError:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
raise ValueError(f"Path not in allowed directories: {path}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def image_to_image_generation(
|
|
45
|
+
base_image_paths: List[str],
|
|
46
|
+
prompt: str = "Create a variation of the provided images",
|
|
47
|
+
model: str = "gpt-4.1",
|
|
48
|
+
storage_path: Optional[str] = None,
|
|
49
|
+
allowed_paths: Optional[List[str]] = None,
|
|
50
|
+
agent_cwd: Optional[str] = None,
|
|
51
|
+
) -> ExecutionResult:
|
|
52
|
+
"""
|
|
53
|
+
Create variations based on multiple input images using OpenAI's gpt-4.1 API.
|
|
54
|
+
|
|
55
|
+
This tool generates image variations based on multiple base images using OpenAI's gpt-4.1 API
|
|
56
|
+
and saves them to the workspace with automatic organization.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
base_image_paths: List of paths to base images (PNG/JPEG files, less than 4MB)
|
|
60
|
+
- Relative path: Resolved relative to agent's workspace
|
|
61
|
+
- Absolute path: Must be within allowed directories
|
|
62
|
+
prompt: Text description for the variation (default: "Create a variation of the provided images")
|
|
63
|
+
model: Model to use (default: "gpt-4.1")
|
|
64
|
+
storage_path: Directory path where to save variations (optional)
|
|
65
|
+
- Relative path: Resolved relative to agent's workspace
|
|
66
|
+
- Absolute path: Must be within allowed directories
|
|
67
|
+
- None/empty: Saves to agent's workspace root
|
|
68
|
+
allowed_paths: List of allowed base paths for validation (optional)
|
|
69
|
+
agent_cwd: Agent\'s current working directory (automatically injected)
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
ExecutionResult containing:
|
|
73
|
+
- success: Whether operation succeeded
|
|
74
|
+
- operation: "generate_and_store_image_with_input_images"
|
|
75
|
+
- note: Note about usage
|
|
76
|
+
- images: List of generated images with file paths and metadata
|
|
77
|
+
- model: Model used for generation
|
|
78
|
+
- prompt: The prompt used
|
|
79
|
+
- total_images: Total number of images generated
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
generate_and_store_image_with_input_images(["cat.png", "dog.png"], "Combine these animals")
|
|
83
|
+
→ Generates a variation combining both images
|
|
84
|
+
|
|
85
|
+
generate_and_store_image_with_input_images(["art/logo.png", "art/icon.png"], "Create a unified design")
|
|
86
|
+
→ Generates variations based on both images
|
|
87
|
+
|
|
88
|
+
Security:
|
|
89
|
+
- Requires valid OpenAI API key
|
|
90
|
+
- Input images must be valid image files less than 4MB
|
|
91
|
+
- Files are saved to specified path within workspace
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
# Convert allowed_paths from strings to Path objects
|
|
95
|
+
allowed_paths_list = [Path(p) for p in allowed_paths] if allowed_paths else None
|
|
96
|
+
|
|
97
|
+
# Use agent_cwd if available, otherwise fall back to Path.cwd()
|
|
98
|
+
base_dir = Path(agent_cwd) if agent_cwd else Path.cwd()
|
|
99
|
+
|
|
100
|
+
# Load environment variables
|
|
101
|
+
script_dir = Path(__file__).parent.parent.parent.parent
|
|
102
|
+
env_path = script_dir / ".env"
|
|
103
|
+
if env_path.exists():
|
|
104
|
+
load_dotenv(env_path)
|
|
105
|
+
else:
|
|
106
|
+
load_dotenv()
|
|
107
|
+
|
|
108
|
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
|
109
|
+
|
|
110
|
+
if not openai_api_key:
|
|
111
|
+
result = {
|
|
112
|
+
"success": False,
|
|
113
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
114
|
+
"error": "OpenAI API key not found. Please set OPENAI_API_KEY in .env file or environment variable.",
|
|
115
|
+
}
|
|
116
|
+
return ExecutionResult(
|
|
117
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Initialize OpenAI client
|
|
121
|
+
client = OpenAI(api_key=openai_api_key)
|
|
122
|
+
|
|
123
|
+
# Prepare content list with prompt and images
|
|
124
|
+
content = [{"type": "input_text", "text": prompt}]
|
|
125
|
+
|
|
126
|
+
# Process and validate all input images
|
|
127
|
+
validated_paths = []
|
|
128
|
+
for image_path_str in base_image_paths:
|
|
129
|
+
# Resolve image path
|
|
130
|
+
if Path(image_path_str).is_absolute():
|
|
131
|
+
image_path = Path(image_path_str).resolve()
|
|
132
|
+
else:
|
|
133
|
+
image_path = (base_dir / image_path_str).resolve()
|
|
134
|
+
|
|
135
|
+
# Validate image path
|
|
136
|
+
_validate_path_access(image_path, allowed_paths_list)
|
|
137
|
+
|
|
138
|
+
if not image_path.exists():
|
|
139
|
+
result = {
|
|
140
|
+
"success": False,
|
|
141
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
142
|
+
"error": f"Image file does not exist: {image_path}",
|
|
143
|
+
}
|
|
144
|
+
return ExecutionResult(
|
|
145
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Allow both PNG and JPEG formats
|
|
149
|
+
if image_path.suffix.lower() not in [".png", ".jpg", ".jpeg"]:
|
|
150
|
+
result = {
|
|
151
|
+
"success": False,
|
|
152
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
153
|
+
"error": f"Image must be PNG or JPEG format: {image_path}",
|
|
154
|
+
}
|
|
155
|
+
return ExecutionResult(
|
|
156
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Check file size (must be less than 4MB)
|
|
160
|
+
file_size = image_path.stat().st_size
|
|
161
|
+
if file_size > 4 * 1024 * 1024:
|
|
162
|
+
result = {
|
|
163
|
+
"success": False,
|
|
164
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
165
|
+
"error": f"Image file too large (must be < 4MB): {image_path} is {file_size / (1024*1024):.2f}MB",
|
|
166
|
+
}
|
|
167
|
+
return ExecutionResult(
|
|
168
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
validated_paths.append(image_path)
|
|
172
|
+
|
|
173
|
+
# Read and encode image to base64
|
|
174
|
+
with open(image_path, "rb") as f:
|
|
175
|
+
image_data = f.read()
|
|
176
|
+
image_base64 = base64.b64encode(image_data).decode("utf-8")
|
|
177
|
+
|
|
178
|
+
# Determine MIME type
|
|
179
|
+
mime_type = "image/jpeg" if image_path.suffix.lower() in [".jpg", ".jpeg"] else "image/png"
|
|
180
|
+
|
|
181
|
+
# Add image to content
|
|
182
|
+
content.append(
|
|
183
|
+
{
|
|
184
|
+
"type": "input_image",
|
|
185
|
+
"image_url": f"data:{mime_type};base64,{image_base64}",
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Determine storage directory
|
|
190
|
+
if storage_path:
|
|
191
|
+
if Path(storage_path).is_absolute():
|
|
192
|
+
storage_dir = Path(storage_path).resolve()
|
|
193
|
+
else:
|
|
194
|
+
storage_dir = (base_dir / storage_path).resolve()
|
|
195
|
+
else:
|
|
196
|
+
storage_dir = base_dir
|
|
197
|
+
|
|
198
|
+
# Validate storage directory
|
|
199
|
+
_validate_path_access(storage_dir, allowed_paths_list)
|
|
200
|
+
storage_dir.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
# Generate variations using gpt-4.1 API with all images at once
|
|
204
|
+
response = client.responses.create(
|
|
205
|
+
model=model,
|
|
206
|
+
input=[
|
|
207
|
+
{
|
|
208
|
+
"role": "user",
|
|
209
|
+
"content": content,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
tools=[{"type": "image_generation"}],
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Extract image generation calls from response
|
|
216
|
+
image_generation_calls = [output for output in response.output if output.type == "image_generation_call"]
|
|
217
|
+
|
|
218
|
+
all_variations = []
|
|
219
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
220
|
+
|
|
221
|
+
# Process generated images
|
|
222
|
+
for idx, output in enumerate(image_generation_calls):
|
|
223
|
+
if hasattr(output, "result"):
|
|
224
|
+
image_base64 = output.result
|
|
225
|
+
image_bytes = base64.b64decode(image_base64)
|
|
226
|
+
|
|
227
|
+
# Generate filename
|
|
228
|
+
if len(image_generation_calls) > 1:
|
|
229
|
+
filename = f"variation_{idx+1}_{timestamp}.png"
|
|
230
|
+
else:
|
|
231
|
+
filename = f"variation_{timestamp}.png"
|
|
232
|
+
|
|
233
|
+
# Full file path
|
|
234
|
+
file_path = storage_dir / filename
|
|
235
|
+
|
|
236
|
+
# Save image
|
|
237
|
+
file_path.write_bytes(image_bytes)
|
|
238
|
+
|
|
239
|
+
all_variations.append(
|
|
240
|
+
{
|
|
241
|
+
"source_images": [str(p) for p in validated_paths],
|
|
242
|
+
"file_path": str(file_path),
|
|
243
|
+
"filename": filename,
|
|
244
|
+
"size": len(image_bytes),
|
|
245
|
+
"index": idx,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# If no images were generated, check for text response
|
|
250
|
+
if not all_variations:
|
|
251
|
+
text_outputs = [output.content for output in response.output if hasattr(output, "content")]
|
|
252
|
+
if text_outputs:
|
|
253
|
+
result = {
|
|
254
|
+
"success": False,
|
|
255
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
256
|
+
"error": f"No images generated. Response: {' '.join(text_outputs)}",
|
|
257
|
+
}
|
|
258
|
+
return ExecutionResult(
|
|
259
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
except Exception as api_error:
|
|
263
|
+
result = {
|
|
264
|
+
"success": False,
|
|
265
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
266
|
+
"error": f"OpenAI API error: {str(api_error)}",
|
|
267
|
+
}
|
|
268
|
+
return ExecutionResult(
|
|
269
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
result = {
|
|
273
|
+
"success": True,
|
|
274
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
275
|
+
"note": "If no input images were provided, you must use generate_and_store_image_no_input_images tool.",
|
|
276
|
+
"images": all_variations,
|
|
277
|
+
"model": model,
|
|
278
|
+
"prompt": prompt,
|
|
279
|
+
"total_images": len(all_variations),
|
|
280
|
+
}
|
|
281
|
+
return ExecutionResult(
|
|
282
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
result = {
|
|
287
|
+
"success": False,
|
|
288
|
+
"operation": "generate_and_store_image_with_input_images",
|
|
289
|
+
"error": f"Failed to generate variations: {str(e)}",
|
|
290
|
+
}
|
|
291
|
+
return ExecutionResult(
|
|
292
|
+
output_blocks=[TextContent(data=json.dumps(result, indent=2))],
|
|
293
|
+
)
|