feat: add configurable AI model pool

This commit is contained in:
Codex
2026-08-01 23:11:44 +08:00
parent 020b9596dc
commit 4430149ee4
13 changed files with 1660 additions and 273 deletions
+107 -9
View File
@@ -1,10 +1,13 @@
from pathlib import Path
import pytest
from app.config import Settings
from app.runtime_settings import (
EncryptedSettingsStore,
RuntimeSettingsTestResult,
generate_secret,
test_model_pool_item as run_model_pool_test,
)
@@ -43,27 +46,122 @@ def test_readiness_requires_configuration_and_successful_tests(tmp_path: Path) -
"s3_public_endpoint": "http://localhost:9000",
"s3_access_key_id": "access",
"s3_secret_access_key": "secret",
"ai_provider": "lingke",
"ai_base_url": "https://example.com/v1",
"ai_api_key": "api-secret",
"orchestrator_model": "planner",
"vision_model": "vision",
"image_model": "gpt-image-2",
"model_pool": [
{
"id": "planner",
"name": "总调度",
"model_id": "planner-model",
"category": "language",
"provider": "lingke",
"base_url": "https://example.com/v1",
"api_key": "planner-secret",
"capabilities": ["orchestration"],
},
{
"id": "designer",
"name": "空间与生图",
"model_id": "designer-model",
"category": "multimodal",
"provider": "lingke",
"base_url": "https://example.com/v1",
"api_key": "designer-secret",
"capabilities": ["spatial_understanding", "image_generation"],
},
],
"orchestrator_model_id": "planner",
"spatial_routing_mode": "auto",
"image_routing_mode": "manual",
"image_model_id": "designer",
}
)
assert store.public_response().readiness.ready is False
for target in ("infrastructure", "storage", "ai_models"):
for target in ("infrastructure", "storage", "model:planner", "model:designer"):
store.record_test(RuntimeSettingsTestResult(target=target, ok=True, message="ok"))
assert store.public_response().readiness.ready is True
store.update({"image_model": "another-image-model"})
pool = store.merged_values()["model_pool"]
pool[1]["model_id"] = "another-image-model"
store.update({"model_pool": pool})
assert store.public_response().readiness.ready is False
assert store.public_response().tests.get("ai_models") is None
assert store.public_response().tests["model:designer"]["ok"] is False
assert "重新测试" in store.public_response().tests["model:designer"]["message"]
def test_model_pool_keys_are_encrypted_and_redacted(tmp_path: Path) -> None:
store = create_store(tmp_path)
store.update(
{
"model_pool": [
{
"id": "planner",
"name": "总调度",
"model_id": "planner-model",
"category": "language",
"provider": "custom",
"base_url": "https://example.com/v1",
"api_key": "nested-model-secret",
"capabilities": ["orchestration"],
}
]
}
)
public_model = store.public_response().values["model_pool"][0]
assert "api_key" not in public_model
assert public_model["api_key_configured"] is True
assert b"nested-model-secret" not in store.data_path.read_bytes()
def test_secret_generators_use_expected_lengths() -> None:
assert len(generate_secret("hex24")) == 48
assert len(generate_secret("hex32")) == 64
assert len(generate_secret("base64_32")) == 44
@pytest.mark.asyncio
async def test_individual_model_error_names_the_failing_model(tmp_path: Path, monkeypatch) -> None:
store = create_store(tmp_path)
store.update(
{
"model_pool": [
{
"id": "image-model",
"name": "客厅效果图模型",
"model_id": "wrong-image-id",
"category": "multimodal",
"provider": "custom",
"base_url": "https://example.com/v1",
"api_key": "secret",
"capabilities": ["image_generation"],
}
]
}
)
class FakeResponse:
status_code = 200
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {"data": [{"id": "available-image-id"}]}
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, *args) -> None:
return None
async def get(self, *args, **kwargs) -> FakeResponse:
return FakeResponse()
monkeypatch.setattr("app.runtime_settings.httpx.AsyncClient", lambda **kwargs: FakeClient())
result = await run_model_pool_test(store.merged_values(), "image-model")
assert result.ok is False
assert "客厅效果图模型" in result.message
assert "wrong-image-id" in result.message