2026-04-04 09:04:04 +00:00
|
|
|
|
"""
|
|
|
|
|
|
儿童心理陪伴 - 筛查器
|
|
|
|
|
|
基于 MiniMax API,对儿童对话进行心理问题筛查
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
2026-04-04 09:11:01 +00:00
|
|
|
|
import re
|
2026-04-04 09:04:04 +00:00
|
|
|
|
import json
|
|
|
|
|
|
import requests
|
|
|
|
|
|
from typing import Literal
|
|
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
# 数据模型
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class ConcernCategory(str):
|
|
|
|
|
|
"""心理问题类别"""
|
|
|
|
|
|
NONE = "none"
|
|
|
|
|
|
BULLYING = "bullying" # 校园霸凌/同伴冲突
|
|
|
|
|
|
DEPRESSION = "depression" # 抑郁情绪
|
|
|
|
|
|
ANXIETY = "anxiety" # 焦虑/恐惧
|
|
|
|
|
|
FAMILY_CONFLICT = "family_conflict" # 家庭矛盾
|
|
|
|
|
|
SELF_ESTEEM = "self_esteem" # 自卑/自我否定
|
|
|
|
|
|
TRAUMA = "trauma" # 创伤事件
|
|
|
|
|
|
SOCIAL_ISOLATION = "social_isolation" # 社交孤立
|
|
|
|
|
|
OTHER = "other" # 其他
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScreeningResult(BaseModel):
|
|
|
|
|
|
"""筛查结果"""
|
|
|
|
|
|
detected: bool = Field(description="是否检测到心理问题")
|
|
|
|
|
|
category: str = Field(default=ConcernCategory.NONE, description="问题类别")
|
|
|
|
|
|
severity: Literal["none", "low", "medium", "high"] = Field(
|
|
|
|
|
|
default="none", description="严重程度"
|
|
|
|
|
|
)
|
|
|
|
|
|
summary: str = Field(default="", description="简要描述检测到的问题")
|
|
|
|
|
|
suggestion: str = Field(default="", description="建议行动")
|
|
|
|
|
|
raw_response: str = Field(default="", description="模型原始响应(调试用)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
# 筛查系统提示词
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
SYSTEM_PROMPT = """你是一个专业的儿童心理咨询师助手,专注于分析3-8岁儿童的对话内容,识别潜在的心理需求或问题。
|
|
|
|
|
|
|
|
|
|
|
|
## 你的任务
|
|
|
|
|
|
分析给定的儿童对话上下文,判断是否存在以下心理问题类别:
|
|
|
|
|
|
|
|
|
|
|
|
1. **bullying** - 霸凌/同伴冲突:孩子表达被欺负、被嘲笑、被孤立、被人威胁等
|
|
|
|
|
|
2. **depression** - 抑郁情绪:孩子表达悲伤、绝望、无助、对事物失去兴趣、提到"不想活了"等
|
|
|
|
|
|
3. **anxiety** - 焦虑/恐惧:孩子表达担心、害怕、做噩梦、回避某些情境等
|
|
|
|
|
|
4. **family_conflict** - 家庭矛盾:孩子表达父母争吵、离婚担心、被忽视、被严厉惩罚等
|
|
|
|
|
|
5. **self_esteem** - 自卑/自我否定:孩子表达"我不行"、"没人喜欢我"、"我太笨了"等
|
|
|
|
|
|
6. **trauma** - 创伤事件:孩子描述意外事故、暴力事件、亲人离世等创伤性经历
|
|
|
|
|
|
7. **social_isolation** - 社交孤立:孩子表达没有朋友、被排斥、孤独感等
|
|
|
|
|
|
8. **other** - 其他值得关注的心理需求
|
|
|
|
|
|
|
|
|
|
|
|
## 输出格式
|
|
|
|
|
|
请严格按以下JSON格式返回(不要添加任何额外内容):
|
|
|
|
|
|
{
|
|
|
|
|
|
"detected": true/false,
|
|
|
|
|
|
"category": "具体类别",
|
|
|
|
|
|
"severity": "none/low/medium/high",
|
|
|
|
|
|
"summary": "一句话描述检测到的问题",
|
|
|
|
|
|
"suggestion": "建议的应对方式(简短,1-2句话)"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
## 判断标准
|
|
|
|
|
|
- **low**: 轻微迹象,需要关注但无需立即介入
|
|
|
|
|
|
- **medium**: 中度迹象,建议与家长沟通
|
|
|
|
|
|
- **high**: 严重迹象,需要专业干预
|
|
|
|
|
|
|
|
|
|
|
|
如果对话内容完全正常,没有任何心理问题迹象,返回:
|
|
|
|
|
|
{
|
|
|
|
|
|
"detected": false,
|
|
|
|
|
|
"category": "none",
|
|
|
|
|
|
"severity": "none",
|
|
|
|
|
|
"summary": "未检测到心理问题",
|
|
|
|
|
|
"suggestion": ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
注意:
|
|
|
|
|
|
- 只关注确实存在问题的迹象,不要过度解读
|
|
|
|
|
|
- 儿童的语言表达可能不精确,需要结合上下文判断
|
|
|
|
|
|
- 正常的情绪表达(偶尔哭、发脾气)不构成问题"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
# 筛查器
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
class PsychoScreener:
|
|
|
|
|
|
"""
|
|
|
|
|
|
儿童心理问题筛查器
|
|
|
|
|
|
|
|
|
|
|
|
使用方法:
|
|
|
|
|
|
screener = PsychoScreener(api_key="your-api-key")
|
|
|
|
|
|
result = screener.screen("今天小明打我了,我很伤心")
|
|
|
|
|
|
if result.detected:
|
|
|
|
|
|
print(f"检测到问题:{result.summary}")
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
api_key: str | None = None,
|
|
|
|
|
|
model: str = "MiniMax-M2.5",
|
|
|
|
|
|
base_url: str = "https://api.minimaxi.com/v1/text/chatcompletion_v2",
|
|
|
|
|
|
):
|
|
|
|
|
|
self.api_key = api_key or os.environ.get("MINIMAX_API_KEY", "")
|
|
|
|
|
|
self.model = model
|
|
|
|
|
|
self.base_url = base_url
|
|
|
|
|
|
|
|
|
|
|
|
if not self.api_key:
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
"MiniMax API key is required. "
|
|
|
|
|
|
"Set MINIMAX_API_KEY env var or pass api_key parameter."
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _call_minimax(self, messages: list[dict]) -> str:
|
|
|
|
|
|
"""调用 MiniMax API"""
|
|
|
|
|
|
headers = {
|
|
|
|
|
|
"Authorization": f"Bearer {self.api_key}",
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"model": self.model,
|
|
|
|
|
|
"messages": messages,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
response = requests.post(
|
|
|
|
|
|
self.base_url,
|
|
|
|
|
|
headers=headers,
|
|
|
|
|
|
json=payload,
|
|
|
|
|
|
timeout=30,
|
|
|
|
|
|
)
|
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
|
|
|
|
|
|
# 兼容不同返回格式
|
|
|
|
|
|
if "choices" in data:
|
|
|
|
|
|
return data["choices"][0]["message"]["content"]
|
|
|
|
|
|
elif "output" in data:
|
|
|
|
|
|
return data["output"]
|
|
|
|
|
|
return str(data)
|
|
|
|
|
|
|
|
|
|
|
|
def screen(self, context: str) -> ScreeningResult:
|
|
|
|
|
|
"""
|
|
|
|
|
|
对给定的对话上下文进行心理问题筛查
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
context: 儿童的对话上下文(可以是多轮对话的汇总文本)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
ScreeningResult: 包含检测结果的数据模型
|
|
|
|
|
|
"""
|
|
|
|
|
|
messages = [
|
|
|
|
|
|
{"role": "system", "content": SYSTEM_PROMPT},
|
|
|
|
|
|
{"role": "user", "content": f"请分析以下儿童对话内容:\n\n{context}"},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
raw_response = self._call_minimax(messages)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
return ScreeningResult(
|
|
|
|
|
|
detected=False,
|
|
|
|
|
|
category=ConcernCategory.OTHER,
|
|
|
|
|
|
severity="none",
|
|
|
|
|
|
summary=f"API调用失败: {str(e)}",
|
|
|
|
|
|
suggestion="",
|
|
|
|
|
|
raw_response=str(e),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 尝试解析 JSON
|
|
|
|
|
|
try:
|
|
|
|
|
|
content = raw_response.strip()
|
2026-04-04 09:11:01 +00:00
|
|
|
|
|
|
|
|
|
|
# 策略1:查找 ```json ... ``` 代码块(Markdown 格式)
|
|
|
|
|
|
md_match = re.search(r"```json\s*(.*?)\s*```", content, re.DOTALL)
|
|
|
|
|
|
if md_match:
|
|
|
|
|
|
content = md_match.group(1).strip()
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 策略2:查找原始 JSON 对象 { ... }
|
|
|
|
|
|
json_start = content.find('{"')
|
|
|
|
|
|
json_end = content.rfind('"}')
|
|
|
|
|
|
if json_start != -1 and json_end != -1 and json_end > json_start:
|
|
|
|
|
|
content = content[json_start:json_end + 2]
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 策略3:去掉思考过程标记,取最后一个 { 之后的内容
|
|
|
|
|
|
last_brace = content.rfind('{')
|
|
|
|
|
|
if last_brace != -1:
|
|
|
|
|
|
content = content[last_brace:]
|
|
|
|
|
|
|
2026-04-04 09:04:04 +00:00
|
|
|
|
parsed = json.loads(content)
|
|
|
|
|
|
return ScreeningResult(
|
|
|
|
|
|
detected=parsed.get("detected", False),
|
|
|
|
|
|
category=parsed.get("category", ConcernCategory.NONE),
|
|
|
|
|
|
severity=parsed.get("severity", "none"),
|
|
|
|
|
|
summary=parsed.get("summary", ""),
|
|
|
|
|
|
suggestion=parsed.get("suggestion", ""),
|
|
|
|
|
|
raw_response=raw_response,
|
|
|
|
|
|
)
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
# 无法解析 JSON,返回原始内容
|
|
|
|
|
|
return ScreeningResult(
|
|
|
|
|
|
detected=False,
|
|
|
|
|
|
category=ConcernCategory.OTHER,
|
|
|
|
|
|
severity="none",
|
|
|
|
|
|
summary="无法解析模型响应",
|
|
|
|
|
|
suggestion="",
|
|
|
|
|
|
raw_response=raw_response,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def build_response_prefix(self, result: ScreeningResult) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
根据筛查结果构建响应前缀
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
result: 筛查结果
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: 如果检测到问题,返回前缀字符串;否则返回空字符串
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not result.detected:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
return f"【已发现特定心理问题】类别:{result.category},严重程度:{result.severity},描述:{result.summary}"
|