mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
feat(pool): 号池额度主动探测、封禁自动清除、调度硬优先级与前端重构
- 新增 PoolQuotaProbeScheduler,按 probing_interval_minutes 主动探测静默 Key 额度 - pool_advanced 增加 probing_enabled / auto_remove_banned_keys 配置项 - error_handler 和 quota_service 支持封禁 Key 自动删除及缓存清理 - multi_score 策略从加权混合重构为硬优先级排序,引入 mutex_group 互斥组 - 指纹注入从 handler 层下移至 ClaudeCode envelope 层 - OAuth 批量导入支持 concurrency 并发参数 - 前端号池管理拆分高级设置/账号批量/代理设置为独立组件 - 号池总览接口精简,仅返回已启用调度的 Provider
This commit is contained in:
@@ -50,6 +50,7 @@ class _FakeDB:
|
||||
self._provider = provider
|
||||
self._keys = keys
|
||||
self.added: list[object] = []
|
||||
self.deleted: list[object] = []
|
||||
self.commit_count = 0
|
||||
|
||||
def query(self, model: Any) -> _FakeQuery:
|
||||
@@ -63,6 +64,9 @@ class _FakeDB:
|
||||
def add(self, obj: object) -> None:
|
||||
self.added.append(obj)
|
||||
|
||||
def delete(self, obj: object) -> None:
|
||||
self.deleted.append(obj)
|
||||
|
||||
def commit(self) -> None:
|
||||
self.commit_count += 1
|
||||
|
||||
@@ -148,6 +152,7 @@ async def test_refresh_provider_quota_no_active_keys_returns_empty() -> None:
|
||||
"total": 0,
|
||||
"results": [],
|
||||
"message": "没有可刷新的 Key",
|
||||
"auto_removed": 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +179,7 @@ async def test_refresh_provider_quota_empty_key_ids_returns_empty() -> None:
|
||||
"total": 0,
|
||||
"results": [],
|
||||
"message": "未提供可刷新的 Key",
|
||||
"auto_removed": 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -324,3 +330,84 @@ async def test_refresh_provider_quota_handler_exception_returns_error(
|
||||
assert result["total"] == 1
|
||||
assert result["results"][0]["status"] == "error"
|
||||
assert "unit-test boom" in result["results"][0]["message"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_provider_quota_auto_removes_banned_keys_when_enabled(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
provider = SimpleNamespace(
|
||||
id="p1",
|
||||
provider_type=ProviderType.CODEX,
|
||||
endpoints=[SimpleNamespace(api_format="openai:cli", is_active=True)],
|
||||
config={"pool_advanced": {"auto_remove_banned_keys": True}},
|
||||
)
|
||||
key1 = SimpleNamespace(
|
||||
id="k1",
|
||||
name="K1",
|
||||
provider_id="p1",
|
||||
allowed_models=["gpt-4o"],
|
||||
upstream_metadata={},
|
||||
is_active=True,
|
||||
oauth_invalid_reason=None,
|
||||
)
|
||||
key2 = SimpleNamespace(
|
||||
id="k2",
|
||||
name="K2",
|
||||
provider_id="p1",
|
||||
allowed_models=None,
|
||||
upstream_metadata={},
|
||||
is_active=True,
|
||||
oauth_invalid_reason=None,
|
||||
)
|
||||
db = _FakeDB(provider=provider, keys=[key1, key2])
|
||||
deleted_side_effect_calls: list[tuple[str | None, list[str] | None]] = []
|
||||
redis_cleared: list[tuple[str, str]] = []
|
||||
|
||||
async def _fake_handler(**kwargs: Any) -> dict[str, Any]:
|
||||
key = kwargs["key"]
|
||||
state_updates = kwargs["state_updates"]
|
||||
if key.id == "k1":
|
||||
state_updates[key.id] = {"is_active": False, "oauth_invalid_reason": "账户已封禁: test"}
|
||||
return {"key_id": key.id, "key_name": key.name, "status": "banned"}
|
||||
return {"key_id": key.id, "key_name": key.name, "status": "success"}
|
||||
|
||||
async def _fake_run_delete_side_effects(
|
||||
*,
|
||||
db: Any,
|
||||
provider_id: str | None,
|
||||
deleted_key_allowed_models: list[str] | None,
|
||||
) -> None:
|
||||
_ = db
|
||||
deleted_side_effect_calls.append((provider_id, deleted_key_allowed_models))
|
||||
|
||||
async def _fake_clear(provider_id: str, key_id: str) -> None:
|
||||
redis_cleared.append((provider_id, key_id))
|
||||
|
||||
monkeypatch.setattr(
|
||||
quota_service_module,
|
||||
"_select_refresh_endpoint",
|
||||
lambda provider, provider_type: provider.endpoints[0],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
quota_service_module, "_resolve_quota_refresh_handler", lambda _: _fake_handler
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
quota_service_module, "run_delete_key_side_effects", _fake_run_delete_side_effects
|
||||
)
|
||||
monkeypatch.setattr(quota_service_module.pool_redis, "clear_cooldown", _fake_clear)
|
||||
monkeypatch.setattr(quota_service_module.pool_redis, "clear_cost", _fake_clear)
|
||||
|
||||
result = await refresh_provider_quota_for_provider(
|
||||
db=cast(Any, db),
|
||||
provider_id="p1",
|
||||
codex_wham_usage_url="https://example.test/wham/usage",
|
||||
)
|
||||
|
||||
assert result["auto_removed"] == 1
|
||||
assert db.commit_count == 1
|
||||
assert db.deleted == [key1]
|
||||
assert key2 not in db.deleted
|
||||
assert result["results"][0]["auto_removed"] is True
|
||||
assert deleted_side_effect_calls == [("p1", ["gpt-4o"])]
|
||||
assert redis_cleared == [("p1", "k1"), ("p1", "k1")]
|
||||
|
||||
Reference in New Issue
Block a user