naas-abi-core 1.4.1__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.
Files changed (124) hide show
  1. assets/favicon.ico +0 -0
  2. assets/logo.png +0 -0
  3. naas_abi_core/__init__.py +1 -0
  4. naas_abi_core/apps/api/api.py +245 -0
  5. naas_abi_core/apps/api/api_test.py +281 -0
  6. naas_abi_core/apps/api/openapi_doc.py +144 -0
  7. naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
  8. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  9. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  10. naas_abi_core/apps/terminal_agent/main.py +555 -0
  11. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  12. naas_abi_core/engine/Engine.py +87 -0
  13. naas_abi_core/engine/EngineProxy.py +109 -0
  14. naas_abi_core/engine/Engine_test.py +6 -0
  15. naas_abi_core/engine/IEngine.py +91 -0
  16. naas_abi_core/engine/conftest.py +45 -0
  17. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
  18. naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
  19. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  20. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
  21. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  29. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  30. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  31. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  32. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  33. naas_abi_core/integration/__init__.py +7 -0
  34. naas_abi_core/integration/integration.py +28 -0
  35. naas_abi_core/models/Model.py +198 -0
  36. naas_abi_core/models/OpenRouter.py +18 -0
  37. naas_abi_core/models/OpenRouter_test.py +36 -0
  38. naas_abi_core/module/Module.py +252 -0
  39. naas_abi_core/module/ModuleAgentLoader.py +50 -0
  40. naas_abi_core/module/ModuleUtils.py +20 -0
  41. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  42. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  43. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  44. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  46. naas_abi_core/pipeline/__init__.py +6 -0
  47. naas_abi_core/pipeline/pipeline.py +70 -0
  48. naas_abi_core/services/__init__.py +0 -0
  49. naas_abi_core/services/agent/Agent.py +1619 -0
  50. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  51. naas_abi_core/services/agent/Agent_test.py +214 -0
  52. naas_abi_core/services/agent/IntentAgent.py +1179 -0
  53. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  54. naas_abi_core/services/agent/beta/Embeddings.py +181 -0
  55. naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
  56. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  57. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  58. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  59. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  60. naas_abi_core/services/cache/CacheFactory.py +31 -0
  61. naas_abi_core/services/cache/CachePort.py +63 -0
  62. naas_abi_core/services/cache/CacheService.py +246 -0
  63. naas_abi_core/services/cache/CacheService_test.py +85 -0
  64. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  65. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  66. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  67. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  68. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  71. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  72. naas_abi_core/services/ontology/OntologyService.py +17 -0
  73. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  74. naas_abi_core/services/secret/Secret.py +138 -0
  75. naas_abi_core/services/secret/SecretPorts.py +45 -0
  76. naas_abi_core/services/secret/Secret_test.py +65 -0
  77. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  79. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  81. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
  82. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  83. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  84. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  85. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  92. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  94. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  95. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  96. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  97. naas_abi_core/services/vector_store/__init__.py +13 -0
  98. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  100. naas_abi_core/tests/test_services_imports.py +69 -0
  101. naas_abi_core/utils/Expose.py +55 -0
  102. naas_abi_core/utils/Graph.py +182 -0
  103. naas_abi_core/utils/JSON.py +49 -0
  104. naas_abi_core/utils/LazyLoader.py +44 -0
  105. naas_abi_core/utils/Logger.py +12 -0
  106. naas_abi_core/utils/OntologyReasoner.py +141 -0
  107. naas_abi_core/utils/OntologyYaml.py +681 -0
  108. naas_abi_core/utils/SPARQL.py +256 -0
  109. naas_abi_core/utils/Storage.py +33 -0
  110. naas_abi_core/utils/StorageUtils.py +398 -0
  111. naas_abi_core/utils/String.py +52 -0
  112. naas_abi_core/utils/Workers.py +114 -0
  113. naas_abi_core/utils/__init__.py +0 -0
  114. naas_abi_core/utils/onto2py/README.md +0 -0
  115. naas_abi_core/utils/onto2py/__init__.py +10 -0
  116. naas_abi_core/utils/onto2py/__main__.py +29 -0
  117. naas_abi_core/utils/onto2py/onto2py.py +611 -0
  118. naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
  119. naas_abi_core/workflow/__init__.py +5 -0
  120. naas_abi_core/workflow/workflow.py +48 -0
  121. naas_abi_core-1.4.1.dist-info/METADATA +630 -0
  122. naas_abi_core-1.4.1.dist-info/RECORD +124 -0
  123. naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
  124. naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,278 @@
1
+ """Tests for Agent memory configuration with PostgreSQL support."""
2
+
3
+ import os
4
+ from unittest.mock import ANY, MagicMock, patch
5
+
6
+ import pytest
7
+ from langgraph.checkpoint.base import BaseCheckpointSaver
8
+ from langgraph.checkpoint.memory import MemorySaver
9
+ from naas_abi_core.services.agent.Agent import (
10
+ Agent,
11
+ AgentSharedState,
12
+ create_checkpointer,
13
+ )
14
+
15
+
16
+ class TestCreateCheckpointer:
17
+ """Test the create_checkpointer function that detects PostgreSQL configuration."""
18
+
19
+ def test_create_checkpointer_without_postgres_url(self):
20
+ """Test that MemorySaver is returned when POSTGRES_URL is not set."""
21
+ with patch.dict(os.environ, {}, clear=True):
22
+ checkpointer = create_checkpointer()
23
+ assert isinstance(checkpointer, MemorySaver)
24
+
25
+ def test_create_checkpointer_with_postgres_url(self):
26
+ """Test that PostgresSaver is returned when POSTGRES_URL is set."""
27
+ test_url = "postgresql://user:pass@localhost:5432/testdb"
28
+
29
+ # Mock the PostgresSaver class, psycopg connection, and instance
30
+ mock_postgres_saver = MagicMock()
31
+ mock_postgres_saver.setup = MagicMock()
32
+ mock_postgres_class = MagicMock(return_value=mock_postgres_saver)
33
+ mock_connection = MagicMock()
34
+ mock_connection_connect = MagicMock(return_value=mock_connection)
35
+
36
+ with patch.dict(os.environ, {"POSTGRES_URL": test_url}):
37
+ with patch(
38
+ "langgraph.checkpoint.postgres.PostgresSaver", mock_postgres_class
39
+ ):
40
+ with patch("psycopg.Connection.connect", mock_connection_connect):
41
+ checkpointer = create_checkpointer()
42
+
43
+ # Verify Connection.connect was called with proper parameters
44
+ mock_connection_connect.assert_called_once_with(
45
+ test_url,
46
+ autocommit=True,
47
+ prepare_threshold=0,
48
+ row_factory=ANY, # dict_row import is mocked
49
+ )
50
+ # Verify PostgresSaver constructor was called with connection
51
+ mock_postgres_class.assert_called_once_with(mock_connection)
52
+ # Verify setup() was called
53
+ mock_postgres_saver.setup.assert_called_once()
54
+ assert checkpointer == mock_postgres_saver
55
+
56
+ def test_create_checkpointer_postgres_import_error(self):
57
+ """Test fallback to MemorySaver when PostgresSaver import fails."""
58
+ test_url = "postgresql://user:pass@localhost:5432/testdb"
59
+
60
+ with patch.dict(os.environ, {"POSTGRES_URL": test_url}):
61
+ # Simulate ImportError when trying to import PostgresSaver
62
+ with patch(
63
+ "builtins.__import__",
64
+ side_effect=ImportError(
65
+ "No module named 'langgraph.checkpoint.postgres'"
66
+ ),
67
+ ):
68
+ checkpointer = create_checkpointer()
69
+ assert isinstance(checkpointer, MemorySaver)
70
+
71
+ def test_create_checkpointer_postgres_connection_error(self):
72
+ """Test fallback to MemorySaver when PostgreSQL connection fails."""
73
+ test_url = "postgresql://user:pass@localhost:5432/testdb"
74
+
75
+ # Mock Connection.connect to raise an exception
76
+ mock_connection_connect = MagicMock(side_effect=Exception("Connection failed"))
77
+
78
+ with patch.dict(os.environ, {"POSTGRES_URL": test_url}):
79
+ with patch("psycopg.Connection.connect", mock_connection_connect):
80
+ checkpointer = create_checkpointer()
81
+ assert isinstance(checkpointer, MemorySaver)
82
+
83
+ def test_create_checkpointer_postgres_setup_error(self):
84
+ """Test fallback to MemorySaver when PostgreSQL setup fails."""
85
+ test_url = "postgresql://user:pass@localhost:5432/testdb"
86
+
87
+ # Mock successful connection but setup failure
88
+ mock_postgres_saver = MagicMock()
89
+ mock_postgres_saver.setup.side_effect = Exception("Table creation failed")
90
+ mock_postgres_class = MagicMock(return_value=mock_postgres_saver)
91
+ mock_connection = MagicMock()
92
+ mock_connection_connect = MagicMock(return_value=mock_connection)
93
+
94
+ with patch.dict(os.environ, {"POSTGRES_URL": test_url}):
95
+ with patch(
96
+ "langgraph.checkpoint.postgres.PostgresSaver", mock_postgres_class
97
+ ):
98
+ with patch("psycopg.Connection.connect", mock_connection_connect):
99
+ checkpointer = create_checkpointer()
100
+ assert isinstance(checkpointer, MemorySaver)
101
+
102
+
103
+ class TestAgentMemoryConfiguration:
104
+ """Test Agent class memory configuration."""
105
+
106
+ @pytest.fixture
107
+ def mock_chat_model(self):
108
+ """Create a mock chat model."""
109
+ model = MagicMock()
110
+ model.bind_tools = MagicMock(return_value=model)
111
+ return model
112
+
113
+ def test_agent_uses_provided_memory(self, mock_chat_model):
114
+ """Test that Agent uses explicitly provided memory."""
115
+ custom_memory = MagicMock(spec=BaseCheckpointSaver)
116
+
117
+ agent = Agent(
118
+ name="test_agent",
119
+ description="Test agent",
120
+ chat_model=mock_chat_model,
121
+ memory=custom_memory,
122
+ )
123
+
124
+ assert agent._checkpointer == custom_memory
125
+
126
+ def test_agent_creates_memory_when_none_provided(self, mock_chat_model):
127
+ """Test that Agent creates memory based on environment when None is provided."""
128
+ with patch("abi.services.agent.Agent.create_checkpointer") as mock_create:
129
+ mock_checkpointer = MagicMock(spec=BaseCheckpointSaver)
130
+ mock_create.return_value = mock_checkpointer
131
+
132
+ agent = Agent(
133
+ name="test_agent",
134
+ description="Test agent",
135
+ chat_model=mock_chat_model,
136
+ memory=None,
137
+ )
138
+
139
+ mock_create.assert_called_once()
140
+ assert agent._checkpointer == mock_checkpointer
141
+
142
+ def test_agent_default_memory_creation(self, mock_chat_model):
143
+ """Test that Agent creates memory automatically when not provided."""
144
+ with patch("abi.services.agent.Agent.create_checkpointer") as mock_create:
145
+ mock_checkpointer = MagicMock(spec=BaseCheckpointSaver)
146
+ mock_create.return_value = mock_checkpointer
147
+
148
+ # Don't provide memory parameter at all
149
+ agent = Agent(
150
+ name="test_agent", description="Test agent", chat_model=mock_chat_model
151
+ )
152
+
153
+ mock_create.assert_called_once()
154
+ assert agent._checkpointer == mock_checkpointer
155
+
156
+
157
+ class TestAgentPostgresIntegration:
158
+ """Integration tests for Agent with PostgreSQL checkpointer."""
159
+
160
+ @pytest.fixture
161
+ def mock_chat_model(self):
162
+ """Create a mock chat model that simulates real behavior."""
163
+ model = MagicMock()
164
+ model.bind_tools = MagicMock(return_value=model)
165
+
166
+ # Mock invoke to return a proper message
167
+ from langchain_core.messages import AIMessage
168
+
169
+ model.invoke = MagicMock(return_value=AIMessage(content="Test response"))
170
+
171
+ return model
172
+
173
+ @pytest.mark.integration
174
+ def test_agent_with_postgres_preserves_state(self, mock_chat_model):
175
+ """Test that Agent with PostgreSQL preserves conversation state."""
176
+ # This test would require a running PostgreSQL instance
177
+ # It's marked as integration test and can be skipped in CI
178
+
179
+ test_url = "postgresql://abi_user:abi_password@localhost:5432/abi_memory"
180
+
181
+ with patch.dict(os.environ, {"POSTGRES_URL": test_url}):
182
+ # First agent instance
183
+ agent1 = Agent(
184
+ name="test_agent",
185
+ description="Test agent",
186
+ chat_model=mock_chat_model,
187
+ state=AgentSharedState(thread_id="123"),
188
+ )
189
+
190
+ # Simulate some conversation
191
+ # In a real test, we would invoke the agent and check state persistence
192
+
193
+ # Second agent instance with same thread_id should have access to same state
194
+ agent2 = Agent(
195
+ name="test_agent",
196
+ description="Test agent",
197
+ chat_model=mock_chat_model,
198
+ state=AgentSharedState(thread_id="123"),
199
+ )
200
+
201
+ # Both agents should share the same checkpointer type
202
+ assert type(agent1._checkpointer) is type(agent2._checkpointer)
203
+
204
+ def test_agent_duplicate_preserves_memory_config(self, mock_chat_model):
205
+ """Test that duplicating an agent preserves memory configuration."""
206
+ custom_memory = MagicMock(spec=BaseCheckpointSaver)
207
+
208
+ original_agent = Agent(
209
+ name="test_agent",
210
+ description="Test agent",
211
+ chat_model=mock_chat_model,
212
+ memory=custom_memory,
213
+ )
214
+
215
+ duplicated_agent = original_agent.duplicate()
216
+
217
+ # The duplicated agent should use the same memory type
218
+ assert duplicated_agent._checkpointer == original_agent._checkpointer
219
+
220
+ def test_agent_thread_id_passed_as_string(self, mock_chat_model):
221
+ """Test that thread_id is passed as string to graph config."""
222
+ # Mock the graph.stream method to capture the config
223
+ mock_graph = MagicMock()
224
+ mock_graph.stream.return_value = iter([("source", {"messages": []})])
225
+
226
+ agent = Agent(
227
+ name="test_agent", description="Test agent", chat_model=mock_chat_model
228
+ )
229
+ agent.graph = mock_graph
230
+
231
+ # Set a specific thread_id
232
+ agent._state.set_thread_id("123")
233
+
234
+ # Call stream which should pass thread_id as string directly
235
+ list(agent.stream("test prompt"))
236
+
237
+ # Verify that graph.stream was called with thread_id as string
238
+ mock_graph.stream.assert_called_once()
239
+ call_args = mock_graph.stream.call_args
240
+ config = call_args[1]["config"] # kwargs
241
+
242
+ assert config["configurable"]["thread_id"] == "123"
243
+ assert isinstance(config["configurable"]["thread_id"], str)
244
+
245
+ def test_agent_thread_id_increment(self, mock_chat_model):
246
+ """Test that thread_id increments correctly with string type."""
247
+ # Create agent with explicit thread_id to avoid test interference
248
+ from naas_abi_core.services.agent.Agent import AgentSharedState
249
+
250
+ state = AgentSharedState(thread_id="1")
251
+
252
+ agent = Agent(
253
+ name="test_agent",
254
+ description="Test agent",
255
+ chat_model=mock_chat_model,
256
+ state=state,
257
+ )
258
+
259
+ # Default thread_id should be "1"
260
+ assert agent._state.thread_id == "1"
261
+
262
+ # Test increment
263
+ agent.reset()
264
+ assert agent._state.thread_id == "2"
265
+
266
+ # Test multiple increments
267
+ agent.reset()
268
+ agent.reset()
269
+ assert agent._state.thread_id == "4"
270
+
271
+ # Test setting a custom string thread_id and incrementing
272
+ agent._state.set_thread_id("100")
273
+ agent.reset()
274
+ assert agent._state.thread_id == "101"
275
+
276
+
277
+ if __name__ == "__main__":
278
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python3
2
+ """Integration test to verify PostgreSQL memory works with Agent.
3
+
4
+ This test requires PostgreSQL to be running. It can be started with:
5
+ make dev-up
6
+
7
+ Run this test with:
8
+ POSTGRES_URL=postgresql://abi_user:abi_password@localhost:5432/abi_memory python test_postgres_integration.py
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ from unittest.mock import MagicMock
14
+
15
+ from langchain_core.messages import AIMessage
16
+
17
+ # Add parent directory to path to import Agent
18
+ sys.path.insert(
19
+ 0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
20
+ )
21
+
22
+ from naas_abi_core.services.agent.Agent import (
23
+ Agent,
24
+ AgentSharedState,
25
+ create_checkpointer,
26
+ )
27
+
28
+
29
+ def test_postgres_checkpointer():
30
+ """Test that PostgreSQL checkpointer is created when POSTGRES_URL is set."""
31
+ postgres_url = os.getenv("POSTGRES_URL")
32
+
33
+ if not postgres_url:
34
+ print("⚠️ POSTGRES_URL not set. Skipping PostgreSQL integration test.")
35
+ print(" To run this test, start PostgreSQL with: make dev-up")
36
+ print(
37
+ " Then set POSTGRES_URL=postgresql://abi_user:abi_password@localhost:5432/abi_memory"
38
+ )
39
+ return False
40
+
41
+ print(f"✓ POSTGRES_URL found: {postgres_url}")
42
+
43
+ try:
44
+ checkpointer = create_checkpointer()
45
+ print(f"✓ Created checkpointer: {type(checkpointer).__name__}")
46
+
47
+ # Check if it's a PostgreSQL checkpointer
48
+ if "PostgresSaver" in type(checkpointer).__name__:
49
+ print("✓ PostgreSQL checkpointer successfully created!")
50
+ print(
51
+ "✓ Connection configured with autocommit=True, prepare_threshold=0, row_factory=dict_row"
52
+ )
53
+ print("✓ Database tables initialized with setup()")
54
+ return True
55
+ else:
56
+ print(f"⚠️ Expected PostgresSaver but got {type(checkpointer).__name__}")
57
+ return False
58
+
59
+ except Exception as e:
60
+ print(f"✗ Error creating checkpointer: {e}")
61
+ return False
62
+
63
+
64
+ def test_agent_with_postgres():
65
+ """Test that Agent can use PostgreSQL for memory persistence."""
66
+ postgres_url = os.getenv("POSTGRES_URL")
67
+
68
+ if not postgres_url:
69
+ print("⚠️ Skipping Agent PostgreSQL test (POSTGRES_URL not set)")
70
+ return False
71
+
72
+ try:
73
+ # Create a mock chat model
74
+ mock_model = MagicMock()
75
+ mock_model.bind_tools = MagicMock(return_value=mock_model)
76
+ mock_model.invoke = MagicMock(return_value=AIMessage(content="Test response"))
77
+
78
+ # Create agent with a specific thread ID
79
+ thread_id = 12345
80
+ agent = Agent(
81
+ name="test_agent",
82
+ description="Test agent for PostgreSQL integration",
83
+ chat_model=mock_model,
84
+ state=AgentSharedState(thread_id=thread_id),
85
+ )
86
+
87
+ print(f"✓ Created agent with thread_id={thread_id}")
88
+ print(f"✓ Agent using checkpointer: {type(agent._checkpointer).__name__}")
89
+
90
+ # Test that the agent's checkpointer is PostgreSQL-based
91
+ if "PostgresSaver" in type(agent._checkpointer).__name__:
92
+ print("✓ Agent successfully using PostgreSQL for memory!")
93
+ return True
94
+ else:
95
+ print(
96
+ f"⚠️ Agent using {type(agent._checkpointer).__name__} instead of PostgresSaver"
97
+ )
98
+ return False
99
+
100
+ except Exception as e:
101
+ print(f"✗ Error creating agent with PostgreSQL: {e}")
102
+ import traceback
103
+
104
+ traceback.print_exc()
105
+ return False
106
+
107
+
108
+ def main():
109
+ """Run all integration tests."""
110
+ print("=" * 60)
111
+ print("PostgreSQL Integration Tests for Agent Memory")
112
+ print("=" * 60)
113
+ print()
114
+
115
+ tests_passed = 0
116
+ tests_total = 2
117
+
118
+ # Test 1: Checkpointer creation
119
+ print("Test 1: PostgreSQL Checkpointer Creation")
120
+ print("-" * 40)
121
+ if test_postgres_checkpointer():
122
+ tests_passed += 1
123
+ print()
124
+
125
+ # Test 2: Agent with PostgreSQL
126
+ print("Test 2: Agent with PostgreSQL Memory")
127
+ print("-" * 40)
128
+ if test_agent_with_postgres():
129
+ tests_passed += 1
130
+ print()
131
+
132
+ # Summary
133
+ print("=" * 60)
134
+ if tests_passed == tests_total:
135
+ print(f"✅ All tests passed! ({tests_passed}/{tests_total})")
136
+ else:
137
+ print(f"⚠️ {tests_passed}/{tests_total} tests passed")
138
+ print("=" * 60)
139
+
140
+ return tests_passed == tests_total
141
+
142
+
143
+ if __name__ == "__main__":
144
+ success = main()
145
+ sys.exit(0 if success else 1)
@@ -0,0 +1,31 @@
1
+ import os
2
+
3
+ from naas_abi_core.services.cache.adapters.secondary.CacheFSAdapter import (
4
+ CacheFSAdapter,
5
+ )
6
+ from naas_abi_core.services.cache.CacheService import CacheService
7
+ from naas_abi_core.utils.Storage import NoStorageFolderFound, find_storage_folder
8
+
9
+
10
+ class CacheFactory:
11
+ @staticmethod
12
+ def CacheFS_find_storage(
13
+ subpath: str = "cache", needle: str = "storage"
14
+ ) -> CacheService:
15
+ if not subpath.startswith("cache"):
16
+ subpath = os.path.join("cache", subpath)
17
+
18
+ try:
19
+ return CacheService(
20
+ CacheFSAdapter(
21
+ os.path.join(find_storage_folder(os.getcwd(), needle), subpath)
22
+ )
23
+ )
24
+ except NoStorageFolderFound as _:
25
+ # Create a "storage" folder for the cache
26
+ os.makedirs(os.path.join(os.getcwd(), "storage"), exist_ok=True)
27
+ return CacheService(
28
+ CacheFSAdapter(
29
+ os.path.join(find_storage_folder(os.getcwd(), needle), subpath)
30
+ )
31
+ )
@@ -0,0 +1,63 @@
1
+ from pydantic import BaseModel, Field
2
+ import datetime
3
+ from enum import Enum
4
+ from typing import Any
5
+
6
+ class CacheNotFoundError(Exception):
7
+ pass
8
+
9
+ class CacheExpiredError(Exception):
10
+ pass
11
+
12
+ class DataType(str, Enum):
13
+ TEXT = "text"
14
+ JSON = "json"
15
+ BINARY = "binary"
16
+ PICKLE = "pickle"
17
+
18
+ class CachedData(BaseModel):
19
+ key: str
20
+ data: Any
21
+ data_type: DataType
22
+ created_at: str = Field(default_factory=lambda: datetime.datetime.now().isoformat())
23
+
24
+ class ICacheAdapter:
25
+
26
+ def get(self, key: str) -> CachedData:
27
+ raise NotImplementedError("Not implemented")
28
+
29
+ def set(self, key: str, value: CachedData) -> None:
30
+ raise NotImplementedError("Not implemented")
31
+
32
+ def delete(self, key: str) -> None:
33
+ raise NotImplementedError("Not implemented")
34
+
35
+ def exists(self, key: str) -> bool:
36
+ raise NotImplementedError("Not implemented")
37
+
38
+ class ICacheService:
39
+
40
+ adapter: ICacheAdapter
41
+
42
+ def __init__(self, adapter: ICacheAdapter):
43
+ self.adapter = adapter
44
+
45
+ def exists(self, key: str) -> bool:
46
+ raise NotImplementedError("Not implemented")
47
+
48
+ def get(self, key: str, ttl: datetime.timedelta | None = None) -> Any:
49
+ raise NotImplementedError("Not implemented")
50
+
51
+ def set_text(self, key: str, value: str) -> None:
52
+ raise NotImplementedError("Not implemented")
53
+
54
+ def set_json(self, key: str, value: dict) -> None:
55
+ raise NotImplementedError("Not implemented")
56
+
57
+ def set_binary(self, key: str, value: bytes) -> None:
58
+ raise NotImplementedError("Not implemented")
59
+
60
+ def set_pickle(self, key: str, value: Any) -> None:
61
+ raise NotImplementedError("Not implemented")
62
+
63
+