Skip to content

fix: append /v1 for OpenAI embedding api base#6910

Open
idiotsj wants to merge 3 commits intoAstrBotDevs:masterfrom
idiotsj:fix/issue-6855-embedding-v1
Open

fix: append /v1 for OpenAI embedding api base#6910
idiotsj wants to merge 3 commits intoAstrBotDevs:masterfrom
idiotsj:fix/issue-6855-embedding-v1

Conversation

@idiotsj
Copy link
Contributor

@idiotsj idiotsj commented Mar 24, 2026

Summary

  • normalize embedding_api_base for the OpenAI embedding provider
  • append /v1 automatically when the configured base URL omits it
  • add regression tests for both missing and existing /v1 suffixes

Problem

When embedding_api_base is configured without /v1, the OpenAI embedding client uses the raw base URL and requests fail against OpenAI-compatible endpoints that expect the /v1 prefix.

Closes #6855

Testing

  • uv run pytest tests/test_openai_source.py -q
  • uv run ruff format .
  • uv run ruff check .

Summary by Sourcery

Normalize OpenAI embedding API base URL handling and add regression coverage for various base URL configurations.

Bug Fixes:

  • Ensure OpenAI embedding provider automatically appends /v1 to root-style embedding_api_base values that omit the version segment.
  • Avoid introducing duplicate slashes and preserve versioned or path-specific embedding API base URLs, including query strings and fragments.
  • Fall back to the default OpenAI embedding base URL when embedding_api_base is blank in the configuration.

Tests:

  • Add unit tests covering normalization of embedding_api_base, including missing or existing /v1, trailing slashes, blank values, versioned paths, and URLs with query/fragment components.

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 24, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where the OpenAI embedding client would fail to make requests to OpenAI-compatible endpoints if the configured embedding_api_base URL did not explicitly include the /v1 API version suffix. The changes introduce a robust URL normalization step that automatically appends /v1 when necessary, ensuring consistent and successful API calls.

Highlights

  • API Base Normalization: Implemented a mechanism to normalize the embedding_api_base for the OpenAI embedding provider.
  • Automatic /v1 Appending: Ensured that /v1 is automatically appended to the embedding_api_base if it's missing, preventing request failures.
  • Comprehensive Testing: Added regression tests to verify the correct behavior for both cases: when /v1 is missing and when it's already present.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label Mar 24, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The _normalize_embedding_api_base helper assumes that any base URL not ending with /v1 should have /v1 appended, which will produce incorrect URLs for already-versioned or path-specific bases like .../v1-beta or .../v1/embeddings; consider narrowing the normalization condition (e.g., only when the path does not already start with /v or when the host matches OpenAI) or documenting that only root-style bases are supported.
  • When embedding_api_base is configured as whitespace only, strip() leaves an empty string that bypasses normalization and is passed through to AsyncOpenAI; consider treating an empty string the same as a missing value and falling back to the default base URL instead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `_normalize_embedding_api_base` helper assumes that any base URL not ending with `/v1` should have `/v1` appended, which will produce incorrect URLs for already-versioned or path-specific bases like `.../v1-beta` or `.../v1/embeddings`; consider narrowing the normalization condition (e.g., only when the path does not already start with `/v` or when the host matches OpenAI) or documenting that only root-style bases are supported.
- When `embedding_api_base` is configured as whitespace only, `strip()` leaves an empty string that bypasses normalization and is passed through to `AsyncOpenAI`; consider treating an empty string the same as a missing value and falling back to the default base URL instead.

## Individual Comments

### Comment 1
<location path="tests/test_openai_source.py" line_range="69-71" />
<code_context>
+    )
+
+
 @pytest.mark.asyncio
 async def test_handle_api_error_content_moderated_removes_images():
     provider = _make_provider(
</code_context>
<issue_to_address>
**suggestion (testing):** Cover edge case where `embedding_api_base` ends with a trailing slash but no `/v1`

Given the current normalization (`rstrip('/')` then conditionally appending `/v1`), please add a test for `embedding_api_base="https://example.com/openai/"` to assert it becomes `https://example.com/openai/v1/` and not `https://example.com/openai//v1/`, so the trailing-slash handling is locked in.

Suggested implementation:

```python
    finally:
        await provider.terminate()


def test_embedding_api_base_trailing_slash_normalized():
    provider = _make_provider(
        overrides={"embedding_api_base": "https://example.com/openai/"}
    )

    # The provider should normalize the embedding API base by removing any
    # trailing slash and then appending `/v1`, resulting in a single slash.
    # This asserts we do *not* end up with `https://example.com/openai//v1/`.
    base_url = str(provider.client._client.base_url)
    assert base_url == "https://example.com/openai/v1/"

```

Depending on how `OpenAIEmbeddingProvider` exposes its underlying OpenAI client, you may need to adjust the attribute chain used to read the base URL:

- If the provider exposes the client as `provider._client` instead of `provider.client`, change `provider.client._client.base_url` to `provider._client.base_url` or `provider._client._client.base_url`.
- If the base URL is stored on a different attribute (e.g. `provider._client.base_url` or `provider.client.base_url`), update the test accordingly while keeping the assertion value `https://example.com/openai/v1/`.

The key behavior to lock in is that `"https://example.com/openai/"` normalizes to `"https://example.com/openai/v1/"` without a double slash.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 69 to 71
@pytest.mark.asyncio
async def test_handle_api_error_content_moderated_removes_images():
provider = _make_provider(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Cover edge case where embedding_api_base ends with a trailing slash but no /v1

Given the current normalization (rstrip('/') then conditionally appending /v1), please add a test for embedding_api_base="https://example.com/openai/" to assert it becomes https://example.com/openai/v1/ and not https://example.com/openai//v1/, so the trailing-slash handling is locked in.

Suggested implementation:

    finally:
        await provider.terminate()


def test_embedding_api_base_trailing_slash_normalized():
    provider = _make_provider(
        overrides={"embedding_api_base": "https://example.com/openai/"}
    )

    # The provider should normalize the embedding API base by removing any
    # trailing slash and then appending `/v1`, resulting in a single slash.
    # This asserts we do *not* end up with `https://example.com/openai//v1/`.
    base_url = str(provider.client._client.base_url)
    assert base_url == "https://example.com/openai/v1/"

Depending on how OpenAIEmbeddingProvider exposes its underlying OpenAI client, you may need to adjust the attribute chain used to read the base URL:

  • If the provider exposes the client as provider._client instead of provider.client, change provider.client._client.base_url to provider._client.base_url or provider._client._client.base_url.
  • If the base URL is stored on a different attribute (e.g. provider._client.base_url or provider.client.base_url), update the test accordingly while keeping the assertion value https://example.com/openai/v1/.

The key behavior to lock in is that "https://example.com/openai/" normalizes to "https://example.com/openai/v1/" without a double slash.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new static method, _normalize_embedding_api_base, within the OpenAIEmbeddingProvider to ensure that API base URLs consistently end with /v1. This normalization is applied during the initialization of the AsyncOpenAI client. New unit tests have been added to validate this behavior. A review comment points out a potential issue where an empty or whitespace-only embedding_api_base configuration could result in an empty api_base being passed to the client, leading to an InvalidURL error. It suggests a more robust approach by falling back to the default URL before normalization in such cases.

Comment on lines +37 to +38
if api_base:
api_base = self._normalize_embedding_api_base(api_base)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

While the current logic correctly normalizes a non-empty api_base, it doesn't handle cases where api_base becomes an empty string (e.g., if the configuration provides an empty or whitespace-only value). This will cause the AsyncOpenAI client to fail with an InvalidURL error. To make this more robust, we should ensure we fall back to the default URL if api_base is empty before normalizing.

api_base = self._normalize_embedding_api_base(api_base or "https://api.openai.com/v1")

@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Mar 24, 2026
@idiotsj
Copy link
Contributor Author

idiotsj commented Mar 24, 2026

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/provider/sources/openai_embedding_source.py" line_range="29" />
<code_context>
+        ``https://example.com`` or ``https://example.com/openai``. More specific
+        paths (for example ``/v1-beta`` or ``/v1/embeddings``) are preserved as-is.
+        """
+        normalized_api_base = api_base.rstrip("/")
+        parsed = urlsplit(normalized_api_base)
+        path_segments = [segment for segment in parsed.path.split("/") if segment]
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid applying `rstrip('/')` to the whole URL string to prevent altering query/fragment parts.

Because `api_base` may include query or fragment components (e.g. `https://example.com?next=/foo/`), calling `rstrip('/')` on the full string will incorrectly change those parts (`next=/foo/``next=/foo`). Instead, parse the URL (`parsed = urlsplit(api_base)`) and apply `rstrip('/')` only to `parsed.path`, then rebuild the URL with `parsed._replace(path=...)` so query and fragment remain intact.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@idiotsj
Copy link
Contributor Author

idiotsj commented Mar 24, 2026

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]调用OpenAI embedding类型的embedding provider时不会自动补全/v1

1 participant