Files
hunjing/backend/app/services/character_templates.py
T
2026-08-21 16:00:13 +08:00

97 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""默认人物模板加载器:从 ``character_templates.yaml`` 读取系统模板定义。
按项目约定,此文件放在应用服务层(``app/services/``),而非 ``core/config/``,
因为人物模板定义属于业务数据(出厂产品内容),而非部署环境配置。
"""
from __future__ import annotations
from typing import Any
import yaml
from pydantic import BaseModel, ConfigDict, Field
from app.core.config import settings
from app.core.errors import AppError
class CharacterTemplateConfig(BaseModel):
"""一条系统模板的配置定义。"""
model_config = ConfigDict(extra="forbid")
key: str = Field(description="模板唯一标识,对应 ORM 的 template_key。")
name: str = Field(description="模板人物内部名称。")
display_name: str | None = Field(default=None, description="模板人物显示昵称。")
language: str = Field(default="zh-CN", description="默认语言。")
privacy_level: str = Field(default="normal", description="隐私等级。")
is_default: bool = Field(
default=False,
description="注册新用户时是否以此模板创建默认角色。至多一个模板为 true,多个则取第一个。",
)
profile: dict[str, Any] = Field(
default_factory=dict,
description="人物档案,可含 background / speaking_style 等(不含 birthday——由创建时兜底)。",
)
model_policy: dict[str, Any] = Field(
default_factory=dict,
description="模型策略(不含 default_chat_model_hint——启动时从 settings 补充)。",
)
class CharacterTemplatesFile(BaseModel):
"""配置文件顶层结构。"""
model_config = ConfigDict(extra="forbid")
version: int = Field(description="配置文件版本号,当前仅支持 1。")
templates: list[CharacterTemplateConfig] = Field(description="系统模板定义列表。")
def load_character_templates() -> dict[str, CharacterTemplateConfig]:
"""读 ``character_templates.yaml`` → parse → validate → ``{key → config}`` 字典。
Returns:
空字典:文件不存在或内容为空时返回空。
Raises:
AppError: 文件存在但 YAML 解析失败或 version 不匹配。
"""
path = settings.resolved_character_templates_path
if not path.exists():
return {}
try:
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
config = CharacterTemplatesFile.model_validate(raw)
except AppError:
raise
except Exception as exc:
raise AppError(
"config_error",
"人物模板配置文件解析失败。",
details={"path": str(path)},
) from exc
if config.version != 1:
raise AppError(
"config_error",
f"仅支持 character_templates.yaml version: 1,当前为 {config.version}。",
details={"path": str(path)},
)
return {t.key: t for t in config.templates}
def get_default_template() -> CharacterTemplateConfig:
"""便利函数:取 ``key='default'`` 的系统模板配置。
Raises:
AppError: 加载失败或缺少 default 模板。
"""
templates = load_character_templates()
template = templates.get("default")
if template is None:
raise AppError(
"config_error",
"人物模板配置中缺少 key='default' 的模板。",
)
return template