hdsp-jupyter-extension 2.0.0__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 (121) hide show
  1. agent_server/__init__.py +8 -0
  2. agent_server/core/__init__.py +92 -0
  3. agent_server/core/api_key_manager.py +427 -0
  4. agent_server/core/code_validator.py +1238 -0
  5. agent_server/core/context_condenser.py +308 -0
  6. agent_server/core/embedding_service.py +254 -0
  7. agent_server/core/error_classifier.py +577 -0
  8. agent_server/core/llm_client.py +95 -0
  9. agent_server/core/llm_service.py +649 -0
  10. agent_server/core/notebook_generator.py +274 -0
  11. agent_server/core/prompt_builder.py +35 -0
  12. agent_server/core/rag_manager.py +742 -0
  13. agent_server/core/reflection_engine.py +489 -0
  14. agent_server/core/retriever.py +248 -0
  15. agent_server/core/state_verifier.py +452 -0
  16. agent_server/core/summary_generator.py +484 -0
  17. agent_server/core/task_manager.py +198 -0
  18. agent_server/knowledge/__init__.py +9 -0
  19. agent_server/knowledge/watchdog_service.py +352 -0
  20. agent_server/main.py +160 -0
  21. agent_server/prompts/__init__.py +60 -0
  22. agent_server/prompts/file_action_prompts.py +113 -0
  23. agent_server/routers/__init__.py +9 -0
  24. agent_server/routers/agent.py +591 -0
  25. agent_server/routers/chat.py +188 -0
  26. agent_server/routers/config.py +100 -0
  27. agent_server/routers/file_resolver.py +293 -0
  28. agent_server/routers/health.py +42 -0
  29. agent_server/routers/rag.py +163 -0
  30. agent_server/schemas/__init__.py +60 -0
  31. hdsp_agent_core/__init__.py +158 -0
  32. hdsp_agent_core/factory.py +252 -0
  33. hdsp_agent_core/interfaces.py +203 -0
  34. hdsp_agent_core/knowledge/__init__.py +31 -0
  35. hdsp_agent_core/knowledge/chunking.py +356 -0
  36. hdsp_agent_core/knowledge/libraries/dask.md +188 -0
  37. hdsp_agent_core/knowledge/libraries/matplotlib.md +164 -0
  38. hdsp_agent_core/knowledge/libraries/polars.md +68 -0
  39. hdsp_agent_core/knowledge/loader.py +337 -0
  40. hdsp_agent_core/llm/__init__.py +13 -0
  41. hdsp_agent_core/llm/service.py +556 -0
  42. hdsp_agent_core/managers/__init__.py +22 -0
  43. hdsp_agent_core/managers/config_manager.py +133 -0
  44. hdsp_agent_core/managers/session_manager.py +251 -0
  45. hdsp_agent_core/models/__init__.py +115 -0
  46. hdsp_agent_core/models/agent.py +316 -0
  47. hdsp_agent_core/models/chat.py +41 -0
  48. hdsp_agent_core/models/common.py +95 -0
  49. hdsp_agent_core/models/rag.py +368 -0
  50. hdsp_agent_core/prompts/__init__.py +63 -0
  51. hdsp_agent_core/prompts/auto_agent_prompts.py +1260 -0
  52. hdsp_agent_core/prompts/cell_action_prompts.py +98 -0
  53. hdsp_agent_core/services/__init__.py +18 -0
  54. hdsp_agent_core/services/agent_service.py +438 -0
  55. hdsp_agent_core/services/chat_service.py +205 -0
  56. hdsp_agent_core/services/rag_service.py +262 -0
  57. hdsp_agent_core/tests/__init__.py +1 -0
  58. hdsp_agent_core/tests/conftest.py +102 -0
  59. hdsp_agent_core/tests/test_factory.py +251 -0
  60. hdsp_agent_core/tests/test_services.py +326 -0
  61. hdsp_jupyter_extension-2.0.0.data/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  62. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/build_log.json +738 -0
  63. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/install.json +5 -0
  64. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/package.json +134 -0
  65. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  66. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  67. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  68. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  69. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  70. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  71. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  72. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  73. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  74. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  75. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/style.js +4 -0
  76. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  77. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  78. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  79. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  80. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  81. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  82. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  83. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  84. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  85. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  86. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  87. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
  88. hdsp_jupyter_extension-2.0.0.dist-info/METADATA +152 -0
  89. hdsp_jupyter_extension-2.0.0.dist-info/RECORD +121 -0
  90. hdsp_jupyter_extension-2.0.0.dist-info/WHEEL +4 -0
  91. hdsp_jupyter_extension-2.0.0.dist-info/licenses/LICENSE +21 -0
  92. jupyter_ext/__init__.py +233 -0
  93. jupyter_ext/_version.py +4 -0
  94. jupyter_ext/config.py +111 -0
  95. jupyter_ext/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  96. jupyter_ext/handlers.py +632 -0
  97. jupyter_ext/labextension/build_log.json +738 -0
  98. jupyter_ext/labextension/package.json +134 -0
  99. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  100. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  101. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  102. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  103. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  104. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  105. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  106. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  107. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  108. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  109. jupyter_ext/labextension/static/style.js +4 -0
  110. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  111. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  112. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  113. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  114. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  115. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  116. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  117. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  118. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  119. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  120. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  121. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
@@ -0,0 +1,251 @@
1
+ """
2
+ HDSP Agent Core - ServiceFactory Tests
3
+
4
+ Tests for ServiceFactory mode detection, initialization, and service creation.
5
+ """
6
+
7
+ import os
8
+ from unittest.mock import AsyncMock, patch
9
+
10
+ import pytest
11
+
12
+ from hdsp_agent_core.factory import AgentMode, ServiceFactory, get_service_factory
13
+ from hdsp_agent_core.interfaces import IAgentService, IChatService, IRAGService
14
+
15
+
16
+ class TestAgentMode:
17
+ """Tests for AgentMode enum"""
18
+
19
+ def test_embedded_mode_value(self):
20
+ """Test embedded mode has correct value"""
21
+ assert AgentMode.EMBEDDED.value == "embedded"
22
+
23
+ def test_proxy_mode_value(self):
24
+ """Test proxy mode has correct value"""
25
+ assert AgentMode.PROXY.value == "proxy"
26
+
27
+
28
+ class TestServiceFactorySingleton:
29
+ """Tests for ServiceFactory singleton pattern"""
30
+
31
+ def test_get_instance_returns_same_instance(self, reset_factory):
32
+ """Test get_instance returns the same instance"""
33
+ instance1 = ServiceFactory.get_instance()
34
+ instance2 = ServiceFactory.get_instance()
35
+ assert instance1 is instance2
36
+
37
+ def test_reset_instance_clears_singleton(self, reset_factory):
38
+ """Test reset_instance clears the singleton"""
39
+ instance1 = ServiceFactory.get_instance()
40
+ ServiceFactory.reset_instance()
41
+ instance2 = ServiceFactory.get_instance()
42
+ assert instance1 is not instance2
43
+
44
+ def test_get_service_factory_convenience_function(self, reset_factory):
45
+ """Test get_service_factory returns singleton"""
46
+ factory1 = get_service_factory()
47
+ factory2 = ServiceFactory.get_instance()
48
+ assert factory1 is factory2
49
+
50
+
51
+ class TestServiceFactoryModeDetection:
52
+ """Tests for mode detection from environment"""
53
+
54
+ def test_default_mode_is_proxy(self, reset_factory):
55
+ """Test default mode is proxy when no env var set"""
56
+ with patch.dict(os.environ, {}, clear=True):
57
+ # Clear HDSP_AGENT_MODE if it exists
58
+ os.environ.pop("HDSP_AGENT_MODE", None)
59
+ factory = ServiceFactory()
60
+ assert factory.mode == AgentMode.PROXY
61
+ assert factory.is_proxy is True
62
+ assert factory.is_embedded is False
63
+
64
+ def test_embedded_mode_detection(self, reset_factory, mock_env_embedded):
65
+ """Test embedded mode is detected from env var"""
66
+ factory = ServiceFactory()
67
+ assert factory.mode == AgentMode.EMBEDDED
68
+ assert factory.is_embedded is True
69
+ assert factory.is_proxy is False
70
+
71
+ def test_proxy_mode_detection(self, reset_factory, mock_env_proxy):
72
+ """Test proxy mode is detected from env var"""
73
+ factory = ServiceFactory()
74
+ assert factory.mode == AgentMode.PROXY
75
+ assert factory.is_proxy is True
76
+ assert factory.is_embedded is False
77
+
78
+ def test_case_insensitive_mode_detection(self, reset_factory):
79
+ """Test mode detection is case-insensitive"""
80
+ with patch.dict(os.environ, {"HDSP_AGENT_MODE": "EMBEDDED"}):
81
+ factory = ServiceFactory()
82
+ assert factory.mode == AgentMode.EMBEDDED
83
+
84
+ def test_invalid_mode_defaults_to_proxy(self, reset_factory):
85
+ """Test invalid mode defaults to proxy"""
86
+ with patch.dict(os.environ, {"HDSP_AGENT_MODE": "invalid_mode"}):
87
+ factory = ServiceFactory()
88
+ assert factory.mode == AgentMode.PROXY
89
+
90
+
91
+ class TestServiceFactoryConfiguration:
92
+ """Tests for proxy mode configuration"""
93
+
94
+ def test_default_server_url(self, reset_factory):
95
+ """Test default server URL"""
96
+ with patch.dict(os.environ, {}, clear=True):
97
+ os.environ.pop("AGENT_SERVER_URL", None)
98
+ factory = ServiceFactory()
99
+ assert factory.server_url == "http://localhost:8000"
100
+
101
+ def test_custom_server_url(self, reset_factory, mock_env_proxy_with_url):
102
+ """Test custom server URL from env var"""
103
+ factory = ServiceFactory()
104
+ assert factory.server_url == "http://agent.example.com:9000"
105
+
106
+ def test_default_timeout(self, reset_factory):
107
+ """Test default timeout"""
108
+ with patch.dict(os.environ, {}, clear=True):
109
+ os.environ.pop("AGENT_SERVER_TIMEOUT", None)
110
+ factory = ServiceFactory()
111
+ assert factory.timeout == 120.0
112
+
113
+ def test_custom_timeout(self, reset_factory, mock_env_proxy_with_url):
114
+ """Test custom timeout from env var"""
115
+ factory = ServiceFactory()
116
+ assert factory.timeout == 60.0
117
+
118
+
119
+ class TestServiceFactoryInitialization:
120
+ """Tests for ServiceFactory initialization"""
121
+
122
+ def test_not_initialized_by_default(self, reset_factory, mock_env_proxy):
123
+ """Test factory is not initialized by default"""
124
+ factory = ServiceFactory.get_instance()
125
+ assert factory.is_initialized is False
126
+
127
+ @pytest.mark.asyncio
128
+ async def test_initialize_proxy_mode(self, reset_factory, mock_env_proxy):
129
+ """Test initialization in proxy mode"""
130
+ factory = ServiceFactory.get_instance()
131
+ await factory.initialize()
132
+ assert factory.is_initialized is True
133
+
134
+ @pytest.mark.asyncio
135
+ async def test_initialize_embedded_mode(self, reset_factory, mock_env_embedded):
136
+ """Test initialization in embedded mode"""
137
+ # Mock the RAG service initialization
138
+ with patch(
139
+ "hdsp_agent_core.services.rag_service.EmbeddedRAGService.initialize",
140
+ new_callable=AsyncMock
141
+ ):
142
+ factory = ServiceFactory.get_instance()
143
+ await factory.initialize()
144
+ assert factory.is_initialized is True
145
+
146
+ @pytest.mark.asyncio
147
+ async def test_double_initialization_is_noop(self, reset_factory, mock_env_proxy):
148
+ """Test calling initialize twice is safe"""
149
+ factory = ServiceFactory.get_instance()
150
+ await factory.initialize()
151
+ await factory.initialize() # Should not raise
152
+ assert factory.is_initialized is True
153
+
154
+ @pytest.mark.asyncio
155
+ async def test_shutdown_resets_state(self, reset_factory, mock_env_proxy):
156
+ """Test shutdown resets initialization state"""
157
+ factory = ServiceFactory.get_instance()
158
+ await factory.initialize()
159
+ assert factory.is_initialized is True
160
+ await factory.shutdown()
161
+ assert factory.is_initialized is False
162
+
163
+
164
+ class TestServiceFactoryServiceAccess:
165
+ """Tests for service accessor methods"""
166
+
167
+ @pytest.mark.asyncio
168
+ async def test_get_agent_service_after_init(self, reset_factory, mock_env_proxy):
169
+ """Test get_agent_service returns service after init"""
170
+ factory = ServiceFactory.get_instance()
171
+ await factory.initialize()
172
+ service = factory.get_agent_service()
173
+ assert service is not None
174
+ assert isinstance(service, IAgentService)
175
+
176
+ @pytest.mark.asyncio
177
+ async def test_get_chat_service_after_init(self, reset_factory, mock_env_proxy):
178
+ """Test get_chat_service returns service after init"""
179
+ factory = ServiceFactory.get_instance()
180
+ await factory.initialize()
181
+ service = factory.get_chat_service()
182
+ assert service is not None
183
+ assert isinstance(service, IChatService)
184
+
185
+ @pytest.mark.asyncio
186
+ async def test_get_rag_service_after_init(self, reset_factory, mock_env_proxy):
187
+ """Test get_rag_service returns service after init"""
188
+ factory = ServiceFactory.get_instance()
189
+ await factory.initialize()
190
+ service = factory.get_rag_service()
191
+ assert service is not None
192
+ assert isinstance(service, IRAGService)
193
+
194
+ def test_get_agent_service_before_init_raises(self, reset_factory, mock_env_proxy):
195
+ """Test get_agent_service raises before initialization"""
196
+ factory = ServiceFactory.get_instance()
197
+ with pytest.raises(RuntimeError, match="not initialized"):
198
+ factory.get_agent_service()
199
+
200
+ def test_get_chat_service_before_init_raises(self, reset_factory, mock_env_proxy):
201
+ """Test get_chat_service raises before initialization"""
202
+ factory = ServiceFactory.get_instance()
203
+ with pytest.raises(RuntimeError, match="not initialized"):
204
+ factory.get_chat_service()
205
+
206
+ def test_get_rag_service_before_init_raises(self, reset_factory, mock_env_proxy):
207
+ """Test get_rag_service raises before initialization"""
208
+ factory = ServiceFactory.get_instance()
209
+ with pytest.raises(RuntimeError, match="not initialized"):
210
+ factory.get_rag_service()
211
+
212
+
213
+ class TestServiceFactoryModeSpecificServices:
214
+ """Tests for mode-specific service creation"""
215
+
216
+ @pytest.mark.asyncio
217
+ async def test_proxy_mode_creates_proxy_services(
218
+ self, reset_factory, mock_env_proxy
219
+ ):
220
+ """Test proxy mode creates ProxyXxxService instances"""
221
+ from hdsp_agent_core.services.agent_service import ProxyAgentService
222
+ from hdsp_agent_core.services.chat_service import ProxyChatService
223
+ from hdsp_agent_core.services.rag_service import ProxyRAGService
224
+
225
+ factory = ServiceFactory.get_instance()
226
+ await factory.initialize()
227
+
228
+ assert isinstance(factory.get_agent_service(), ProxyAgentService)
229
+ assert isinstance(factory.get_chat_service(), ProxyChatService)
230
+ assert isinstance(factory.get_rag_service(), ProxyRAGService)
231
+
232
+ @pytest.mark.asyncio
233
+ async def test_embedded_mode_creates_embedded_services(
234
+ self, reset_factory, mock_env_embedded
235
+ ):
236
+ """Test embedded mode creates EmbeddedXxxService instances"""
237
+ from hdsp_agent_core.services.agent_service import EmbeddedAgentService
238
+ from hdsp_agent_core.services.chat_service import EmbeddedChatService
239
+ from hdsp_agent_core.services.rag_service import EmbeddedRAGService
240
+
241
+ # Mock RAG initialization
242
+ with patch(
243
+ "hdsp_agent_core.services.rag_service.EmbeddedRAGService.initialize",
244
+ new_callable=AsyncMock
245
+ ):
246
+ factory = ServiceFactory.get_instance()
247
+ await factory.initialize()
248
+
249
+ assert isinstance(factory.get_agent_service(), EmbeddedAgentService)
250
+ assert isinstance(factory.get_chat_service(), EmbeddedChatService)
251
+ assert isinstance(factory.get_rag_service(), EmbeddedRAGService)
@@ -0,0 +1,326 @@
1
+ """
2
+ HDSP Agent Core - Service Implementation Tests
3
+
4
+ Tests for Embedded and Proxy service implementations.
5
+ """
6
+
7
+ from unittest.mock import AsyncMock, MagicMock, patch
8
+
9
+ import pytest
10
+
11
+ from hdsp_agent_core.interfaces import IAgentService, IChatService, IRAGService
12
+
13
+
14
+ class TestProxyAgentService:
15
+ """Tests for ProxyAgentService"""
16
+
17
+ @pytest.fixture
18
+ def proxy_agent_service(self):
19
+ """Create ProxyAgentService instance"""
20
+ from hdsp_agent_core.services.agent_service import ProxyAgentService
21
+ return ProxyAgentService(
22
+ base_url="http://localhost:8000",
23
+ timeout=30.0
24
+ )
25
+
26
+ def test_implements_interface(self, proxy_agent_service):
27
+ """Test ProxyAgentService implements IAgentService"""
28
+ assert isinstance(proxy_agent_service, IAgentService)
29
+
30
+ def test_base_url_configuration(self, proxy_agent_service):
31
+ """Test base URL is configured correctly"""
32
+ assert proxy_agent_service._base_url == "http://localhost:8000"
33
+
34
+ def test_timeout_configuration(self, proxy_agent_service):
35
+ """Test timeout is configured correctly"""
36
+ assert proxy_agent_service._timeout == 30.0
37
+
38
+ @pytest.mark.asyncio
39
+ async def test_generate_plan_makes_http_request(
40
+ self, proxy_agent_service, sample_plan_request
41
+ ):
42
+ """Test generate_plan makes HTTP POST request"""
43
+ mock_response_data = {
44
+ "plan": {
45
+ "goal": "Create a simple plot",
46
+ "totalSteps": 1,
47
+ "steps": []
48
+ },
49
+ "reasoning": "Test reasoning"
50
+ }
51
+
52
+ with patch(
53
+ "hdsp_agent_core.services.agent_service.httpx.AsyncClient"
54
+ ) as mock_client_class:
55
+ mock_client = AsyncMock()
56
+ mock_response = MagicMock()
57
+ mock_response.json.return_value = mock_response_data
58
+ mock_response.raise_for_status = MagicMock()
59
+ mock_client.post = AsyncMock(return_value=mock_response)
60
+ mock_client.__aenter__ = AsyncMock(return_value=mock_client)
61
+ mock_client.__aexit__ = AsyncMock(return_value=None)
62
+ mock_client_class.return_value = mock_client
63
+
64
+ result = await proxy_agent_service.generate_plan(sample_plan_request)
65
+
66
+ # Verify HTTP call was made
67
+ mock_client.post.assert_called_once()
68
+ call_args = mock_client.post.call_args
69
+ assert "/agent/plan" in call_args[0][0]
70
+
71
+
72
+ class TestProxyChatService:
73
+ """Tests for ProxyChatService"""
74
+
75
+ @pytest.fixture
76
+ def proxy_chat_service(self):
77
+ """Create ProxyChatService instance"""
78
+ from hdsp_agent_core.services.chat_service import ProxyChatService
79
+ return ProxyChatService(
80
+ base_url="http://localhost:8000",
81
+ timeout=30.0
82
+ )
83
+
84
+ def test_implements_interface(self, proxy_chat_service):
85
+ """Test ProxyChatService implements IChatService"""
86
+ assert isinstance(proxy_chat_service, IChatService)
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_send_message_makes_http_request(
90
+ self, proxy_chat_service, sample_chat_request
91
+ ):
92
+ """Test send_message makes HTTP POST request"""
93
+ mock_response_data = {
94
+ "response": "Hello! I can help you analyze the data.",
95
+ "conversationId": "test-conversation",
96
+ }
97
+
98
+ with patch(
99
+ "hdsp_agent_core.services.chat_service.httpx.AsyncClient"
100
+ ) as mock_client_class:
101
+ mock_client = AsyncMock()
102
+ mock_response = MagicMock()
103
+ mock_response.json.return_value = mock_response_data
104
+ mock_response.raise_for_status = MagicMock()
105
+ mock_client.post = AsyncMock(return_value=mock_response)
106
+ mock_client.__aenter__ = AsyncMock(return_value=mock_client)
107
+ mock_client.__aexit__ = AsyncMock(return_value=None)
108
+ mock_client_class.return_value = mock_client
109
+
110
+ result = await proxy_chat_service.send_message(sample_chat_request)
111
+
112
+ mock_client.post.assert_called_once()
113
+
114
+
115
+ class TestProxyRAGService:
116
+ """Tests for ProxyRAGService"""
117
+
118
+ @pytest.fixture
119
+ def proxy_rag_service(self):
120
+ """Create ProxyRAGService instance"""
121
+ from hdsp_agent_core.services.rag_service import ProxyRAGService
122
+ return ProxyRAGService(
123
+ base_url="http://localhost:8000",
124
+ timeout=30.0
125
+ )
126
+
127
+ def test_implements_interface(self, proxy_rag_service):
128
+ """Test ProxyRAGService implements IRAGService"""
129
+ assert isinstance(proxy_rag_service, IRAGService)
130
+
131
+ @pytest.mark.asyncio
132
+ async def test_search_makes_http_request(
133
+ self, proxy_rag_service, sample_search_request
134
+ ):
135
+ """Test search makes HTTP POST request"""
136
+ mock_response_data = {
137
+ "results": [],
138
+ "query": "pandas dataframe operations",
139
+ "total_results": 0,
140
+ }
141
+
142
+ with patch(
143
+ "hdsp_agent_core.services.rag_service.httpx.AsyncClient"
144
+ ) as mock_client_class:
145
+ mock_client = AsyncMock()
146
+ mock_response = MagicMock()
147
+ mock_response.json.return_value = mock_response_data
148
+ mock_response.raise_for_status = MagicMock()
149
+ mock_client.post = AsyncMock(return_value=mock_response)
150
+ mock_client.__aenter__ = AsyncMock(return_value=mock_client)
151
+ mock_client.__aexit__ = AsyncMock(return_value=None)
152
+ mock_client_class.return_value = mock_client
153
+
154
+ result = await proxy_rag_service.search(sample_search_request)
155
+
156
+ mock_client.post.assert_called_once()
157
+
158
+ def test_is_ready_returns_false_before_init(self, proxy_rag_service):
159
+ """Test is_ready returns False before initialization"""
160
+ # Proxy service is not ready until initialize() is called
161
+ assert proxy_rag_service.is_ready() is False
162
+
163
+ @pytest.mark.asyncio
164
+ async def test_is_ready_after_init(self, proxy_rag_service):
165
+ """Test is_ready after successful initialization"""
166
+ mock_status = {"ready": True}
167
+
168
+ with patch(
169
+ "hdsp_agent_core.services.rag_service.httpx.AsyncClient"
170
+ ) as mock_client_class:
171
+ mock_client = AsyncMock()
172
+ mock_response = MagicMock()
173
+ mock_response.json.return_value = mock_status
174
+ mock_response.raise_for_status = MagicMock()
175
+ mock_client.get = AsyncMock(return_value=mock_response)
176
+ mock_client.__aenter__ = AsyncMock(return_value=mock_client)
177
+ mock_client.__aexit__ = AsyncMock(return_value=None)
178
+ mock_client_class.return_value = mock_client
179
+
180
+ await proxy_rag_service.initialize()
181
+
182
+ assert proxy_rag_service.is_ready() is True
183
+
184
+
185
+ class TestEmbeddedAgentService:
186
+ """Tests for EmbeddedAgentService"""
187
+
188
+ @pytest.fixture
189
+ def embedded_agent_service(self):
190
+ """Create EmbeddedAgentService instance"""
191
+ from hdsp_agent_core.services.agent_service import EmbeddedAgentService
192
+ return EmbeddedAgentService()
193
+
194
+ def test_implements_interface(self, embedded_agent_service):
195
+ """Test EmbeddedAgentService implements IAgentService"""
196
+ assert isinstance(embedded_agent_service, IAgentService)
197
+
198
+ @pytest.mark.asyncio
199
+ async def test_generate_plan_with_mock_llm(
200
+ self, embedded_agent_service, sample_plan_request
201
+ ):
202
+ """Test generate_plan calls LLM service"""
203
+ # Mock the LLM service
204
+ with patch.object(
205
+ embedded_agent_service, '_llm_service', create=True
206
+ ) as mock_llm:
207
+ mock_llm.generate_response = AsyncMock(
208
+ return_value='{"steps": [], "plan_id": "test"}'
209
+ )
210
+
211
+ # Note: Full integration test would require more mocking
212
+ # This is a placeholder for when LLM integration is complete
213
+
214
+
215
+ class TestEmbeddedChatService:
216
+ """Tests for EmbeddedChatService"""
217
+
218
+ @pytest.fixture
219
+ def embedded_chat_service(self):
220
+ """Create EmbeddedChatService instance"""
221
+ from hdsp_agent_core.services.chat_service import EmbeddedChatService
222
+ return EmbeddedChatService()
223
+
224
+ def test_implements_interface(self, embedded_chat_service):
225
+ """Test EmbeddedChatService implements IChatService"""
226
+ assert isinstance(embedded_chat_service, IChatService)
227
+
228
+
229
+ class TestEmbeddedRAGService:
230
+ """Tests for EmbeddedRAGService"""
231
+
232
+ @pytest.fixture
233
+ def embedded_rag_service(self):
234
+ """Create EmbeddedRAGService instance with mocked RAG manager"""
235
+ from hdsp_agent_core.services.rag_service import EmbeddedRAGService
236
+ service = EmbeddedRAGService()
237
+ return service
238
+
239
+ def test_implements_interface(self, embedded_rag_service):
240
+ """Test EmbeddedRAGService implements IRAGService"""
241
+ assert isinstance(embedded_rag_service, IRAGService)
242
+
243
+ def test_is_ready_before_init(self, embedded_rag_service):
244
+ """Test is_ready returns False before initialization"""
245
+ assert embedded_rag_service.is_ready() is False
246
+
247
+ @pytest.mark.asyncio
248
+ async def test_initialize_sets_ready(self, embedded_rag_service, mock_rag_manager):
249
+ """Test initialize makes service ready when RAGManager is available"""
250
+ # Directly set the internal state to simulate successful initialization
251
+ # since the actual rag_manager module might not be available
252
+ mock_rag_manager.is_ready = True
253
+ embedded_rag_service._rag_manager = mock_rag_manager
254
+ embedded_rag_service._initialized = True
255
+
256
+ assert embedded_rag_service.is_ready() is True
257
+
258
+ @pytest.mark.asyncio
259
+ async def test_initialize_handles_import_error(self, embedded_rag_service):
260
+ """Test initialize handles ImportError gracefully"""
261
+ # The service should remain not ready if rag_manager import fails
262
+ # Since rag_manager doesn't exist yet, initialize() should catch the error
263
+ await embedded_rag_service.initialize()
264
+
265
+ # Service should not be ready because rag_manager module doesn't exist
266
+ assert embedded_rag_service.is_ready() is False
267
+
268
+
269
+ class TestServiceInterfaceCompliance:
270
+ """Tests to verify all services implement their interfaces correctly"""
271
+
272
+ def test_all_agent_service_methods_exist(self):
273
+ """Verify all IAgentService methods are implemented"""
274
+ from hdsp_agent_core.services.agent_service import (
275
+ EmbeddedAgentService,
276
+ ProxyAgentService,
277
+ )
278
+
279
+ required_methods = [
280
+ "generate_plan",
281
+ "refine_code",
282
+ "replan",
283
+ "validate_code",
284
+ ]
285
+
286
+ for cls in [EmbeddedAgentService, ProxyAgentService]:
287
+ for method in required_methods:
288
+ assert hasattr(cls, method), f"{cls.__name__} missing {method}"
289
+ assert callable(getattr(cls, method))
290
+
291
+ def test_all_chat_service_methods_exist(self):
292
+ """Verify all IChatService methods are implemented"""
293
+ from hdsp_agent_core.services.chat_service import (
294
+ EmbeddedChatService,
295
+ ProxyChatService,
296
+ )
297
+
298
+ required_methods = [
299
+ "send_message",
300
+ "send_message_stream",
301
+ ]
302
+
303
+ for cls in [EmbeddedChatService, ProxyChatService]:
304
+ for method in required_methods:
305
+ assert hasattr(cls, method), f"{cls.__name__} missing {method}"
306
+ assert callable(getattr(cls, method))
307
+
308
+ def test_all_rag_service_methods_exist(self):
309
+ """Verify all IRAGService methods are implemented"""
310
+ from hdsp_agent_core.services.rag_service import (
311
+ EmbeddedRAGService,
312
+ ProxyRAGService,
313
+ )
314
+
315
+ required_methods = [
316
+ "search",
317
+ "get_context_for_query",
318
+ "is_ready",
319
+ "get_index_status",
320
+ "trigger_reindex",
321
+ ]
322
+
323
+ for cls in [EmbeddedRAGService, ProxyRAGService]:
324
+ for method in required_methods:
325
+ assert hasattr(cls, method), f"{cls.__name__} missing {method}"
326
+ assert callable(getattr(cls, method))
@@ -0,0 +1,7 @@
1
+ {
2
+ "ServerApp": {
3
+ "jpserver_extensions": {
4
+ "jupyter_ext": true
5
+ }
6
+ }
7
+ }