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.
- agent_server/__init__.py +8 -0
- agent_server/core/__init__.py +92 -0
- agent_server/core/api_key_manager.py +427 -0
- agent_server/core/code_validator.py +1238 -0
- agent_server/core/context_condenser.py +308 -0
- agent_server/core/embedding_service.py +254 -0
- agent_server/core/error_classifier.py +577 -0
- agent_server/core/llm_client.py +95 -0
- agent_server/core/llm_service.py +649 -0
- agent_server/core/notebook_generator.py +274 -0
- agent_server/core/prompt_builder.py +35 -0
- agent_server/core/rag_manager.py +742 -0
- agent_server/core/reflection_engine.py +489 -0
- agent_server/core/retriever.py +248 -0
- agent_server/core/state_verifier.py +452 -0
- agent_server/core/summary_generator.py +484 -0
- agent_server/core/task_manager.py +198 -0
- agent_server/knowledge/__init__.py +9 -0
- agent_server/knowledge/watchdog_service.py +352 -0
- agent_server/main.py +160 -0
- agent_server/prompts/__init__.py +60 -0
- agent_server/prompts/file_action_prompts.py +113 -0
- agent_server/routers/__init__.py +9 -0
- agent_server/routers/agent.py +591 -0
- agent_server/routers/chat.py +188 -0
- agent_server/routers/config.py +100 -0
- agent_server/routers/file_resolver.py +293 -0
- agent_server/routers/health.py +42 -0
- agent_server/routers/rag.py +163 -0
- agent_server/schemas/__init__.py +60 -0
- hdsp_agent_core/__init__.py +158 -0
- hdsp_agent_core/factory.py +252 -0
- hdsp_agent_core/interfaces.py +203 -0
- hdsp_agent_core/knowledge/__init__.py +31 -0
- hdsp_agent_core/knowledge/chunking.py +356 -0
- hdsp_agent_core/knowledge/libraries/dask.md +188 -0
- hdsp_agent_core/knowledge/libraries/matplotlib.md +164 -0
- hdsp_agent_core/knowledge/libraries/polars.md +68 -0
- hdsp_agent_core/knowledge/loader.py +337 -0
- hdsp_agent_core/llm/__init__.py +13 -0
- hdsp_agent_core/llm/service.py +556 -0
- hdsp_agent_core/managers/__init__.py +22 -0
- hdsp_agent_core/managers/config_manager.py +133 -0
- hdsp_agent_core/managers/session_manager.py +251 -0
- hdsp_agent_core/models/__init__.py +115 -0
- hdsp_agent_core/models/agent.py +316 -0
- hdsp_agent_core/models/chat.py +41 -0
- hdsp_agent_core/models/common.py +95 -0
- hdsp_agent_core/models/rag.py +368 -0
- hdsp_agent_core/prompts/__init__.py +63 -0
- hdsp_agent_core/prompts/auto_agent_prompts.py +1260 -0
- hdsp_agent_core/prompts/cell_action_prompts.py +98 -0
- hdsp_agent_core/services/__init__.py +18 -0
- hdsp_agent_core/services/agent_service.py +438 -0
- hdsp_agent_core/services/chat_service.py +205 -0
- hdsp_agent_core/services/rag_service.py +262 -0
- hdsp_agent_core/tests/__init__.py +1 -0
- hdsp_agent_core/tests/conftest.py +102 -0
- hdsp_agent_core/tests/test_factory.py +251 -0
- hdsp_agent_core/tests/test_services.py +326 -0
- hdsp_jupyter_extension-2.0.0.data/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/build_log.json +738 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/install.json +5 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/package.json +134 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
- 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
- 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
- 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
- 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
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/style.js +4 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- hdsp_jupyter_extension-2.0.0.dist-info/METADATA +152 -0
- hdsp_jupyter_extension-2.0.0.dist-info/RECORD +121 -0
- hdsp_jupyter_extension-2.0.0.dist-info/WHEEL +4 -0
- hdsp_jupyter_extension-2.0.0.dist-info/licenses/LICENSE +21 -0
- jupyter_ext/__init__.py +233 -0
- jupyter_ext/_version.py +4 -0
- jupyter_ext/config.py +111 -0
- jupyter_ext/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
- jupyter_ext/handlers.py +632 -0
- jupyter_ext/labextension/build_log.json +738 -0
- jupyter_ext/labextension/package.json +134 -0
- jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
- jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
- jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
- jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
- jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
- jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
- jupyter_ext/labextension/static/style.js +4 -0
- jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
- jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
- 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))
|