Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion keep/providers/deepseek_provider/deepseek_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ def _query(
max_tokens=max_tokens,
response_format=structured_output_format,
)
response = response.choices[0].message.content
# An OpenAI-compatible backend can return HTTP 200 with an empty
# choices list (e.g. a content-filter block), so response.choices[0]
# would raise IndexError. Degrade to an empty string instead.
try:
response = response.choices[0].message.content
except (IndexError, AttributeError):
response = ""
try:
response = json.loads(response)
except Exception:
Expand Down
8 changes: 7 additions & 1 deletion keep/providers/openai_provider/openai_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ def _query(
max_tokens=max_tokens,
response_format=structured_output_format,
)
response = response.choices[0].message.content
# An OpenAI-compatible backend can return HTTP 200 with an empty
# choices list (e.g. a content-filter block), so response.choices[0]
# would raise IndexError. Degrade to an empty string instead.
try:
response = response.choices[0].message.content
except (IndexError, AttributeError):
response = ""
try:
response = json.loads(response)
except Exception:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Tests for how the DeepSeek provider parses a chat completion response.

DeepSeek is accessed through the OpenAI-compatible SDK, and an
OpenAI-compatible backend can return HTTP 200 with an empty ``choices`` list
(e.g. a content-filter block). The provider must not crash when it indexes
into ``choices[0]`` in that case.
"""

from unittest.mock import MagicMock, patch

from keep.contextmanager.contextmanager import ContextManager
from keep.providers.deepseek_provider.deepseek_provider import DeepseekProvider
from keep.providers.models.provider_config import ProviderConfig


def _build_provider() -> DeepseekProvider:
config = ProviderConfig(
description="DeepSeek Provider",
authentication={"api_key": "test-key"},
)
return DeepseekProvider(ContextManager(tenant_id="test"), "deepseek-test", config)


def _completion(choices):
response = MagicMock()
response.choices = choices
return response


def test_empty_choices_does_not_crash():
provider = _build_provider()
with patch(
"keep.providers.deepseek_provider.deepseek_provider.OpenAI"
) as mock_openai:
mock_openai.return_value.chat.completions.create.return_value = _completion(
[]
)
assert provider._query(prompt="hi")["response"] == ""


def test_normal_response_is_returned():
provider = _build_provider()
message = MagicMock()
message.content = "hello"
choice = MagicMock()
choice.message = message
with patch(
"keep.providers.deepseek_provider.deepseek_provider.OpenAI"
) as mock_openai:
mock_openai.return_value.chat.completions.create.return_value = _completion(
[choice]
)
assert provider._query(prompt="hi")["response"] == "hello"
52 changes: 52 additions & 0 deletions tests/providers/openai_provider/test_openai_response_parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Tests for how the OpenAI provider parses a chat completion response.

An OpenAI-compatible backend can return HTTP 200 with an empty ``choices``
list (e.g. a content-filter block). The SDK does not raise in that case, so
the provider must not crash when it indexes into ``choices[0]``.
"""

from unittest.mock import MagicMock, patch

from keep.contextmanager.contextmanager import ContextManager
from keep.providers.openai_provider.openai_provider import OpenaiProvider
from keep.providers.models.provider_config import ProviderConfig


def _build_provider() -> OpenaiProvider:
config = ProviderConfig(
description="OpenAI Provider",
authentication={"api_key": "test-key"},
)
return OpenaiProvider(ContextManager(tenant_id="test"), "openai-test", config)


def _completion(choices):
response = MagicMock()
response.choices = choices
return response


def test_empty_choices_does_not_crash():
provider = _build_provider()
with patch(
"keep.providers.openai_provider.openai_provider.OpenAI"
) as mock_openai:
mock_openai.return_value.chat.completions.create.return_value = _completion(
[]
)
assert provider._query(prompt="hi")["response"] == ""


def test_normal_response_is_returned():
provider = _build_provider()
message = MagicMock()
message.content = "hello"
choice = MagicMock()
choice.message = message
with patch(
"keep.providers.openai_provider.openai_provider.OpenAI"
) as mock_openai:
mock_openai.return_value.chat.completions.create.return_value = _completion(
[choice]
)
assert provider._query(prompt="hi")["response"] == "hello"
Loading