massgen 0.1.4__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/chat_agent.py +340 -20
- massgen/cli.py +399 -25
- massgen/config_builder.py +20 -54
- massgen/config_validator.py +931 -0
- massgen/configs/README.md +95 -10
- massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
- massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
- massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
- massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
- massgen/configs/memory/single_agent_compression_test.yaml +64 -0
- 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/memory/README.md +277 -0
- massgen/memory/__init__.py +26 -0
- massgen/memory/_base.py +193 -0
- massgen/memory/_compression.py +237 -0
- massgen/memory/_context_monitor.py +211 -0
- massgen/memory/_conversation.py +255 -0
- massgen/memory/_fact_extraction_prompts.py +333 -0
- massgen/memory/_mem0_adapters.py +257 -0
- massgen/memory/_persistent.py +687 -0
- massgen/memory/docker-compose.qdrant.yml +36 -0
- massgen/memory/docs/DESIGN.md +388 -0
- massgen/memory/docs/QUICKSTART.md +409 -0
- massgen/memory/docs/SUMMARY.md +319 -0
- massgen/memory/docs/agent_use_memory.md +408 -0
- massgen/memory/docs/orchestrator_use_memory.md +586 -0
- massgen/memory/examples.py +237 -0
- massgen/orchestrator.py +207 -7
- massgen/tests/memory/test_agent_compression.py +174 -0
- massgen/tests/memory/test_context_window_management.py +286 -0
- massgen/tests/memory/test_force_compression.py +154 -0
- massgen/tests/memory/test_simple_compression.py +147 -0
- massgen/tests/test_ag2_lesson_planner.py +223 -0
- massgen/tests/test_agent_memory.py +534 -0
- massgen/tests/test_config_validator.py +1156 -0
- massgen/tests/test_conversation_memory.py +382 -0
- massgen/tests/test_langgraph_lesson_planner.py +223 -0
- massgen/tests/test_orchestrator_memory.py +620 -0
- massgen/tests/test_persistent_memory.py +435 -0
- massgen/token_manager/token_manager.py +6 -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.4.dist-info → massgen-0.1.6.dist-info}/METADATA +138 -77
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/RECORD +82 -37
- massgen/backend/gemini_mcp_manager.py +0 -545
- massgen/backend/gemini_trackers.py +0 -344
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/WHEEL +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.4.dist-info → massgen-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Simple Compression Test (No Persistent Memory)
|
|
5
|
+
|
|
6
|
+
Tests core compression logic by:
|
|
7
|
+
1. Creating an agent with only conversation_memory (no persistent_memory)
|
|
8
|
+
2. Adding many long messages to trigger compression
|
|
9
|
+
3. Verifying old messages are removed
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
uv run python massgen/configs/memory/test_simple_compression.py
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
|
21
|
+
|
|
22
|
+
from dotenv import load_dotenv # noqa: E402
|
|
23
|
+
|
|
24
|
+
from massgen.backend.chat_completions import ChatCompletionsBackend # noqa: E402
|
|
25
|
+
from massgen.chat_agent import SingleAgent # noqa: E402
|
|
26
|
+
from massgen.memory import ConversationMemory # noqa: E402
|
|
27
|
+
from massgen.memory._context_monitor import ContextWindowMonitor # noqa: E402
|
|
28
|
+
|
|
29
|
+
load_dotenv()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def main():
|
|
33
|
+
"""Test compression without persistent memory."""
|
|
34
|
+
print("=" * 80)
|
|
35
|
+
print("Simple Compression Test (Conversation Memory Only)")
|
|
36
|
+
print("=" * 80 + "\n")
|
|
37
|
+
|
|
38
|
+
# Check API key
|
|
39
|
+
if not os.getenv("OPENAI_API_KEY"):
|
|
40
|
+
print("❌ Error: OPENAI_API_KEY not set")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Configuration - set very low thresholds to trigger quickly
|
|
44
|
+
model_name = "gpt-4o-mini"
|
|
45
|
+
provider = "openai"
|
|
46
|
+
trigger_threshold = 0.03 # Trigger at 3% (very low for testing)
|
|
47
|
+
target_ratio = 0.01 # Keep only 1% after compression
|
|
48
|
+
|
|
49
|
+
print("Configuration:")
|
|
50
|
+
print(f" Model: {model_name}")
|
|
51
|
+
print(" Context window: 128,000 tokens")
|
|
52
|
+
print(f" Trigger at: {int(128000 * trigger_threshold):,} tokens (3%)")
|
|
53
|
+
print(f" Target after: {int(128000 * target_ratio):,} tokens (1%)\n")
|
|
54
|
+
|
|
55
|
+
# 1. Create backend
|
|
56
|
+
backend = ChatCompletionsBackend(
|
|
57
|
+
type=provider,
|
|
58
|
+
model=model_name,
|
|
59
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 2. Create conversation memory ONLY (no persistent memory)
|
|
63
|
+
conversation_memory = ConversationMemory()
|
|
64
|
+
print("✅ Conversation memory created (NO persistent memory)")
|
|
65
|
+
|
|
66
|
+
# 3. Create context monitor
|
|
67
|
+
monitor = ContextWindowMonitor(
|
|
68
|
+
model_name=model_name,
|
|
69
|
+
provider=provider,
|
|
70
|
+
trigger_threshold=trigger_threshold,
|
|
71
|
+
target_ratio=target_ratio,
|
|
72
|
+
enabled=True,
|
|
73
|
+
)
|
|
74
|
+
print("✅ Monitor created\n")
|
|
75
|
+
|
|
76
|
+
# 4. Create agent (no persistent_memory!)
|
|
77
|
+
agent = SingleAgent(
|
|
78
|
+
backend=backend,
|
|
79
|
+
agent_id="test_agent",
|
|
80
|
+
system_message="You are a helpful assistant.",
|
|
81
|
+
conversation_memory=conversation_memory,
|
|
82
|
+
persistent_memory=None, # Explicitly None
|
|
83
|
+
context_monitor=monitor,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Verify compressor was created
|
|
87
|
+
if agent.context_compressor:
|
|
88
|
+
print("✅ Context compressor created!")
|
|
89
|
+
print(f" Persistent memory: {agent.context_compressor.persistent_memory is not None}\n")
|
|
90
|
+
else:
|
|
91
|
+
print("❌ Context compressor NOT created (need both monitor + conversation_memory)\n")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# 5. Run one turn with a complex question
|
|
95
|
+
print("=" * 80)
|
|
96
|
+
print("Running conversation to trigger compression...")
|
|
97
|
+
print("=" * 80 + "\n")
|
|
98
|
+
|
|
99
|
+
prompt = """Explain in extreme detail:
|
|
100
|
+
1. How Python's garbage collection works
|
|
101
|
+
2. The Global Interpreter Lock (GIL)
|
|
102
|
+
3. Python's asyncio event loop
|
|
103
|
+
4. The descriptor protocol
|
|
104
|
+
5. Metaclasses and the type system
|
|
105
|
+
|
|
106
|
+
Provide comprehensive explanations with code examples for each topic."""
|
|
107
|
+
|
|
108
|
+
print("Sending complex prompt to generate long response...\n")
|
|
109
|
+
|
|
110
|
+
response_text = ""
|
|
111
|
+
async for chunk in agent.chat([{"role": "user", "content": prompt}]):
|
|
112
|
+
if chunk.type == "content" and chunk.content:
|
|
113
|
+
response_text += chunk.content
|
|
114
|
+
print(".", end="", flush=True)
|
|
115
|
+
|
|
116
|
+
print(f"\n\nResponse generated: {len(response_text):,} characters\n")
|
|
117
|
+
|
|
118
|
+
# 6. Check results
|
|
119
|
+
print("=" * 80)
|
|
120
|
+
print("Results")
|
|
121
|
+
print("=" * 80 + "\n")
|
|
122
|
+
|
|
123
|
+
final_messages = await conversation_memory.get_messages()
|
|
124
|
+
print(f"Messages in conversation_memory: {len(final_messages)}")
|
|
125
|
+
|
|
126
|
+
stats = monitor.get_stats()
|
|
127
|
+
print("\n📊 Monitor Stats:")
|
|
128
|
+
print(f" Turns: {stats['turn_count']}")
|
|
129
|
+
print(f" Total tokens: {stats['total_tokens']:,}")
|
|
130
|
+
|
|
131
|
+
comp_stats = agent.context_compressor.get_stats()
|
|
132
|
+
print("\n📦 Compression Stats:")
|
|
133
|
+
print(f" Compressions: {comp_stats['total_compressions']}")
|
|
134
|
+
print(f" Messages removed: {comp_stats['total_messages_removed']}")
|
|
135
|
+
print(f" Tokens removed: {comp_stats['total_tokens_removed']:,}")
|
|
136
|
+
|
|
137
|
+
print("\n" + "=" * 80)
|
|
138
|
+
if comp_stats["total_compressions"] > 0:
|
|
139
|
+
print("✅ SUCCESS: Compression triggered and removed messages!")
|
|
140
|
+
else:
|
|
141
|
+
print("⚠️ No compression - response may not have been long enough")
|
|
142
|
+
print(f" (Needed {int(128000 * trigger_threshold):,} tokens to trigger)")
|
|
143
|
+
print("=" * 80 + "\n")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Test AG2 (AutoGen) Lesson Planner Tool
|
|
4
|
+
Tests the interoperability feature where AutoGen nested chat 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.ag2_lesson_planner_tool import ( # noqa: E402
|
|
18
|
+
ag2_lesson_planner,
|
|
19
|
+
)
|
|
20
|
+
from massgen.tool._result import ExecutionResult # noqa: E402
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestAG2LessonPlannerTool:
|
|
24
|
+
"""Test AG2 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 ag2_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 ag2_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 ag2_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 ag2_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 = [ag2_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 TestAG2ToolIntegration:
|
|
128
|
+
"""Test AG2 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(ag2_lesson_planner)
|
|
135
|
+
|
|
136
|
+
# Get function signature
|
|
137
|
+
sig = inspect.signature(ag2_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 ag2_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 TestAG2ToolWithBackend:
|
|
173
|
+
"""Test AG2 tool with ResponseBackend."""
|
|
174
|
+
|
|
175
|
+
@pytest.mark.asyncio
|
|
176
|
+
async def test_backend_registration(self):
|
|
177
|
+
"""Test registering AG2 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.ag2_lesson_planner_tool import (
|
|
184
|
+
ag2_lesson_planner,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Register with backend
|
|
188
|
+
backend = ResponseBackend(
|
|
189
|
+
api_key=api_key,
|
|
190
|
+
custom_tools=[
|
|
191
|
+
{
|
|
192
|
+
"func": ag2_lesson_planner,
|
|
193
|
+
"description": "Create a comprehensive lesson plan using AG2 nested chat",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Verify tool is registered
|
|
199
|
+
assert "ag2_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
|
+
ag2_schema = None
|
|
207
|
+
for schema in schemas:
|
|
208
|
+
if schema["function"]["name"] == "ag2_lesson_planner":
|
|
209
|
+
ag2_schema = schema
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
assert ag2_schema is not None
|
|
213
|
+
assert ag2_schema["type"] == "function"
|
|
214
|
+
assert "parameters" in ag2_schema["function"]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ============================================================================
|
|
218
|
+
# Run tests
|
|
219
|
+
# ============================================================================
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
# Run pytest
|
|
223
|
+
pytest.main([__file__, "-v"])
|