"""人物 API schema。""" from typing import Any from pydantic import BaseModel, ConfigDict, Field class CharacterResponse(BaseModel): model_config = ConfigDict( json_schema_extra={ "examples": [ { "id": "character_ec83d55746c54e47ab6f98d683032626", "name": "default", "display_name": "魂镜默认人物", "status": "active", "language": "zh-CN", "profile": {"bio": "本地 MVP 默认人物。"}, "model_policy": {"default_model_hint": "deepseek-v4-flash"}, "privacy_level": "normal", "owner_scope": "local-user", "is_system_template": False, "source_template_id": "character_template_default", "created_at": "2026-06-19T09:00:00.000000Z", "updated_at": "2026-06-19T09:00:00.000000Z", } ] } ) id: str = Field(description="人物 ID,前缀为 character_。") name: str = Field(description="人物内部名称。") display_name: str | None = Field(description="人物显示名称。") status: str = Field(description="人物状态,取值见契约字典 character.status。") language: str = Field(description="人物默认语言。") profile: dict[str, Any] = Field(description="人物档案 JSON。") model_policy: dict[str, Any] = Field(description="人物模型策略。") privacy_level: str = Field(description="隐私等级:normal / sensitive / local_only。") owner_scope: str = Field(description="归属范围;当前本地 MVP 为 local-user。") is_system_template: bool = Field(description="是否系统人物模板;active API 通常返回 false。") source_template_id: str | None = Field( description="来源系统模板人物 ID;非模板复制人物通常有值。" ) avatar_url: str | None = Field( default=None, description="角色头像 URL。无头像时为 null。" ) created_at: str = Field(description="创建时间,UTC ISO 8601 字符串。") updated_at: str = Field(description="更新时间,UTC ISO 8601 字符串。") class CharacterSummaryResponse(BaseModel): """人物列表项(api-spec §6 列出人物)。""" id: str = Field(description="人物 ID,前缀为 character_。") name: str = Field(description="人物内部名称。") display_name: str | None = Field(description="人物显示名称。") status: str = Field(description="人物状态,取值见契约字典 character.status。") memory_count: int = Field(default=0, description="人物关联的未删除记忆数量。") personality_version: int = Field(default=0, description="当前人格版本号;尚未形成人格时为 0。") activated_at: str | None = Field( default=None, description="最近激活时间,UTC ISO 8601 字符串。" ) class CreateCharacterRequest(BaseModel): name: str = Field(min_length=1, description="人物内部名称,必填。") display_name: str | None = Field(default=None, description="可选显示昵称。") template_key: str | None = Field( default=None, description="系统模板 key;传此值则从模板创建(继承档案/模型策略,status=active)。", ) profile: dict[str, Any] | None = Field( default=None, description="人物档案,可含 birthday/height/weight/background/speaking_style。", ) model_policy: dict[str, Any] | None = Field( default=None, description="模型策略,可含 default_chat_model_hint / memory_extraction_model_hint 等。", ) class UpdateProfileRequest(BaseModel): """更新人物档案(api-spec §6 PATCH /characters/{id}/profile)。手动编辑视为用户确认。""" display_name: str | None = Field(default=None, description="可选显示昵称。") birthday: str | None = Field(default=None) height: str | None = Field(default=None) weight: str | None = Field(default=None) description: str | None = Field(default=None, description="一句话简介。") background: str | None = Field(default=None) speaking_style: str | None = Field(default=None, description="表达风格。") temperament: str | None = Field(default=None, description="性情气质。") hobbies: list[str] | None = Field(default=None, description="兴趣爱好列表。") values: list[str] | None = Field(default=None, description="价值观列表。") class UpdateModelPolicyRequest(BaseModel): """更新人物模型策略(api-spec §6 PATCH /characters/{id}/model-policy)。""" default_chat_model_hint: str | None = Field(default=None) memory_extraction_model_hint: str | None = Field(default=None) personality_analysis_model_hint: str | None = Field(default=None) embedding_model_hint: str | None = Field(default=None) privacy_level: str | None = Field(default=None) class ActivateCharacterResponse(BaseModel): """激活人物响应(api-spec §6 POST /characters/{id}/activate)。""" active_character_id: str = Field(description="当前激活人物 ID。") status: str = Field(description="激活后状态,恒为 active。") class CharacterTemplateSummaryResponse(BaseModel): """系统模板列表项(GET /character-templates)。""" key: str = Field(description="模板唯一标识(template_key)。") name: str = Field(description="模板人物内部名称。") display_name: str | None = Field(description="模板人物显示昵称。") description: str | None = Field(description="档案中的一句话描述(profile.description)。") temperament: str | None = Field(description="档案中的性情气质(profile.temperament)。") speaking_style: str | None = Field(description="档案中的说话风格(profile.speaking_style)。") language: str = Field(description="默认语言。") class ImportCardResponse(BaseModel): """角色卡导入结果。""" character_id: str = Field(description="新建人物 ID。") name: str = Field(description="人物名称。") display_name: str | None = Field(default=None, description="显示名(V3 nickname 优先)。") lorebook_id: str | None = Field( default=None, description="导入的世谱 ID(character_book);无则为 null。" ) entry_count: int = Field(default=0, description="世谱条目数。")