amd-gaia 0.15.0__py3-none-any.whl → 0.15.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.
Files changed (185) hide show
  1. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
  2. amd_gaia-0.15.2.dist-info/RECORD +182 -0
  3. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
  4. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
  5. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
  6. gaia/__init__.py +29 -29
  7. gaia/agents/__init__.py +19 -19
  8. gaia/agents/base/__init__.py +9 -9
  9. gaia/agents/base/agent.py +2132 -2177
  10. gaia/agents/base/api_agent.py +119 -120
  11. gaia/agents/base/console.py +1967 -1841
  12. gaia/agents/base/errors.py +237 -237
  13. gaia/agents/base/mcp_agent.py +86 -86
  14. gaia/agents/base/tools.py +88 -83
  15. gaia/agents/blender/__init__.py +7 -0
  16. gaia/agents/blender/agent.py +553 -556
  17. gaia/agents/blender/agent_simple.py +133 -135
  18. gaia/agents/blender/app.py +211 -211
  19. gaia/agents/blender/app_simple.py +41 -41
  20. gaia/agents/blender/core/__init__.py +16 -16
  21. gaia/agents/blender/core/materials.py +506 -506
  22. gaia/agents/blender/core/objects.py +316 -316
  23. gaia/agents/blender/core/rendering.py +225 -225
  24. gaia/agents/blender/core/scene.py +220 -220
  25. gaia/agents/blender/core/view.py +146 -146
  26. gaia/agents/chat/__init__.py +9 -9
  27. gaia/agents/chat/agent.py +809 -835
  28. gaia/agents/chat/app.py +1065 -1058
  29. gaia/agents/chat/session.py +508 -508
  30. gaia/agents/chat/tools/__init__.py +15 -15
  31. gaia/agents/chat/tools/file_tools.py +96 -96
  32. gaia/agents/chat/tools/rag_tools.py +1744 -1729
  33. gaia/agents/chat/tools/shell_tools.py +437 -436
  34. gaia/agents/code/__init__.py +7 -7
  35. gaia/agents/code/agent.py +549 -549
  36. gaia/agents/code/cli.py +377 -0
  37. gaia/agents/code/models.py +135 -135
  38. gaia/agents/code/orchestration/__init__.py +24 -24
  39. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  40. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  41. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  42. gaia/agents/code/orchestration/factories/base.py +63 -63
  43. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  44. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  45. gaia/agents/code/orchestration/orchestrator.py +841 -841
  46. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  47. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  48. gaia/agents/code/orchestration/steps/base.py +188 -188
  49. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  50. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  51. gaia/agents/code/orchestration/steps/python.py +307 -307
  52. gaia/agents/code/orchestration/template_catalog.py +469 -469
  53. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  54. gaia/agents/code/orchestration/workflows/base.py +80 -80
  55. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  56. gaia/agents/code/orchestration/workflows/python.py +94 -94
  57. gaia/agents/code/prompts/__init__.py +11 -11
  58. gaia/agents/code/prompts/base_prompt.py +77 -77
  59. gaia/agents/code/prompts/code_patterns.py +2034 -2036
  60. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  61. gaia/agents/code/prompts/python_prompt.py +109 -109
  62. gaia/agents/code/schema_inference.py +365 -365
  63. gaia/agents/code/system_prompt.py +41 -41
  64. gaia/agents/code/tools/__init__.py +42 -42
  65. gaia/agents/code/tools/cli_tools.py +1138 -1138
  66. gaia/agents/code/tools/code_formatting.py +319 -319
  67. gaia/agents/code/tools/code_tools.py +769 -769
  68. gaia/agents/code/tools/error_fixing.py +1347 -1347
  69. gaia/agents/code/tools/external_tools.py +180 -180
  70. gaia/agents/code/tools/file_io.py +845 -845
  71. gaia/agents/code/tools/prisma_tools.py +190 -190
  72. gaia/agents/code/tools/project_management.py +1016 -1016
  73. gaia/agents/code/tools/testing.py +321 -321
  74. gaia/agents/code/tools/typescript_tools.py +122 -122
  75. gaia/agents/code/tools/validation_parsing.py +461 -461
  76. gaia/agents/code/tools/validation_tools.py +806 -806
  77. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  78. gaia/agents/code/validators/__init__.py +16 -16
  79. gaia/agents/code/validators/antipattern_checker.py +241 -241
  80. gaia/agents/code/validators/ast_analyzer.py +197 -197
  81. gaia/agents/code/validators/requirements_validator.py +145 -145
  82. gaia/agents/code/validators/syntax_validator.py +171 -171
  83. gaia/agents/docker/__init__.py +7 -7
  84. gaia/agents/docker/agent.py +643 -642
  85. gaia/agents/emr/__init__.py +8 -8
  86. gaia/agents/emr/agent.py +1504 -1506
  87. gaia/agents/emr/cli.py +1322 -1322
  88. gaia/agents/emr/constants.py +475 -475
  89. gaia/agents/emr/dashboard/__init__.py +4 -4
  90. gaia/agents/emr/dashboard/server.py +1972 -1974
  91. gaia/agents/jira/__init__.py +11 -11
  92. gaia/agents/jira/agent.py +894 -894
  93. gaia/agents/jira/jql_templates.py +299 -299
  94. gaia/agents/routing/__init__.py +7 -7
  95. gaia/agents/routing/agent.py +567 -570
  96. gaia/agents/routing/system_prompt.py +75 -75
  97. gaia/agents/summarize/__init__.py +11 -0
  98. gaia/agents/summarize/agent.py +885 -0
  99. gaia/agents/summarize/prompts.py +129 -0
  100. gaia/api/__init__.py +23 -23
  101. gaia/api/agent_registry.py +238 -238
  102. gaia/api/app.py +305 -305
  103. gaia/api/openai_server.py +575 -575
  104. gaia/api/schemas.py +186 -186
  105. gaia/api/sse_handler.py +373 -373
  106. gaia/apps/__init__.py +4 -4
  107. gaia/apps/llm/__init__.py +6 -6
  108. gaia/apps/llm/app.py +184 -169
  109. gaia/apps/summarize/app.py +116 -633
  110. gaia/apps/summarize/html_viewer.py +133 -133
  111. gaia/apps/summarize/pdf_formatter.py +284 -284
  112. gaia/audio/__init__.py +2 -2
  113. gaia/audio/audio_client.py +439 -439
  114. gaia/audio/audio_recorder.py +269 -269
  115. gaia/audio/kokoro_tts.py +599 -599
  116. gaia/audio/whisper_asr.py +432 -432
  117. gaia/chat/__init__.py +16 -16
  118. gaia/chat/app.py +428 -430
  119. gaia/chat/prompts.py +522 -522
  120. gaia/chat/sdk.py +1228 -1225
  121. gaia/cli.py +5659 -5632
  122. gaia/database/__init__.py +10 -10
  123. gaia/database/agent.py +176 -176
  124. gaia/database/mixin.py +290 -290
  125. gaia/database/testing.py +64 -64
  126. gaia/eval/batch_experiment.py +2332 -2332
  127. gaia/eval/claude.py +542 -542
  128. gaia/eval/config.py +37 -37
  129. gaia/eval/email_generator.py +512 -512
  130. gaia/eval/eval.py +3179 -3179
  131. gaia/eval/groundtruth.py +1130 -1130
  132. gaia/eval/transcript_generator.py +582 -582
  133. gaia/eval/webapp/README.md +167 -167
  134. gaia/eval/webapp/package-lock.json +875 -875
  135. gaia/eval/webapp/package.json +20 -20
  136. gaia/eval/webapp/public/app.js +3402 -3402
  137. gaia/eval/webapp/public/index.html +87 -87
  138. gaia/eval/webapp/public/styles.css +3661 -3661
  139. gaia/eval/webapp/server.js +415 -415
  140. gaia/eval/webapp/test-setup.js +72 -72
  141. gaia/installer/__init__.py +23 -0
  142. gaia/installer/init_command.py +1275 -0
  143. gaia/installer/lemonade_installer.py +619 -0
  144. gaia/llm/__init__.py +10 -2
  145. gaia/llm/base_client.py +60 -0
  146. gaia/llm/exceptions.py +12 -0
  147. gaia/llm/factory.py +70 -0
  148. gaia/llm/lemonade_client.py +3421 -3221
  149. gaia/llm/lemonade_manager.py +294 -294
  150. gaia/llm/providers/__init__.py +9 -0
  151. gaia/llm/providers/claude.py +108 -0
  152. gaia/llm/providers/lemonade.py +118 -0
  153. gaia/llm/providers/openai_provider.py +79 -0
  154. gaia/llm/vlm_client.py +382 -382
  155. gaia/logger.py +189 -189
  156. gaia/mcp/agent_mcp_server.py +245 -245
  157. gaia/mcp/blender_mcp_client.py +138 -138
  158. gaia/mcp/blender_mcp_server.py +648 -648
  159. gaia/mcp/context7_cache.py +332 -332
  160. gaia/mcp/external_services.py +518 -518
  161. gaia/mcp/mcp_bridge.py +811 -550
  162. gaia/mcp/servers/__init__.py +6 -6
  163. gaia/mcp/servers/docker_mcp.py +83 -83
  164. gaia/perf_analysis.py +361 -0
  165. gaia/rag/__init__.py +10 -10
  166. gaia/rag/app.py +293 -293
  167. gaia/rag/demo.py +304 -304
  168. gaia/rag/pdf_utils.py +235 -235
  169. gaia/rag/sdk.py +2194 -2194
  170. gaia/security.py +183 -163
  171. gaia/talk/app.py +287 -289
  172. gaia/talk/sdk.py +538 -538
  173. gaia/testing/__init__.py +87 -87
  174. gaia/testing/assertions.py +330 -330
  175. gaia/testing/fixtures.py +333 -333
  176. gaia/testing/mocks.py +493 -493
  177. gaia/util.py +46 -46
  178. gaia/utils/__init__.py +33 -33
  179. gaia/utils/file_watcher.py +675 -675
  180. gaia/utils/parsing.py +223 -223
  181. gaia/version.py +100 -100
  182. amd_gaia-0.15.0.dist-info/RECORD +0 -168
  183. gaia/agents/code/app.py +0 -266
  184. gaia/llm/llm_client.py +0 -723
  185. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
@@ -1,556 +1,553 @@
1
- # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
- # SPDX-License-Identifier: MIT
3
- """
4
- Blender-specific agent for creating and modifying 3D scenes.
5
- """
6
-
7
- import logging
8
- from typing import Any, Dict, Optional
9
-
10
- from gaia.agents.base.agent import Agent
11
- from gaia.agents.base.console import AgentConsole
12
- from gaia.agents.base.tools import tool
13
- from gaia.agents.blender.core.scene import generate_scene_diagnosis_code
14
- from gaia.mcp.blender_mcp_client import MCPClient
15
-
16
- # Set up logging
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class BlenderAgent(Agent):
22
- """
23
- Blender-specific agent focused on 3D scene creation and modification.
24
- Inherits core functionality from the base Agent class.
25
- """
26
-
27
- # Define Blender-specific tools that can execute directly without requiring a plan
28
- SIMPLE_TOOLS = ["clear_scene", "get_scene_info"]
29
-
30
- def __init__(
31
- self,
32
- mcp: Optional[MCPClient] = None,
33
- model_id: str = None,
34
- base_url: str = "http://localhost:8000/api/v1",
35
- max_steps: int = 5,
36
- debug_prompts: bool = False,
37
- output_dir: str = None,
38
- streaming: bool = False,
39
- show_stats: bool = True,
40
- ):
41
- """
42
- Initialize the BlenderAgent with MCP client and LLM client.
43
-
44
- Args:
45
- mcp: An optional pre-configured MCP client, otherwise a new one will be created
46
- model_id: The ID of the model to use with LLM server
47
- base_url: Base URL for the local LLM server API
48
- max_steps: Maximum number of steps the agent can take before terminating
49
- debug_prompts: If True, includes prompts in the conversation history
50
- output_dir: Directory for storing JSON output files (default: current directory)
51
- streaming: If True, enables real-time streaming of LLM responses (default: False)
52
- show_stats: If True, displays LLM performance stats after each response (default: True)
53
- """
54
- # Initialize the MCP client for Blender communication
55
- self.mcp = mcp if mcp else MCPClient()
56
-
57
- # Call the parent class constructor
58
- super().__init__(
59
- model_id=model_id,
60
- base_url=base_url,
61
- max_steps=max_steps,
62
- debug_prompts=debug_prompts,
63
- output_dir=output_dir,
64
- streaming=streaming,
65
- show_stats=show_stats,
66
- )
67
-
68
- # Register Blender-specific tools
69
- self._register_tools()
70
-
71
- def _create_console(self) -> AgentConsole:
72
- """
73
- Create and return a Agent-specific console output handler.
74
-
75
- Returns:
76
- A AgentConsole instance
77
- """
78
- return AgentConsole()
79
-
80
- def _get_system_prompt(self) -> str:
81
- """Generate the system prompt for the Blender agent."""
82
- # Get formatted tools from registry
83
- return f"""
84
- You are a specialized Blender 3D assistant that can create and modify 3D scenes.
85
- You will use a set of tools to accomplish tasks based on the user's request.
86
-
87
- ==== JSON RESPONSE FORMAT ====
88
- ALWAYS respond with a single valid JSON object. NO text outside this structure.
89
- - Use double quotes for keys and string values
90
- - Ensure all braces and brackets are properly closed
91
- - No trailing commas in arrays or objects
92
- - All required fields must be included
93
- - Never wrap your JSON in code blocks or backticks
94
-
95
- Your JSON response must follow this format:
96
- {{
97
- "thought": "your reasoning about what to do",
98
- "goal": "clear statement of what you're achieving",
99
- "plan": [
100
- {{"tool": "tool1", "tool_args": {{"arg1": "val1"}}}},
101
- {{"tool": "tool2", "tool_args": {{"arg1": "val1"}}}}
102
- ],
103
- "tool": "first_tool_to_execute",
104
- "tool_args": {{"arg1": "val1", "arg2": "val2"}}
105
- }}
106
-
107
- For final answers:
108
- {{
109
- "thought": "your reasoning",
110
- "goal": "what was achieved",
111
- "answer": "your final answer"
112
- }}
113
-
114
- ==== CRITICAL RULES ====
115
- 1. Create a plan for multi-step tasks, but simple single operations (like clear_scene) can execute directly
116
- 2. Each plan step must be atomic (one tool call per step)
117
- 3. For colored objects, ALWAYS include both create_object AND set_material_color steps
118
- 4. When clearing a scene, ONLY use clear_scene without creating new objects unless requested
119
- 5. Always use the actual returned object names for subsequent operations
120
- 6. Never repeat the same tool call with identical arguments
121
-
122
- ==== COLORED OBJECT DETECTION ====
123
- 🔍 SCAN the user request for color words:
124
- - "red", "green", "blue", "yellow", "purple", "cyan", "white", "black"
125
- - "colored", "paint", "material"
126
-
127
- ⚠️ IF you find ANY color words, you MUST:
128
- 1. Create the object with create_object
129
- 2. Set its color with set_material_color
130
- 3. Then do any other modifications
131
-
132
- NEVER skip the color step if a color is mentioned!
133
-
134
- Examples of colored requests:
135
- - "blue cylinder" → needs create_object + set_material_color
136
- - "red sphere" needs create_object + set_material_color
137
- - "green cube and yellow cone" needs 4 steps total
138
-
139
- ==== TOOL PARAMETER RULES ====
140
- ⚠️ CRITICAL: create_object does NOT accept a 'color' parameter!
141
- ✅ CORRECT workflow for colored objects:
142
- Step 1: create_object (type, name, location, rotation, scale ONLY)
143
- Step 2: set_material_color (object_name, color)
144
-
145
- ⚠️ CRITICAL: Colors must be RGBA format with 4 values [r, g, b, a]
146
- ❌ WRONG: [0, 0, 1] (only 3 values)
147
- CORRECT: [0, 0, 1, 1] (4 values including alpha)
148
-
149
- ⚠️ CRITICAL: EVERY colored object must have BOTH steps!
150
- If user asks for "green cube and red sphere", you need 4 steps:
151
- 1. create_object (cube)
152
- 2. set_material_color (cube, green)
153
- 3. create_object (sphere)
154
- 4. set_material_color (sphere, red)
155
-
156
- ==== COMMON WORKFLOWS ====
157
- 1. Clearing a scene: Use clear_scene() with no arguments
158
- 2. Creating a single colored object:
159
- - Step 1: create_object(type="CYLINDER", name="my_obj", location=[0,0,0])
160
- - Step 2: set_material_color(object_name="my_obj", color=[0,0,1,1])
161
- 3. Creating multiple colored objects:
162
- - Step 1: create_object(type="CUBE", name="cube1", location=[0,0,0])
163
- - Step 2: set_material_color(object_name="cube1", color=[0,1,0,1])
164
- - Step 3: create_object(type="SPHERE", name="sphere1", location=[3,0,0])
165
- - Step 4: set_material_color(object_name="sphere1", color=[1,0,0,1])
166
- 4. Modifying objects: Use modify_object with the parameters you want to change
167
- """
168
-
169
- def _register_tools(self):
170
- """Register all Blender-related tools for the agent."""
171
-
172
- @tool
173
- def clear_scene() -> Dict[str, Any]:
174
- """
175
- Remove all objects from the current Blender scene.
176
-
177
- Returns:
178
- Dictionary containing the operation result
179
-
180
- Example JSON response:
181
- ```json
182
- {
183
- "thought": "I will clear the scene to start fresh",
184
- "goal": "Clear the scene to start fresh",
185
- "tool": "clear_scene",
186
- "tool_args": {}
187
- }
188
- ```
189
- """
190
- try:
191
- from gaia.agents.blender.core.scene import SceneManager
192
-
193
- scene_manager = SceneManager(self.mcp)
194
- return scene_manager.clear_scene()
195
- except Exception as e:
196
- self.error_history.append(str(e))
197
- return {"status": "error", "error": str(e)}
198
-
199
- @tool
200
- def create_object(
201
- type: str = "CUBE",
202
- name: str = None,
203
- location: tuple = (0, 0, 0),
204
- rotation: tuple = (0, 0, 0),
205
- scale: tuple = (1, 1, 1),
206
- ) -> Dict[str, Any]:
207
- """
208
- Create a 3D object in Blender.
209
-
210
- Args:
211
- type: Object type (CUBE, SPHERE, CYLINDER, CONE, TORUS)
212
- name: Optional name for the object (default: generated from type)
213
- location: (x, y, z) coordinates for object position (default: (0,0,0))
214
- rotation: (rx, ry, rz) rotation in radians (default: (0,0,0))
215
- scale: (sx, sy, sz) scaling factors for the object (default: (1,1,1))
216
-
217
- Returns:
218
- Dictionary containing the creation result
219
-
220
- Example JSON response:
221
- ```json
222
- {
223
- "thought": "I will create a cube at the center of the scene",
224
- "goal": "Create a red cube at the center of the scene",
225
- "tool": "create_object",
226
- "tool_args": {
227
- "type": "CUBE",
228
- "name": "my_cube",
229
- "location": [0, 0, 0],
230
- "rotation": [0, 0, 0],
231
- "scale": [1, 1, 1]
232
- }
233
- }
234
- ```
235
- """
236
- try:
237
- result = self.mcp.create_object(
238
- type=type.upper(),
239
- name=name or f"generated_{type.lower()}",
240
- location=location,
241
- rotation=rotation,
242
- scale=scale,
243
- )
244
- return result
245
- except Exception as e:
246
- self.error_history.append(str(e))
247
- return {"status": "error", "error": str(e)}
248
-
249
- @tool
250
- def set_material_color(
251
- object_name: str, color: tuple = (1, 0, 0, 1)
252
- ) -> Dict[str, Any]:
253
- """
254
- Set the material color for an object. Creates a new material if one doesn't exist.
255
-
256
- Args:
257
- object_name: Name of the object to modify
258
- color: RGBA color values as tuple (red, green, blue, alpha), values from 0-1
259
-
260
- Returns:
261
- Dictionary with the operation result
262
-
263
- Example JSON response:
264
- ```json
265
- {
266
- "thought": "I will set the cube's material to red",
267
- "goal": "Create a red cube at the center of the scene",
268
- "tool": "set_material_color",
269
- "tool_args": {
270
- "object_name": "my_cube",
271
- "color": [1, 0, 0, 1]
272
- }
273
- }
274
- ```
275
- """
276
- try:
277
- from gaia.agents.blender.core.materials import MaterialManager
278
-
279
- material_manager = MaterialManager(self.mcp)
280
- return material_manager.set_material_color(object_name, color)
281
- except Exception as e:
282
- self.error_history.append(str(e))
283
- return {"status": "error", "error": str(e)}
284
-
285
- # @tool
286
- def get_object_info(name: str) -> Dict[str, Any]:
287
- """
288
- Get information about an object in the scene.
289
-
290
- Args:
291
- name: Name of the object
292
-
293
- Returns:
294
- Dictionary containing object information
295
-
296
- Example JSON response:
297
- ```json
298
- {
299
- "thought": "I will get information about the cube",
300
- "goal": "Create a red cube at the center of the scene",
301
- "tool": "get_object_info",
302
- "tool_args": {
303
- "name": "my_cube"
304
- }
305
- }
306
- ```
307
- """
308
- try:
309
- return self.mcp.get_object_info(name)
310
- except Exception as e:
311
- self.error_history.append(str(e))
312
- return {"status": "error", "error": str(e)}
313
-
314
- @tool
315
- def modify_object(
316
- name: str,
317
- location: tuple = None,
318
- scale: tuple = None,
319
- rotation: tuple = None,
320
- ) -> Dict[str, Any]:
321
- """
322
- Modify an existing object in Blender.
323
-
324
- Args:
325
- name: Name of the object to modify
326
- location: New (x, y, z) location or None to keep current
327
- scale: New (sx, sy, sz) scale or None to keep current
328
- rotation: New (rx, ry, rz) rotation or None to keep current
329
-
330
- Returns:
331
- Dictionary with the modification result
332
-
333
- Example JSON response:
334
- ```json
335
- {
336
- "thought": "I will move the cube up by 2 units",
337
- "goal": "Create a red cube at the center of the scene",
338
- "tool": "modify_object",
339
- "tool_args": {
340
- "name": "my_cube",
341
- "location": [0, 0, 2],
342
- "scale": null,
343
- "rotation": null
344
- }
345
- }
346
- ```
347
- """
348
- try:
349
- return self.mcp.modify_object(
350
- name=name, location=location, scale=scale, rotation=rotation
351
- )
352
- except Exception as e:
353
- self.error_history.append(str(e))
354
- return {"status": "error", "error": str(e)}
355
-
356
- # @tool
357
- def delete_object(name: str) -> Dict[str, Any]:
358
- """
359
- Delete an object from the scene.
360
-
361
- Args:
362
- name: Name of the object to delete
363
-
364
- Returns:
365
- Dictionary with the deletion result
366
-
367
- Example JSON response:
368
- ```json
369
- {
370
- "thought": "I will delete the cube",
371
- "goal": "Clear the scene to start fresh",
372
- "tool": "delete_object",
373
- "tool_args": {
374
- "name": "my_cube"
375
- }
376
- }
377
- ```
378
- """
379
- try:
380
- return self.mcp.delete_object(name)
381
- except Exception as e:
382
- self.error_history.append(str(e))
383
- return {"status": "error", "error": str(e)}
384
-
385
- @tool
386
- def get_scene_info() -> Dict[str, Any]:
387
- """
388
- Get information about the current scene.
389
-
390
- Returns:
391
- Dictionary containing scene information
392
-
393
- Example JSON response:
394
- ```json
395
- {
396
- "thought": "I will get information about the current scene",
397
- "goal": "Clear the scene to start fresh",
398
- "tool": "get_scene_info",
399
- "tool_args": {}
400
- }
401
- ```
402
- """
403
- try:
404
- return self.mcp.get_scene_info()
405
- except Exception as e:
406
- self.error_history.append(str(e))
407
- return {"status": "error", "error": str(e)}
408
-
409
- # @tool
410
- def execute_blender_code(code: str) -> Dict[str, Any]:
411
- """
412
- Execute arbitrary Python code in Blender with error handling.
413
-
414
- Args:
415
- code: Python code to execute in Blender
416
-
417
- Returns:
418
- Dictionary with execution results or error information
419
-
420
- Example JSON response:
421
- ```json
422
- {
423
- "thought": "I will execute custom code to create a complex shape",
424
- "goal": "Create a red cube at the center of the scene",
425
- "tool": "execute_blender_code",
426
- "tool_args": {
427
- "code": "import bpy\\nbpy.ops.mesh.primitive_cube_add()"
428
- }
429
- }
430
- ```
431
- """
432
- try:
433
- return self.mcp.execute_code(code)
434
- except Exception as e:
435
- self.error_history.append(str(e))
436
- return {"status": "error", "error": str(e)}
437
-
438
- # @tool
439
- def diagnose_scene() -> Dict[str, Any]:
440
- """
441
- Diagnose the current Blender scene for common issues.
442
- Returns information about objects, materials, and potential problems.
443
-
444
- Returns:
445
- Dictionary with diagnostic information
446
-
447
- Example JSON response:
448
- ```json
449
- {
450
- "thought": "I will diagnose the scene for any issues",
451
- "goal": "Clear the scene to start fresh",
452
- "tool": "diagnose_scene",
453
- "tool_args": {}
454
- }
455
- ```
456
- """
457
- try:
458
- # Use the core library's scene diagnosis code generator
459
- diagnostic_code = generate_scene_diagnosis_code()
460
- return self.mcp.execute_code(diagnostic_code)
461
- except Exception as e:
462
- self.error_history.append(str(e))
463
- return {"status": "error", "error": str(e)}
464
-
465
- def _post_process_tool_result(
466
- self, tool_name: str, tool_args: Dict[str, Any], tool_result: Dict[str, Any]
467
- ) -> None:
468
- """
469
- Post-process the tool result for Blender-specific handling.
470
-
471
- Args:
472
- tool_name: Name of the tool that was executed
473
- tool_args: Arguments that were passed to the tool
474
- tool_result: Result returned by the tool
475
- """
476
- # Track object name if created
477
- if tool_name == "create_object":
478
- actual_name = self._track_object_name(tool_result)
479
- if actual_name:
480
- logger.debug(f"Actual object name created: {actual_name}")
481
- self.console.print_info(
482
- f"Note: Blender assigned name '{actual_name}' to the created object"
483
- )
484
-
485
- # Update subsequent steps in the plan that might use this object
486
- if self.current_plan and self.current_step < len(self.current_plan) - 1:
487
- for i in range(self.current_step + 1, len(self.current_plan)):
488
- future_step = self.current_plan[i]
489
- if isinstance(future_step, dict) and "tool_args" in future_step:
490
- args = future_step["tool_args"]
491
- # Look for object_name or name parameters
492
- if "object_name" in args and args[
493
- "object_name"
494
- ] == tool_args.get("name"):
495
- logger.debug(
496
- f"Updating object_name in future step {i+1} from {args['object_name']} to {actual_name}"
497
- )
498
- self.current_plan[i]["tool_args"][
499
- "object_name"
500
- ] = actual_name
501
- if "name" in args and args["name"] == tool_args.get("name"):
502
- logger.debug(
503
- f"Updating name in future step {i+1} from {args['name']} to {actual_name}"
504
- )
505
- self.current_plan[i]["tool_args"]["name"] = actual_name
506
-
507
- def _track_object_name(self, result):
508
- """
509
- Extract and track the actual object name returned by Blender.
510
-
511
- Args:
512
- result: The result dictionary from a tool execution
513
-
514
- Returns:
515
- The actual object name if found, None otherwise
516
- """
517
- try:
518
- if isinstance(result, dict):
519
- if result.get("status") == "success":
520
- if "result" in result and isinstance(result["result"], dict):
521
- # Extract name from create_object result
522
- if "name" in result["result"]:
523
- actual_name = result["result"]["name"]
524
- logger.debug(f"Extracted object name: {actual_name}")
525
- return actual_name
526
- return None
527
- except Exception as e:
528
- logger.error(f"Error extracting object name: {str(e)}")
529
- return None
530
-
531
- def create_interactive_scene(
532
- self,
533
- scene_description: str,
534
- max_steps: int = None,
535
- trace: bool = True,
536
- filename: str = None,
537
- ) -> Dict[str, Any]:
538
- """
539
- Create a more complex scene with multiple objects and relationships.
540
-
541
- Args:
542
- scene_description: Description of the scene to create
543
- max_steps: Maximum number of steps to take in the conversation (overrides class default if provided)
544
- trace: If True, write detailed trace to file
545
- filename: Optional filename for trace output, if None a timestamped name will be generated
546
-
547
- Returns:
548
- Dict containing the scene creation result
549
- """
550
- # Same process as process_query but with more steps allowed if specified
551
- return self.process_query(
552
- f"Create a complete 3D scene with the following description: {scene_description}",
553
- max_steps=max_steps if max_steps is not None else self.max_steps * 2,
554
- trace=trace,
555
- filename=filename,
556
- )
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+ """
4
+ Blender-specific agent for creating and modifying 3D scenes.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, Optional
9
+
10
+ from gaia.agents.base.agent import Agent
11
+ from gaia.agents.base.console import AgentConsole
12
+ from gaia.agents.base.tools import tool
13
+ from gaia.agents.blender.core.scene import generate_scene_diagnosis_code
14
+ from gaia.mcp.blender_mcp_client import MCPClient
15
+
16
+ # Set up logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class BlenderAgent(Agent):
22
+ """
23
+ Blender-specific agent focused on 3D scene creation and modification.
24
+ Inherits core functionality from the base Agent class.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ mcp: Optional[MCPClient] = None,
30
+ model_id: str = None,
31
+ base_url: str = "http://localhost:8000/api/v1",
32
+ max_steps: int = 5,
33
+ debug_prompts: bool = False,
34
+ output_dir: str = None,
35
+ streaming: bool = False,
36
+ show_stats: bool = True,
37
+ ):
38
+ """
39
+ Initialize the BlenderAgent with MCP client and LLM client.
40
+
41
+ Args:
42
+ mcp: An optional pre-configured MCP client, otherwise a new one will be created
43
+ model_id: The ID of the model to use with LLM server
44
+ base_url: Base URL for the local LLM server API
45
+ max_steps: Maximum number of steps the agent can take before terminating
46
+ debug_prompts: If True, includes prompts in the conversation history
47
+ output_dir: Directory for storing JSON output files (default: current directory)
48
+ streaming: If True, enables real-time streaming of LLM responses (default: False)
49
+ show_stats: If True, displays LLM performance stats after each response (default: True)
50
+ """
51
+ # Initialize the MCP client for Blender communication
52
+ self.mcp = mcp if mcp else MCPClient()
53
+
54
+ # Call the parent class constructor
55
+ super().__init__(
56
+ model_id=model_id,
57
+ base_url=base_url,
58
+ max_steps=max_steps,
59
+ debug_prompts=debug_prompts,
60
+ output_dir=output_dir,
61
+ streaming=streaming,
62
+ show_stats=show_stats,
63
+ )
64
+
65
+ # Register Blender-specific tools
66
+ self._register_tools()
67
+
68
+ def _create_console(self) -> AgentConsole:
69
+ """
70
+ Create and return a Agent-specific console output handler.
71
+
72
+ Returns:
73
+ A AgentConsole instance
74
+ """
75
+ return AgentConsole()
76
+
77
+ def _get_system_prompt(self) -> str:
78
+ """Generate the system prompt for the Blender agent."""
79
+ # Get formatted tools from registry
80
+ return """
81
+ You are a specialized Blender 3D assistant that can create and modify 3D scenes.
82
+ You will use a set of tools to accomplish tasks based on the user's request.
83
+
84
+ ==== JSON RESPONSE FORMAT ====
85
+ ALWAYS respond with a single valid JSON object. NO text outside this structure.
86
+ - Use double quotes for keys and string values
87
+ - Ensure all braces and brackets are properly closed
88
+ - No trailing commas in arrays or objects
89
+ - All required fields must be included
90
+ - Never wrap your JSON in code blocks or backticks
91
+
92
+ Your JSON response must follow this format:
93
+ {{
94
+ "thought": "your reasoning about what to do",
95
+ "goal": "clear statement of what you're achieving",
96
+ "plan": [
97
+ {{"tool": "tool1", "tool_args": {{"arg1": "val1"}}}},
98
+ {{"tool": "tool2", "tool_args": {{"arg1": "val1"}}}}
99
+ ],
100
+ "tool": "first_tool_to_execute",
101
+ "tool_args": {{"arg1": "val1", "arg2": "val2"}}
102
+ }}
103
+
104
+ For final answers:
105
+ {{
106
+ "thought": "your reasoning",
107
+ "goal": "what was achieved",
108
+ "answer": "your final answer"
109
+ }}
110
+
111
+ ==== CRITICAL RULES ====
112
+ 1. Create a plan for multi-step tasks, but simple single operations (like clear_scene) can execute directly
113
+ 2. Each plan step must be atomic (one tool call per step)
114
+ 3. For colored objects, ALWAYS include both create_object AND set_material_color steps
115
+ 4. When clearing a scene, ONLY use clear_scene without creating new objects unless requested
116
+ 5. Always use the actual returned object names for subsequent operations
117
+ 6. Never repeat the same tool call with identical arguments
118
+
119
+ ==== COLORED OBJECT DETECTION ====
120
+ 🔍 SCAN the user request for color words:
121
+ - "red", "green", "blue", "yellow", "purple", "cyan", "white", "black"
122
+ - "colored", "paint", "material"
123
+
124
+ ⚠️ IF you find ANY color words, you MUST:
125
+ 1. Create the object with create_object
126
+ 2. Set its color with set_material_color
127
+ 3. Then do any other modifications
128
+
129
+ NEVER skip the color step if a color is mentioned!
130
+
131
+ Examples of colored requests:
132
+ - "blue cylinder" needs create_object + set_material_color
133
+ - "red sphere" → needs create_object + set_material_color
134
+ - "green cube and yellow cone" → needs 4 steps total
135
+
136
+ ==== TOOL PARAMETER RULES ====
137
+ ⚠️ CRITICAL: create_object does NOT accept a 'color' parameter!
138
+ ✅ CORRECT workflow for colored objects:
139
+ Step 1: create_object (type, name, location, rotation, scale ONLY)
140
+ Step 2: set_material_color (object_name, color)
141
+
142
+ ⚠️ CRITICAL: Colors must be RGBA format with 4 values [r, g, b, a]
143
+ WRONG: [0, 0, 1] (only 3 values)
144
+ ✅ CORRECT: [0, 0, 1, 1] (4 values including alpha)
145
+
146
+ ⚠️ CRITICAL: EVERY colored object must have BOTH steps!
147
+ If user asks for "green cube and red sphere", you need 4 steps:
148
+ 1. create_object (cube)
149
+ 2. set_material_color (cube, green)
150
+ 3. create_object (sphere)
151
+ 4. set_material_color (sphere, red)
152
+
153
+ ==== COMMON WORKFLOWS ====
154
+ 1. Clearing a scene: Use clear_scene() with no arguments
155
+ 2. Creating a single colored object:
156
+ - Step 1: create_object(type="CYLINDER", name="my_obj", location=[0,0,0])
157
+ - Step 2: set_material_color(object_name="my_obj", color=[0,0,1,1])
158
+ 3. Creating multiple colored objects:
159
+ - Step 1: create_object(type="CUBE", name="cube1", location=[0,0,0])
160
+ - Step 2: set_material_color(object_name="cube1", color=[0,1,0,1])
161
+ - Step 3: create_object(type="SPHERE", name="sphere1", location=[3,0,0])
162
+ - Step 4: set_material_color(object_name="sphere1", color=[1,0,0,1])
163
+ 4. Modifying objects: Use modify_object with the parameters you want to change
164
+ """
165
+
166
+ def _register_tools(self):
167
+ """Register all Blender-related tools for the agent."""
168
+
169
+ @tool(atomic=True)
170
+ def clear_scene() -> Dict[str, Any]:
171
+ """
172
+ Remove all objects from the current Blender scene.
173
+
174
+ Returns:
175
+ Dictionary containing the operation result
176
+
177
+ Example JSON response:
178
+ ```json
179
+ {
180
+ "thought": "I will clear the scene to start fresh",
181
+ "goal": "Clear the scene to start fresh",
182
+ "tool": "clear_scene",
183
+ "tool_args": {}
184
+ }
185
+ ```
186
+ """
187
+ try:
188
+ from gaia.agents.blender.core.scene import SceneManager
189
+
190
+ scene_manager = SceneManager(self.mcp)
191
+ return scene_manager.clear_scene()
192
+ except Exception as e:
193
+ self.error_history.append(str(e))
194
+ return {"status": "error", "error": str(e)}
195
+
196
+ @tool
197
+ def create_object(
198
+ type: str = "CUBE",
199
+ name: str = None,
200
+ location: tuple = (0, 0, 0),
201
+ rotation: tuple = (0, 0, 0),
202
+ scale: tuple = (1, 1, 1),
203
+ ) -> Dict[str, Any]:
204
+ """
205
+ Create a 3D object in Blender.
206
+
207
+ Args:
208
+ type: Object type (CUBE, SPHERE, CYLINDER, CONE, TORUS)
209
+ name: Optional name for the object (default: generated from type)
210
+ location: (x, y, z) coordinates for object position (default: (0,0,0))
211
+ rotation: (rx, ry, rz) rotation in radians (default: (0,0,0))
212
+ scale: (sx, sy, sz) scaling factors for the object (default: (1,1,1))
213
+
214
+ Returns:
215
+ Dictionary containing the creation result
216
+
217
+ Example JSON response:
218
+ ```json
219
+ {
220
+ "thought": "I will create a cube at the center of the scene",
221
+ "goal": "Create a red cube at the center of the scene",
222
+ "tool": "create_object",
223
+ "tool_args": {
224
+ "type": "CUBE",
225
+ "name": "my_cube",
226
+ "location": [0, 0, 0],
227
+ "rotation": [0, 0, 0],
228
+ "scale": [1, 1, 1]
229
+ }
230
+ }
231
+ ```
232
+ """
233
+ try:
234
+ result = self.mcp.create_object(
235
+ type=type.upper(),
236
+ name=name or f"generated_{type.lower()}",
237
+ location=location,
238
+ rotation=rotation,
239
+ scale=scale,
240
+ )
241
+ return result
242
+ except Exception as e:
243
+ self.error_history.append(str(e))
244
+ return {"status": "error", "error": str(e)}
245
+
246
+ @tool
247
+ def set_material_color(
248
+ object_name: str, color: tuple = (1, 0, 0, 1)
249
+ ) -> Dict[str, Any]:
250
+ """
251
+ Set the material color for an object. Creates a new material if one doesn't exist.
252
+
253
+ Args:
254
+ object_name: Name of the object to modify
255
+ color: RGBA color values as tuple (red, green, blue, alpha), values from 0-1
256
+
257
+ Returns:
258
+ Dictionary with the operation result
259
+
260
+ Example JSON response:
261
+ ```json
262
+ {
263
+ "thought": "I will set the cube's material to red",
264
+ "goal": "Create a red cube at the center of the scene",
265
+ "tool": "set_material_color",
266
+ "tool_args": {
267
+ "object_name": "my_cube",
268
+ "color": [1, 0, 0, 1]
269
+ }
270
+ }
271
+ ```
272
+ """
273
+ try:
274
+ from gaia.agents.blender.core.materials import MaterialManager
275
+
276
+ material_manager = MaterialManager(self.mcp)
277
+ return material_manager.set_material_color(object_name, color)
278
+ except Exception as e:
279
+ self.error_history.append(str(e))
280
+ return {"status": "error", "error": str(e)}
281
+
282
+ # @tool
283
+ def _get_object_info(name: str) -> Dict[str, Any]:
284
+ """
285
+ Get information about an object in the scene.
286
+
287
+ Args:
288
+ name: Name of the object
289
+
290
+ Returns:
291
+ Dictionary containing object information
292
+
293
+ Example JSON response:
294
+ ```json
295
+ {
296
+ "thought": "I will get information about the cube",
297
+ "goal": "Create a red cube at the center of the scene",
298
+ "tool": "get_object_info",
299
+ "tool_args": {
300
+ "name": "my_cube"
301
+ }
302
+ }
303
+ ```
304
+ """
305
+ try:
306
+ return self.mcp.get_object_info(name)
307
+ except Exception as e:
308
+ self.error_history.append(str(e))
309
+ return {"status": "error", "error": str(e)}
310
+
311
+ @tool
312
+ def modify_object(
313
+ name: str,
314
+ location: tuple = None,
315
+ scale: tuple = None,
316
+ rotation: tuple = None,
317
+ ) -> Dict[str, Any]:
318
+ """
319
+ Modify an existing object in Blender.
320
+
321
+ Args:
322
+ name: Name of the object to modify
323
+ location: New (x, y, z) location or None to keep current
324
+ scale: New (sx, sy, sz) scale or None to keep current
325
+ rotation: New (rx, ry, rz) rotation or None to keep current
326
+
327
+ Returns:
328
+ Dictionary with the modification result
329
+
330
+ Example JSON response:
331
+ ```json
332
+ {
333
+ "thought": "I will move the cube up by 2 units",
334
+ "goal": "Create a red cube at the center of the scene",
335
+ "tool": "modify_object",
336
+ "tool_args": {
337
+ "name": "my_cube",
338
+ "location": [0, 0, 2],
339
+ "scale": null,
340
+ "rotation": null
341
+ }
342
+ }
343
+ ```
344
+ """
345
+ try:
346
+ return self.mcp.modify_object(
347
+ name=name, location=location, scale=scale, rotation=rotation
348
+ )
349
+ except Exception as e:
350
+ self.error_history.append(str(e))
351
+ return {"status": "error", "error": str(e)}
352
+
353
+ # @tool
354
+ def _delete_object(name: str) -> Dict[str, Any]:
355
+ """
356
+ Delete an object from the scene.
357
+
358
+ Args:
359
+ name: Name of the object to delete
360
+
361
+ Returns:
362
+ Dictionary with the deletion result
363
+
364
+ Example JSON response:
365
+ ```json
366
+ {
367
+ "thought": "I will delete the cube",
368
+ "goal": "Clear the scene to start fresh",
369
+ "tool": "delete_object",
370
+ "tool_args": {
371
+ "name": "my_cube"
372
+ }
373
+ }
374
+ ```
375
+ """
376
+ try:
377
+ return self.mcp.delete_object(name)
378
+ except Exception as e:
379
+ self.error_history.append(str(e))
380
+ return {"status": "error", "error": str(e)}
381
+
382
+ @tool(atomic=True)
383
+ def get_scene_info() -> Dict[str, Any]:
384
+ """
385
+ Get information about the current scene.
386
+
387
+ Returns:
388
+ Dictionary containing scene information
389
+
390
+ Example JSON response:
391
+ ```json
392
+ {
393
+ "thought": "I will get information about the current scene",
394
+ "goal": "Clear the scene to start fresh",
395
+ "tool": "get_scene_info",
396
+ "tool_args": {}
397
+ }
398
+ ```
399
+ """
400
+ try:
401
+ return self.mcp.get_scene_info()
402
+ except Exception as e:
403
+ self.error_history.append(str(e))
404
+ return {"status": "error", "error": str(e)}
405
+
406
+ # @tool
407
+ def _execute_blender_code(code: str) -> Dict[str, Any]:
408
+ """
409
+ Execute arbitrary Python code in Blender with error handling.
410
+
411
+ Args:
412
+ code: Python code to execute in Blender
413
+
414
+ Returns:
415
+ Dictionary with execution results or error information
416
+
417
+ Example JSON response:
418
+ ```json
419
+ {
420
+ "thought": "I will execute custom code to create a complex shape",
421
+ "goal": "Create a red cube at the center of the scene",
422
+ "tool": "execute_blender_code",
423
+ "tool_args": {
424
+ "code": "import bpy\\nbpy.ops.mesh.primitive_cube_add()"
425
+ }
426
+ }
427
+ ```
428
+ """
429
+ try:
430
+ return self.mcp.execute_code(code)
431
+ except Exception as e:
432
+ self.error_history.append(str(e))
433
+ return {"status": "error", "error": str(e)}
434
+
435
+ # @tool
436
+ def _diagnose_scene() -> Dict[str, Any]:
437
+ """
438
+ Diagnose the current Blender scene for common issues.
439
+ Returns information about objects, materials, and potential problems.
440
+
441
+ Returns:
442
+ Dictionary with diagnostic information
443
+
444
+ Example JSON response:
445
+ ```json
446
+ {
447
+ "thought": "I will diagnose the scene for any issues",
448
+ "goal": "Clear the scene to start fresh",
449
+ "tool": "diagnose_scene",
450
+ "tool_args": {}
451
+ }
452
+ ```
453
+ """
454
+ try:
455
+ # Use the core library's scene diagnosis code generator
456
+ diagnostic_code = generate_scene_diagnosis_code()
457
+ return self.mcp.execute_code(diagnostic_code)
458
+ except Exception as e:
459
+ self.error_history.append(str(e))
460
+ return {"status": "error", "error": str(e)}
461
+
462
+ def _post_process_tool_result(
463
+ self, tool_name: str, tool_args: Dict[str, Any], tool_result: Dict[str, Any]
464
+ ) -> None:
465
+ """
466
+ Post-process the tool result for Blender-specific handling.
467
+
468
+ Args:
469
+ tool_name: Name of the tool that was executed
470
+ tool_args: Arguments that were passed to the tool
471
+ tool_result: Result returned by the tool
472
+ """
473
+ # Track object name if created
474
+ if tool_name == "create_object":
475
+ actual_name = self._track_object_name(tool_result)
476
+ if actual_name:
477
+ logger.debug(f"Actual object name created: {actual_name}")
478
+ self.console.print_info(
479
+ f"Note: Blender assigned name '{actual_name}' to the created object"
480
+ )
481
+
482
+ # Update subsequent steps in the plan that might use this object
483
+ if self.current_plan and self.current_step < len(self.current_plan) - 1:
484
+ for i in range(self.current_step + 1, len(self.current_plan)):
485
+ future_step = self.current_plan[i]
486
+ if isinstance(future_step, dict) and "tool_args" in future_step:
487
+ args = future_step["tool_args"]
488
+ # Look for object_name or name parameters
489
+ if "object_name" in args and args[
490
+ "object_name"
491
+ ] == tool_args.get("name"):
492
+ logger.debug(
493
+ f"Updating object_name in future step {i+1} from {args['object_name']} to {actual_name}"
494
+ )
495
+ self.current_plan[i]["tool_args"][
496
+ "object_name"
497
+ ] = actual_name
498
+ if "name" in args and args["name"] == tool_args.get("name"):
499
+ logger.debug(
500
+ f"Updating name in future step {i+1} from {args['name']} to {actual_name}"
501
+ )
502
+ self.current_plan[i]["tool_args"]["name"] = actual_name
503
+
504
+ def _track_object_name(self, result):
505
+ """
506
+ Extract and track the actual object name returned by Blender.
507
+
508
+ Args:
509
+ result: The result dictionary from a tool execution
510
+
511
+ Returns:
512
+ The actual object name if found, None otherwise
513
+ """
514
+ try:
515
+ if isinstance(result, dict):
516
+ if result.get("status") == "success":
517
+ if "result" in result and isinstance(result["result"], dict):
518
+ # Extract name from create_object result
519
+ if "name" in result["result"]:
520
+ actual_name = result["result"]["name"]
521
+ logger.debug(f"Extracted object name: {actual_name}")
522
+ return actual_name
523
+ return None
524
+ except Exception as e:
525
+ logger.error(f"Error extracting object name: {str(e)}")
526
+ return None
527
+
528
+ def create_interactive_scene(
529
+ self,
530
+ scene_description: str,
531
+ max_steps: int = None,
532
+ trace: bool = True,
533
+ filename: str = None,
534
+ ) -> Dict[str, Any]:
535
+ """
536
+ Create a more complex scene with multiple objects and relationships.
537
+
538
+ Args:
539
+ scene_description: Description of the scene to create
540
+ max_steps: Maximum number of steps to take in the conversation (overrides class default if provided)
541
+ trace: If True, write detailed trace to file
542
+ filename: Optional filename for trace output, if None a timestamped name will be generated
543
+
544
+ Returns:
545
+ Dict containing the scene creation result
546
+ """
547
+ # Same process as process_query but with more steps allowed if specified
548
+ return self.process_query(
549
+ f"Create a complete 3D scene with the following description: {scene_description}",
550
+ max_steps=max_steps if max_steps is not None else self.max_steps * 2,
551
+ trace=trace,
552
+ filename=filename,
553
+ )