Skip to content

Commit 48761c9

Browse files
authored
fix: reference correct PyPI package names in provider load error messages (#164)
1 parent fb53e99 commit 48761c9

2 files changed

Lines changed: 93 additions & 5 deletions

File tree

packages/sdk/server-ai/src/ldai/providers/runner_factory.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
# Multi-provider packages should be last in the list.
1414
SUPPORTED_AI_PROVIDERS = ('openai', 'langchain')
1515

16+
# Mapping from internal Python module name to the pip-installable PyPI package name.
17+
_PYPI_PACKAGE_NAMES = {
18+
'ldai_openai': 'launchdarkly-server-sdk-ai-openai',
19+
'ldai_langchain': 'launchdarkly-server-sdk-ai-langchain',
20+
}
21+
1622

1723
class RunnerFactory:
1824
"""
@@ -51,10 +57,7 @@ def _get_provider_factory(provider_type: str) -> Optional[AIProvider]:
5157
)
5258
return None
5359
except ImportError as error:
54-
log.warning(
55-
f"Could not load provider '{provider_type}': {error}. "
56-
f"Make sure the corresponding package is installed."
57-
)
60+
log.warning(f"Could not load provider '{provider_type}': {error}")
5861
return None
5962

6063
@staticmethod
@@ -188,4 +191,5 @@ def _pkg_exists(package_name: str) -> None:
188191
:param package_name: Name of the package to check
189192
"""
190193
if util.find_spec(package_name) is None:
191-
raise ImportError(f"Package '{package_name}' not found")
194+
pypi_name = _PYPI_PACKAGE_NAMES.get(package_name, package_name)
195+
raise ImportError(f"Package '{pypi_name}' not found. Make sure it is installed.")
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Tests for RunnerFactory provider loading and error messages."""
2+
3+
from unittest.mock import patch
4+
5+
import pytest
6+
7+
from ldai.providers.runner_factory import RunnerFactory, _PYPI_PACKAGE_NAMES
8+
9+
10+
class TestPkgExists:
11+
"""_pkg_exists raises ImportError with the PyPI package name when a module is missing."""
12+
13+
def test_raises_import_error_with_pypi_name_for_openai(self):
14+
with patch('ldai.providers.runner_factory.util') as mock_util:
15+
mock_util.find_spec.return_value = None
16+
with pytest.raises(ImportError) as exc_info:
17+
RunnerFactory._pkg_exists('ldai_openai')
18+
assert 'launchdarkly-server-sdk-ai-openai' in str(exc_info.value)
19+
assert 'pip install' not in str(exc_info.value)
20+
21+
def test_raises_import_error_with_pypi_name_for_langchain(self):
22+
with patch('ldai.providers.runner_factory.util') as mock_util:
23+
mock_util.find_spec.return_value = None
24+
with pytest.raises(ImportError) as exc_info:
25+
RunnerFactory._pkg_exists('ldai_langchain')
26+
assert 'launchdarkly-server-sdk-ai-langchain' in str(exc_info.value)
27+
assert 'pip install' not in str(exc_info.value)
28+
29+
def test_raises_import_error_with_module_name_when_no_mapping(self):
30+
"""Unknown module names fall back to the module name itself."""
31+
with patch('ldai.providers.runner_factory.util') as mock_util:
32+
mock_util.find_spec.return_value = None
33+
with pytest.raises(ImportError) as exc_info:
34+
RunnerFactory._pkg_exists('some_unknown_module')
35+
assert 'some_unknown_module' in str(exc_info.value)
36+
37+
def test_does_not_raise_when_package_is_found(self):
38+
with patch('ldai.providers.runner_factory.util') as mock_util:
39+
mock_util.find_spec.return_value = object() # non-None means found
40+
# Should not raise
41+
RunnerFactory._pkg_exists('ldai_openai')
42+
43+
44+
class TestPypiPackageNameMapping:
45+
"""The _PYPI_PACKAGE_NAMES mapping covers all supported providers."""
46+
47+
def test_openai_module_maps_to_pypi_name(self):
48+
assert _PYPI_PACKAGE_NAMES['ldai_openai'] == 'launchdarkly-server-sdk-ai-openai'
49+
50+
def test_langchain_module_maps_to_pypi_name(self):
51+
assert _PYPI_PACKAGE_NAMES['ldai_langchain'] == 'launchdarkly-server-sdk-ai-langchain'
52+
53+
54+
class TestGetProviderFactory:
55+
"""_get_provider_factory logs the PyPI package name in its warning when a package is missing."""
56+
57+
def test_warning_includes_pypi_name_for_openai(self):
58+
with patch('ldai.providers.runner_factory.util') as mock_util, \
59+
patch('ldai.providers.runner_factory.log') as mock_log:
60+
mock_util.find_spec.return_value = None
61+
result = RunnerFactory._get_provider_factory('openai')
62+
assert result is None
63+
warning_text = mock_log.warning.call_args[0][0]
64+
assert 'launchdarkly-server-sdk-ai-openai' in warning_text
65+
assert 'ldai_openai' not in warning_text
66+
67+
def test_warning_includes_pypi_name_for_langchain(self):
68+
with patch('ldai.providers.runner_factory.util') as mock_util, \
69+
patch('ldai.providers.runner_factory.log') as mock_log:
70+
mock_util.find_spec.return_value = None
71+
result = RunnerFactory._get_provider_factory('langchain')
72+
assert result is None
73+
warning_text = mock_log.warning.call_args[0][0]
74+
assert 'launchdarkly-server-sdk-ai-langchain' in warning_text
75+
assert 'ldai_langchain' not in warning_text
76+
77+
def test_warning_does_not_reference_pip(self):
78+
"""Warning should be package-manager agnostic — no pip install command."""
79+
with patch('ldai.providers.runner_factory.util') as mock_util, \
80+
patch('ldai.providers.runner_factory.log') as mock_log:
81+
mock_util.find_spec.return_value = None
82+
RunnerFactory._get_provider_factory('openai')
83+
warning_text = mock_log.warning.call_args[0][0]
84+
assert 'pip install' not in warning_text

0 commit comments

Comments
 (0)