From 99b7df64ea2604564f31eefca0003540ad9065f2 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Mon, 13 Jul 2026 03:29:19 +0800 Subject: [PATCH] Ensure live provider checks reach the provider Accept Docker Compose's exact string representation of the fixed 1024-dimensional embedding contract while rejecting every widened or ambiguous value. Entry-level smoke tests now exercise the same environment shape used by the container. Constraint: The current local Bailian credential still returns 401 for all three capabilities and is not stored in Git. Rejected: Change the field to an unconstrained integer | would allow a runtime dimension that disagrees with vector(1024). Confidence: high Scope-risk: narrow Directive: Keep the database vector dimension and provider dimension locked together through an ADR-backed migration. Tested: make verify; 75 backend tests; 14 frontend tests; rebuilt provider-smoke image; normal Compose smoke reached all three provider endpoints. Not-tested: Successful live provider response remains blocked by external authentication. --- backend/app/core/config.py | 10 ++++ backend/tests/unit/test_config_and_secrets.py | 26 ++++++++++ backend/tests/unit/test_provider_smoke.py | 51 ++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/backend/app/core/config.py b/backend/app/core/config.py index c9006f6..ae8b8f9 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -71,6 +71,16 @@ class Settings(BaseSettings): def normalize_base_url(cls, value: str) -> str: return value.rstrip("/") + @field_validator("embedding_dimension", mode="before") + @classmethod + def parse_embedding_dimension(cls, value: object) -> int: + """Accept Compose's string representation without widening the contract.""" + if value == "1024" or ( + isinstance(value, int) and not isinstance(value, bool) and value == 1024 + ): + return 1024 + raise ValueError("EMBEDDING_DIMENSION must be exactly 1024") + @model_validator(mode="after") def validate_rag_limits(self) -> Self: if self.chunk_overlap_tokens >= self.chunk_target_tokens: diff --git a/backend/tests/unit/test_config_and_secrets.py b/backend/tests/unit/test_config_and_secrets.py index 156a25b..74ff233 100644 --- a/backend/tests/unit/test_config_and_secrets.py +++ b/backend/tests/unit/test_config_and_secrets.py @@ -46,6 +46,32 @@ def test_base_urls_drop_trailing_slashes() -> None: assert settings.bailian_rerank_base_url.endswith("/v1") +def test_embedding_dimension_accepts_compose_string(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("EMBEDDING_DIMENSION", "1024") + + settings = Settings(_env_file=None) + + assert settings.embedding_dimension == 1024 + assert isinstance(settings.embedding_dimension, int) + + +@pytest.mark.parametrize("configured", ["1536", "1024.0", "01024", " 1024 "]) +def test_embedding_dimension_rejects_any_other_environment_value( + monkeypatch: pytest.MonkeyPatch, + configured: str, +) -> None: + monkeypatch.setenv("EMBEDDING_DIMENSION", configured) + + with pytest.raises(ValueError, match="EMBEDDING_DIMENSION must be exactly 1024"): + Settings(_env_file=None) + + +@pytest.mark.parametrize("configured", [1024.0, True, None]) +def test_embedding_dimension_rejects_non_integer_programmatic_values(configured: object) -> None: + with pytest.raises(ValueError, match="EMBEDDING_DIMENSION must be exactly 1024"): + Settings.model_validate({"embedding_dimension": configured}) + + def test_live_endpoints_must_share_one_beijing_workspace(tmp_path: Path) -> None: key_path = tmp_path / "key" key_path.write_text("test-key-value", encoding="utf-8") diff --git a/backend/tests/unit/test_provider_smoke.py b/backend/tests/unit/test_provider_smoke.py index 4cdcda9..ee78ec9 100644 --- a/backend/tests/unit/test_provider_smoke.py +++ b/backend/tests/unit/test_provider_smoke.py @@ -1,5 +1,54 @@ +import pytest + +from app.core.config import Settings from app.ports.model_providers import ModelProviderError, ProviderErrorKind -from app.tools.provider_smoke import failed_probe +from app.tools import provider_smoke +from app.tools.provider_smoke import ProbeResult, failed_probe + + +def test_provider_smoke_settings_accept_compose_embedding_dimension( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("EMBEDDING_DIMENSION", "1024") + + settings = Settings(_env_file=None) + + assert settings.embedding_dimension == 1024 + + +async def test_provider_smoke_entrypoint_accepts_compose_embedding_dimension( + monkeypatch: pytest.MonkeyPatch, +) -> None: + workspace_host = "workspace-test.cn-beijing.maas.aliyuncs.com" + monkeypatch.setenv("EMBEDDING_DIMENSION", "1024") + monkeypatch.setenv( + "BAILIAN_OPENAI_BASE_URL", + f"https://{workspace_host}/compatible-mode/v1", + ) + monkeypatch.setenv( + "BAILIAN_RERANK_BASE_URL", + f"https://{workspace_host}/compatible-api/v1", + ) + observed_dimensions: list[int] = [] + + def fake_api_key(settings: Settings) -> str: + observed_dimensions.append(settings.embedding_dimension) + return "test-only-api-key" + + async def successful_probe(settings: Settings, api_key: str) -> ProbeResult: + observed_dimensions.append(settings.embedding_dimension) + assert api_key == "test-only-api-key" + return ProbeResult(capability="test", status="ok") + + monkeypatch.setattr(Settings, "bailian_api_key", fake_api_key) + monkeypatch.setattr(provider_smoke, "probe_embedding", successful_probe) + monkeypatch.setattr(provider_smoke, "probe_rerank", successful_probe) + monkeypatch.setattr(provider_smoke, "probe_chat", successful_probe) + + exit_code = await provider_smoke.async_main() + + assert exit_code == 0 + assert observed_dimensions == [1024, 1024, 1024, 1024] def test_failed_probe_only_exposes_sanitized_provider_fields() -> None: