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:
Teknium
2026-04-11 14:07:12 -07:00
committed by Teknium
parent 0e6354df50
commit 4bede272cf
3 changed files with 118 additions and 1 deletions

View File

@@ -1497,7 +1497,7 @@ _KNOWN_ROOT_KEYS = {
# Valid fields inside a custom_providers list entry
_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",
}

View File

@@ -332,6 +332,11 @@ def _resolve_named_custom_runtime(
# 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"))
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
api_key_candidates = [

View File

@@ -1214,3 +1214,115 @@ def test_openrouter_provider_not_affected_by_custom_fix(monkeypatch):
resolved = rp.resolve_runtime_provider(requested="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