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,620 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Tests for Orchestrator Shared Memory.
|
|
5
|
+
|
|
6
|
+
This module tests the shared memory functionality in the Orchestrator,
|
|
7
|
+
including how agents can access and contribute to shared memory that
|
|
8
|
+
all agents can see.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import List
|
|
12
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from massgen.chat_agent import SingleAgent
|
|
17
|
+
from massgen.memory import ConversationMemory
|
|
18
|
+
|
|
19
|
+
# Import orchestrator and memory classes
|
|
20
|
+
from massgen.orchestrator import Orchestrator
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Helper functions
|
|
24
|
+
def create_mock_backend(agent_responses: List[str] = None):
|
|
25
|
+
"""Create a mock backend with predefined responses."""
|
|
26
|
+
if agent_responses is None:
|
|
27
|
+
agent_responses = ["Test response"]
|
|
28
|
+
|
|
29
|
+
backend = MagicMock()
|
|
30
|
+
backend.is_stateful = MagicMock(return_value=False)
|
|
31
|
+
backend.set_stage = MagicMock()
|
|
32
|
+
backend.set_planning_mode = MagicMock()
|
|
33
|
+
backend.extract_tool_name = MagicMock(side_effect=lambda tc: tc.get("name", ""))
|
|
34
|
+
backend.extract_tool_arguments = MagicMock(side_effect=lambda tc: tc.get("arguments", {}))
|
|
35
|
+
backend.get_provider_name = MagicMock(return_value="test_provider")
|
|
36
|
+
backend.filesystem_manager = None
|
|
37
|
+
|
|
38
|
+
# Track which response to return
|
|
39
|
+
response_index = [0]
|
|
40
|
+
|
|
41
|
+
async def mock_stream():
|
|
42
|
+
idx = response_index[0]
|
|
43
|
+
response = agent_responses[idx % len(agent_responses)]
|
|
44
|
+
response_index[0] += 1
|
|
45
|
+
|
|
46
|
+
yield MagicMock(type="content", content=response)
|
|
47
|
+
yield MagicMock(
|
|
48
|
+
type="complete_message",
|
|
49
|
+
complete_message={"role": "assistant", "content": response},
|
|
50
|
+
)
|
|
51
|
+
yield MagicMock(type="done")
|
|
52
|
+
|
|
53
|
+
backend.stream_with_tools = MagicMock(return_value=mock_stream())
|
|
54
|
+
return backend
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def create_mock_agent(agent_id: str, backend=None):
|
|
58
|
+
"""Create a mock agent for testing."""
|
|
59
|
+
if backend is None:
|
|
60
|
+
backend = create_mock_backend()
|
|
61
|
+
|
|
62
|
+
agent = SingleAgent(
|
|
63
|
+
backend=backend,
|
|
64
|
+
agent_id=agent_id,
|
|
65
|
+
system_message=f"You are {agent_id}",
|
|
66
|
+
)
|
|
67
|
+
return agent
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_mock_persistent_memory():
|
|
71
|
+
"""Create a mock persistent memory."""
|
|
72
|
+
memory = MagicMock()
|
|
73
|
+
memory.record = AsyncMock()
|
|
74
|
+
memory.retrieve = AsyncMock(return_value="")
|
|
75
|
+
return memory
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.asyncio
|
|
79
|
+
class TestOrchestratorSharedConversationMemory:
|
|
80
|
+
"""Tests for Orchestrator with shared ConversationMemory."""
|
|
81
|
+
|
|
82
|
+
async def test_orchestrator_with_shared_conversation_memory(self):
|
|
83
|
+
"""Test that orchestrator initializes with shared conversation memory."""
|
|
84
|
+
shared_memory = ConversationMemory()
|
|
85
|
+
agents = {
|
|
86
|
+
"agent1": create_mock_agent("agent1"),
|
|
87
|
+
"agent2": create_mock_agent("agent2"),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
orchestrator = Orchestrator(
|
|
91
|
+
agents=agents,
|
|
92
|
+
shared_conversation_memory=shared_memory,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
assert orchestrator.shared_conversation_memory is shared_memory
|
|
96
|
+
assert orchestrator.shared_persistent_memory is None
|
|
97
|
+
print("✅ Orchestrator with shared conversation memory initialization works")
|
|
98
|
+
|
|
99
|
+
async def test_shared_memory_injection_to_agents(self):
|
|
100
|
+
"""Test that shared memory content is injected into agent messages."""
|
|
101
|
+
shared_memory = ConversationMemory()
|
|
102
|
+
|
|
103
|
+
# Add some messages to shared memory first
|
|
104
|
+
await shared_memory.add(
|
|
105
|
+
[
|
|
106
|
+
{"role": "assistant", "content": "Previous insight", "agent_id": "agent1"},
|
|
107
|
+
{"role": "assistant", "content": "Another finding", "agent_id": "agent2"},
|
|
108
|
+
],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
agents = {
|
|
112
|
+
"agent1": create_mock_agent("agent1"),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
orchestrator = Orchestrator(
|
|
116
|
+
agents=agents,
|
|
117
|
+
shared_conversation_memory=shared_memory,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Test the injection method
|
|
121
|
+
original_messages = [
|
|
122
|
+
{"role": "system", "content": "You are an agent"},
|
|
123
|
+
{"role": "user", "content": "Solve this task"},
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
injected_messages = await orchestrator._inject_shared_memory_context(
|
|
127
|
+
original_messages,
|
|
128
|
+
"agent1",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Should have an additional system message with shared memory
|
|
132
|
+
assert len(injected_messages) > len(original_messages)
|
|
133
|
+
|
|
134
|
+
# Find the memory injection message
|
|
135
|
+
memory_messages = [msg for msg in injected_messages if "SHARED CONVERSATION MEMORY" in msg.get("content", "")]
|
|
136
|
+
assert len(memory_messages) == 1
|
|
137
|
+
|
|
138
|
+
memory_content = memory_messages[0]["content"]
|
|
139
|
+
assert "Previous insight" in memory_content
|
|
140
|
+
assert "Another finding" in memory_content
|
|
141
|
+
|
|
142
|
+
print("✅ Shared memory injection to agents works")
|
|
143
|
+
|
|
144
|
+
async def test_agent_contributions_recorded_to_shared_memory(self):
|
|
145
|
+
"""Test that agent contributions are recorded to shared memory."""
|
|
146
|
+
shared_memory = ConversationMemory()
|
|
147
|
+
agents = {
|
|
148
|
+
"agent1": create_mock_agent("agent1"),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
orchestrator = Orchestrator(
|
|
152
|
+
agents=agents,
|
|
153
|
+
shared_conversation_memory=shared_memory,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Simulate recording to shared memory
|
|
157
|
+
await orchestrator._record_to_shared_memory(
|
|
158
|
+
agent_id="agent1",
|
|
159
|
+
content="I found the solution",
|
|
160
|
+
role="assistant",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Check if the message was recorded
|
|
164
|
+
memory_size = await shared_memory.size()
|
|
165
|
+
assert memory_size == 1
|
|
166
|
+
|
|
167
|
+
messages = await shared_memory.get_messages()
|
|
168
|
+
assert len(messages) == 1
|
|
169
|
+
assert messages[0]["content"] == "I found the solution"
|
|
170
|
+
assert messages[0]["agent_id"] == "agent1"
|
|
171
|
+
|
|
172
|
+
print("✅ Agent contributions recorded to shared memory")
|
|
173
|
+
|
|
174
|
+
async def test_multiple_agents_share_same_memory(self):
|
|
175
|
+
"""Test that multiple agents can see the same shared memory."""
|
|
176
|
+
shared_memory = ConversationMemory()
|
|
177
|
+
|
|
178
|
+
# Agent1 contributes to memory
|
|
179
|
+
await shared_memory.add(
|
|
180
|
+
{
|
|
181
|
+
"role": "assistant",
|
|
182
|
+
"content": "Agent1's discovery",
|
|
183
|
+
"agent_id": "agent1",
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
agents = {
|
|
188
|
+
"agent1": create_mock_agent("agent1"),
|
|
189
|
+
"agent2": create_mock_agent("agent2"),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
orchestrator = Orchestrator(
|
|
193
|
+
agents=agents,
|
|
194
|
+
shared_conversation_memory=shared_memory,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Both agents should see the same memory
|
|
198
|
+
messages1 = await orchestrator._inject_shared_memory_context(
|
|
199
|
+
[{"role": "user", "content": "Task"}],
|
|
200
|
+
"agent1",
|
|
201
|
+
)
|
|
202
|
+
messages2 = await orchestrator._inject_shared_memory_context(
|
|
203
|
+
[{"role": "user", "content": "Task"}],
|
|
204
|
+
"agent2",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Both should have memory injection
|
|
208
|
+
assert any("Agent1's discovery" in msg.get("content", "") for msg in messages1)
|
|
209
|
+
assert any("Agent1's discovery" in msg.get("content", "") for msg in messages2)
|
|
210
|
+
|
|
211
|
+
print("✅ Multiple agents share the same memory")
|
|
212
|
+
|
|
213
|
+
async def test_shared_memory_accumulates_over_time(self):
|
|
214
|
+
"""Test that shared memory accumulates contributions from multiple agents."""
|
|
215
|
+
shared_memory = ConversationMemory()
|
|
216
|
+
agents = {
|
|
217
|
+
"agent1": create_mock_agent("agent1"),
|
|
218
|
+
"agent2": create_mock_agent("agent2"),
|
|
219
|
+
"agent3": create_mock_agent("agent3"),
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
orchestrator = Orchestrator(
|
|
223
|
+
agents=agents,
|
|
224
|
+
shared_conversation_memory=shared_memory,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Simulate multiple agents contributing
|
|
228
|
+
await orchestrator._record_to_shared_memory("agent1", "First finding", "assistant")
|
|
229
|
+
await orchestrator._record_to_shared_memory("agent2", "Second finding", "assistant")
|
|
230
|
+
await orchestrator._record_to_shared_memory("agent3", "Third finding", "assistant")
|
|
231
|
+
|
|
232
|
+
# Check memory size
|
|
233
|
+
memory_size = await shared_memory.size()
|
|
234
|
+
assert memory_size == 3
|
|
235
|
+
|
|
236
|
+
# All contributions should be visible
|
|
237
|
+
messages = await shared_memory.get_messages()
|
|
238
|
+
contents = [msg["content"] for msg in messages]
|
|
239
|
+
assert "First finding" in contents
|
|
240
|
+
assert "Second finding" in contents
|
|
241
|
+
assert "Third finding" in contents
|
|
242
|
+
|
|
243
|
+
print("✅ Shared memory accumulates over time")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@pytest.mark.asyncio
|
|
247
|
+
class TestOrchestratorSharedPersistentMemory:
|
|
248
|
+
"""Tests for Orchestrator with shared PersistentMemory."""
|
|
249
|
+
|
|
250
|
+
async def test_orchestrator_with_shared_persistent_memory(self):
|
|
251
|
+
"""Test that orchestrator initializes with shared persistent memory."""
|
|
252
|
+
persistent_memory = create_mock_persistent_memory()
|
|
253
|
+
agents = {
|
|
254
|
+
"agent1": create_mock_agent("agent1"),
|
|
255
|
+
"agent2": create_mock_agent("agent2"),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
orchestrator = Orchestrator(
|
|
259
|
+
agents=agents,
|
|
260
|
+
shared_persistent_memory=persistent_memory,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
assert orchestrator.shared_persistent_memory is persistent_memory
|
|
264
|
+
print("✅ Orchestrator with shared persistent memory initialization works")
|
|
265
|
+
|
|
266
|
+
async def test_persistent_memory_retrieval_for_agents(self):
|
|
267
|
+
"""Test that persistent memory is retrieved and injected for agents."""
|
|
268
|
+
persistent_memory = create_mock_persistent_memory()
|
|
269
|
+
persistent_memory.retrieve = AsyncMock(return_value="Historical context: Previous session insights")
|
|
270
|
+
|
|
271
|
+
agents = {
|
|
272
|
+
"agent1": create_mock_agent("agent1"),
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
orchestrator = Orchestrator(
|
|
276
|
+
agents=agents,
|
|
277
|
+
shared_persistent_memory=persistent_memory,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Test injection
|
|
281
|
+
original_messages = [
|
|
282
|
+
{"role": "user", "content": "New task"},
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
injected_messages = await orchestrator._inject_shared_memory_context(
|
|
286
|
+
original_messages,
|
|
287
|
+
"agent1",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Should have persistent memory context
|
|
291
|
+
memory_content = "\n".join([msg.get("content", "") for msg in injected_messages])
|
|
292
|
+
assert "SHARED PERSISTENT MEMORY" in memory_content
|
|
293
|
+
assert "Historical context" in memory_content
|
|
294
|
+
|
|
295
|
+
print("✅ Persistent memory retrieval for agents works")
|
|
296
|
+
|
|
297
|
+
async def test_persistent_memory_recording(self):
|
|
298
|
+
"""Test that agent contributions are recorded to persistent memory."""
|
|
299
|
+
persistent_memory = create_mock_persistent_memory()
|
|
300
|
+
agents = {
|
|
301
|
+
"agent1": create_mock_agent("agent1"),
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
orchestrator = Orchestrator(
|
|
305
|
+
agents=agents,
|
|
306
|
+
shared_persistent_memory=persistent_memory,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Record to memory
|
|
310
|
+
await orchestrator._record_to_shared_memory(
|
|
311
|
+
agent_id="agent1",
|
|
312
|
+
content="Important discovery",
|
|
313
|
+
role="assistant",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Verify record was called
|
|
317
|
+
assert persistent_memory.record.called
|
|
318
|
+
call_args = persistent_memory.record.call_args[0][0]
|
|
319
|
+
assert len(call_args) == 1
|
|
320
|
+
assert call_args[0]["content"] == "Important discovery"
|
|
321
|
+
|
|
322
|
+
print("✅ Persistent memory recording works")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@pytest.mark.asyncio
|
|
326
|
+
class TestOrchestratorBothMemories:
|
|
327
|
+
"""Tests for Orchestrator with both shared memories."""
|
|
328
|
+
|
|
329
|
+
async def test_orchestrator_with_both_shared_memories(self):
|
|
330
|
+
"""Test orchestrator with both conversation and persistent memory."""
|
|
331
|
+
conv_memory = ConversationMemory()
|
|
332
|
+
persist_memory = create_mock_persistent_memory()
|
|
333
|
+
|
|
334
|
+
agents = {
|
|
335
|
+
"agent1": create_mock_agent("agent1"),
|
|
336
|
+
"agent2": create_mock_agent("agent2"),
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
orchestrator = Orchestrator(
|
|
340
|
+
agents=agents,
|
|
341
|
+
shared_conversation_memory=conv_memory,
|
|
342
|
+
shared_persistent_memory=persist_memory,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
assert orchestrator.shared_conversation_memory is conv_memory
|
|
346
|
+
assert orchestrator.shared_persistent_memory is persist_memory
|
|
347
|
+
|
|
348
|
+
print("✅ Orchestrator with both shared memories works")
|
|
349
|
+
|
|
350
|
+
async def test_both_memories_used_together(self):
|
|
351
|
+
"""Test that both memory types are used together correctly."""
|
|
352
|
+
conv_memory = ConversationMemory()
|
|
353
|
+
await conv_memory.add(
|
|
354
|
+
{
|
|
355
|
+
"role": "assistant",
|
|
356
|
+
"content": "Recent conversation",
|
|
357
|
+
"agent_id": "agent1",
|
|
358
|
+
},
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
persist_memory = create_mock_persistent_memory()
|
|
362
|
+
persist_memory.retrieve = AsyncMock(return_value="Long-term knowledge")
|
|
363
|
+
|
|
364
|
+
agents = {
|
|
365
|
+
"agent1": create_mock_agent("agent1"),
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
orchestrator = Orchestrator(
|
|
369
|
+
agents=agents,
|
|
370
|
+
shared_conversation_memory=conv_memory,
|
|
371
|
+
shared_persistent_memory=persist_memory,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Inject both memories
|
|
375
|
+
injected_messages = await orchestrator._inject_shared_memory_context(
|
|
376
|
+
[{"role": "user", "content": "Task"}],
|
|
377
|
+
"agent1",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Should have both memory types
|
|
381
|
+
all_content = "\n".join([msg.get("content", "") for msg in injected_messages])
|
|
382
|
+
assert "SHARED CONVERSATION MEMORY" in all_content
|
|
383
|
+
assert "Recent conversation" in all_content
|
|
384
|
+
assert "SHARED PERSISTENT MEMORY" in all_content
|
|
385
|
+
assert "Long-term knowledge" in all_content
|
|
386
|
+
|
|
387
|
+
print("✅ Both memories used together works")
|
|
388
|
+
|
|
389
|
+
async def test_recording_to_both_memories(self):
|
|
390
|
+
"""Test that recordings go to both memory types."""
|
|
391
|
+
conv_memory = ConversationMemory()
|
|
392
|
+
persist_memory = create_mock_persistent_memory()
|
|
393
|
+
|
|
394
|
+
agents = {
|
|
395
|
+
"agent1": create_mock_agent("agent1"),
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
orchestrator = Orchestrator(
|
|
399
|
+
agents=agents,
|
|
400
|
+
shared_conversation_memory=conv_memory,
|
|
401
|
+
shared_persistent_memory=persist_memory,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Record once
|
|
405
|
+
await orchestrator._record_to_shared_memory(
|
|
406
|
+
agent_id="agent1",
|
|
407
|
+
content="Shared finding",
|
|
408
|
+
role="assistant",
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# Check conversation memory
|
|
412
|
+
conv_size = await conv_memory.size()
|
|
413
|
+
assert conv_size == 1
|
|
414
|
+
|
|
415
|
+
# Check persistent memory was called
|
|
416
|
+
assert persist_memory.record.called
|
|
417
|
+
|
|
418
|
+
print("✅ Recording to both memories works")
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@pytest.mark.asyncio
|
|
422
|
+
class TestSharedMemoryErrorHandling:
|
|
423
|
+
"""Tests for error handling in shared memory operations."""
|
|
424
|
+
|
|
425
|
+
async def test_orchestrator_without_shared_memory(self):
|
|
426
|
+
"""Test that orchestrator works without shared memory."""
|
|
427
|
+
agents = {
|
|
428
|
+
"agent1": create_mock_agent("agent1"),
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
orchestrator = Orchestrator(agents=agents)
|
|
432
|
+
|
|
433
|
+
assert orchestrator.shared_conversation_memory is None
|
|
434
|
+
assert orchestrator.shared_persistent_memory is None
|
|
435
|
+
|
|
436
|
+
# Injection should return original messages
|
|
437
|
+
messages = [{"role": "user", "content": "Task"}]
|
|
438
|
+
injected = await orchestrator._inject_shared_memory_context(messages, "agent1")
|
|
439
|
+
assert injected == messages
|
|
440
|
+
|
|
441
|
+
# Recording should not fail
|
|
442
|
+
await orchestrator._record_to_shared_memory("agent1", "test", "assistant")
|
|
443
|
+
|
|
444
|
+
print("✅ Orchestrator works without shared memory")
|
|
445
|
+
|
|
446
|
+
async def test_memory_failure_doesnt_crash_orchestrator(self):
|
|
447
|
+
"""Test that memory failures don't crash the orchestrator."""
|
|
448
|
+
# Create faulty memory
|
|
449
|
+
conv_memory = MagicMock()
|
|
450
|
+
conv_memory.get_messages = AsyncMock(side_effect=Exception("Memory error"))
|
|
451
|
+
conv_memory.add = AsyncMock(side_effect=Exception("Memory error"))
|
|
452
|
+
|
|
453
|
+
agents = {
|
|
454
|
+
"agent1": create_mock_agent("agent1"),
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
orchestrator = Orchestrator(
|
|
458
|
+
agents=agents,
|
|
459
|
+
shared_conversation_memory=conv_memory,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Injection should not crash even if memory fails
|
|
463
|
+
messages = [{"role": "user", "content": "Task"}]
|
|
464
|
+
injected = await orchestrator._inject_shared_memory_context(messages, "agent1")
|
|
465
|
+
# Should return something (at least original messages)
|
|
466
|
+
assert injected is not None
|
|
467
|
+
|
|
468
|
+
# Recording should not crash
|
|
469
|
+
await orchestrator._record_to_shared_memory("agent1", "test", "assistant")
|
|
470
|
+
|
|
471
|
+
print("✅ Memory failures don't crash orchestrator")
|
|
472
|
+
|
|
473
|
+
async def test_persistent_memory_not_implemented_handled(self):
|
|
474
|
+
"""Test that NotImplementedError from persistent memory is handled."""
|
|
475
|
+
persist_memory = MagicMock()
|
|
476
|
+
persist_memory.retrieve = AsyncMock(side_effect=NotImplementedError())
|
|
477
|
+
persist_memory.record = AsyncMock(side_effect=NotImplementedError())
|
|
478
|
+
|
|
479
|
+
agents = {
|
|
480
|
+
"agent1": create_mock_agent("agent1"),
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
orchestrator = Orchestrator(
|
|
484
|
+
agents=agents,
|
|
485
|
+
shared_persistent_memory=persist_memory,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Should handle NotImplementedError gracefully
|
|
489
|
+
messages = [{"role": "user", "content": "Task"}]
|
|
490
|
+
injected = await orchestrator._inject_shared_memory_context(messages, "agent1")
|
|
491
|
+
assert injected is not None
|
|
492
|
+
|
|
493
|
+
await orchestrator._record_to_shared_memory("agent1", "test", "assistant")
|
|
494
|
+
|
|
495
|
+
print("✅ NotImplementedError from persistent memory handled")
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@pytest.mark.asyncio
|
|
499
|
+
class TestCrossAgentMemoryVisibility:
|
|
500
|
+
"""Tests for verifying agents can see each other's memory contributions."""
|
|
501
|
+
|
|
502
|
+
async def test_agent_can_see_other_agents_contributions(self):
|
|
503
|
+
"""Test that one agent can see another agent's contributions in shared memory."""
|
|
504
|
+
shared_memory = ConversationMemory()
|
|
505
|
+
|
|
506
|
+
agents = {
|
|
507
|
+
"agent1": create_mock_agent("agent1"),
|
|
508
|
+
"agent2": create_mock_agent("agent2"),
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
orchestrator = Orchestrator(
|
|
512
|
+
agents=agents,
|
|
513
|
+
shared_conversation_memory=shared_memory,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Agent1 makes a contribution
|
|
517
|
+
await orchestrator._record_to_shared_memory(
|
|
518
|
+
agent_id="agent1",
|
|
519
|
+
content="Agent1 discovered X",
|
|
520
|
+
role="assistant",
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
# Agent2 should see Agent1's contribution
|
|
524
|
+
messages_for_agent2 = await orchestrator._inject_shared_memory_context(
|
|
525
|
+
[{"role": "user", "content": "Continue the work"}],
|
|
526
|
+
"agent2",
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Check that agent2 can see agent1's contribution
|
|
530
|
+
all_content = "\n".join([msg.get("content", "") for msg in messages_for_agent2])
|
|
531
|
+
assert "agent1" in all_content.lower()
|
|
532
|
+
assert "Agent1 discovered X" in all_content
|
|
533
|
+
|
|
534
|
+
print("✅ Agent can see other agents' contributions")
|
|
535
|
+
|
|
536
|
+
async def test_memory_shows_agent_attribution(self):
|
|
537
|
+
"""Test that shared memory properly attributes contributions to agents."""
|
|
538
|
+
shared_memory = ConversationMemory()
|
|
539
|
+
|
|
540
|
+
agents = {
|
|
541
|
+
"agent1": create_mock_agent("agent1"),
|
|
542
|
+
"agent2": create_mock_agent("agent2"),
|
|
543
|
+
"agent3": create_mock_agent("agent3"),
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
orchestrator = Orchestrator(
|
|
547
|
+
agents=agents,
|
|
548
|
+
shared_conversation_memory=shared_memory,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Multiple agents contribute
|
|
552
|
+
await orchestrator._record_to_shared_memory("agent1", "Finding A", "assistant")
|
|
553
|
+
await orchestrator._record_to_shared_memory("agent2", "Finding B", "assistant")
|
|
554
|
+
await orchestrator._record_to_shared_memory("agent3", "Finding C", "assistant")
|
|
555
|
+
|
|
556
|
+
# Check that any agent can see all contributions with attribution
|
|
557
|
+
messages = await orchestrator._inject_shared_memory_context(
|
|
558
|
+
[{"role": "user", "content": "Task"}],
|
|
559
|
+
"agent1",
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
all_content = "\n".join([msg.get("content", "") for msg in messages])
|
|
563
|
+
|
|
564
|
+
# Should show all agents' contributions with attribution
|
|
565
|
+
assert "[agent1]" in all_content
|
|
566
|
+
assert "Finding A" in all_content
|
|
567
|
+
assert "[agent2]" in all_content
|
|
568
|
+
assert "Finding B" in all_content
|
|
569
|
+
assert "[agent3]" in all_content
|
|
570
|
+
assert "Finding C" in all_content
|
|
571
|
+
|
|
572
|
+
print("✅ Memory shows agent attribution")
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
if __name__ == "__main__":
|
|
576
|
+
import asyncio
|
|
577
|
+
|
|
578
|
+
async def run_all_tests():
|
|
579
|
+
"""Run all tests manually."""
|
|
580
|
+
print("\n=== Running Orchestrator Shared Memory Tests ===\n")
|
|
581
|
+
|
|
582
|
+
# Shared conversation memory tests
|
|
583
|
+
print("\n--- Orchestrator with Shared Conversation Memory ---")
|
|
584
|
+
test_conv = TestOrchestratorSharedConversationMemory()
|
|
585
|
+
await test_conv.test_orchestrator_with_shared_conversation_memory()
|
|
586
|
+
await test_conv.test_shared_memory_injection_to_agents()
|
|
587
|
+
await test_conv.test_agent_contributions_recorded_to_shared_memory()
|
|
588
|
+
await test_conv.test_multiple_agents_share_same_memory()
|
|
589
|
+
await test_conv.test_shared_memory_accumulates_over_time()
|
|
590
|
+
|
|
591
|
+
# Shared persistent memory tests
|
|
592
|
+
print("\n--- Orchestrator with Shared Persistent Memory ---")
|
|
593
|
+
test_persist = TestOrchestratorSharedPersistentMemory()
|
|
594
|
+
await test_persist.test_orchestrator_with_shared_persistent_memory()
|
|
595
|
+
await test_persist.test_persistent_memory_retrieval_for_agents()
|
|
596
|
+
await test_persist.test_persistent_memory_recording()
|
|
597
|
+
|
|
598
|
+
# Both memories tests
|
|
599
|
+
print("\n--- Orchestrator with Both Shared Memories ---")
|
|
600
|
+
test_both = TestOrchestratorBothMemories()
|
|
601
|
+
await test_both.test_orchestrator_with_both_shared_memories()
|
|
602
|
+
await test_both.test_both_memories_used_together()
|
|
603
|
+
await test_both.test_recording_to_both_memories()
|
|
604
|
+
|
|
605
|
+
# Error handling tests
|
|
606
|
+
print("\n--- Shared Memory Error Handling ---")
|
|
607
|
+
test_errors = TestSharedMemoryErrorHandling()
|
|
608
|
+
await test_errors.test_orchestrator_without_shared_memory()
|
|
609
|
+
await test_errors.test_memory_failure_doesnt_crash_orchestrator()
|
|
610
|
+
await test_errors.test_persistent_memory_not_implemented_handled()
|
|
611
|
+
|
|
612
|
+
# Cross-agent visibility tests
|
|
613
|
+
print("\n--- Cross-Agent Memory Visibility ---")
|
|
614
|
+
test_cross = TestCrossAgentMemoryVisibility()
|
|
615
|
+
await test_cross.test_agent_can_see_other_agents_contributions()
|
|
616
|
+
await test_cross.test_memory_shows_agent_attribution()
|
|
617
|
+
|
|
618
|
+
print("\n=== All Orchestrator Shared Memory Tests Passed! ===\n")
|
|
619
|
+
|
|
620
|
+
asyncio.run(run_all_tests())
|