Skip to content

feat: set default API URLs for various model credentials - #7889

Merged
wxg0103 merged 1 commit into
v3from
pr@v3@feat_set_default_api_urls
Nov 13, 2026
Merged

wxg0103 merged 1 commit into
v3from
pr@v3@feat_set_default_api_urls

Conversation

@shaohuzhang1

Copy link
Copy Markdown
Contributor

feat: set default API URLs for various model credentials


def get_model_provide_info(self):
return ModelProvideInfo(
provider="model_volcanic_engine_provider",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Summary

The change is small and the intent is reasonable (give Volcanic Ark LLMs the v3 base URL without poisoning a shared credential class), but the implementation relies on a fragile monkey-patch of a credential-field object. Below are the concerns, ordered by severity.


1. copy.copy on a field descriptor is risky (high)

model_credential.api_base is presumably a field descriptor (pydantic FieldInfo, dataclass field, or a custom descriptor). A shallow copy:

  • Shares any mutable internals (choices, validators, default_factory, metadata, nested config objects). Mutating anything other than the plain string default_value later will leak back into the original.
  • May not be a copy at all if api_base is implemented as a property / __get__ descriptor — you'd be copying a bound wrapper, not the field.
  • Breaks descriptor protocol semantics if the class relies on __set_name__, __set__, or identity checks between the field object and the class registry.

Fix: use copy.deepcopy, or better, construct a fresh field via the field's own constructor:

api_base_field = type(model_credential.api_base)(**{...})   # if the API allows it
# or: dataclasses.replace / model_credential.__class__.__fields__["api_base"].copy()

Then assign the URL through the normal field/default API so validators and __post_init__ still run.

2. Thread-safety / shared-state assumption (high)

The comment says this avoids overwriting the shared class's default_value, but that guarantee only holds if super().get_model_credential(...) returns a fresh instance per call. Confirm that:

  • If the base method caches/returns a singleton credential, model_credential.api_base = ... mutates shared state on the instance, and concurrent requests (LLM vs. another provider) can observe mixed values.
  • Even with a fresh instance, mutating a class-level descriptor's default_value on any reference is process-global. If copy.copy returns an object that still shares the class's __dict__ entry, the guard is illusory.

Add a comment/assertion, or make the field construction fully local.

3. Bypasses validation (medium)

Setting default_value directly after construction skips field validation and any __post_init__/validator hooks. If the field or credential validates URLs, this write may silently produce an invalid state.

4. Magic strings and hard-coded URL (medium)

  • "LLM" should be an enum/constant (e.g. ModelType.LLM) already defined elsewhere in the codebase.
  • The URL should be a module constant and ideally overridable via env/config so private/sovereign deployments and tests can point elsewhere:
VOLCANIC_ARK_LLM_BASE_URL = os.getenv(
    "VOLCANIC_ARK_LLM_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"
)

5. Silent no-op on missing api_base (low–medium)

hasattr(..., "api_base") silently returns the unmodified credential. If the upstream credential class ever renames the field, this degrades silently. Log a warning (or assert) so the misconfiguration is visible. Use getattr(model_credential, "api_base", None) for clarity; also handles model_credential is None.

6. Missing type hints / docstring (low)

Other methods in the class appear to follow a consistent style; add -> Any (or the concrete credential type) and a one-line docstring explaining why the copy is needed.

7. Missing regression test (low–medium)

The whole point of the copy is "don't affect other providers." Add a test asserting:

before = SomeOpenAICredential.api_base.default_value
provider.get_model_credential("LLM", "x")
assert SomeOpenAICredential.api_base.default_value == before
assert provider.get_model_credential("LLM", "x").api_base.value == VOLCANIC_ARK_LLM_BASE_URL

This test would have caught a shallow-copy leak.

8. Minor / style

  • import copy placement is fine (stdlib group), but if common.utils.common is a heavy import this file already pulls in, no concern.
  • Consider caching the modified field on the provider instance only if it is provably immutable and thread-safe — otherwise keep per-call copies (cheap enough).

Suggested shape

VOLCANIC_ARK_LLM_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"

def get_model_credential(self, model_type: str, model_name: str):
    """Return a credential whose api_base points at Ark v3.

    The base credential class is shared across providers, so we copy the
    api_base field instead of mutating the shared default.
    """
    credential = super().get_model_credential(model_type, model_name)
    if model_type != ModelType.LLM:
        return credential

    api_base = getattr(credential, "api_base", None)
    if api_base is None:
        logger.warning("Credential for %s has no api_base; skipping Ark URL override", model_name)
        return credential

    new_field = copy.deepcopy(api_base)          # avoid shared mutable internals
    new_field.default_value = VOLCANIC_ARK_LLM_BASE_URL
    credential.api_base = new_field
    return credential

Bottom line: the approach works today for a plain string field, but it is one refactor away from a cross-provider bug. Deep-copy (or reconstruct) the field, avoid magic strings, surface the missing-attribute case, and lock the behavior in with a regression test.

)
api_key = forms.PasswordInputField(_("API Key"), required=True)

def is_valid(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review of Code Changes

The provided diff adds a default_value to an API URL field in what appears to be a credential form for Aliyun BaiLian models within a Dify-like framework. Here are the findings:

1. Potential Issues & Irregularities

  • Hardcoded Environment Dependency: Hardcoding "https://dashscope.aliyuncs.com/api/v1" assumes all users will access the public internet endpoint. This ignores:
    • Regional Endpoints: Users might need regional endpoints (e.g., https://dashscope-intl.aliyuncs.com/...).
    • Private VPC/Proxy Environments: Enterprise users often use internal mirrors or proxies. Forcing this specific URL as a default makes it less flexible and potentially confusing if their setup differs from the standard public endpoint.
    • Path Versioning: The path /api/v1 is hardcoded. If Alibaba changes the API version, this code becomes stale. While unlikely to break immediately, it is generally better to let the SDK/client library handle base URLs internally rather than exposing and hardcoding them in UI forms unless strictly necessary for multi-tenant proxy setups.
  • Redundancy/Confusion with relation_show_field_dict: The field is shown for types ["qwen", "omni"]. Ensure that for these types, the backend logic expects this exact URL format. If some types don't require custom URLs (and rely on SDK defaults), showing this field with a hardcoded default could mislead users into thinking it's mandatory configuration when it's not.

2. Optimization Suggestions

  • Consider Making It Optional or Context-Aware:
    • Instead of a hardcoded default_value, consider making required=False and letting the backend fall back to the standard SDK behavior if no URL is provided.
    • Alternatively, provide multiple common options in a dropdown (SelectField) instead of a free-text input for the API URL, such as "Public Internet," "International," "Custom."
  • Configuration Management: For production systems, hardcoded URLs should ideally be managed via environment variables or a central config file, not baked into the UI definition. If this UI allows overriding for testing/proxies, that’s acceptable, but the default should probably align with what the SDK uses by default to avoid maintenance burden.
  • String Constant Extraction: Move the URL string to a class constant or module-level variable for easier maintenance if it's referenced elsewhere or needs to be updated frequently.

3. Recommendation

If this change is to simplify setup for most users (who use the public dashscope), it is functionally acceptable but architecturally slightly risky. A better approach would be:

  1. Make the field required=False.
  2. Document that leaving it blank uses the default official endpoint.
  3. Remove the hardcoded default_value unless strictly required to populate existing user settings (migration scenario).

If you must keep the default, consider adding a comment explaining why this specific URL/path is chosen, especially regarding versioning (v1).

@wxg0103
wxg0103 merged commit 9e09ea7 into v3 Nov 13, 2026
3 of 4 checks passed
@wxg0103
wxg0103 deleted the pr@v3@feat_set_default_api_urls branch November 13, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants