Repository navigation
feat: set default API URLs for various model credentials - #7889
Conversation
|
|
||
| def get_model_provide_info(self): | ||
| return ModelProvideInfo( | ||
| provider="model_volcanic_engine_provider", |
There was a problem hiding this comment.
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 stringdefault_valuelater will leak back into the original. - May not be a copy at all if
api_baseis implemented as aproperty/__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_valueon any reference is process-global. Ifcopy.copyreturns 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_URLThis test would have caught a shallow-copy leak.
8. Minor / style
import copyplacement is fine (stdlib group), but ifcommon.utils.commonis 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 credentialBottom 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( |
There was a problem hiding this comment.
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/v1is 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.
- Regional Endpoints: Users might need regional endpoints (e.g.,
- 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 makingrequired=Falseand 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."
- Instead of a hardcoded
- 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:
- Make the field
required=False. - Document that leaving it blank uses the default official endpoint.
- Remove the hardcoded
default_valueunless 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).
feat: set default API URLs for various model credentials