136 lines
3.6 KiB
Python
136 lines
3.6 KiB
Python
"""Safety helpers for child-friendly voice co-creation sessions."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
UNSAFE_KEYWORD_GROUPS: dict[str, tuple[str, ...]] = {
|
|
"violence": (
|
|
"打死",
|
|
"杀掉",
|
|
"砍伤",
|
|
"流很多血",
|
|
"炸弹",
|
|
"爆炸",
|
|
"开枪",
|
|
"刀子",
|
|
"互相打",
|
|
),
|
|
"horror": (
|
|
"鬼屋",
|
|
"鬼怪",
|
|
"僵尸",
|
|
"诅咒",
|
|
"恶魔",
|
|
"吃人",
|
|
"恐怖",
|
|
"吓死人",
|
|
),
|
|
"danger": (
|
|
"毒药",
|
|
"绑架",
|
|
"自杀",
|
|
"跳楼",
|
|
"伤害自己",
|
|
"把人关起来",
|
|
),
|
|
"adult": (
|
|
"色情",
|
|
"裸",
|
|
"亲热",
|
|
"不穿衣服",
|
|
),
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VoiceSafetyResult:
|
|
"""Result of one voice safety evaluation."""
|
|
|
|
is_safe: bool
|
|
flags: list[str]
|
|
replacement_text: str | None = None
|
|
message: str | None = None
|
|
|
|
|
|
def _collect_safety_flags(text: str) -> list[str]:
|
|
normalized = text.replace(" ", "").strip()
|
|
flags: list[str] = []
|
|
if not normalized:
|
|
return flags
|
|
|
|
for flag, keywords in UNSAFE_KEYWORD_GROUPS.items():
|
|
if any(keyword in normalized for keyword in keywords):
|
|
flags.append(flag)
|
|
return flags
|
|
|
|
|
|
def _redirect_prefix(flags: list[str]) -> str:
|
|
if "adult" in flags:
|
|
return "这个方向不适合小朋友的睡前故事。"
|
|
if "danger" in flags or "violence" in flags:
|
|
return "这个方向有点太危险了。"
|
|
if "horror" in flags:
|
|
return "这个方向有点太吓人了。"
|
|
return "这个方向现在不太适合继续讲下去。"
|
|
|
|
|
|
def build_child_safe_redirect(flags: list[str]) -> str:
|
|
"""Build a child-friendly redirect prompt after an unsafe request."""
|
|
|
|
return (
|
|
f"{_redirect_prefix(flags)}"
|
|
"我们把它改成温柔、安全、适合小朋友的冒险吧。"
|
|
"你可以试试说:让小伙伴一起想办法、让事情变得更明亮,或者让新朋友来帮忙。"
|
|
)
|
|
|
|
|
|
def build_safe_story_fallback(*, premise: str | None = None) -> str:
|
|
"""Build a safe replacement narrative segment for unsafe assistant output."""
|
|
|
|
subject = (premise or "小伙伴们").strip()
|
|
if len(subject) > 12:
|
|
subject = subject[:12]
|
|
|
|
return (
|
|
f"{subject}决定把眼前的难题变成一次温柔又勇敢的冒险。"
|
|
"大家先停下来想一想,再一起找到一个善良、安全、让人安心的解决办法,"
|
|
"故事也朝着明亮的方向继续展开。"
|
|
)
|
|
|
|
|
|
def check_user_transcript_safety(transcript_text: str) -> VoiceSafetyResult:
|
|
"""Screen user transcript text before it enters the story flow."""
|
|
|
|
flags = _collect_safety_flags(transcript_text)
|
|
if not flags:
|
|
return VoiceSafetyResult(is_safe=True, flags=[])
|
|
|
|
message = build_child_safe_redirect(flags)
|
|
return VoiceSafetyResult(
|
|
is_safe=False,
|
|
flags=flags,
|
|
replacement_text=message,
|
|
message=message,
|
|
)
|
|
|
|
|
|
def check_assistant_output_safety(
|
|
assistant_text: str,
|
|
*,
|
|
premise: str | None = None,
|
|
) -> VoiceSafetyResult:
|
|
"""Screen assistant output and replace it with a child-safe segment when needed."""
|
|
|
|
flags = _collect_safety_flags(assistant_text)
|
|
if not flags:
|
|
return VoiceSafetyResult(is_safe=True, flags=[])
|
|
|
|
replacement_text = build_safe_story_fallback(premise=premise)
|
|
return VoiceSafetyResult(
|
|
is_safe=False,
|
|
flags=flags,
|
|
replacement_text=replacement_text,
|
|
message="系统已把不适合孩子的内容改写为更温和安全的版本。",
|
|
)
|