# 故事宇宙记忆结构 ## 概述 故事宇宙用于在多次故事生成中保持角色、世界观与成长成就的连续性。每个孩子档案可以拥有多个宇宙,故事生成时可选择“延续上一个故事”,系统自动带入宇宙设定。 --- ## 一、数据库模型 ### 1.1 主表: story_universes ```sql CREATE TABLE story_universes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), child_profile_id UUID NOT NULL REFERENCES child_profiles(id) ON DELETE CASCADE, -- 宇宙基础 name VARCHAR(100) NOT NULL, -- 记忆结构 protagonist JSONB NOT NULL, -- 主角设定 recurring_characters JSONB DEFAULT '[]', world_settings JSONB DEFAULT '{}', achievements JSONB DEFAULT '[]', -- 时间戳 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_story_universes_child_id ON story_universes(child_profile_id); CREATE INDEX idx_story_universes_updated_at ON story_universes(updated_at); ``` ### 1.2 JSON 结构示例 **protagonist** ```json { "name": "小明", "role": "星际船长", "traits": ["勇敢", "好奇"], "goal": "寻找失落的星球", "backstory": "来自地球的探险家" } ``` **recurring_characters** ```json [ { "name": "星星", "role": "魔法猫咪", "traits": ["聪明", "调皮"], "relation": "伙伴", "first_story_id": "story-uuid" } ] ``` **world_settings** ```json { "world_name": "梦幻森林", "era": "童话时代", "locations": ["彩虹河", "月光山"], "rules": ["动物会说话", "星星会指路"], "tone": "温暖治愈" } ``` **achievements** ```json [ { "type": "勇气", "description": "克服了对黑暗的恐惧", "story_id": "story-uuid", "achieved_at": "2025-01-10T12:00:00Z" } ] ``` --- ## 二、SQLAlchemy 模型 ```python # backend/app/db/models.py from sqlalchemy import Column, String, ForeignKey, JSON from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship import uuid class StoryUniverse(Base): __tablename__ = "story_universes" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) child_profile_id = Column(UUID(as_uuid=True), ForeignKey("child_profiles.id", ondelete="CASCADE"), nullable=False) name = Column(String(100), nullable=False) protagonist = Column(JSON, nullable=False) recurring_characters = Column(JSON, default=list) world_settings = Column(JSON, default=dict) achievements = Column(JSON, default=list) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) child_profile = relationship("ChildProfile", back_populates="story_universes") ``` --- ## 三、Pydantic Schema ```python # backend/app/schemas/story_universe.py from pydantic import BaseModel, Field from typing import Any from uuid import UUID class StoryUniverseCreate(BaseModel): name: str = Field(..., min_length=1, max_length=100) protagonist: dict[str, Any] recurring_characters: list[dict[str, Any]] = Field(default_factory=list) world_settings: dict[str, Any] = Field(default_factory=dict) class StoryUniverseUpdate(BaseModel): name: str | None = Field(None, min_length=1, max_length=100) protagonist: dict[str, Any] | None = None recurring_characters: list[dict[str, Any]] | None = None world_settings: dict[str, Any] | None = None class StoryUniverseResponse(BaseModel): id: UUID child_profile_id: UUID name: str protagonist: dict[str, Any] recurring_characters: list[dict[str, Any]] world_settings: dict[str, Any] achievements: list[dict[str, Any]] class Config: from_attributes = True ``` --- ## 四、API 约定 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/profiles/{id}/universes` | 获取孩子的故事宇宙列表 | | POST | `/api/profiles/{id}/universes` | 创建新宇宙 | | GET | `/api/universes/{id}` | 获取宇宙详情 | | PUT | `/api/universes/{id}` | 更新宇宙设定 | | POST | `/api/universes/{id}/achievements` | 添加成就 | --- ## 五、业务规则 - **延续故事**: “延续上一个故事”默认选最近更新的宇宙(按 `updated_at` 倒序)。 - **成就追加**: 新成就追加到 `achievements`,以 `type + description` 去重。 - **成长轨迹**: 成就保留顺序,优先展示最新项。 --- ## 六、Prompt 集成 当选择宇宙时,生成 Prompt 需带入宇宙记忆: ``` 【故事宇宙】 - 主角设定: {protagonist} - 常驻角色: {recurring_characters} - 世界观: {world_settings} - 已获成就: {achievements} ``` 未选择宇宙时,提示词忽略该块,避免混淆。 --- ## 七、数据迁移示例 ```python # backend/alembic/versions/xxx_add_story_universes.py def upgrade(): op.create_table( "story_universes", sa.Column("id", sa.UUID(), nullable=False), sa.Column("child_profile_id", sa.UUID(), nullable=False), sa.Column("name", sa.String(100), nullable=False), sa.Column("protagonist", sa.JSON(), nullable=False), sa.Column("recurring_characters", sa.JSON(), server_default='[]'), sa.Column("world_settings", sa.JSON(), server_default='{}'), sa.Column("achievements", sa.JSON(), server_default='[]'), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()), sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()), sa.ForeignKeyConstraint(["child_profile_id"], ["child_profiles.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index("idx_story_universes_child_id", "story_universes", ["child_profile_id"]) op.create_index("idx_story_universes_updated_at", "story_universes", ["updated_at"]) def downgrade(): op.drop_index("idx_story_universes_updated_at") op.drop_index("idx_story_universes_child_id") op.drop_table("story_universes") ``` --- ## 八、权限与安全 - 宇宙数据必须通过 `child_profile_id` 归属校验,确保仅拥有者可访问。 - 删除用户或档案时,级联删除所有宇宙数据。 --- ## 九、相关文档 - [孩子档案数据模型](./CHILD-PROFILE-MODEL.md) - [记忆智能系统 PRD](./MEMORY-INTELLIGENCE-PRD.md)