massgen 0.1.4__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 (46) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/chat_agent.py +340 -20
  3. massgen/cli.py +326 -19
  4. massgen/configs/README.md +52 -10
  5. massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
  6. massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
  7. massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
  8. massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
  9. massgen/configs/memory/single_agent_compression_test.yaml +64 -0
  10. massgen/configs/tools/custom_tools/multimodal_tools/playwright_with_img_understanding.yaml +98 -0
  11. massgen/configs/tools/custom_tools/multimodal_tools/understand_video_example.yaml +54 -0
  12. massgen/memory/README.md +277 -0
  13. massgen/memory/__init__.py +26 -0
  14. massgen/memory/_base.py +193 -0
  15. massgen/memory/_compression.py +237 -0
  16. massgen/memory/_context_monitor.py +211 -0
  17. massgen/memory/_conversation.py +255 -0
  18. massgen/memory/_fact_extraction_prompts.py +333 -0
  19. massgen/memory/_mem0_adapters.py +257 -0
  20. massgen/memory/_persistent.py +687 -0
  21. massgen/memory/docker-compose.qdrant.yml +36 -0
  22. massgen/memory/docs/DESIGN.md +388 -0
  23. massgen/memory/docs/QUICKSTART.md +409 -0
  24. massgen/memory/docs/SUMMARY.md +319 -0
  25. massgen/memory/docs/agent_use_memory.md +408 -0
  26. massgen/memory/docs/orchestrator_use_memory.md +586 -0
  27. massgen/memory/examples.py +237 -0
  28. massgen/orchestrator.py +207 -7
  29. massgen/tests/memory/test_agent_compression.py +174 -0
  30. massgen/tests/memory/test_context_window_management.py +286 -0
  31. massgen/tests/memory/test_force_compression.py +154 -0
  32. massgen/tests/memory/test_simple_compression.py +147 -0
  33. massgen/tests/test_agent_memory.py +534 -0
  34. massgen/tests/test_conversation_memory.py +382 -0
  35. massgen/tests/test_orchestrator_memory.py +620 -0
  36. massgen/tests/test_persistent_memory.py +435 -0
  37. massgen/token_manager/token_manager.py +6 -0
  38. massgen/tools/__init__.py +8 -0
  39. massgen/tools/_planning_mcp_server.py +520 -0
  40. massgen/tools/planning_dataclasses.py +434 -0
  41. {massgen-0.1.4.dist-info → massgen-0.1.5.dist-info}/METADATA +109 -76
  42. {massgen-0.1.4.dist-info → massgen-0.1.5.dist-info}/RECORD +46 -12
  43. {massgen-0.1.4.dist-info → massgen-0.1.5.dist-info}/WHEEL +0 -0
  44. {massgen-0.1.4.dist-info → massgen-0.1.5.dist-info}/entry_points.txt +0 -0
  45. {massgen-0.1.4.dist-info → massgen-0.1.5.dist-info}/licenses/LICENSE +0 -0
  46. {massgen-0.1.4.dist-info → massgen-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Tests for ConversationMemory implementation.
5
+
6
+ This module tests the in-memory conversation storage functionality,
7
+ including adding, retrieving, deleting, and managing messages.
8
+ """
9
+
10
+ import pytest
11
+
12
+ from massgen.memory import ConversationMemory
13
+
14
+
15
+ @pytest.mark.asyncio
16
+ async def test_conversation_memory_initialization():
17
+ """Test that ConversationMemory initializes correctly."""
18
+ memory = ConversationMemory()
19
+
20
+ assert await memory.size() == 0
21
+ assert memory.messages == []
22
+ print("✅ ConversationMemory initialization works")
23
+
24
+
25
+ @pytest.mark.asyncio
26
+ async def test_add_single_message():
27
+ """Test adding a single message to memory."""
28
+ memory = ConversationMemory()
29
+
30
+ message = {"role": "user", "content": "Hello, world!"}
31
+ await memory.add(message)
32
+
33
+ assert await memory.size() == 1
34
+ messages = await memory.get_messages()
35
+ assert len(messages) == 1
36
+ assert messages[0]["role"] == "user"
37
+ assert messages[0]["content"] == "Hello, world!"
38
+ assert "id" in messages[0] # Auto-generated ID
39
+ print("✅ Adding single message works")
40
+
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_add_multiple_messages():
44
+ """Test adding multiple messages at once."""
45
+ memory = ConversationMemory()
46
+
47
+ messages = [
48
+ {"role": "user", "content": "Hello"},
49
+ {"role": "assistant", "content": "Hi there!"},
50
+ {"role": "user", "content": "How are you?"},
51
+ ]
52
+ await memory.add(messages)
53
+
54
+ assert await memory.size() == 3
55
+ retrieved = await memory.get_messages()
56
+ assert len(retrieved) == 3
57
+ assert retrieved[0]["content"] == "Hello"
58
+ assert retrieved[1]["content"] == "Hi there!"
59
+ assert retrieved[2]["content"] == "How are you?"
60
+ print("✅ Adding multiple messages works")
61
+
62
+
63
+ @pytest.mark.asyncio
64
+ async def test_duplicate_prevention():
65
+ """Test that duplicate messages are prevented by default."""
66
+ memory = ConversationMemory()
67
+
68
+ message = {"id": "msg_123", "role": "user", "content": "Hello"}
69
+
70
+ # Add same message twice
71
+ await memory.add(message)
72
+ await memory.add(message)
73
+
74
+ # Should only have one message
75
+ assert await memory.size() == 1
76
+ print("✅ Duplicate prevention works")
77
+
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_allow_duplicates():
81
+ """Test allowing duplicate messages when explicitly enabled."""
82
+ memory = ConversationMemory()
83
+
84
+ message = {"id": "msg_123", "role": "user", "content": "Hello"}
85
+
86
+ # Add same message twice with allow_duplicates=True
87
+ await memory.add(message, allow_duplicates=True)
88
+ await memory.add(message, allow_duplicates=True)
89
+
90
+ # Should have two messages
91
+ assert await memory.size() == 2
92
+ print("✅ Allowing duplicates works")
93
+
94
+
95
+ @pytest.mark.asyncio
96
+ async def test_delete_by_index():
97
+ """Test deleting messages by index."""
98
+ memory = ConversationMemory()
99
+
100
+ messages = [
101
+ {"role": "user", "content": "Message 1"},
102
+ {"role": "assistant", "content": "Message 2"},
103
+ {"role": "user", "content": "Message 3"},
104
+ ]
105
+ await memory.add(messages)
106
+
107
+ # Delete middle message
108
+ await memory.delete(1)
109
+
110
+ assert await memory.size() == 2
111
+ retrieved = await memory.get_messages()
112
+ assert retrieved[0]["content"] == "Message 1"
113
+ assert retrieved[1]["content"] == "Message 3"
114
+ print("✅ Deleting by index works")
115
+
116
+
117
+ @pytest.mark.asyncio
118
+ async def test_delete_multiple_indices():
119
+ """Test deleting multiple messages at once."""
120
+ memory = ConversationMemory()
121
+
122
+ messages = [{"role": "user", "content": f"Message {i}"} for i in range(5)]
123
+ await memory.add(messages)
124
+
125
+ # Delete indices 1 and 3
126
+ await memory.delete([1, 3])
127
+
128
+ assert await memory.size() == 3
129
+ retrieved = await memory.get_messages()
130
+ assert retrieved[0]["content"] == "Message 0"
131
+ assert retrieved[1]["content"] == "Message 2"
132
+ assert retrieved[2]["content"] == "Message 4"
133
+ print("✅ Deleting multiple indices works")
134
+
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_delete_invalid_index():
138
+ """Test that deleting invalid index raises error."""
139
+ memory = ConversationMemory()
140
+
141
+ await memory.add({"role": "user", "content": "Hello"})
142
+
143
+ # Try to delete out of range index
144
+ with pytest.raises(IndexError):
145
+ await memory.delete(10)
146
+
147
+ print("✅ Invalid index deletion raises error correctly")
148
+
149
+
150
+ @pytest.mark.asyncio
151
+ async def test_get_last_message():
152
+ """Test getting the last message."""
153
+ memory = ConversationMemory()
154
+
155
+ # Empty memory
156
+ assert await memory.get_last_message() is None
157
+
158
+ # Add messages
159
+ messages = [
160
+ {"role": "user", "content": "First"},
161
+ {"role": "assistant", "content": "Second"},
162
+ {"role": "user", "content": "Third"},
163
+ ]
164
+ await memory.add(messages)
165
+
166
+ last = await memory.get_last_message()
167
+ assert last is not None
168
+ assert last["content"] == "Third"
169
+ print("✅ Getting last message works")
170
+
171
+
172
+ @pytest.mark.asyncio
173
+ async def test_get_messages_by_role():
174
+ """Test filtering messages by role."""
175
+ memory = ConversationMemory()
176
+
177
+ messages = [
178
+ {"role": "user", "content": "User 1"},
179
+ {"role": "assistant", "content": "Assistant 1"},
180
+ {"role": "user", "content": "User 2"},
181
+ {"role": "assistant", "content": "Assistant 2"},
182
+ {"role": "system", "content": "System 1"},
183
+ ]
184
+ await memory.add(messages)
185
+
186
+ user_messages = await memory.get_messages_by_role("user")
187
+ assert len(user_messages) == 2
188
+ assert all(msg["role"] == "user" for msg in user_messages)
189
+
190
+ assistant_messages = await memory.get_messages_by_role("assistant")
191
+ assert len(assistant_messages) == 2
192
+ assert all(msg["role"] == "assistant" for msg in assistant_messages)
193
+
194
+ system_messages = await memory.get_messages_by_role("system")
195
+ assert len(system_messages) == 1
196
+
197
+ print("✅ Filtering by role works")
198
+
199
+
200
+ @pytest.mark.asyncio
201
+ async def test_get_messages_with_limit():
202
+ """Test getting messages with limit."""
203
+ memory = ConversationMemory()
204
+
205
+ messages = [{"role": "user", "content": f"Message {i}"} for i in range(10)]
206
+ await memory.add(messages)
207
+
208
+ # Get last 3 messages
209
+ recent = await memory.get_messages(limit=3)
210
+ assert len(recent) == 3
211
+ assert recent[0]["content"] == "Message 7"
212
+ assert recent[1]["content"] == "Message 8"
213
+ assert recent[2]["content"] == "Message 9"
214
+ print("✅ Getting messages with limit works")
215
+
216
+
217
+ @pytest.mark.asyncio
218
+ async def test_truncate_to_size():
219
+ """Test truncating memory to a maximum size."""
220
+ memory = ConversationMemory()
221
+
222
+ messages = [{"role": "user", "content": f"Message {i}"} for i in range(10)]
223
+ await memory.add(messages)
224
+
225
+ # Truncate to last 5 messages
226
+ await memory.truncate_to_size(5)
227
+
228
+ assert await memory.size() == 5
229
+ retrieved = await memory.get_messages()
230
+ assert retrieved[0]["content"] == "Message 5"
231
+ assert retrieved[4]["content"] == "Message 9"
232
+ print("✅ Truncating to size works")
233
+
234
+
235
+ @pytest.mark.asyncio
236
+ async def test_clear_memory():
237
+ """Test clearing all messages from memory."""
238
+ memory = ConversationMemory()
239
+
240
+ messages = [{"role": "user", "content": f"Message {i}"} for i in range(5)]
241
+ await memory.add(messages)
242
+
243
+ assert await memory.size() == 5
244
+
245
+ await memory.clear()
246
+
247
+ assert await memory.size() == 0
248
+ assert await memory.get_messages() == []
249
+ print("✅ Clearing memory works")
250
+
251
+
252
+ @pytest.mark.asyncio
253
+ async def test_state_dict_serialization():
254
+ """Test state serialization and deserialization."""
255
+ memory1 = ConversationMemory()
256
+
257
+ messages = [
258
+ {"role": "user", "content": "Hello"},
259
+ {"role": "assistant", "content": "Hi!"},
260
+ ]
261
+ await memory1.add(messages)
262
+
263
+ # Export state
264
+ state = memory1.state_dict()
265
+ assert "messages" in state
266
+ assert len(state["messages"]) == 2
267
+
268
+ # Load into new memory
269
+ memory2 = ConversationMemory()
270
+ memory2.load_state_dict(state)
271
+
272
+ assert await memory2.size() == 2
273
+ retrieved = await memory2.get_messages()
274
+ assert retrieved[0]["content"] == "Hello"
275
+ assert retrieved[1]["content"] == "Hi!"
276
+ print("✅ State serialization works")
277
+
278
+
279
+ @pytest.mark.asyncio
280
+ async def test_state_dict_strict_mode():
281
+ """Test state loading with strict mode validation."""
282
+ memory = ConversationMemory()
283
+
284
+ # Invalid state dict (missing 'messages' key)
285
+ invalid_state = {"wrong_key": []}
286
+
287
+ # Should raise error in strict mode
288
+ with pytest.raises(ValueError):
289
+ memory.load_state_dict(invalid_state, strict=True)
290
+
291
+ # Should not raise error in non-strict mode
292
+ memory.load_state_dict(invalid_state, strict=False)
293
+ assert await memory.size() == 0
294
+
295
+ print("✅ State dict strict mode works")
296
+
297
+
298
+ @pytest.mark.asyncio
299
+ async def test_add_none_message():
300
+ """Test that adding None message is handled gracefully."""
301
+ memory = ConversationMemory()
302
+
303
+ await memory.add(None)
304
+ assert await memory.size() == 0
305
+ print("✅ Adding None message handled gracefully")
306
+
307
+
308
+ @pytest.mark.asyncio
309
+ async def test_add_invalid_message_type():
310
+ """Test that adding invalid message type raises error."""
311
+ memory = ConversationMemory()
312
+
313
+ # Try to add a string instead of dict
314
+ with pytest.raises(TypeError):
315
+ await memory.add("invalid message")
316
+
317
+ # Try to add list of non-dicts
318
+ with pytest.raises(TypeError):
319
+ await memory.add(["invalid", "messages"])
320
+
321
+ print("✅ Invalid message type raises error correctly")
322
+
323
+
324
+ @pytest.mark.asyncio
325
+ async def test_retrieve_not_implemented():
326
+ """Test that retrieve method raises NotImplementedError."""
327
+ memory = ConversationMemory()
328
+
329
+ with pytest.raises(NotImplementedError):
330
+ await memory.retrieve("some query")
331
+
332
+ print("✅ Retrieve method correctly not implemented")
333
+
334
+
335
+ @pytest.mark.asyncio
336
+ async def test_message_isolation():
337
+ """Test that returned messages are copies, not references."""
338
+ memory = ConversationMemory()
339
+
340
+ original = {"role": "user", "content": "Original"}
341
+ await memory.add(original)
342
+
343
+ # Get messages and modify
344
+ retrieved = await memory.get_messages()
345
+ retrieved[0]["content"] = "Modified"
346
+
347
+ # Original in memory should be unchanged
348
+ messages = await memory.get_messages()
349
+ assert messages[0]["content"] == "Original"
350
+ print("✅ Message isolation works correctly")
351
+
352
+
353
+ if __name__ == "__main__":
354
+ import asyncio
355
+
356
+ async def run_all_tests():
357
+ """Run all tests manually."""
358
+ print("\n=== Running ConversationMemory Tests ===\n")
359
+
360
+ await test_conversation_memory_initialization()
361
+ await test_add_single_message()
362
+ await test_add_multiple_messages()
363
+ await test_duplicate_prevention()
364
+ await test_allow_duplicates()
365
+ await test_delete_by_index()
366
+ await test_delete_multiple_indices()
367
+ await test_delete_invalid_index()
368
+ await test_get_last_message()
369
+ await test_get_messages_by_role()
370
+ await test_get_messages_with_limit()
371
+ await test_truncate_to_size()
372
+ await test_clear_memory()
373
+ await test_state_dict_serialization()
374
+ await test_state_dict_strict_mode()
375
+ await test_add_none_message()
376
+ await test_add_invalid_message_type()
377
+ await test_retrieve_not_implemented()
378
+ await test_message_isolation()
379
+
380
+ print("\n=== All ConversationMemory Tests Passed! ===\n")
381
+
382
+ asyncio.run(run_all_tests())