Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

728 lines
28 KiB
Python

"""
Tests for embeddings/providers/implementations/openai.py
Tests cover:
- OpenAIEmbeddingsProvider.create_embeddings()
- OpenAIEmbeddingsProvider.is_available()
- OpenAIEmbeddingsProvider.get_available_models()
- Class attributes and metadata
"""
import pytest
from unittest.mock import patch, MagicMock
class TestOpenAIEmbeddingsProviderMetadata:
"""Tests for OpenAIEmbeddingsProvider class metadata."""
def test_provider_name(self):
"""Test provider name is set correctly."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
assert OpenAIEmbeddingsProvider.provider_name == "OpenAI"
def test_provider_key(self):
"""Test provider key is set correctly."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
assert OpenAIEmbeddingsProvider.provider_key == "OPENAI"
def test_requires_api_key(self):
"""Provider does not strictly require an API key.
Cloud OpenAI does need one, but OpenAI-compatible local servers
(LM Studio, vLLM, llama.cpp) don't — the runtime path in
``is_available`` / ``create_embeddings`` enforces the rule when
a base_url is *not* set. The class-level flag therefore stays
``False`` (inherited from BaseEmbeddingProvider) so any future
UI consumer doesn't show a misleading "API key required" badge
for keyless local-server users.
"""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
assert OpenAIEmbeddingsProvider.requires_api_key is False
def test_supports_local(self):
"""Test that OpenAI does not support local."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
assert OpenAIEmbeddingsProvider.supports_local is False
def test_default_model(self):
"""Test default model is set."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
assert (
OpenAIEmbeddingsProvider.default_model == "text-embedding-3-small"
)
class TestOpenAIEmbeddingsProviderCreateEmbeddings:
"""Tests for OpenAIEmbeddingsProvider.create_embeddings method."""
def test_create_embeddings_with_api_key(self):
"""Test creating embeddings with API key provided."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
# Mock get_setting_from_snapshot to return None for other settings
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
) as mock_class:
result = OpenAIEmbeddingsProvider.create_embeddings(
model="text-embedding-3-small",
api_key="test-api-key",
)
assert result is mock_embeddings
mock_class.assert_called_once()
call_kwargs = mock_class.call_args[1]
assert call_kwargs["model"] == "text-embedding-3-small"
assert call_kwargs["openai_api_key"] == "test-api-key"
# No base_url → LangChain default ctx-check is preserved.
assert "check_embedding_ctx_length" not in call_kwargs
def test_create_embeddings_missing_api_key_raises(self):
"""Test that missing API key raises ValueError."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with pytest.raises(ValueError, match="API key not configured"):
OpenAIEmbeddingsProvider.create_embeddings()
def test_create_embeddings_with_settings_snapshot(self):
"""Test creating embeddings with settings snapshot."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
settings = {"embeddings.openai.api_key": "snapshot-key"}
def mock_get_setting(key, default=None, settings_snapshot=None):
if key == "embeddings.openai.api_key":
return "snapshot-key"
return default
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=mock_get_setting,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
):
result = OpenAIEmbeddingsProvider.create_embeddings(
settings_snapshot=settings
)
assert result is mock_embeddings
def test_create_embeddings_with_base_url(self):
"""Test creating embeddings with custom base URL."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
) as mock_class:
OpenAIEmbeddingsProvider.create_embeddings(
api_key="test-key",
base_url="https://custom.openai.com",
)
call_kwargs = mock_class.call_args[1]
assert (
call_kwargs["openai_api_base"]
== "https://custom.openai.com"
)
assert call_kwargs["check_embedding_ctx_length"] is False
def test_create_embeddings_with_official_openai_base_url_keeps_ctx_check(
self,
):
"""Explicit base_url pointing at api.openai.com must NOT disable the
client-side context-length check; that guard is only meant to be
skipped for non-OpenAI hosts (LM Studio, vLLM, llama.cpp, etc.)."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
) as mock_class:
OpenAIEmbeddingsProvider.create_embeddings(
api_key="test-key",
base_url="https://api.openai.com/v1",
)
call_kwargs = mock_class.call_args[1]
assert (
call_kwargs["openai_api_base"]
== "https://api.openai.com/v1"
)
assert "check_embedding_ctx_length" not in call_kwargs
def test_create_embeddings_with_schemeless_openai_base_url_keeps_ctx_check(
self,
):
"""A scheme-less base_url like ``api.openai.com`` must still be
recognized as the real OpenAI endpoint. ``urlparse`` on a bare
host returns ``hostname=None``, so without URL normalization the
ctx-length guard would silently be dropped for the cloud API."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
) as mock_class:
OpenAIEmbeddingsProvider.create_embeddings(
api_key="test-key",
base_url="api.openai.com",
)
call_kwargs = mock_class.call_args[1]
# normalize_url prepends https:// for external hosts.
assert (
call_kwargs["openai_api_base"] == "https://api.openai.com"
)
assert "check_embedding_ctx_length" not in call_kwargs
def test_create_embeddings_with_dimensions(self):
"""Test creating embeddings with custom dimensions for v3 model."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
) as mock_class:
OpenAIEmbeddingsProvider.create_embeddings(
model="text-embedding-3-small",
api_key="test-key",
dimensions=256,
)
call_kwargs = mock_class.call_args[1]
assert call_kwargs["dimensions"] == 256
def test_create_embeddings_dimensions_ignored_for_non_v3_model(self):
"""Test that dimensions are ignored for non-v3 models."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_embeddings = MagicMock()
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=mock_embeddings,
) as mock_class:
OpenAIEmbeddingsProvider.create_embeddings(
model="text-embedding-ada-002",
api_key="test-key",
dimensions=256,
)
call_kwargs = mock_class.call_args[1]
assert "dimensions" not in call_kwargs
class TestOpenAIEmbeddingsProviderIsAvailable:
"""Tests for OpenAIEmbeddingsProvider.is_available method."""
def test_is_available_with_api_key(self):
"""Test that provider is available when API key is set."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value="test-api-key",
):
assert OpenAIEmbeddingsProvider.is_available() is True
def test_is_available_without_api_key(self):
"""Test that provider is not available without API key."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
assert OpenAIEmbeddingsProvider.is_available() is False
def test_is_available_with_empty_api_key(self):
"""Test that provider is not available with empty API key."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value="",
):
assert OpenAIEmbeddingsProvider.is_available() is False
def test_is_available_exception_returns_false(self):
"""Test that exception during availability check returns False."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=Exception("Settings error"),
):
assert OpenAIEmbeddingsProvider.is_available() is False
class TestOpenAIEmbeddingsProviderGetAvailableModels:
"""Tests for OpenAIEmbeddingsProvider.get_available_models method."""
def test_get_available_models_success(self):
"""Test getting available models from OpenAI API.
The provider returns every model the endpoint reports — there's
no reliable signal in /v1/models for "is this an embedding
model?", so guessing from the name is left to the user.
"""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
mock_model1 = MagicMock()
mock_model1.id = "text-embedding-3-small"
mock_model2 = MagicMock()
mock_model2.id = "text-embedding-3-large"
mock_model3 = MagicMock()
mock_model3.id = "gpt-4"
mock_response = MagicMock()
mock_response.data = [mock_model1, mock_model2, mock_model3]
mock_client = MagicMock()
mock_client.models.list.return_value = mock_response
def _settings(key, default=None, settings_snapshot=None):
if key == "embeddings.openai.api_key":
return "test-api-key"
return default
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=_settings,
):
with patch(
"openai.OpenAI",
return_value=mock_client,
):
models = OpenAIEmbeddingsProvider.get_available_models()
assert [m["value"] for m in models] == [
"text-embedding-3-small",
"text-embedding-3-large",
"gpt-4",
]
# No name-based tagging — the endpoint can't be trusted
# to identify embedding models for us.
assert all("is_embedding" not in m for m in models)
def test_get_available_models_no_api_key(self):
"""Test getting models returns empty list when no API key."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value=None,
):
models = OpenAIEmbeddingsProvider.get_available_models()
assert models == []
def test_get_available_models_api_error(self):
"""Test getting models returns empty list on API error."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
return_value="test-api-key",
):
with patch(
"openai.OpenAI",
side_effect=Exception("API error"),
):
models = OpenAIEmbeddingsProvider.get_available_models()
assert models == []
class TestOpenAIEmbeddingsProviderCompatibleEndpoint:
"""Regression tests for issue #3883.
OpenAI-compatible local servers (LM Studio, vLLM, llama.cpp) speak
the same wire protocol as the OpenAI API but typically run on
``http://localhost:<port>/v1`` and do not require an API key. The
provider must:
1. report ``is_available() == True`` when the user has set a
``base_url`` even with no API key (so the UI lists it);
2. accept a missing API key at ``create_embeddings`` time as long
as a base_url is set, falling back to a placeholder so the
OpenAI client request still goes out;
3. still raise the configuration error when *neither* an API key
nor a base_url is set (so a blank install doesn't silently
hit an unconfigured endpoint).
"""
@staticmethod
def _settings_mock(api_key, base_url):
"""Return a side_effect callable for get_setting_from_snapshot.
The provider reads four ``embeddings.openai.*`` keys; this
helper routes each to a per-test value and returns ``default``
for anything else.
"""
values = {
"embeddings.openai.api_key": api_key,
"embeddings.openai.base_url": base_url,
}
def _side_effect(key, default=None, settings_snapshot=None):
if key in values:
return values[key]
return default
return _side_effect
def test_is_available_with_base_url_only(self):
"""Provider must be advertised when only base_url is set.
Red on master, green on branch — this is the headline
regression for issue #3883.
"""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(
api_key=None, base_url="http://localhost:1234/v1"
),
):
assert OpenAIEmbeddingsProvider.is_available() is True
def test_is_available_with_blank_base_url_and_no_key(self):
"""Provider stays hidden on a blank install."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(api_key="", base_url=""),
):
assert OpenAIEmbeddingsProvider.is_available() is False
def test_is_available_whitespace_only_settings_still_unavailable(self):
"""Whitespace-only values should not count as configured."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(api_key=" ", base_url="\t \n"),
):
assert OpenAIEmbeddingsProvider.is_available() is False
def test_create_embeddings_base_url_only_uses_placeholder_key(self):
"""No API key + base_url set → use placeholder, pass base_url through."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(
api_key=None,
base_url="http://host.docker.internal:1234/v1",
),
):
with patch(
"langchain_openai.OpenAIEmbeddings",
return_value=MagicMock(),
) as mock_class:
OpenAIEmbeddingsProvider.create_embeddings(
model="nomic-embed-text-v1.5",
)
call_kwargs = mock_class.call_args[1]
assert (
call_kwargs["openai_api_key"]
== OpenAIEmbeddingsProvider._PLACEHOLDER_API_KEY
)
assert (
call_kwargs["openai_api_base"]
== "http://host.docker.internal:1234/v1"
)
assert call_kwargs["check_embedding_ctx_length"] is False
def test_create_embeddings_error_message_mentions_base_url(self):
"""When both api_key and base_url are missing, the error must
tell the user about the local-server fallback path so they
don't go hunting for a key they don't have."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(api_key=None, base_url=None),
):
with pytest.raises(ValueError, match="base_url"):
OpenAIEmbeddingsProvider.create_embeddings()
def test_get_available_models_uses_base_url_when_no_key(self):
"""Model discovery on a keyless local server must route through
the configured base_url instead of api.openai.com."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
fake_client = MagicMock()
fake_client.models.list.return_value = MagicMock(data=[])
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(
api_key=None, base_url="http://localhost:1234/v1"
),
):
with patch(
"openai.OpenAI", return_value=fake_client
) as mock_openai:
OpenAIEmbeddingsProvider.get_available_models()
kwargs = mock_openai.call_args[1]
assert kwargs["base_url"] == "http://localhost:1234/v1"
assert (
kwargs["api_key"]
== OpenAIEmbeddingsProvider._PLACEHOLDER_API_KEY
)
class TestOpenAIEmbeddingsProviderLMStudioModelDiscovery:
"""Regression tests for issue #4195.
LM Studio (and other OpenAI-compatible local servers) returns every
loaded model from ``/v1/models``, both embedding models and LLMs,
without a reliable ``type`` field to distinguish them. The previous
``"embedding" in id`` filter dropped real embedding models whose
names use the shorter ``embed`` token (``nomic-embed-text-v1.5``)
and so the embedding-model dropdown ended up empty.
The fix: stop guessing from the name. Show every model the endpoint
returns and let the user pick the one they actually loaded.
"""
@staticmethod
def _settings_mock(api_key, base_url):
values = {
"embeddings.openai.api_key": api_key,
"embeddings.openai.base_url": base_url,
}
def _side_effect(key, default=None, settings_snapshot=None):
if key in values:
return values[key]
return default
return _side_effect
def _fake_models_response(self, ids):
models = []
for model_id in ids:
m = MagicMock()
m.id = model_id
models.append(m)
response = MagicMock()
response.data = models
return response
def test_lmstudio_nomic_embed_text_is_listed(self):
"""The headline regression for #4195: a Nomic embedding model
loaded in LM Studio must appear in the dropdown — its name
doesn't contain the literal ``embedding`` token but it is the
embedding model the user wants to use."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
fake_client = MagicMock()
fake_client.models.list.return_value = self._fake_models_response(
["nomic-embed-text-v1.5"]
)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(
api_key=None, base_url="http://localhost:1234/v1"
),
):
with patch("openai.OpenAI", return_value=fake_client):
models = OpenAIEmbeddingsProvider.get_available_models()
assert [m["value"] for m in models] == ["nomic-embed-text-v1.5"]
def test_lmstudio_returns_every_loaded_model_unfiltered(self):
"""Every loaded model — embedding or LLM, well-known name or
custom fine-tune — is returned. We don't pre-filter the list."""
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
ids = [
"llama-3.1-8b-instruct",
"nomic-embed-text-v1.5",
"qwen3-embedding-8b",
"bge-large-en-v1.5",
"some-custom-finetune",
]
fake_client = MagicMock()
fake_client.models.list.return_value = self._fake_models_response(ids)
with patch(
"local_deep_research.embeddings.providers.implementations.openai.get_setting_from_snapshot",
side_effect=self._settings_mock(
api_key=None, base_url="http://localhost:1234/v1"
),
):
with patch("openai.OpenAI", return_value=fake_client):
models = OpenAIEmbeddingsProvider.get_available_models()
# Order preserved, nothing dropped, no name-based flags added.
assert [m["value"] for m in models] == ids
assert all("is_embedding" not in m for m in models)
class TestOpenAIEmbeddingsSettingsRegistration:
"""The settings file shipped for issue #3883 must register the
keys the UI needs to surface the embeddings form, and the OpenAI
provider must be wired up by ``_get_provider_classes`` regardless
of API-key presence so it can be selected for keyless local
servers."""
def test_openai_embeddings_settings_file_registers_all_keys(self):
"""``settings_openai_embeddings.json`` must declare the four
``embeddings.openai.*`` keys that the provider reads at
runtime (api_key, base_url, model, dimensions)."""
import json
from pathlib import Path
import local_deep_research.defaults as defaults_pkg
defaults_dir = Path(defaults_pkg.__file__).parent
path = defaults_dir / "settings_openai_embeddings.json"
assert path.exists(), (
"Issue #3883: ship settings_openai_embeddings.json so the "
"UI can surface the embeddings configuration form."
)
with open(path) as f:
data = json.load(f)
required = {
"embeddings.openai.api_key",
"embeddings.openai.base_url",
"embeddings.openai.model",
"embeddings.openai.dimensions",
}
missing = required - set(data.keys())
assert not missing, f"settings file missing keys: {missing}"
base_url_meta = data["embeddings.openai.base_url"]
assert base_url_meta["category"] == "embeddings"
assert base_url_meta["editable"] is True
# The description is what tells users *why* this matters for
# local servers — guard against silent removal.
desc_lower = base_url_meta["description"].lower()
assert any(
tag in desc_lower for tag in ("lm studio", "vllm", "llama.cpp")
), "base_url description should mention an OpenAI-compatible server"
def test_openai_in_provider_classes_dict(self):
"""The 'openai' key must remain wired to the provider class so
the embeddings dispatch path can resolve it."""
from local_deep_research.embeddings.embeddings_config import (
_get_provider_classes,
)
from local_deep_research.embeddings.providers.implementations.openai import (
OpenAIEmbeddingsProvider,
)
classes = _get_provider_classes()
assert classes.get("openai") is OpenAIEmbeddingsProvider