Skip to content

Commit 0d2ff25

Browse files
authored
To fix chatbot streaming request response with TLS communication enabled (#1732)
* fix streaming with TLS * add UTC for added changes * fix utc failure
1 parent 81847dc commit 0d2ff25

File tree

2 files changed

+102
-4
lines changed

2 files changed

+102
-4
lines changed

ansible_ai_connect/ai/api/model_pipelines/http/pipelines.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,11 @@ def send_schema1_event(self, ev):
262262

263263
async def async_invoke(self, params: StreamingChatBotParameters) -> AsyncGenerator:
264264

265-
async with aiohttp.ClientSession(raise_for_status=True) as session:
265+
# Configure SSL context based on verify_ssl setting
266+
ssl_context = self.config.verify_ssl
267+
connector = aiohttp.TCPConnector(ssl=ssl_context)
268+
269+
async with aiohttp.ClientSession(raise_for_status=True, connector=connector) as session:
266270
headers = {
267271
"Content-Type": "application/json",
268272
"Accept": "application/json,text/event-stream",

ansible_ai_connect/ai/api/model_pipelines/http/tests/test_pipelines.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
# limitations under the License.
1515
import json
1616
import logging
17+
from typing import cast
1718
from unittest import IsolatedAsyncioTestCase
18-
from unittest.mock import patch
19+
from unittest.mock import MagicMock, patch
1920

21+
from ansible_ai_connect.ai.api.model_pipelines.http.configuration import (
22+
HttpConfiguration,
23+
)
2024
from ansible_ai_connect.ai.api.model_pipelines.http.pipelines import (
2125
HttpStreamingChatBotPipeline,
2226
)
@@ -105,7 +109,9 @@ class TestHttpStreamingChatBotPipeline(IsolatedAsyncioTestCase, WisdomLogAwareMi
105109
]
106110

107111
def setUp(self):
108-
self.pipeline = HttpStreamingChatBotPipeline(mock_pipeline_config("http"))
112+
self.pipeline = HttpStreamingChatBotPipeline(
113+
cast(HttpConfiguration, mock_pipeline_config("http"))
114+
)
109115
self.call_counter = 0
110116

111117
def assertInLog(self, s, logs, number_of_matches_expected=None):
@@ -139,7 +145,7 @@ def get_params(self) -> StreamingChatBotParameters:
139145
provider="",
140146
model_id="",
141147
conversation_id=None,
142-
system_prompt=None,
148+
system_prompt="You are a helpful assistant",
143149
media_type="application/json",
144150
)
145151

@@ -200,3 +206,91 @@ async def test_async_invoke_error_with_no_data(self, mock_post):
200206
async for _ in self.pipeline.async_invoke(self.get_params()):
201207
pass
202208
self.assertInLog("(not provided)", log)
209+
210+
@patch("aiohttp.ClientSession.post")
211+
@patch("aiohttp.TCPConnector")
212+
async def test_ssl_context_verify_ssl_false(self, mock_tcp_connector, mock_post):
213+
"""Test that SSL context is correctly configured when verify_ssl=False"""
214+
# Setup pipeline with verify_ssl=False
215+
config = cast(HttpConfiguration, mock_pipeline_config("http", verify_ssl=False))
216+
pipeline = HttpStreamingChatBotPipeline(config)
217+
# Mock the connector
218+
mock_connector_instance = MagicMock()
219+
mock_tcp_connector.return_value = mock_connector_instance
220+
# Mock the post method to return our test data
221+
mock_post.return_value = self.get_return_value(self.STREAM_DATA)
222+
# Execute the async_invoke method
223+
params = self.get_params()
224+
async for _ in pipeline.async_invoke(params):
225+
pass
226+
# Verify TCPConnector was created with ssl=False
227+
mock_tcp_connector.assert_called_once_with(ssl=False)
228+
229+
@patch("aiohttp.ClientSession.post")
230+
@patch("aiohttp.TCPConnector")
231+
async def test_ssl_context_verify_ssl_true(self, mock_tcp_connector, mock_post):
232+
"""Test that SSL context is correctly configured when verify_ssl=True"""
233+
# Setup pipeline with verify_ssl=True
234+
config = cast(HttpConfiguration, mock_pipeline_config("http", verify_ssl=True))
235+
pipeline = HttpStreamingChatBotPipeline(config)
236+
# Mock the connector
237+
mock_connector_instance = MagicMock()
238+
mock_tcp_connector.return_value = mock_connector_instance
239+
# Mock the post method to return our test data
240+
mock_post.return_value = self.get_return_value(self.STREAM_DATA)
241+
# Execute the async_invoke method
242+
params = self.get_params()
243+
async for _ in pipeline.async_invoke(params):
244+
pass
245+
# Verify TCPConnector was created with ssl=True
246+
mock_tcp_connector.assert_called_once_with(ssl=True)
247+
248+
@patch("aiohttp.ClientSession.post")
249+
@patch("aiohttp.TCPConnector")
250+
async def test_ssl_context_uses_config_value(self, mock_tcp_connector, mock_post):
251+
"""Test that SSL context directly uses the config.verify_ssl value"""
252+
# Setup pipeline with a specific verify_ssl value
253+
config = cast(HttpConfiguration, mock_pipeline_config("http", verify_ssl=False))
254+
pipeline = HttpStreamingChatBotPipeline(config)
255+
# Mock the connector
256+
mock_connector_instance = MagicMock()
257+
mock_tcp_connector.return_value = mock_connector_instance
258+
# Mock the post method to return our test data
259+
mock_post.return_value = self.get_return_value(self.STREAM_DATA)
260+
# Execute the async_invoke method
261+
params = self.get_params()
262+
async for _ in pipeline.async_invoke(params):
263+
pass
264+
# Verify that ssl_context was set to the exact config value
265+
# The ssl parameter should match config.verify_ssl exactly
266+
expected_ssl_value = config.verify_ssl # False in this case
267+
mock_tcp_connector.assert_called_once_with(ssl=expected_ssl_value)
268+
269+
@patch("aiohttp.ClientSession.post")
270+
@patch("aiohttp.TCPConnector")
271+
async def test_ssl_context_integration_with_existing_flow(self, mock_tcp_connector, mock_post):
272+
"""Test that SSL changes don't break existing functionality"""
273+
# Test with both verify_ssl values to ensure no regression
274+
for verify_ssl_value in [True, False]:
275+
with self.subTest(verify_ssl=verify_ssl_value):
276+
config = cast(
277+
HttpConfiguration, mock_pipeline_config("http", verify_ssl=verify_ssl_value)
278+
)
279+
pipeline = HttpStreamingChatBotPipeline(config)
280+
# Mock the connector
281+
mock_connector_instance = MagicMock()
282+
mock_tcp_connector.return_value = mock_connector_instance
283+
# Mock the post method to return our test data
284+
mock_post.return_value = self.get_return_value(self.STREAM_DATA)
285+
# Execute the async_invoke method
286+
params = self.get_params()
287+
result_count = 0
288+
async for _ in pipeline.async_invoke(params):
289+
result_count += 1
290+
# Verify that streaming still works
291+
self.assertGreater(result_count, 0, "Streaming should return data")
292+
# Verify SSL configuration is correct
293+
mock_tcp_connector.assert_called_with(ssl=verify_ssl_value)
294+
# Reset mocks for next iteration
295+
mock_tcp_connector.reset_mock()
296+
mock_post.reset_mock()

0 commit comments

Comments
 (0)