Files
hunjing/backend/app/modules/character/__init__.py
T
2026-08-21 16:00:13 +08:00

155 lines
5.2 KiB
Python

"""人物领域逻辑:纯函数与常量,无 IO。
包含角色档案处理、状态转换规则、模型策略提取等不依赖外部系统的领域逻辑。
设计见 ``docs/domain/identity-continuity.md``。
模块导入限制:禁止引入 ``repositories`` / ``db`` / ``integrations`` / ``ai_provider`` / ``httpx`` / ``sqlalchemy``。
"""
from __future__ import annotations
from datetime import date, datetime
from typing import Any
from app.repositories.json_utils import dumps, loads_dict
# ============================================================
# 常量
# ============================================================
# create_character API 构建人物时的兜底模型策略(仅能力槽标识,不映射具体模型)。
# 系统模板的模型策略由 config/character_templates.yaml 定义,不走此常量为源。
MODEL_POLICY_CREATE_DEFAULTS: dict[str, str] = {
"memory_extraction_model_hint": "memory-extraction",
"personality_analysis_model_hint": "personality-analysis",
}
DEFAULT_SYSTEM_TEMPLATE_KEY: str = "default"
SYSTEM_TEMPLATE_OWNER_SCOPE: str = "system"
# 允许的状态转换:{当前状态 → [可转换的目标状态]}
VALID_STATUS_TRANSITIONS: dict[str, list[str]] = {
"initialized": ["active", "archived"],
"active": ["paused", "archived"],
"paused": ["active", "archived"],
"archived": [], # 终态,不可再转换
}
# 可变状态列表:此列表中的状态允许 profile / model_policy 修改
MUTABLE_STATUSES: tuple[str, ...] = ("active", "paused", "initialized")
# ============================================================
# 档案处理
# ============================================================
def sanitize_profile(profile: dict[str, Any]) -> None:
"""原地转换 profile 中不可 JSON 序列化的类型(如 date / datetime)为 ISO 字符串。"""
for key, value in profile.items():
if isinstance(value, (date, datetime)):
profile[key] = value.isoformat()
def build_profile_json(profile: dict[str, Any], **extra: Any) -> str:
"""构建 profile JSON 字符串:注入额外字段并序列化。"""
merged = {**profile, **extra}
sanitize_profile(merged)
return dumps(merged)
def build_model_policy_json(
default_chat_model_hint: str | None = None,
overrides: dict[str, Any] | None = None,
) -> str:
"""构建模型策略 JSON 字符串:以默认策略为基底,叠加 overrides。"""
policy = dict(MODEL_POLICY_CREATE_DEFAULTS)
if default_chat_model_hint:
policy["default_chat_model_hint"] = default_chat_model_hint
if overrides:
policy.update(overrides)
return dumps(policy)
# ============================================================
# 模型策略提取
# ============================================================
def extract_model_hint(model_policy_json: str, setting_default: str = "") -> str:
"""从 ``model_policy_json`` 中提取 ``default_chat_model_hint``。
Args:
model_policy_json: ORM 字段 ``character.model_policy_json`` 原始 JSON 文本。
setting_default: 全局默认值(来自 ``settings.default_model_hint``)。
Returns:
提取到的模型提示名,未配置时回退 ``setting_default``。
"""
policy = loads_dict(model_policy_json)
value = policy.get("default_chat_model_hint")
return value if isinstance(value, str) and value else setting_default
# ============================================================
# 状态转换验证
# ============================================================
def validate_status_transition(current_status: str, target_status: str) -> None:
"""验证人物状态转换是否合法。
Raises:
ValueError: 转换不允许时。
"""
allowed = VALID_STATUS_TRANSITIONS.get(current_status, [])
if target_status not in allowed:
raise ValueError(
f"不允许的状态转换:{current_status!r} → {target_status!r}。"
f"允许的目标状态:{allowed or '(无,终态不可转换)'}"
)
def validate_is_mutable(status: str) -> None:
"""检查人物当前状态是否允许修改档案/策略。
Raises:
ValueError: 不可修改时。
"""
if status not in MUTABLE_STATUSES:
raise ValueError(f"状态 {status!r} 的人物不可修改。")
def require_not_archived(status: str) -> None:
"""检查人物是否已归档(终态拦截,状态无关)。"""
if status == "archived":
raise ValueError("已归档人物不可操作,请先恢复。")
# ============================================================
# 模板相关
# ============================================================
def ensure_character_mutable(is_system_template: bool) -> None:
"""系统人物模板不可修改。"""
if is_system_template:
raise ValueError("系统人物模板不可修改。")
__all__ = [
"MODEL_POLICY_CREATE_DEFAULTS",
"DEFAULT_SYSTEM_TEMPLATE_KEY",
"SYSTEM_TEMPLATE_OWNER_SCOPE",
"VALID_STATUS_TRANSITIONS",
"MUTABLE_STATUSES",
"build_model_policy_json",
"build_profile_json",
"ensure_character_mutable",
"extract_model_hint",
"require_not_archived",
"sanitize_profile",
"validate_is_mutable",
"validate_status_transition",
]