perf(backend): 角色/世界观入库改批量——create_many 一次 flush 落全部行

角色入库端点与 toolbox world_entities 入库端点原逐卡/逐条 create(每行一次
SELECT/flush/refresh,N+1 round-trip)。新增 create_many:世界观用 add_all,
角色用一次 IN 查已有名 + 一次 flush(保持 (project_id,name) upsert 幂等与批内
重名去重、保序返回)。单条 create 薄封装 create_many,行为不变(既有调用方/E2E
复用)。复活并复用 CharacterWriteFields、新增 WorldEntityFields 作批量入参。
补两端点「多行一次落库」测试;既有幂等/冲突 gate 测试与 m5 真 pg E2E 全绿。
This commit is contained in:
Yaojia Wang
2026-07-08 12:44:07 +02:00
parent 0892ff6cda
commit bf3dabea82
7 changed files with 221 additions and 71 deletions

View File

@@ -32,7 +32,7 @@ from ww_core.domain.outline_write_repo import OutlineWriteView
from ww_core.domain.project_repo import ProjectCreate
from ww_core.domain.repositories import OutlineView
from ww_core.domain.rule_repo import RuleWriteView
from ww_core.domain.world_entity_repo import WorldEntityWriteView
from ww_core.domain.world_entity_repo import WorldEntityFields, WorldEntityWriteView
from ww_llm_gateway.types import LlmRequest, LlmResponse, ServedBy, Usage
from ww_shared import AppError, ErrorCode
@@ -75,6 +75,15 @@ class _FakeWorldWriteRepo:
self.rows.append({"type": type, "name": name, "rules": list(rules)})
return WorldEntityWriteView(id=uuid.uuid4(), type=type, name=name)
async def create_many(
self, project_id: uuid.UUID, entities: list[WorldEntityFields]
) -> list[WorldEntityWriteView]:
# 镜像 Sql批量插入保序返回逐条 View。
return [
await self.create(project_id, type=e.type, name=e.name, rules=list(e.rules))
for e in entities
]
class _FakeOutlineWriteRepo:
def __init__(self) -> None:
@@ -569,6 +578,30 @@ async def test_ingest_world_entities_no_conflict_writes_201() -> None:
assert session.commits == 1
@pytest.mark.asyncio
async def test_ingest_multiple_world_entities_all_land_in_one_batch() -> None:
# 批量入库:多条实体一次 create_many 全部落库created 保序,单次 commit。
repo = FakeProjectRepo()
pid = await _seed_project(repo)
gateway = _SchemaRoutingGateway({ContinuityReview: _no_conflicts()})
app, session, world_repo, _ = _make_app(project_repo=repo, gateway=gateway)
payload = {
"world_entities": [
{"type": "力量体系", "name": "吞噬系统", "rules": ["每日上限"]},
{"type": "势力", "name": "天机阁", "rules": ["中立"]},
{"type": "地点", "name": "无尽海", "rules": []},
]
}
async with _client(app) as client:
resp = await client.post(f"/projects/{pid}/skills/golden-finger/ingest", json=payload)
assert resp.status_code == 201
assert resp.json()["created"] == ["吞噬系统", "天机阁", "无尽海"]
assert [r["name"] for r in world_repo.rows] == ["吞噬系统", "天机阁", "无尽海"]
assert session.commits == 1
@pytest.mark.asyncio
async def test_ingest_world_entities_conflict_blocks_409() -> None:
repo = FakeProjectRepo()