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.
- massgen/__init__.py +1 -1
- massgen/api_params_handler/_chat_completions_api_params_handler.py +4 -0
- massgen/api_params_handler/_claude_api_params_handler.py +4 -0
- massgen/api_params_handler/_gemini_api_params_handler.py +4 -0
- massgen/api_params_handler/_response_api_params_handler.py +4 -0
- massgen/backend/base_with_custom_tool_and_mcp.py +25 -5
- massgen/backend/docs/permissions_and_context_files.md +2 -2
- massgen/backend/response.py +2 -0
- massgen/chat_agent.py +340 -20
- massgen/cli.py +326 -19
- massgen/configs/README.md +92 -41
- 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/crawl4ai_example.yaml +55 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_multi.yaml +61 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_single.yaml +29 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_multi.yaml +51 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_single.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_multi.yaml +55 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_single.yaml +33 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_multi.yaml +47 -0
- massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_single.yaml +29 -0
- massgen/configs/tools/custom_tools/multimodal_tools/understand_audio.yaml +1 -1
- massgen/configs/tools/custom_tools/multimodal_tools/understand_file.yaml +1 -1
- massgen/configs/tools/custom_tools/multimodal_tools/understand_image.yaml +1 -1
- massgen/configs/tools/custom_tools/multimodal_tools/understand_video.yaml +1 -1
- massgen/configs/tools/custom_tools/multimodal_tools/youtube_video_analysis.yaml +1 -1
- massgen/filesystem_manager/_filesystem_manager.py +1 -0
- massgen/filesystem_manager/_path_permission_manager.py +148 -0
- 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/message_templates.py +160 -12
- massgen/orchestrator.py +223 -7
- massgen/tests/memory/test_agent_compression.py +174 -0
- massgen/{configs/tools → tests}/memory/test_context_window_management.py +30 -30
- massgen/tests/memory/test_force_compression.py +154 -0
- massgen/tests/memory/test_simple_compression.py +147 -0
- massgen/tests/test_agent_memory.py +534 -0
- massgen/tests/test_binary_file_blocking.py +274 -0
- massgen/tests/test_case_studies.md +12 -12
- massgen/tests/test_conversation_memory.py +382 -0
- massgen/tests/test_multimodal_size_limits.py +407 -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/_manager.py +7 -2
- massgen/tool/_multimodal_tools/image_to_image_generation.py +293 -0
- massgen/tool/_multimodal_tools/text_to_file_generation.py +455 -0
- massgen/tool/_multimodal_tools/text_to_image_generation.py +222 -0
- massgen/tool/_multimodal_tools/text_to_speech_continue_generation.py +226 -0
- massgen/tool/_multimodal_tools/text_to_speech_transcription_generation.py +217 -0
- massgen/tool/_multimodal_tools/text_to_video_generation.py +223 -0
- massgen/tool/_multimodal_tools/understand_audio.py +19 -1
- massgen/tool/_multimodal_tools/understand_file.py +6 -1
- massgen/tool/_multimodal_tools/understand_image.py +112 -8
- massgen/tool/_multimodal_tools/understand_video.py +32 -5
- massgen/tool/_web_tools/crawl4ai_tool.py +718 -0
- massgen/tool/docs/multimodal_tools.md +589 -0
- massgen/tools/__init__.py +8 -0
- massgen/tools/_planning_mcp_server.py +520 -0
- massgen/tools/planning_dataclasses.py +434 -0
- {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/METADATA +142 -82
- {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/RECORD +84 -41
- massgen/configs/tools/custom_tools/crawl4ai_mcp_example.yaml +0 -67
- massgen/configs/tools/custom_tools/crawl4ai_multi_agent_example.yaml +0 -68
- massgen/configs/tools/memory/README.md +0 -199
- massgen/configs/tools/memory/gpt5mini_gemini_context_window_management.yaml +0 -131
- massgen/configs/tools/memory/gpt5mini_gemini_no_persistent_memory.yaml +0 -133
- massgen/configs/tools/multimodal/gpt5mini_gpt5nano_documentation_evolution.yaml +0 -97
- {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/WHEEL +0 -0
- {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {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())
|