From 503099c729745fe9753ecb4040e421922df3260d Mon Sep 17 00:00:00 2001 From: BillionClaw <267901332+BillionClaw@users.noreply.github.com> Date: Wed, 25 Mar 2026 06:19:46 +0800 Subject: [PATCH] fix: auto-append /v1 suffix to embedding_api_base in OpenAI embedding provider --- .../sources/openai_embedding_source.py | 2 + tests/unit/test_openai_embedding_source.py | 72 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/unit/test_openai_embedding_source.py diff --git a/astrbot/core/provider/sources/openai_embedding_source.py b/astrbot/core/provider/sources/openai_embedding_source.py index 2b62d865c2..7b2845bd22 100644 --- a/astrbot/core/provider/sources/openai_embedding_source.py +++ b/astrbot/core/provider/sources/openai_embedding_source.py @@ -27,6 +27,8 @@ def __init__(self, provider_config: dict, provider_settings: dict) -> None: api_base = provider_config.get( "embedding_api_base", "https://api.openai.com/v1" ).strip() + if api_base and not api_base.endswith("/v1") and not api_base.endswith("/v1/"): + api_base = api_base.rstrip("/") + "/v1" logger.info(f"[OpenAI Embedding] {provider_id} Using API Base: {api_base}") self.client = AsyncOpenAI( api_key=provider_config.get("embedding_api_key"), diff --git a/tests/unit/test_openai_embedding_source.py b/tests/unit/test_openai_embedding_source.py new file mode 100644 index 0000000000..dbe97b9d32 --- /dev/null +++ b/tests/unit/test_openai_embedding_source.py @@ -0,0 +1,72 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from astrbot.core.provider.sources.openai_embedding_source import ( + OpenAIEmbeddingProvider, +) + + +def _make_provider(overrides: dict | None = None) -> OpenAIEmbeddingProvider: + provider_config = { + "id": "test-openai-embedding", + "embedding_api_key": "test-key", + "embedding_model": "text-embedding-3-small", + } + if overrides: + provider_config.update(overrides) + return OpenAIEmbeddingProvider( + provider_config=provider_config, + provider_settings={}, + ) + + +class TestOpenAIEmbeddingProviderApiBaseV1Suffix: + """Test that /v1 suffix is auto-appended to embedding_api_base. + + Regression test for: https://github.com/AstrBotDevs/AstrBot/issues/6887 + PR #6669 removed automatic /v1 suffix because some providers don't use + standard /v1/embeddings endpoint, but this broke OpenAI-compatible + providers. PR #6863 reintroduces the auto-append logic. + """ + + def test_api_base_without_v1_gets_v1_appended(self) -> None: + """api_base like 'https://api.openai.com' should become 'https://api.openai.com/v1'.""" + provider = _make_provider({"embedding_api_base": "https://api.openai.com"}) + # The provider should auto-append /v1 + assert provider.client.base_url == "https://api.openai.com/v1" + + def test_api_base_with_trailing_slash_gets_v1_appended(self) -> None: + """api_base like 'https://api.openai.com/' should become 'https://api.openai.com/v1'.""" + provider = _make_provider({"embedding_api_base": "https://api.openai.com/"}) + assert provider.client.base_url == "https://api.openai.com/v1" + + def test_api_base_already_with_v1_is_unchanged(self) -> None: + """api_base already ending with /v1 should not double-append.""" + provider = _make_provider({"embedding_api_base": "https://api.openai.com/v1"}) + assert provider.client.base_url == "https://api.openai.com/v1" + + def test_api_base_with_v1_trailing_slash_is_unchanged(self) -> None: + """api_base already ending with /v1/ should not double-append.""" + provider = _make_provider({"embedding_api_base": "https://api.openai.com/v1/"}) + assert provider.client.base_url == "https://api.openai.com/v1/" + + def test_api_base_custom_endpoint_without_v1_gets_v1_appended(self) -> None: + """Custom API base like 'https://openai.example.com' should become 'https://openai.example.com/v1'.""" + provider = _make_provider({"embedding_api_base": "https://openai.example.com"}) + assert provider.client.base_url == "https://openai.example.com/v1" + + def test_api_base_custom_endpoint_already_with_v1_is_unchanged(self) -> None: + """Custom API base already with /v1 should not change.""" + provider = _make_provider({"embedding_api_base": "https://openai.example.com/v1"}) + assert provider.client.base_url == "https://openai.example.com/v1" + + def test_empty_api_base_uses_default(self) -> None: + """Empty api_base should use the default OpenAI endpoint.""" + provider = _make_provider({"embedding_api_base": ""}) + assert provider.client.base_url == "https://api.openai.com/v1" + + def test_default_api_base_is_unchanged(self) -> None: + """Default api_base (not set) should be the standard OpenAI endpoint.""" + provider = _make_provider() + assert provider.client.base_url == "https://api.openai.com/v1"