初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,198 @@
|
||||
"""世界模型服务(河图枢运行时,api-spec §12)。
|
||||
|
||||
按人物视角隔离:所有读写经 ``resolve_active_character`` 绑定当前激活人物。
|
||||
自动抽取(世界引擎)属 v1 半自动留后项,本层先承载手动 CRUD。
|
||||
"""
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.errors import AppError
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.models.world import CharacterWorldEntity, WorldRelation
|
||||
from app.repositories import world as world_repo
|
||||
from app.repositories.json_utils import dumps
|
||||
from app.schemas.common import Page
|
||||
from app.schemas.world import (
|
||||
WorldEntityCreateRequest,
|
||||
WorldEntityResponse,
|
||||
WorldGraphResponse,
|
||||
WorldRelationCreateRequest,
|
||||
WorldRelationResponse,
|
||||
)
|
||||
from app.services.audit import record_audit_event
|
||||
from app.services.characters import resolve_active_character
|
||||
from app.services.serializers import (
|
||||
character_world_entity_to_response,
|
||||
world_relation_to_response,
|
||||
)
|
||||
|
||||
|
||||
async def list_entities_page(
|
||||
character_id: str | None = None,
|
||||
*,
|
||||
entity_type: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
owner_scope: str = settings.default_owner_scope,
|
||||
) -> Page[WorldEntityResponse]:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(session, character_id, owner_scope=owner_scope)
|
||||
safe_page = max(page, 1)
|
||||
safe_size = min(max(page_size, 1), 100)
|
||||
items, total = await world_repo.list_entities(
|
||||
session,
|
||||
character.id,
|
||||
entity_type=entity_type,
|
||||
page=safe_page,
|
||||
page_size=safe_size,
|
||||
)
|
||||
return Page(
|
||||
items=[character_world_entity_to_response(item) for item in items],
|
||||
page=safe_page,
|
||||
page_size=safe_size,
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
async def get_entity_by_id(
|
||||
entity_id: str, owner_scope: str = settings.default_owner_scope
|
||||
) -> WorldEntityResponse:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(session, None, owner_scope=owner_scope)
|
||||
entity = await world_repo.get_entity(session, entity_id)
|
||||
if entity is None or entity.character_id != character.id:
|
||||
raise AppError("not_found", "世界实体不存在。")
|
||||
return character_world_entity_to_response(entity)
|
||||
|
||||
|
||||
async def create_entity(
|
||||
payload: WorldEntityCreateRequest, owner_scope: str = settings.default_owner_scope
|
||||
) -> WorldEntityResponse:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(
|
||||
session, payload.character_id, owner_scope=owner_scope
|
||||
)
|
||||
entity = CharacterWorldEntity(
|
||||
character_id=character.id,
|
||||
type=payload.type,
|
||||
name=payload.name,
|
||||
attributes_json=dumps(payload.attributes),
|
||||
state_type=payload.state_type,
|
||||
confidence=payload.confidence,
|
||||
evidence_ids_json=dumps(payload.evidence_ids),
|
||||
sources_json=dumps(payload.sources),
|
||||
)
|
||||
await world_repo.add_entity(session, entity)
|
||||
await _audit(session, character.id, "world.entity.created", entity.name, entity.id)
|
||||
await session.commit()
|
||||
await session.refresh(entity)
|
||||
return character_world_entity_to_response(entity)
|
||||
|
||||
|
||||
async def list_relations_page(
|
||||
character_id: str | None = None,
|
||||
*,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
owner_scope: str = settings.default_owner_scope,
|
||||
) -> Page[WorldRelationResponse]:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(session, character_id, owner_scope=owner_scope)
|
||||
safe_page = max(page, 1)
|
||||
safe_size = min(max(page_size, 1), 100)
|
||||
items, total = await world_repo.list_relations(
|
||||
session, character.id, page=safe_page, page_size=safe_size
|
||||
)
|
||||
return Page(
|
||||
items=[world_relation_to_response(item) for item in items],
|
||||
page=safe_page,
|
||||
page_size=safe_size,
|
||||
total=total,
|
||||
)
|
||||
|
||||
|
||||
async def create_relation(
|
||||
payload: WorldRelationCreateRequest, owner_scope: str = settings.default_owner_scope
|
||||
) -> WorldRelationResponse:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(
|
||||
session, payload.character_id, owner_scope=owner_scope
|
||||
)
|
||||
subject = await _ensure_owned_entity(session, character.id, payload.subject_id, "主体")
|
||||
obj = await _ensure_owned_entity(session, character.id, payload.object_id, "客体")
|
||||
relation = WorldRelation(
|
||||
character_id=character.id,
|
||||
subject_id=subject.id,
|
||||
predicate=payload.predicate,
|
||||
object_id=obj.id,
|
||||
confidence=payload.confidence,
|
||||
evidence_ids_json=dumps(payload.evidence_ids),
|
||||
)
|
||||
await world_repo.add_relation(session, relation)
|
||||
await _audit(
|
||||
session, character.id, "world.relation.created", relation.predicate, relation.id
|
||||
)
|
||||
await session.commit()
|
||||
await session.refresh(relation)
|
||||
return world_relation_to_response(relation)
|
||||
|
||||
|
||||
async def delete_relation(
|
||||
relation_id: str, owner_scope: str = settings.default_owner_scope
|
||||
) -> None:
|
||||
"""删除世界关系。"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(session, None, owner_scope=owner_scope)
|
||||
relation = await world_repo.get_relation(session, relation_id)
|
||||
if relation is None or relation.character_id != character.id:
|
||||
raise AppError("not_found", "关系不存在。")
|
||||
await world_repo.delete_relation(session, relation_id)
|
||||
await _audit(
|
||||
session, character.id, "world.relation.deleted", relation.predicate, relation_id
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_graph(
|
||||
character_id: str | None = None,
|
||||
*,
|
||||
owner_scope: str = settings.default_owner_scope,
|
||||
) -> WorldGraphResponse:
|
||||
"""返回人物的完整世界图谱(无分页,所有节点与边),供前端渲染力导向图。"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await resolve_active_character(session, character_id, owner_scope=owner_scope)
|
||||
entities = await world_repo.list_all_entities(session, character.id)
|
||||
relations = await world_repo.list_all_relations(session, character.id)
|
||||
return WorldGraphResponse(
|
||||
nodes=[character_world_entity_to_response(e) for e in entities],
|
||||
edges=[world_relation_to_response(r) for r in relations],
|
||||
)
|
||||
|
||||
|
||||
async def _ensure_owned_entity(
|
||||
session: AsyncSession, character_id: str, entity_id: str, role: str
|
||||
) -> CharacterWorldEntity:
|
||||
entity = await world_repo.get_entity(session, entity_id)
|
||||
if entity is None or entity.character_id != character_id:
|
||||
raise AppError("not_found", f"关系{role}实体不存在。")
|
||||
return entity
|
||||
|
||||
|
||||
async def _audit(
|
||||
session: AsyncSession,
|
||||
character_id: str,
|
||||
action: str,
|
||||
summary: str,
|
||||
resource_id: str,
|
||||
) -> None:
|
||||
await record_audit_event(
|
||||
session,
|
||||
action=action,
|
||||
character_id=character_id,
|
||||
actor="user",
|
||||
decision="auto",
|
||||
input_summary=summary,
|
||||
resource_type="world_entity" if "entity" in action else "world_relation",
|
||||
resource_id=resource_id,
|
||||
)
|
||||
Reference in new issue
Block a user