massgen 0.1.3__py3-none-any.whl → 0.1.5__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 (90) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/api_params_handler/_chat_completions_api_params_handler.py +4 -0
  3. massgen/api_params_handler/_claude_api_params_handler.py +4 -0
  4. massgen/api_params_handler/_gemini_api_params_handler.py +4 -0
  5. massgen/api_params_handler/_response_api_params_handler.py +4 -0
  6. massgen/backend/base_with_custom_tool_and_mcp.py +25 -5
  7. massgen/backend/docs/permissions_and_context_files.md +2 -2
  8. massgen/backend/response.py +2 -0
  9. massgen/chat_agent.py +340 -20
  10. massgen/cli.py +326 -19
  11. massgen/configs/README.md +92 -41
  12. massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
  13. massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
  14. massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
  15. massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
  16. massgen/configs/memory/single_agent_compression_test.yaml +64 -0
  17. massgen/configs/tools/custom_tools/crawl4ai_example.yaml +55 -0
  18. massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_multi.yaml +61 -0
  19. massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_single.yaml +29 -0
  20. massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_multi.yaml +51 -0
  21. massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_single.yaml +33 -0
  22. massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_multi.yaml +55 -0
  23. massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_single.yaml +33 -0
  24. massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_multi.yaml +47 -0
  25. massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_single.yaml +29 -0
  26. massgen/configs/tools/custom_tools/multimodal_tools/understand_audio.yaml +1 -1
  27. massgen/configs/tools/custom_tools/multimodal_tools/understand_file.yaml +1 -1
  28. massgen/configs/tools/custom_tools/multimodal_tools/understand_image.yaml +1 -1
  29. massgen/configs/tools/custom_tools/multimodal_tools/understand_video.yaml +1 -1
  30. massgen/configs/tools/custom_tools/multimodal_tools/youtube_video_analysis.yaml +1 -1
  31. massgen/filesystem_manager/_filesystem_manager.py +1 -0
  32. massgen/filesystem_manager/_path_permission_manager.py +148 -0
  33. massgen/memory/README.md +277 -0
  34. massgen/memory/__init__.py +26 -0
  35. massgen/memory/_base.py +193 -0
  36. massgen/memory/_compression.py +237 -0
  37. massgen/memory/_context_monitor.py +211 -0
  38. massgen/memory/_conversation.py +255 -0
  39. massgen/memory/_fact_extraction_prompts.py +333 -0
  40. massgen/memory/_mem0_adapters.py +257 -0
  41. massgen/memory/_persistent.py +687 -0
  42. massgen/memory/docker-compose.qdrant.yml +36 -0
  43. massgen/memory/docs/DESIGN.md +388 -0
  44. massgen/memory/docs/QUICKSTART.md +409 -0
  45. massgen/memory/docs/SUMMARY.md +319 -0
  46. massgen/memory/docs/agent_use_memory.md +408 -0
  47. massgen/memory/docs/orchestrator_use_memory.md +586 -0
  48. massgen/memory/examples.py +237 -0
  49. massgen/message_templates.py +160 -12
  50. massgen/orchestrator.py +223 -7
  51. massgen/tests/memory/test_agent_compression.py +174 -0
  52. massgen/{configs/tools → tests}/memory/test_context_window_management.py +30 -30
  53. massgen/tests/memory/test_force_compression.py +154 -0
  54. massgen/tests/memory/test_simple_compression.py +147 -0
  55. massgen/tests/test_agent_memory.py +534 -0
  56. massgen/tests/test_binary_file_blocking.py +274 -0
  57. massgen/tests/test_case_studies.md +12 -12
  58. massgen/tests/test_conversation_memory.py +382 -0
  59. massgen/tests/test_multimodal_size_limits.py +407 -0
  60. massgen/tests/test_orchestrator_memory.py +620 -0
  61. massgen/tests/test_persistent_memory.py +435 -0
  62. massgen/token_manager/token_manager.py +6 -0
  63. massgen/tool/_manager.py +7 -2
  64. massgen/tool/_multimodal_tools/image_to_image_generation.py +293 -0
  65. massgen/tool/_multimodal_tools/text_to_file_generation.py +455 -0
  66. massgen/tool/_multimodal_tools/text_to_image_generation.py +222 -0
  67. massgen/tool/_multimodal_tools/text_to_speech_continue_generation.py +226 -0
  68. massgen/tool/_multimodal_tools/text_to_speech_transcription_generation.py +217 -0
  69. massgen/tool/_multimodal_tools/text_to_video_generation.py +223 -0
  70. massgen/tool/_multimodal_tools/understand_audio.py +19 -1
  71. massgen/tool/_multimodal_tools/understand_file.py +6 -1
  72. massgen/tool/_multimodal_tools/understand_image.py +112 -8
  73. massgen/tool/_multimodal_tools/understand_video.py +32 -5
  74. massgen/tool/_web_tools/crawl4ai_tool.py +718 -0
  75. massgen/tool/docs/multimodal_tools.md +589 -0
  76. massgen/tools/__init__.py +8 -0
  77. massgen/tools/_planning_mcp_server.py +520 -0
  78. massgen/tools/planning_dataclasses.py +434 -0
  79. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/METADATA +142 -82
  80. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/RECORD +84 -41
  81. massgen/configs/tools/custom_tools/crawl4ai_mcp_example.yaml +0 -67
  82. massgen/configs/tools/custom_tools/crawl4ai_multi_agent_example.yaml +0 -68
  83. massgen/configs/tools/memory/README.md +0 -199
  84. massgen/configs/tools/memory/gpt5mini_gemini_context_window_management.yaml +0 -131
  85. massgen/configs/tools/memory/gpt5mini_gemini_no_persistent_memory.yaml +0 -133
  86. massgen/configs/tools/multimodal/gpt5mini_gpt5nano_documentation_evolution.yaml +0 -97
  87. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/WHEEL +0 -0
  88. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/entry_points.txt +0 -0
  89. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/licenses/LICENSE +0 -0
  90. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Force Compression Test
5
+
6
+ Directly tests compression by manually adding many messages to trigger it.
7
+ Bypasses LLM calls for faster testing.
8
+
9
+ Usage:
10
+ uv run python massgen/configs/memory/test_force_compression.py
11
+ """
12
+
13
+ import asyncio
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
18
+
19
+ from massgen.memory import ContextCompressor, ConversationMemory # noqa: E402
20
+ from massgen.memory._context_monitor import ContextWindowMonitor # noqa: E402
21
+ from massgen.token_manager.token_manager import TokenCostCalculator # noqa: E402
22
+
23
+
24
+ async def main():
25
+ """Test compression by manually creating a large conversation."""
26
+ print("=" * 80)
27
+ print("Force Compression Test")
28
+ print("=" * 80 + "\n")
29
+
30
+ # Create components
31
+ calculator = TokenCostCalculator()
32
+ conversation_memory = ConversationMemory()
33
+
34
+ # Create monitor with low threshold
35
+ monitor = ContextWindowMonitor(
36
+ model_name="gpt-4o-mini",
37
+ provider="openai",
38
+ trigger_threshold=0.10, # 10% = 12,800 tokens
39
+ target_ratio=0.05, # 5% = 6,400 tokens
40
+ enabled=True,
41
+ )
42
+
43
+ # Create compressor (no persistent memory for this test)
44
+ compressor = ContextCompressor(
45
+ token_calculator=calculator,
46
+ conversation_memory=conversation_memory,
47
+ persistent_memory=None, # Test without it first
48
+ )
49
+
50
+ print("Configuration:")
51
+ print(f" Context window: {monitor.context_window:,} tokens")
52
+ print(f" Trigger at: {int(monitor.context_window * monitor.trigger_threshold):,} tokens ({monitor.trigger_threshold*100:.0f}%)")
53
+ print(f" Target after: {int(monitor.context_window * monitor.target_ratio):,} tokens ({monitor.target_ratio*100:.0f}%)\n")
54
+
55
+ # Manually create a large conversation
56
+ print("Creating large conversation...")
57
+
58
+ messages = [
59
+ {"role": "system", "content": "You are a helpful assistant."},
60
+ ]
61
+
62
+ # Add many long messages to exceed threshold
63
+ long_content = "This is a detailed explanation about Python programming. " * 200 # ~2000 tokens per message
64
+
65
+ for i in range(10):
66
+ messages.append({"role": "user", "content": f"Question {i}: {long_content}"})
67
+ messages.append({"role": "assistant", "content": f"Answer {i}: {long_content}"})
68
+
69
+ # Add to conversation memory
70
+ await conversation_memory.add(messages)
71
+
72
+ message_count = len(messages)
73
+ total_tokens = calculator.estimate_tokens(messages)
74
+
75
+ print("✅ Created conversation:")
76
+ print(f" Messages: {message_count}")
77
+ print(f" Estimated tokens: {total_tokens:,}\n")
78
+
79
+ # Check if we should compress
80
+ usage_info = monitor.log_context_usage(messages, turn_number=1)
81
+
82
+ print("\n📊 Context Analysis:")
83
+ print(f" Current: {usage_info['current_tokens']:,} / {usage_info['max_tokens']:,} tokens")
84
+ print(f" Usage: {usage_info['usage_percent']*100:.1f}%")
85
+ print(f" Should compress: {usage_info['should_compress']}\n")
86
+
87
+ if not usage_info["should_compress"]:
88
+ print("⚠️ Not over threshold yet, adding more messages...\n")
89
+ # Add more messages
90
+ for i in range(10, 20):
91
+ messages.append({"role": "user", "content": f"Question {i}: {long_content}"})
92
+ messages.append({"role": "assistant", "content": f"Answer {i}: {long_content}"})
93
+
94
+ await conversation_memory.add(messages[21:]) # Add new messages
95
+ total_tokens = calculator.estimate_tokens(messages)
96
+ usage_info = monitor.log_context_usage(messages, turn_number=2)
97
+
98
+ print("\n📊 After adding more:")
99
+ print(f" Messages: {len(messages)}")
100
+ print(f" Current: {usage_info['current_tokens']:,} tokens")
101
+ print(f" Usage: {usage_info['usage_percent']*100:.1f}%")
102
+ print(f" Should compress: {usage_info['should_compress']}\n")
103
+
104
+ # Trigger compression
105
+ print("=" * 80)
106
+ print("Triggering Compression...")
107
+ print("=" * 80 + "\n")
108
+
109
+ compression_stats = await compressor.compress_if_needed(
110
+ messages=messages,
111
+ current_tokens=usage_info["current_tokens"],
112
+ target_tokens=usage_info["target_tokens"],
113
+ should_compress=True, # Force it
114
+ )
115
+
116
+ # Show results
117
+ print("\n" + "=" * 80)
118
+ print("Compression Results")
119
+ print("=" * 80 + "\n")
120
+
121
+ if compression_stats:
122
+ print("✅ COMPRESSION OCCURRED!")
123
+ print("\n📦 Stats:")
124
+ print(f" Messages removed: {compression_stats.messages_removed}")
125
+ print(f" Tokens removed: {compression_stats.tokens_removed:,}")
126
+ print(f" Messages kept: {compression_stats.messages_kept}")
127
+ print(f" Tokens kept: {compression_stats.tokens_kept:,}")
128
+
129
+ # Verify conversation memory was updated
130
+ final_messages = await conversation_memory.get_messages()
131
+ print("\n💾 Conversation Memory After Compression:")
132
+ print(f" Messages remaining: {len(final_messages)}")
133
+ print(f" Expected: {compression_stats.messages_kept}")
134
+
135
+ if len(final_messages) == compression_stats.messages_kept:
136
+ print("\n✅ SUCCESS: Conversation memory correctly updated!")
137
+ else:
138
+ print("\n❌ ERROR: Message count mismatch!")
139
+
140
+ # Show compressor overall stats
141
+ comp_stats = compressor.get_stats()
142
+ print("\n📊 Compressor Total Stats:")
143
+ print(f" Total compressions: {comp_stats['total_compressions']}")
144
+ print(f" Total messages removed: {comp_stats['total_messages_removed']}")
145
+ print(f" Total tokens removed: {comp_stats['total_tokens_removed']:,}")
146
+
147
+ else:
148
+ print("❌ No compression occurred")
149
+
150
+ print("\n" + "=" * 80 + "\n")
151
+
152
+
153
+ if __name__ == "__main__":
154
+ asyncio.run(main())
@@ -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())