155 lines
5.2 KiB
Python
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",
|
|
]
|