fix: propagate model through credential pool path + add tests
The cherry-picked fix from PR #7916 placed model propagation after the credential pool early-return in _resolve_named_custom_runtime(), making it dead code when a pool is active (which happens whenever custom_providers has an api_key that auto-seeds the pool). - Inject model into pool_result before returning - Add 5 regression tests covering direct path, pool path, empty model, and absent model scenarios - Add 'model' to _VALID_CUSTOM_PROVIDER_FIELDS for config validation
This commit is contained in:
@@ -1497,7 +1497,7 @@ _KNOWN_ROOT_KEYS = {
|
|||||||
|
|
||||||
# Valid fields inside a custom_providers list entry
|
# Valid fields inside a custom_providers list entry
|
||||||
_VALID_CUSTOM_PROVIDER_FIELDS = {
|
_VALID_CUSTOM_PROVIDER_FIELDS = {
|
||||||
"name", "base_url", "api_key", "api_mode", "models",
|
"name", "base_url", "api_key", "api_mode", "model", "models",
|
||||||
"context_length", "rate_limit_delay",
|
"context_length", "rate_limit_delay",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -332,6 +332,11 @@ def _resolve_named_custom_runtime(
|
|||||||
# Check if a credential pool exists for this custom endpoint
|
# Check if a credential pool exists for this custom endpoint
|
||||||
pool_result = _try_resolve_from_custom_pool(base_url, "custom", custom_provider.get("api_mode"))
|
pool_result = _try_resolve_from_custom_pool(base_url, "custom", custom_provider.get("api_mode"))
|
||||||
if pool_result:
|
if pool_result:
|
||||||
|
# Propagate the model name even when using pooled credentials —
|
||||||
|
# the pool doesn't know about the custom_providers model field.
|
||||||
|
model_name = custom_provider.get("model")
|
||||||
|
if model_name:
|
||||||
|
pool_result["model"] = model_name
|
||||||
return pool_result
|
return pool_result
|
||||||
|
|
||||||
api_key_candidates = [
|
api_key_candidates = [
|
||||||
|
|||||||
@@ -1214,3 +1214,115 @@ def test_openrouter_provider_not_affected_by_custom_fix(monkeypatch):
|
|||||||
|
|
||||||
resolved = rp.resolve_runtime_provider(requested="openrouter")
|
resolved = rp.resolve_runtime_provider(requested="openrouter")
|
||||||
assert resolved["provider"] == "openrouter"
|
assert resolved["provider"] == "openrouter"
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# fix #7828 — custom_providers model field must propagate to runtime
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_named_custom_provider_includes_model(monkeypatch):
|
||||||
|
"""_get_named_custom_provider should include the model field from config."""
|
||||||
|
monkeypatch.setattr(rp, "load_config", lambda: {
|
||||||
|
"custom_providers": [{
|
||||||
|
"name": "my-dashscope",
|
||||||
|
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||||
|
"api_key": "test-key",
|
||||||
|
"api_mode": "chat_completions",
|
||||||
|
"model": "qwen3.6-plus",
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
|
||||||
|
result = rp._get_named_custom_provider("my-dashscope")
|
||||||
|
assert result is not None
|
||||||
|
assert result["model"] == "qwen3.6-plus"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_named_custom_provider_excludes_empty_model(monkeypatch):
|
||||||
|
"""Empty or whitespace-only model field should not appear in result."""
|
||||||
|
for model_val in ["", " ", None]:
|
||||||
|
entry = {
|
||||||
|
"name": "test-ep",
|
||||||
|
"base_url": "https://example.com/v1",
|
||||||
|
"api_key": "key",
|
||||||
|
}
|
||||||
|
if model_val is not None:
|
||||||
|
entry["model"] = model_val
|
||||||
|
|
||||||
|
monkeypatch.setattr(rp, "load_config", lambda e=entry: {
|
||||||
|
"custom_providers": [e],
|
||||||
|
})
|
||||||
|
|
||||||
|
result = rp._get_named_custom_provider("test-ep")
|
||||||
|
assert result is not None
|
||||||
|
assert "model" not in result, (
|
||||||
|
f"model field {model_val!r} should not be included in result"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_custom_runtime_propagates_model_direct_path(monkeypatch):
|
||||||
|
"""Model should propagate through the direct (non-pool) resolution path."""
|
||||||
|
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
rp, "_get_named_custom_provider",
|
||||||
|
lambda p: {
|
||||||
|
"name": "my-server",
|
||||||
|
"base_url": "http://localhost:8000/v1",
|
||||||
|
"api_key": "test-key",
|
||||||
|
"model": "qwen3.6-plus",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Ensure pool doesn't intercept
|
||||||
|
monkeypatch.setattr(rp, "_try_resolve_from_custom_pool", lambda *a, **k: None)
|
||||||
|
|
||||||
|
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||||
|
assert resolved["model"] == "qwen3.6-plus"
|
||||||
|
assert resolved["provider"] == "custom"
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_custom_runtime_propagates_model_pool_path(monkeypatch):
|
||||||
|
"""Model should propagate even when credential pool handles credentials."""
|
||||||
|
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
rp, "_get_named_custom_provider",
|
||||||
|
lambda p: {
|
||||||
|
"name": "my-server",
|
||||||
|
"base_url": "http://localhost:8000/v1",
|
||||||
|
"api_key": "test-key",
|
||||||
|
"model": "qwen3.6-plus",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Pool returns a result (intercepting the normal path)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
rp, "_try_resolve_from_custom_pool",
|
||||||
|
lambda *a, **k: {
|
||||||
|
"provider": "custom",
|
||||||
|
"api_mode": "chat_completions",
|
||||||
|
"base_url": "http://localhost:8000/v1",
|
||||||
|
"api_key": "pool-key",
|
||||||
|
"source": "pool:custom:my-server",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||||
|
assert resolved["model"] == "qwen3.6-plus", (
|
||||||
|
"model must be injected into pool result"
|
||||||
|
)
|
||||||
|
assert resolved["api_key"] == "pool-key", "pool credentials should be used"
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_custom_runtime_no_model_when_absent(monkeypatch):
|
||||||
|
"""When custom_providers entry has no model field, runtime should not either."""
|
||||||
|
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
rp, "_get_named_custom_provider",
|
||||||
|
lambda p: {
|
||||||
|
"name": "my-server",
|
||||||
|
"base_url": "http://localhost:8000/v1",
|
||||||
|
"api_key": "test-key",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(rp, "_try_resolve_from_custom_pool", lambda *a, **k: None)
|
||||||
|
|
||||||
|
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||||
|
assert "model" not in resolved
|
||||||
|
|||||||
Reference in New Issue
Block a user