mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
perf: 优化请求鉴权链路并批量化统计/调度查询
- 为 Pipeline/Context 增加按需读取请求体能力,支持 async 懒加载 JSON body - 为 chat/cli/video/claude/openai-cli 适配器关闭默认预读,减少无效 body 读取与超时风险 - 将本地登录、JWT 用户加载、API Key 鉴权迁移到线程池隔离会话执行,避免阻塞事件循环 - 为 API Key 鉴权返回结构化余额结果,并在主请求会话中重新绑定 user/api_key 后再校验状态、过期和锁定信息 - 为 management/user token 前缀认证引入独立会话与结果回绑,避免跨会话对象写入失效 - 为 Usage 余额检查补充结构化返回,统一透出 remaining 与欠费/不可用文案映射 - 为用户与管理端活跃请求查询增加 maintain_status 开关,避免轮询指定 id 时误触发状态修复 - 重写 user_me usage 汇总逻辑,支持 group_by=None 的粗粒度聚合 - 修正 provider 维度成功率与平均响应时间统计,基于 success_count 和成功响应耗时汇总计算 - 前端 Usage 轮询由 setInterval 改为串行 setTimeout,避免并发轮询叠加 - 为 StatsAggregator 增加按本地日期批量计算百分位能力,替代逐天 fan-out 查询 - 为混合统计查询合并连续实时日期区间,并批量读取 StatsDaily,减少逐日查询次数 - 为用户日统计增加批量聚合入口,替代逐用户循环聚合 - 为系统配置导出改用 selectinload 预加载 provider 关联数据,减少 N+1 查询 - 为管理员用户列表增加钱包批量查询,避免逐用户回表 - 为调度器增加 provider 轻量引用预过滤,先按 allowed_providers 缩小范围再加载完整 provider 图 - 为 CandidateBuilder 增加 provider refs/provider_ids 查询能力,保留分页顺序 - 为模型缓存增加 provider_model_mappings 索引缓存与 model_mappings 规则缓存,减少重复全量扫描 - 为请求候选中间态改为 flush/batch commit,降低 pending/streaming 状态切换的事务往返 - 为钱包访问结果补充 balance_snapshot,并抽取余额快照复用逻辑 - 补充 pipeline、auth、admin users、user_me usage、stats aggregator、model cache、 scheduler、wallet、request candidate 等回归与契约测试
This commit is contained in:
@@ -1,36 +1,45 @@
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Callable, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.cache_service import CacheService
|
||||
from src.models.database import GlobalModel, Model
|
||||
from src.services.cache.model_cache import ModelCacheService
|
||||
from src.core.cache_service import CacheService
|
||||
|
||||
|
||||
class _FakeQuery:
|
||||
def __init__(self, *, first_result=None, all_result=None, on_all=None):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
first_result: Any = None,
|
||||
all_result: list[Any] | None = None,
|
||||
on_all: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
self._first_result = first_result
|
||||
self._all_result = all_result if all_result is not None else []
|
||||
self._on_all = on_all
|
||||
|
||||
def join(self, *_args, **_kwargs):
|
||||
def join(self, *_args: object, **_kwargs: object) -> "_FakeQuery":
|
||||
return self
|
||||
|
||||
def filter(self, *_args, **_kwargs):
|
||||
def filter(self, *_args: object, **_kwargs: object) -> "_FakeQuery":
|
||||
return self
|
||||
|
||||
def first(self):
|
||||
def first(self) -> Any:
|
||||
return self._first_result
|
||||
|
||||
def all(self):
|
||||
def all(self) -> list[Any]:
|
||||
if self._on_all:
|
||||
self._on_all()
|
||||
return self._all_result
|
||||
|
||||
|
||||
class _FakeSession:
|
||||
def __init__(self, *, direct_match: GlobalModel):
|
||||
def __init__(self, *, direct_match: GlobalModel) -> None:
|
||||
self._direct_match = direct_match
|
||||
|
||||
def query(self, *entities):
|
||||
def query(self, *entities: object) -> "_FakeQuery":
|
||||
if entities == (GlobalModel,):
|
||||
return _FakeQuery(first_result=self._direct_match)
|
||||
|
||||
@@ -43,12 +52,43 @@ class _FakeSession:
|
||||
raise AssertionError(f"Unexpected query entities: {entities}")
|
||||
|
||||
|
||||
class _MappingIndexSession:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
provider_mapping_rows: list[tuple[object, GlobalModel]],
|
||||
) -> None:
|
||||
self._provider_mapping_rows = provider_mapping_rows
|
||||
self.provider_mapping_scan_count = 0
|
||||
self.model_global_query_count = 0
|
||||
|
||||
def query(self, *entities: object) -> "_FakeQuery":
|
||||
if entities == (GlobalModel,):
|
||||
return _FakeQuery(first_result=None, all_result=[])
|
||||
|
||||
if entities == (Model, GlobalModel):
|
||||
self.model_global_query_count += 1
|
||||
if self.model_global_query_count in {1, 3}:
|
||||
return _FakeQuery(all_result=[])
|
||||
if self.model_global_query_count == 2:
|
||||
return _FakeQuery(
|
||||
all_result=self._provider_mapping_rows,
|
||||
on_all=self._record_provider_mapping_scan,
|
||||
)
|
||||
raise AssertionError("provider_model_mappings 全量扫描被重复触发")
|
||||
|
||||
raise AssertionError(f"Unexpected query entities: {entities}")
|
||||
|
||||
def _record_provider_mapping_scan(self) -> None:
|
||||
self.provider_mapping_scan_count += 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_global_model_prefers_direct_match(monkeypatch) -> None:
|
||||
async def _fake_get(_key: str):
|
||||
async def test_resolve_global_model_prefers_direct_match(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def _fake_get(_key: str) -> None:
|
||||
return None
|
||||
|
||||
async def _fake_set(_key: str, _value, ttl_seconds: int = 60): # noqa: ARG001
|
||||
async def _fake_set(_key: str, _value: object, ttl_seconds: int = 60) -> bool: # noqa: ARG001
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(CacheService, "get", staticmethod(_fake_get))
|
||||
@@ -67,6 +107,98 @@ async def test_resolve_global_model_prefers_direct_match(monkeypatch) -> None:
|
||||
db = _FakeSession(direct_match=global_model)
|
||||
|
||||
resolved = await ModelCacheService.resolve_global_model_by_name_or_mapping(
|
||||
db, global_model.name
|
||||
cast(Any, db),
|
||||
cast(str, global_model.name),
|
||||
)
|
||||
assert resolved is global_model
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_global_model_reuses_provider_mapping_index_cache(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
cache_store: dict[str, object] = {}
|
||||
|
||||
async def _fake_get(key: str) -> object | None:
|
||||
return cache_store.get(key)
|
||||
|
||||
async def _fake_set(key: str, value: object, ttl_seconds: int = 60) -> bool: # noqa: ARG001
|
||||
cache_store[key] = value
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(CacheService, "get", staticmethod(_fake_get))
|
||||
monkeypatch.setattr(CacheService, "set", staticmethod(_fake_set))
|
||||
|
||||
global_model_one = GlobalModel(
|
||||
id="gm-1",
|
||||
name="gpt-4o",
|
||||
display_name="GPT-4o",
|
||||
supported_capabilities=[],
|
||||
config={},
|
||||
default_tiered_pricing=None,
|
||||
default_price_per_request=None,
|
||||
is_active=True,
|
||||
)
|
||||
global_model_two = GlobalModel(
|
||||
id="gm-2",
|
||||
name="claude-3-7-sonnet",
|
||||
display_name="Claude 3.7 Sonnet",
|
||||
supported_capabilities=[],
|
||||
config={},
|
||||
default_tiered_pricing=None,
|
||||
default_price_per_request=None,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
model_one = SimpleNamespace(
|
||||
id="m-1",
|
||||
provider_model_mappings=[{"name": "mapped-one"}],
|
||||
)
|
||||
model_two = SimpleNamespace(
|
||||
id="m-2",
|
||||
provider_model_mappings=[{"name": "mapped-two"}],
|
||||
)
|
||||
|
||||
db = _MappingIndexSession(
|
||||
provider_mapping_rows=[
|
||||
(model_one, global_model_one),
|
||||
(model_two, global_model_two),
|
||||
]
|
||||
)
|
||||
|
||||
resolved_one = await ModelCacheService.resolve_global_model_by_name_or_mapping(
|
||||
cast(Any, db), "mapped-one"
|
||||
)
|
||||
resolved_two = await ModelCacheService.resolve_global_model_by_name_or_mapping(
|
||||
cast(Any, db), "mapped-two"
|
||||
)
|
||||
|
||||
assert resolved_one is not None
|
||||
assert resolved_one.name == "gpt-4o"
|
||||
assert resolved_two is not None
|
||||
assert resolved_two.name == "claude-3-7-sonnet"
|
||||
assert db.provider_mapping_scan_count == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalidate_model_cache_clears_provider_mapping_index(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
deleted_keys: list[str] = []
|
||||
|
||||
async def _fake_delete(key: str) -> bool:
|
||||
deleted_keys.append(key)
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(CacheService, "delete", staticmethod(_fake_delete))
|
||||
|
||||
await ModelCacheService.invalidate_model_cache(
|
||||
model_id="model-1",
|
||||
provider_model_name="provider-model",
|
||||
provider_model_mappings=[{"name": "alias-model"}],
|
||||
)
|
||||
|
||||
assert "model:id:model-1" in deleted_keys
|
||||
assert "global_model:resolve:provider-model" in deleted_keys
|
||||
assert "global_model:resolve:alias-model" in deleted_keys
|
||||
assert ModelCacheService.PROVIDER_MAPPING_INDEX_CACHE_KEY in deleted_keys
|
||||
|
||||
Reference in New Issue
Block a user