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,435 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Tests for PersistentMemory implementation.
5
+
6
+ This module tests the long-term persistent memory functionality using mem0,
7
+ including recording, retrieving, and managing memories across sessions.
8
+
9
+ Note: Some tests require mem0ai to be installed and may be skipped if unavailable.
10
+ """
11
+
12
+ from unittest.mock import AsyncMock, MagicMock, patch
13
+
14
+ import pytest
15
+
16
+ # Try to import memory components
17
+ try:
18
+ from massgen.memory import PersistentMemory
19
+
20
+ MEMORY_AVAILABLE = True
21
+ except ImportError:
22
+ MEMORY_AVAILABLE = False
23
+ PersistentMemory = None
24
+
25
+ # Check if mem0 is available
26
+ try:
27
+ MEM0_AVAILABLE = True
28
+ except ImportError:
29
+ MEM0_AVAILABLE = False
30
+
31
+
32
+ # Helper function to create mock backend
33
+ def create_mock_backend():
34
+ """Create a mock backend for testing."""
35
+ backend = MagicMock()
36
+ backend.chat_completion = AsyncMock(
37
+ return_value={
38
+ "choices": [{"message": {"content": "Test response"}}],
39
+ },
40
+ )
41
+ return backend
42
+
43
+
44
+ @pytest.mark.skipif(not MEMORY_AVAILABLE, reason="Memory module not available")
45
+ class TestPersistentMemoryInitialization:
46
+ """Tests for PersistentMemory initialization."""
47
+
48
+ @pytest.mark.skipif(not MEM0_AVAILABLE, reason="mem0 not installed")
49
+ def test_initialization_without_identifiers_fails(self):
50
+ """Test that initialization fails without agent/user/session identifiers."""
51
+ with pytest.raises(ValueError, match="At least one of"):
52
+ PersistentMemory(
53
+ llm_backend=create_mock_backend(),
54
+ embedding_backend=create_mock_backend(),
55
+ )
56
+ print("✅ Initialization validation works")
57
+
58
+ @pytest.mark.skipif(not MEM0_AVAILABLE, reason="mem0 not installed")
59
+ def test_initialization_without_backends_fails(self):
60
+ """Test that initialization fails without required backends."""
61
+ with pytest.raises(ValueError, match="Both llm_backend and embedding_backend"):
62
+ PersistentMemory(agent_name="test_agent")
63
+ print("✅ Backend validation works")
64
+
65
+ @pytest.mark.skipif(not MEM0_AVAILABLE, reason="mem0 not installed")
66
+ def test_initialization_with_agent_name(self):
67
+ """Test successful initialization with agent name."""
68
+ memory = PersistentMemory(
69
+ agent_name="test_agent",
70
+ llm_backend=create_mock_backend(),
71
+ embedding_backend=create_mock_backend(),
72
+ )
73
+ assert memory.agent_id == "test_agent"
74
+ assert memory.user_id is None
75
+ assert memory.session_id is None
76
+ print("✅ Initialization with agent_name works")
77
+
78
+ @pytest.mark.skipif(not MEM0_AVAILABLE, reason="mem0 not installed")
79
+ def test_initialization_with_all_identifiers(self):
80
+ """Test initialization with all identifiers."""
81
+ memory = PersistentMemory(
82
+ agent_name="test_agent",
83
+ user_name="test_user",
84
+ session_name="test_session",
85
+ llm_backend=create_mock_backend(),
86
+ embedding_backend=create_mock_backend(),
87
+ )
88
+ assert memory.agent_id == "test_agent"
89
+ assert memory.user_id == "test_user"
90
+ assert memory.session_id == "test_session"
91
+ print("✅ Initialization with all identifiers works")
92
+
93
+
94
+ @pytest.mark.skipif(
95
+ not MEMORY_AVAILABLE or not MEM0_AVAILABLE,
96
+ reason="Memory module or mem0 not available",
97
+ )
98
+ class TestPersistentMemoryMocked:
99
+ """Tests for PersistentMemory with mocked mem0 backend."""
100
+
101
+ @pytest.fixture
102
+ def mock_memory(self):
103
+ """Create a PersistentMemory instance with mocked mem0."""
104
+ with patch("mem0.AsyncMemory") as mock_mem0:
105
+ # Configure mock
106
+ mock_mem0_instance = AsyncMock()
107
+ mock_mem0.return_value = mock_mem0_instance
108
+
109
+ memory = PersistentMemory(
110
+ agent_name="test_agent",
111
+ llm_backend=create_mock_backend(),
112
+ embedding_backend=create_mock_backend(),
113
+ )
114
+
115
+ # Replace with our mock
116
+ memory.mem0_memory = mock_mem0_instance
117
+
118
+ yield memory, mock_mem0_instance
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_record_messages(self, mock_memory):
122
+ """Test recording messages to persistent memory."""
123
+ memory, mock_mem0 = mock_memory
124
+
125
+ # Mock the add method
126
+ mock_mem0.add = AsyncMock(return_value={"results": ["mem_1", "mem_2"]})
127
+
128
+ messages = [
129
+ {"role": "user", "content": "What is quantum computing?"},
130
+ {"role": "assistant", "content": "Quantum computing uses qubits..."},
131
+ ]
132
+
133
+ await memory.record(messages)
134
+
135
+ # Verify mem0.add was called
136
+ assert mock_mem0.add.called
137
+ call_kwargs = mock_mem0.add.call_args.kwargs
138
+ assert call_kwargs["agent_id"] == "test_agent"
139
+ print("✅ Recording messages works")
140
+
141
+ @pytest.mark.asyncio
142
+ async def test_retrieve_memories(self, mock_memory):
143
+ """Test retrieving memories based on query."""
144
+ memory, mock_mem0 = mock_memory
145
+
146
+ # Mock search results
147
+ mock_mem0.search = AsyncMock(
148
+ return_value={
149
+ "results": [
150
+ {"memory": "Quantum computing uses qubits"},
151
+ {"memory": "Qubits can be in superposition"},
152
+ ],
153
+ },
154
+ )
155
+
156
+ result = await memory.retrieve("quantum computing")
157
+
158
+ assert "Quantum computing uses qubits" in result
159
+ assert "Qubits can be in superposition" in result
160
+ assert mock_mem0.search.called
161
+ print("✅ Retrieving memories works")
162
+
163
+ @pytest.mark.asyncio
164
+ async def test_retrieve_with_message_dict(self, mock_memory):
165
+ """Test retrieving with message dictionary."""
166
+ memory, mock_mem0 = mock_memory
167
+
168
+ mock_mem0.search = AsyncMock(
169
+ return_value={
170
+ "results": [{"memory": "Relevant information"}],
171
+ },
172
+ )
173
+
174
+ query = {"role": "user", "content": "Tell me about AI"}
175
+ result = await memory.retrieve(query)
176
+
177
+ assert "Relevant information" in result
178
+ print("✅ Retrieving with message dict works")
179
+
180
+ @pytest.mark.asyncio
181
+ async def test_retrieve_with_message_list(self, mock_memory):
182
+ """Test retrieving with list of messages."""
183
+ memory, mock_mem0 = mock_memory
184
+
185
+ mock_mem0.search = AsyncMock(
186
+ return_value={
187
+ "results": [{"memory": "AI information"}],
188
+ },
189
+ )
190
+
191
+ queries = [
192
+ {"role": "user", "content": "What is AI?"},
193
+ {"role": "user", "content": "How does it work?"},
194
+ ]
195
+ await memory.retrieve(queries)
196
+
197
+ assert mock_mem0.search.call_count == 2
198
+ print("✅ Retrieving with message list works")
199
+
200
+ @pytest.mark.asyncio
201
+ async def test_save_to_memory_tool(self, mock_memory):
202
+ """Test the save_to_memory agent tool."""
203
+ memory, mock_mem0 = mock_memory
204
+
205
+ mock_mem0.add = AsyncMock(return_value={"results": ["mem_123"]})
206
+
207
+ result = await memory.save_to_memory(
208
+ thinking="User mentioned their birthday",
209
+ content=["User's birthday is March 15"],
210
+ )
211
+
212
+ assert result["success"] is True
213
+ assert "Successfully saved" in result["message"]
214
+ assert "mem_123" in result["memory_ids"]
215
+ print("✅ save_to_memory tool works")
216
+
217
+ @pytest.mark.asyncio
218
+ async def test_save_to_memory_error_handling(self, mock_memory):
219
+ """Test save_to_memory error handling."""
220
+ memory, mock_mem0 = mock_memory
221
+
222
+ # Mock an error
223
+ mock_mem0.add = AsyncMock(side_effect=Exception("Database error"))
224
+
225
+ result = await memory.save_to_memory(
226
+ thinking="Test thinking",
227
+ content=["Test content"],
228
+ )
229
+
230
+ assert result["success"] is False
231
+ assert "Error saving to memory" in result["message"]
232
+ print("✅ save_to_memory error handling works")
233
+
234
+ @pytest.mark.asyncio
235
+ async def test_recall_from_memory_tool(self, mock_memory):
236
+ """Test the recall_from_memory agent tool."""
237
+ memory, mock_mem0 = mock_memory
238
+
239
+ mock_mem0.search = AsyncMock(
240
+ return_value={
241
+ "results": [
242
+ {"memory": "User likes Python programming"},
243
+ {"memory": "User's favorite framework is Django"},
244
+ ],
245
+ },
246
+ )
247
+
248
+ result = await memory.recall_from_memory(
249
+ keywords=["programming", "preferences"],
250
+ )
251
+
252
+ assert result["success"] is True
253
+ assert len(result["memories"]) == 4 # 2 results per keyword
254
+ assert result["count"] == 4
255
+ print("✅ recall_from_memory tool works")
256
+
257
+ @pytest.mark.asyncio
258
+ async def test_recall_from_memory_with_limit(self, mock_memory):
259
+ """Test recall with custom limit."""
260
+ memory, mock_mem0 = mock_memory
261
+
262
+ mock_mem0.search = AsyncMock(
263
+ return_value={
264
+ "results": [{"memory": f"Memory {i}"} for i in range(3)],
265
+ },
266
+ )
267
+
268
+ await memory.recall_from_memory(
269
+ keywords=["test"],
270
+ limit=3,
271
+ )
272
+
273
+ call_kwargs = mock_mem0.search.call_args.kwargs
274
+ assert call_kwargs["limit"] == 3
275
+ print("✅ recall_from_memory with limit works")
276
+
277
+ @pytest.mark.asyncio
278
+ async def test_recall_from_memory_error_handling(self, mock_memory):
279
+ """Test recall_from_memory error handling."""
280
+ memory, mock_mem0 = mock_memory
281
+
282
+ mock_mem0.search = AsyncMock(side_effect=Exception("Search error"))
283
+
284
+ result = await memory.recall_from_memory(keywords=["test"])
285
+
286
+ assert result["success"] is False
287
+ assert "Error retrieving memories" in result["message"]
288
+ print("✅ recall_from_memory error handling works")
289
+
290
+ @pytest.mark.asyncio
291
+ async def test_record_empty_messages(self, mock_memory):
292
+ """Test recording empty or None messages."""
293
+ memory, mock_mem0 = mock_memory
294
+
295
+ # Should not call mem0.add
296
+ await memory.record([])
297
+ await memory.record(None)
298
+ await memory.record([None, None])
299
+
300
+ assert not mock_mem0.add.called
301
+ print("✅ Recording empty messages handled gracefully")
302
+
303
+ @pytest.mark.asyncio
304
+ async def test_retrieve_empty_query(self, mock_memory):
305
+ """Test retrieving with empty query."""
306
+ memory, mock_mem0 = mock_memory
307
+
308
+ result = await memory.retrieve("")
309
+ assert result == ""
310
+
311
+ result = await memory.retrieve([])
312
+ assert result == ""
313
+
314
+ result = await memory.retrieve({"role": "user", "content": ""})
315
+ assert result == ""
316
+
317
+ print("✅ Retrieving with empty query handled gracefully")
318
+
319
+
320
+ @pytest.mark.skipif(
321
+ not MEMORY_AVAILABLE or not MEM0_AVAILABLE,
322
+ reason="Memory module or mem0 not available",
323
+ )
324
+ class TestPersistentMemoryIntegration:
325
+ """Integration tests with actual mem0 (if available)."""
326
+
327
+ @pytest.mark.asyncio
328
+ async def test_full_memory_workflow(self):
329
+ """Test a complete memory workflow: record and retrieve."""
330
+ try:
331
+ # Create memory instance
332
+ memory = PersistentMemory(
333
+ agent_name="test_integration_agent",
334
+ llm_backend=create_mock_backend(),
335
+ embedding_backend=create_mock_backend(),
336
+ on_disk=False, # Use in-memory for tests
337
+ )
338
+
339
+ # Record some information
340
+ messages = [
341
+ {"role": "user", "content": "I love Python programming"},
342
+ {"role": "assistant", "content": "That's great! Python is versatile."},
343
+ ]
344
+ await memory.record(messages)
345
+
346
+ # Try to retrieve
347
+ result = await memory.retrieve("Python")
348
+
349
+ # Should return something (exact match depends on embeddings)
350
+ assert isinstance(result, str)
351
+ print("✅ Full memory workflow works")
352
+
353
+ except Exception as e:
354
+ pytest.skip(f"Integration test skipped: {e}")
355
+
356
+ @pytest.mark.asyncio
357
+ async def test_memory_with_multiple_identifiers(self):
358
+ """Test memory filtering with multiple identifiers."""
359
+ try:
360
+ memory = PersistentMemory(
361
+ agent_name="agent_1",
362
+ user_name="user_1",
363
+ session_name="session_1",
364
+ llm_backend=create_mock_backend(),
365
+ embedding_backend=create_mock_backend(),
366
+ on_disk=False,
367
+ )
368
+
369
+ # Record and verify identifiers are used
370
+ await memory.record(
371
+ [
372
+ {"role": "user", "content": "Test message"},
373
+ ],
374
+ )
375
+
376
+ assert memory.agent_id == "agent_1"
377
+ assert memory.user_id == "user_1"
378
+ assert memory.session_id == "session_1"
379
+ print("✅ Memory with multiple identifiers works")
380
+
381
+ except Exception as e:
382
+ pytest.skip(f"Integration test skipped: {e}")
383
+
384
+
385
+ @pytest.mark.skipif(not MEMORY_AVAILABLE, reason="Memory module not available")
386
+ class TestPersistentMemoryBase:
387
+ """Tests for PersistentMemoryBase abstract methods."""
388
+
389
+ def test_base_class_methods(self):
390
+ """Test that base class has expected abstract methods."""
391
+ from massgen.memory import PersistentMemoryBase
392
+
393
+ # Check that methods exist
394
+ assert hasattr(PersistentMemoryBase, "record")
395
+ assert hasattr(PersistentMemoryBase, "retrieve")
396
+ assert hasattr(PersistentMemoryBase, "save_to_memory")
397
+ assert hasattr(PersistentMemoryBase, "recall_from_memory")
398
+ print("✅ PersistentMemoryBase has expected methods")
399
+
400
+
401
+ if __name__ == "__main__":
402
+ import asyncio
403
+
404
+ async def run_all_tests():
405
+ """Run all tests manually."""
406
+ print("\n=== Running PersistentMemory Tests ===\n")
407
+
408
+ if not MEMORY_AVAILABLE:
409
+ print("❌ Memory module not available, skipping tests")
410
+ return
411
+
412
+ if not MEM0_AVAILABLE:
413
+ print("⚠️ mem0 not installed, some tests will be skipped")
414
+
415
+ # Run initialization tests
416
+ print("\n--- Initialization Tests ---")
417
+ test_init = TestPersistentMemoryInitialization()
418
+ test_init.test_initialization_without_identifiers_fails()
419
+
420
+ if MEM0_AVAILABLE:
421
+ test_init.test_initialization_without_backends_fails()
422
+ test_init.test_initialization_with_agent_name()
423
+ test_init.test_initialization_with_all_identifiers()
424
+
425
+ # Run base class tests
426
+ print("\n--- Base Class Tests ---")
427
+ test_base = TestPersistentMemoryBase()
428
+ test_base.test_base_class_methods()
429
+
430
+ print("\n⚠️ For complete testing, run with pytest to execute mocked and integration tests")
431
+ print(" Command: pytest massgen/tests/test_persistent_memory.py -v")
432
+
433
+ print("\n=== PersistentMemory Basic Tests Passed! ===\n")
434
+
435
+ asyncio.run(run_all_tests())
@@ -49,11 +49,17 @@ class TokenCostCalculator:
49
49
  # Default pricing data for various providers and models
50
50
  PROVIDER_PRICING: Dict[str, Dict[str, ModelPricing]] = {
51
51
  "OpenAI": {
52
+ # GPT-5 models (400K context window)
53
+ "gpt-5": ModelPricing(0.00125, 0.01, 400000, 128000),
54
+ "gpt-5-mini": ModelPricing(0.00025, 0.002, 400000, 128000),
55
+ "gpt-5-nano": ModelPricing(0.00005, 0.0004, 400000, 128000),
56
+ # GPT-4 series
52
57
  "gpt-4o": ModelPricing(0.0025, 0.01, 128000, 16384),
53
58
  "gpt-4o-mini": ModelPricing(0.00015, 0.0006, 128000, 16384),
54
59
  "gpt-4-turbo": ModelPricing(0.01, 0.03, 128000, 4096),
55
60
  "gpt-4": ModelPricing(0.03, 0.06, 8192, 8192),
56
61
  "gpt-3.5-turbo": ModelPricing(0.0005, 0.0015, 16385, 4096),
62
+ # O-series models
57
63
  "o1-preview": ModelPricing(0.015, 0.06, 128000, 32768),
58
64
  "o1-mini": ModelPricing(0.003, 0.012, 128000, 65536),
59
65
  "o3-mini": ModelPricing(0.0011, 0.0044, 200000, 100000),
massgen/tool/_manager.py CHANGED
@@ -312,9 +312,14 @@ class ToolManager:
312
312
  return
313
313
 
314
314
  tool_entry = self.registered_tools[tool_name]
315
+
316
+ # Merge parameters: model input first, then preset params override
317
+ # This ensures preset_params (like agent_cwd) always take precedence
318
+ # and won't be overridden by null values from model
319
+ model_input = tool_request.get("input", {}) or {}
315
320
  exec_kwargs = {
316
- **tool_entry.preset_params,
317
- **(tool_request.get("input", {}) or {}),
321
+ **model_input,
322
+ **tool_entry.preset_params, # preset_params override model input
318
323
  }
319
324
 
320
325
  # Prepare post-processor if exists