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.
- massgen/__init__.py +1 -1
- massgen/backend/base_with_custom_tool_and_mcp.py +453 -23
- massgen/backend/capabilities.py +39 -0
- massgen/backend/chat_completions.py +111 -197
- massgen/backend/claude.py +210 -181
- massgen/backend/gemini.py +1015 -1559
- massgen/backend/grok.py +3 -2
- massgen/backend/response.py +160 -220
- massgen/cli.py +73 -6
- massgen/config_builder.py +20 -54
- massgen/config_validator.py +931 -0
- massgen/configs/README.md +51 -8
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +1 -1
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/computer_use_browser_example.yaml +1 -1
- massgen/configs/tools/custom_tools/computer_use_docker_example.yaml +1 -1
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_langgraph_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_and_openai_assistant_lesson_planner.yaml +65 -0
- massgen/configs/tools/custom_tools/interop/ag2_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/agentscope_lesson_planner_example.yaml +48 -0
- massgen/configs/tools/custom_tools/interop/langgraph_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/interop/openai_assistant_lesson_planner_example.yaml +50 -0
- massgen/configs/tools/custom_tools/interop/smolagent_lesson_planner_example.yaml +49 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +1 -0
- massgen/configs/tools/custom_tools/two_models_with_tools_example.yaml +44 -0
- massgen/formatter/_gemini_formatter.py +61 -15
- massgen/tests/test_ag2_lesson_planner.py +223 -0
- massgen/tests/test_config_validator.py +1156 -0
- massgen/tests/test_langgraph_lesson_planner.py +223 -0
- massgen/tool/__init__.py +2 -9
- massgen/tool/_decorators.py +52 -0
- massgen/tool/_extraframework_agents/ag2_lesson_planner_tool.py +251 -0
- massgen/tool/_extraframework_agents/agentscope_lesson_planner_tool.py +303 -0
- massgen/tool/_extraframework_agents/langgraph_lesson_planner_tool.py +275 -0
- massgen/tool/_extraframework_agents/openai_assistant_lesson_planner_tool.py +247 -0
- massgen/tool/_extraframework_agents/smolagent_lesson_planner_tool.py +180 -0
- massgen/tool/_manager.py +102 -16
- massgen/tool/_registered_tool.py +3 -0
- massgen/tool/_result.py +3 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/METADATA +104 -76
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/RECORD +50 -39
- massgen/backend/gemini_mcp_manager.py +0 -545
- massgen/backend/gemini_trackers.py +0 -344
- massgen/configs/tools/custom_tools/multimodal_tools/playwright_with_img_understanding.yaml +0 -98
- massgen/configs/tools/custom_tools/multimodal_tools/understand_video_example.yaml +0 -54
- massgen/tools/__init__.py +0 -8
- massgen/tools/_planning_mcp_server.py +0 -520
- massgen/tools/planning_dataclasses.py +0 -434
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.5.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
)
|