massgen 0.1.5__py3-none-any.whl → 0.1.6__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 (57) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
  3. massgen/backend/capabilities.py +39 -0
  4. massgen/backend/chat_completions.py +111 -197
  5. massgen/backend/claude.py +210 -181
  6. massgen/backend/gemini.py +1015 -1559
  7. massgen/backend/grok.py +3 -2
  8. massgen/backend/response.py +160 -220
  9. massgen/cli.py +73 -6
  10. massgen/config_builder.py +20 -54
  11. massgen/config_validator.py +931 -0
  12. massgen/configs/README.md +51 -8
  13. massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
  14. massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
  15. massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
  16. massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
  17. massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
  18. massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
  19. massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
  20. massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
  21. massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
  22. massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
  23. massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
  24. massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
  25. massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
  26. massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
  27. massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
  28. massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
  29. massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
  30. massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
  31. massgen/formatter/_gemini_formatter.py +61 -15
  32. massgen/tests/test_ag2_lesson_planner.py +223 -0
  33. massgen/tests/test_config_validator.py +1156 -0
  34. massgen/tests/test_langgraph_lesson_planner.py +223 -0
  35. massgen/tool/__init__.py +2 -9
  36. massgen/tool/_decorators.py +52 -0
  37. massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
  38. massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
  39. massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
  40. massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
  41. massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
  42. massgen/tool/_manager.py +102 -16
  43. massgen/tool/_registered_tool.py +3 -0
  44. massgen/tool/_result.py +3 -0
  45. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/METADATA +104 -76
  46. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/RECORD +50 -39
  47. massgen/backend/gemini_mcp_manager.py +0 -545
  48. massgen/backend/gemini_trackers.py +0 -344
  49. massgen/configs/tools/custom_tools/multimodal_tools/playwright_with_img_understanding.yaml +0 -98
  50. massgen/configs/tools/custom_tools/multimodal_tools/understand_video_example.yaml +0 -54
  51. massgen/tools/__init__.py +0 -8
  52. massgen/tools/_planning_mcp_server.py +0 -520
  53. massgen/tools/planning_dataclasses.py +0 -434
  54. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
  55. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
  56. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
  57. {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,223 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Test LangGraph Lesson Planner Tool
4
+ Tests the interoperability feature where LangGraph state graph is wrapped as a MassGen custom tool.
5
+ """
6
+
7
+ import asyncio
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ # Add parent directory to path for imports
15
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
16
+
17
+ from massgen.tool._extraframework_agents.langgraph_lesson_planner_tool import ( # noqa: E402
18
+ langgraph_lesson_planner,
19
+ )
20
+ from massgen.tool._result import ExecutionResult # noqa: E402
21
+
22
+
23
+ class TestLangGraphLessonPlannerTool:
24
+ """Test LangGraph Lesson Planner Tool functionality."""
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_basic_lesson_plan_creation(self):
28
+ """Test basic lesson plan creation with a simple topic."""
29
+ # Skip if no API key
30
+ api_key = os.getenv("OPENAI_API_KEY")
31
+ if not api_key:
32
+ pytest.skip("OPENAI_API_KEY not set")
33
+
34
+ # Test with a simple topic
35
+ result = await langgraph_lesson_planner(topic="photosynthesis", api_key=api_key)
36
+
37
+ # Verify result structure
38
+ assert isinstance(result, ExecutionResult)
39
+ assert len(result.output_blocks) > 0
40
+ # Check that the result doesn't contain an error
41
+ assert not result.output_blocks[0].data.startswith("Error:")
42
+
43
+ # Verify lesson plan contains expected elements
44
+ lesson_plan = result.output_blocks[0].data
45
+ assert "photosynthesis" in lesson_plan.lower()
46
+
47
+ @pytest.mark.asyncio
48
+ async def test_lesson_plan_with_env_api_key(self):
49
+ """Test lesson plan creation using environment variable for API key."""
50
+ # Skip if no API key
51
+ if not os.getenv("OPENAI_API_KEY"):
52
+ pytest.skip("OPENAI_API_KEY not set")
53
+
54
+ # Test without passing api_key parameter (should use env var)
55
+ result = await langgraph_lesson_planner(topic="fractions")
56
+
57
+ assert isinstance(result, ExecutionResult)
58
+ assert len(result.output_blocks) > 0
59
+ # Check that the result doesn't contain an error
60
+ assert not result.output_blocks[0].data.startswith("Error:")
61
+
62
+ @pytest.mark.asyncio
63
+ async def test_missing_api_key_error(self):
64
+ """Test error handling when API key is missing."""
65
+ # Temporarily save and remove env var
66
+ original_key = os.environ.get("OPENAI_API_KEY")
67
+ if "OPENAI_API_KEY" in os.environ:
68
+ del os.environ["OPENAI_API_KEY"]
69
+
70
+ try:
71
+ result = await langgraph_lesson_planner(topic="test topic")
72
+
73
+ # Should return error result
74
+ assert isinstance(result, ExecutionResult)
75
+ assert result.output_blocks[0].data.startswith("Error:")
76
+ assert "OPENAI_API_KEY not found" in result.output_blocks[0].data
77
+ finally:
78
+ # Restore env var
79
+ if original_key:
80
+ os.environ["OPENAI_API_KEY"] = original_key
81
+
82
+ @pytest.mark.asyncio
83
+ async def test_different_topics(self):
84
+ """Test lesson plan creation with different topics."""
85
+ # Skip if no API key
86
+ api_key = os.getenv("OPENAI_API_KEY")
87
+ if not api_key:
88
+ pytest.skip("OPENAI_API_KEY not set")
89
+
90
+ topics = ["addition", "animals", "water cycle"]
91
+
92
+ for topic in topics:
93
+ result = await langgraph_lesson_planner(topic=topic, api_key=api_key)
94
+
95
+ assert isinstance(result, ExecutionResult)
96
+ assert len(result.output_blocks) > 0
97
+ # Check that the result doesn't contain an error
98
+ assert not result.output_blocks[0].data.startswith("Error:")
99
+ assert topic.lower() in result.output_blocks[0].data.lower()
100
+
101
+ @pytest.mark.asyncio
102
+ async def test_concurrent_lesson_plan_creation(self):
103
+ """Test creating multiple lesson plans concurrently."""
104
+ # Skip if no API key
105
+ api_key = os.getenv("OPENAI_API_KEY")
106
+ if not api_key:
107
+ pytest.skip("OPENAI_API_KEY not set")
108
+
109
+ topics = ["math", "science", "reading"]
110
+
111
+ # Create tasks for concurrent execution
112
+ tasks = [langgraph_lesson_planner(topic=topic, api_key=api_key) for topic in topics]
113
+
114
+ # Execute concurrently
115
+ results = await asyncio.gather(*tasks)
116
+
117
+ # Verify all results
118
+ assert len(results) == len(topics)
119
+ for i, result in enumerate(results):
120
+ assert isinstance(result, ExecutionResult)
121
+ assert len(result.output_blocks) > 0
122
+ # Check that the result doesn't contain an error
123
+ assert not result.output_blocks[0].data.startswith("Error:")
124
+ assert topics[i].lower() in result.output_blocks[0].data.lower()
125
+
126
+
127
+ class TestLangGraphToolIntegration:
128
+ """Test LangGraph tool integration with MassGen tool system."""
129
+
130
+ def test_tool_function_signature(self):
131
+ """Test that the tool has the correct async signature."""
132
+ import inspect
133
+
134
+ assert inspect.iscoroutinefunction(langgraph_lesson_planner)
135
+
136
+ # Get function signature
137
+ sig = inspect.signature(langgraph_lesson_planner)
138
+ params = sig.parameters
139
+
140
+ # Verify parameters
141
+ assert "topic" in params
142
+ assert "api_key" in params
143
+
144
+ # Verify return annotation
145
+ assert sig.return_annotation == ExecutionResult
146
+
147
+ @pytest.mark.asyncio
148
+ async def test_execution_result_structure(self):
149
+ """Test that the returned ExecutionResult has the correct structure."""
150
+ # Skip if no API key
151
+ api_key = os.getenv("OPENAI_API_KEY")
152
+ if not api_key:
153
+ pytest.skip("OPENAI_API_KEY not set")
154
+
155
+ result = await langgraph_lesson_planner(topic="test", api_key=api_key)
156
+
157
+ # Verify ExecutionResult structure
158
+ assert hasattr(result, "output_blocks")
159
+ assert isinstance(result.output_blocks, list)
160
+ assert len(result.output_blocks) > 0
161
+ # Check that the result doesn't contain an error
162
+ assert not result.output_blocks[0].data.startswith("Error:")
163
+
164
+ # Verify TextContent structure
165
+ from massgen.tool._result import TextContent
166
+
167
+ assert isinstance(result.output_blocks[0], TextContent)
168
+ assert hasattr(result.output_blocks[0], "data")
169
+ assert isinstance(result.output_blocks[0].data, str)
170
+
171
+
172
+ class TestLangGraphToolWithBackend:
173
+ """Test LangGraph tool with ResponseBackend."""
174
+
175
+ @pytest.mark.asyncio
176
+ async def test_backend_registration(self):
177
+ """Test registering LangGraph tool with ResponseBackend."""
178
+ from massgen.backend.response import ResponseBackend
179
+
180
+ api_key = os.getenv("OPENAI_API_KEY", "test-key")
181
+
182
+ # Import the tool
183
+ from massgen.tool._extraframework_agents.langgraph_lesson_planner_tool import (
184
+ langgraph_lesson_planner,
185
+ )
186
+
187
+ # Register with backend
188
+ backend = ResponseBackend(
189
+ api_key=api_key,
190
+ custom_tools=[
191
+ {
192
+ "func": langgraph_lesson_planner,
193
+ "description": "Create a comprehensive lesson plan using LangGraph state graph",
194
+ },
195
+ ],
196
+ )
197
+
198
+ # Verify tool is registered
199
+ assert "langgraph_lesson_planner" in backend._custom_tool_names
200
+
201
+ # Verify schema generation
202
+ schemas = backend._get_custom_tools_schemas()
203
+ assert len(schemas) >= 1
204
+
205
+ # Find our tool's schema
206
+ langgraph_schema = None
207
+ for schema in schemas:
208
+ if schema["function"]["name"] == "langgraph_lesson_planner":
209
+ langgraph_schema = schema
210
+ break
211
+
212
+ assert langgraph_schema is not None
213
+ assert langgraph_schema["type"] == "function"
214
+ assert "parameters" in langgraph_schema["function"]
215
+
216
+
217
+ # ============================================================================
218
+ # Run tests
219
+ # ============================================================================
220
+
221
+ if __name__ == "__main__":
222
+ # Run pytest
223
+ pytest.main([__file__, "-v"])
massgen/tool/__init__.py CHANGED
@@ -2,6 +2,7 @@
2
2
  """Tool module for MassGen framework."""
3
3
 
4
4
  from ._code_executors import run_python_script, run_shell_script
5
+ from ._decorators import context_params
5
6
  from ._file_handlers import append_file_content, read_file_content, save_file_content
6
7
  from ._manager import ToolManager
7
8
  from ._result import ExecutionResult
@@ -18,21 +19,13 @@ from .workflow_toolkits import (
18
19
  __all__ = [
19
20
  "ToolManager",
20
21
  "ExecutionResult",
22
+ "context_params",
21
23
  "two_num_tool",
22
24
  "run_python_script",
23
25
  "run_shell_script",
24
26
  "read_file_content",
25
27
  "save_file_content",
26
28
  "append_file_content",
27
- "dashscope_generate_image",
28
- "dashscope_generate_audio",
29
- "dashscope_analyze_image",
30
- "openai_generate_image",
31
- "openai_generate_audio",
32
- "openai_modify_image",
33
- "openai_create_variation",
34
- "openai_analyze_image",
35
- "openai_transcribe_audio",
36
29
  "BaseToolkit",
37
30
  "ToolType",
38
31
  "NewAnswerToolkit",
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Decorators for custom tool functions."""
3
+
4
+ from typing import Callable
5
+
6
+
7
+ def context_params(*param_names: str) -> Callable[[Callable], Callable]:
8
+ """Mark parameters for auto-injection from ExecutionContext.
9
+
10
+ Parameters marked with this decorator will be:
11
+ 1. Excluded from the LLM schema (not visible to the model)
12
+ 2. Automatically injected from execution_context at runtime
13
+
14
+ This is useful for parameters that should come from the backend runtime
15
+ context rather than from the LLM, such as:
16
+ - messages: Conversation history
17
+ - agent_id: Current agent identifier
18
+ - backend_name: Backend provider name
19
+ - current_stage: Current coordination stage
20
+
21
+ Args:
22
+ *param_names: Names of parameters to mark as context parameters
23
+
24
+ Returns:
25
+ Decorator function that marks the parameters
26
+
27
+ Example:
28
+ >>> from massgen.tool import context_params, ExecutionResult
29
+ >>> from typing import List, Dict, Any
30
+ >>>
31
+ >>> @context_params("messages", "agent_id")
32
+ >>> async def analyze_conversation(
33
+ ... query: str, # LLM provides this
34
+ ... messages: List[Dict[str, Any]], # Auto-injected from context
35
+ ... agent_id: str, # Auto-injected from context
36
+ ... ) -> ExecutionResult:
37
+ ... '''Analyze conversation with full context.'''
38
+ ... # messages and agent_id are automatically filled from execution_context
39
+ ... system_msg = next((m for m in messages if m.get("role") == "system"), None)
40
+ ... return ExecutionResult(...)
41
+
42
+ Note:
43
+ The execution_context is provided by the backend when executing tools.
44
+ Only parameters marked by this decorator will be injected from the context.
45
+ """
46
+
47
+ def decorator(func: Callable) -> Callable:
48
+ """Store context parameter names in function metadata."""
49
+ func.__context_params__ = set(param_names)
50
+ return func
51
+
52
+ return decorator
@@ -0,0 +1,251 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ AG2 (AutoGen) Nested Chat Lesson Planner Tool
4
+ This tool demonstrates interoperability by wrapping AutoGen's nested chat functionality as a MassGen custom tool.
5
+ """
6
+
7
+ import os
8
+ from typing import Any, AsyncGenerator, Dict, List
9
+
10
+ from autogen import ConversableAgent, GroupChat, GroupChatManager
11
+
12
+ from massgen.tool import context_params
13
+ from massgen.tool._result import ExecutionResult, TextContent
14
+
15
+
16
+ def run_ag2_lesson_planner_agent(
17
+ messages: List[Dict[str, Any]],
18
+ api_key: str,
19
+ ):
20
+ """
21
+ Core AG2 lesson planner agent - pure AutoGen implementation.
22
+
23
+ This function contains the pure AG2/AutoGen logic for creating lesson plans
24
+ using nested chats and multiple specialized agents.
25
+
26
+ Args:
27
+ messages: messages for the agent to execute
28
+ api_key: OpenAI API key for the agents
29
+
30
+ Returns:
31
+ The formatted lesson plan as a string
32
+
33
+ Raises:
34
+ Exception: Any errors during agent execution
35
+ """
36
+ if not messages:
37
+ raise ValueError("No messages provided for lesson planning.")
38
+ # Configure LLM
39
+ llm_config = {
40
+ "config_list": [
41
+ {
42
+ "api_type": "openai",
43
+ "model": "gpt-4o",
44
+ "api_key": api_key,
45
+ },
46
+ ],
47
+ }
48
+
49
+ # Curriculum Standards Agent
50
+ curriculum_agent = ConversableAgent(
51
+ name="Curriculum_Agent",
52
+ system_message="""You are a curriculum standards expert for fourth grade education.
53
+ When given a topic, you provide relevant grade-level standards and learning objectives.
54
+ Format every response as:
55
+ STANDARDS:
56
+ - [Standard 1]
57
+ - [Standard 2]
58
+ OBJECTIVES:
59
+ - By the end of this lesson, students will be able to [objective 1]
60
+ - By the end of this lesson, students will be able to [objective 2]""",
61
+ human_input_mode="NEVER",
62
+ llm_config=llm_config,
63
+ )
64
+
65
+ # Lesson Planner Agent
66
+ lesson_planner_agent = ConversableAgent(
67
+ name="Lesson_Planner_Agent",
68
+ system_message="""You are a lesson planning specialist.
69
+ Given standards and objectives, you create detailed lesson plans including:
70
+ - Opening/Hook (5-10 minutes)
71
+ - Main Activity (20-30 minutes)
72
+ - Practice Activity (15-20 minutes)
73
+ - Assessment/Closure (5-10 minutes)
74
+ Format as a structured lesson plan with clear timing and materials needed.""",
75
+ human_input_mode="NEVER",
76
+ llm_config=llm_config,
77
+ )
78
+
79
+ # Lesson Reviewer Agent
80
+ lesson_reviewer_agent = ConversableAgent(
81
+ name="Lesson_Reviewer_Agent",
82
+ system_message="""You are a lesson plan reviewer who ensures:
83
+ 1. Age-appropriate content and activities
84
+ 2. Alignment with provided standards
85
+ 3. Realistic timing
86
+ 4. Clear instructions
87
+ 5. Differentiation opportunities
88
+ Provide specific feedback in these areas and suggest improvements if needed.""",
89
+ human_input_mode="NEVER",
90
+ llm_config=llm_config,
91
+ )
92
+
93
+ # Lead Teacher Agent
94
+ lead_teacher_agent = ConversableAgent(
95
+ name="Lead_Teacher_Agent",
96
+ system_message="""You are an experienced fourth grade teacher who oversees the lesson planning process.
97
+ Your role is to:
98
+ 1. Initiate the planning process with a clear topic
99
+ 2. Review and integrate feedback from other agents
100
+ 3. Ensure the final lesson plan is practical and engaging
101
+ 4. Make final adjustments based on classroom experience""",
102
+ human_input_mode="NEVER",
103
+ llm_config=llm_config,
104
+ )
105
+
106
+ # Create the group chat for collaborative lesson planning
107
+ planning_chat = GroupChat(
108
+ agents=[curriculum_agent, lesson_planner_agent, lesson_reviewer_agent],
109
+ messages=[],
110
+ max_round=2,
111
+ send_introductions=True,
112
+ )
113
+
114
+ planning_manager = GroupChatManager(
115
+ groupchat=planning_chat,
116
+ llm_config=llm_config,
117
+ )
118
+
119
+ # Formatter of the final lesson plan to a standard format
120
+ formatter_message = """You are a lesson plan formatter. Format the complete plan as follows:
121
+ <title>Lesson plan title</title>
122
+ <standards>Standards covered</standards>
123
+ <learning_objectives>Key learning objectives</learning_objectives>
124
+ <materials>Materials required</materials>
125
+ <activities>Lesson plan activities</activities>
126
+ <assessment>Assessment details</assessment>
127
+ """
128
+
129
+ lesson_formatter = ConversableAgent(
130
+ name="formatter_agent",
131
+ system_message=formatter_message,
132
+ llm_config=llm_config,
133
+ )
134
+
135
+ # Create nested chats configuration
136
+ nested_chats = [
137
+ {
138
+ # The first internal chat determines the standards and objectives
139
+ "recipient": curriculum_agent,
140
+ "message": f"Please provide fourth grade standards and objectives for: {messages}",
141
+ "max_turns": 2,
142
+ "summary_method": "last_msg",
143
+ },
144
+ {
145
+ # A group chat follows, where the lesson plan is created
146
+ "recipient": planning_manager,
147
+ "message": "Based on these standards and objectives, create a detailed lesson plan.",
148
+ "max_turns": 1,
149
+ "summary_method": "last_msg",
150
+ },
151
+ {
152
+ # Finally, a two-agent chat formats the lesson plan
153
+ "recipient": lesson_formatter,
154
+ "message": "Format the lesson plan.",
155
+ "max_turns": 1,
156
+ "summary_method": "last_msg",
157
+ },
158
+ ]
159
+
160
+ # Register nested chats with the lead teacher
161
+ lead_teacher_agent.register_nested_chats(
162
+ chat_queue=nested_chats,
163
+ trigger=lambda sender: sender.name == "Assistant_Agent",
164
+ )
165
+
166
+ # Create a simple assistant agent to trigger the nested chat
167
+ assistant_agent = ConversableAgent(
168
+ name="Assistant_Agent",
169
+ system_message="You are a helpful assistant.",
170
+ human_input_mode="NEVER",
171
+ llm_config=llm_config,
172
+ )
173
+
174
+ # Initiate the chat and get the result
175
+ response = assistant_agent.run(
176
+ recipient=lead_teacher_agent,
177
+ message=str(messages),
178
+ max_turns=1,
179
+ )
180
+
181
+ # Extract the lesson plan from the result
182
+
183
+ return response
184
+
185
+
186
+ @context_params("prompt")
187
+ async def ag2_lesson_planner(
188
+ prompt: List[Dict[str, Any]],
189
+ ) -> AsyncGenerator[ExecutionResult, None]:
190
+ """
191
+ MassGen custom tool wrapper for AG2 lesson planner.
192
+
193
+ This is the interface exposed to MassGen's backend. It handles environment setup,
194
+ error handling, and wraps the core agent logic in ExecutionResult.
195
+
196
+ Args:
197
+ prompt: processed message list from orchestrator (auto-injected via execution_context)
198
+
199
+ Returns:
200
+ ExecutionResult containing the formatted lesson plan or error message
201
+ """
202
+ # Get API key from environment
203
+ api_key = os.getenv("OPENAI_API_KEY")
204
+
205
+ if not api_key:
206
+ yield ExecutionResult(
207
+ output_blocks=[
208
+ TextContent(data="Error: OPENAI_API_KEY not found. Please set the environment variable."),
209
+ ],
210
+ )
211
+
212
+ try:
213
+ # Call the core agent function with processed messages
214
+ response = run_ag2_lesson_planner_agent(
215
+ messages=prompt,
216
+ api_key=api_key,
217
+ )
218
+
219
+ last_nested_chat_event_msgs = []
220
+
221
+ def process_and_log_event(*args, **kwargs) -> None:
222
+ """Process and log AG2 event, returning string representation."""
223
+ line = " ".join(str(arg) for arg in args)
224
+ last_nested_chat_event_msgs.append(line)
225
+
226
+ for event in response.events:
227
+ last_nested_chat_event_msgs.clear()
228
+ event.print(f=process_and_log_event)
229
+ formatted_message = "\n".join(last_nested_chat_event_msgs)
230
+
231
+ if event.type == "run_completion":
232
+ # Final output
233
+ yield ExecutionResult(
234
+ output_blocks=[
235
+ TextContent(data=event.content.summary),
236
+ ],
237
+ )
238
+ else:
239
+ yield ExecutionResult(
240
+ output_blocks=[
241
+ TextContent(data=formatted_message),
242
+ ],
243
+ is_log=True,
244
+ )
245
+
246
+ except Exception as e:
247
+ yield ExecutionResult(
248
+ output_blocks=[
249
+ TextContent(data=f"Error creating lesson plan: {str(e)}"),
250
+ ],
251
+ )