Fix/bug en zh (#382)
* [fix]The log retains genuine alerts and errors, while filtering out unnecessary noise. * [fix]Scenario English and Chinese, emotion specifications * [fix]Change the "no data" scenario from 0.0 to None
This commit is contained in:
@@ -186,7 +186,7 @@ async def get_emotion_health(
|
||||
"情绪健康指数获取成功",
|
||||
extra={
|
||||
"end_user_id": request.end_user_id,
|
||||
"health_score": data.get("health_score", 0),
|
||||
"health_score": data.get("health_score") or 0,
|
||||
"level": data.get("level", "未知")
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
包含情景记忆总览和详情查询接口
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Header
|
||||
|
||||
from app.core.error_codes import BizCode
|
||||
from app.core.language_utils import get_language_from_header
|
||||
from app.core.logging_config import get_api_logger
|
||||
from app.core.response_utils import fail, success
|
||||
from app.dependencies import get_current_user
|
||||
@@ -14,6 +15,7 @@ from app.schemas.response_schema import ApiResponse
|
||||
from app.schemas.memory_episodic_schema import (
|
||||
EpisodicMemoryOverviewRequest,
|
||||
EpisodicMemoryDetailsRequest,
|
||||
translate_episodic_type,
|
||||
)
|
||||
from app.services.memory_episodic_service import memory_episodic_service
|
||||
|
||||
@@ -84,6 +86,7 @@ async def get_episodic_memory_overview_api(
|
||||
@router.post("/details", response_model=ApiResponse)
|
||||
async def get_episodic_memory_details_api(
|
||||
request: EpisodicMemoryDetailsRequest,
|
||||
language_type: str = Header(default=None, alias="X-Language-Type"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
"""
|
||||
@@ -111,6 +114,11 @@ async def get_episodic_memory_details_api(
|
||||
summary_id=request.summary_id
|
||||
)
|
||||
|
||||
# 根据语言参数翻译 episodic_type
|
||||
language = get_language_from_header(language_type)
|
||||
if "episodic_type" in result:
|
||||
result["episodic_type"] = translate_episodic_type(result["episodic_type"], language)
|
||||
|
||||
api_logger.info(
|
||||
f"成功获取情景记忆详情: end_user_id={request.end_user_id}, summary_id={request.summary_id}"
|
||||
)
|
||||
|
||||
@@ -233,6 +233,14 @@ async def extract_ontology(
|
||||
language=language
|
||||
)
|
||||
|
||||
# 根据语言类型统一 name 字段
|
||||
# zh: name 使用 name_chinese(中文名)
|
||||
# en: name 保持原值(英文 PascalCase)
|
||||
if language == "zh":
|
||||
for cls in result.classes:
|
||||
if cls.name_chinese:
|
||||
cls.name = cls.name_chinese
|
||||
|
||||
# 构建响应
|
||||
response = ExtractionResponse(
|
||||
classes=result.classes,
|
||||
|
||||
@@ -70,8 +70,10 @@ class Neo4jSuccessNotificationFilter(logging.Filter):
|
||||
Returns:
|
||||
True表示允许记录,False表示拒绝(过滤掉)
|
||||
"""
|
||||
# 只处理 WARNING 级别的日志
|
||||
if record.levelno != logging.WARNING:
|
||||
# 只处理 INFO 和 WARNING 级别的日志
|
||||
# Neo4j 驱动对 severity='INFORMATION' 的通知使用 INFO 级别,
|
||||
# 对 severity='WARNING' 的通知使用 WARNING 级别
|
||||
if record.levelno not in (logging.INFO, logging.WARNING):
|
||||
return True
|
||||
|
||||
# 检查是否是 Neo4j 的成功通知
|
||||
@@ -110,17 +112,25 @@ class LoggingConfig:
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
|
||||
|
||||
# 为 Neo4j 驱动添加过滤器,过滤成功/信息性通知但保留真正的警告
|
||||
# Neo4j 驱动会以 WARNING 级别记录所有数据库通知,包括成功(00000)和信息性(00NA0)通知
|
||||
# 使用过滤器而不是改变日志级别,这样可以保留真正的警告和错误
|
||||
neo4j_filter = Neo4jSuccessNotificationFilter()
|
||||
for neo4j_logger_name in ["neo4j", "neo4j.io", "neo4j.pool"]:
|
||||
neo4j_logger = logging.getLogger(neo4j_logger_name)
|
||||
neo4j_logger.addFilter(neo4j_filter)
|
||||
|
||||
# 清除现有处理器
|
||||
root_logger.handlers.clear()
|
||||
|
||||
# Neo4j 通知过滤器 - 挂在 handler 上确保所有传播上来的日志都能被过滤
|
||||
neo4j_filter = Neo4jSuccessNotificationFilter()
|
||||
|
||||
# 抑制 Neo4j 通知日志
|
||||
# Neo4j 驱动内部会给 neo4j.notifications logger 配置自己的 handler,
|
||||
# 导致日志绕过根 logger 的 filter 直接输出。
|
||||
# 多管齐下确保过滤生效:
|
||||
# 1. 设置 neo4j.notifications 级别为 WARNING(过滤 INFO 级别的 00NA0 通知)
|
||||
# 2. 在所有 neo4j logger 上添加 filter(过滤 WARNING 级别的成功通知)
|
||||
# 3. 在根 handler 上也添加 filter(兜底)
|
||||
neo4j_notifications_logger = logging.getLogger("neo4j.notifications")
|
||||
neo4j_notifications_logger.setLevel(logging.WARNING)
|
||||
for neo4j_logger_name in ["neo4j", "neo4j.io", "neo4j.pool", "neo4j.notifications"]:
|
||||
neo4j_logger = logging.getLogger(neo4j_logger_name)
|
||||
neo4j_logger.addFilter(neo4j_filter)
|
||||
|
||||
# 创建格式化器
|
||||
formatter = logging.Formatter(
|
||||
fmt=settings.LOG_FORMAT,
|
||||
@@ -136,6 +146,7 @@ class LoggingConfig:
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
|
||||
console_handler.addFilter(sensitive_filter)
|
||||
console_handler.addFilter(neo4j_filter)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
# 文件处理器(带轮转)
|
||||
@@ -149,6 +160,7 @@ class LoggingConfig:
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
|
||||
file_handler.addFilter(sensitive_filter)
|
||||
file_handler.addFilter(neo4j_filter)
|
||||
root_logger.addHandler(file_handler)
|
||||
|
||||
cls._initialized = True
|
||||
|
||||
@@ -349,19 +349,39 @@ async def render_emotion_suggestions_prompt(
|
||||
import json
|
||||
|
||||
# 预处理 emotion_distribution 为 JSON 字符串
|
||||
# 如果是中文,将 emotion_distribution 的 key 翻译为中文
|
||||
emotion_distribution = health_data.get('emotion_distribution', {})
|
||||
if language == "zh":
|
||||
emotion_type_zh = {
|
||||
'joy': '喜悦', 'sadness': '悲伤', 'anger': '愤怒',
|
||||
'fear': '恐惧', 'surprise': '惊讶', 'neutral': '中性'
|
||||
}
|
||||
emotion_distribution = {
|
||||
emotion_type_zh.get(k, k): v for k, v in emotion_distribution.items()
|
||||
}
|
||||
emotion_distribution_json = json.dumps(
|
||||
health_data.get('emotion_distribution', {}),
|
||||
emotion_distribution,
|
||||
ensure_ascii=False,
|
||||
indent=2
|
||||
)
|
||||
|
||||
# 翻译 dominant_negative_emotion
|
||||
dominant_negative_translated = None
|
||||
dominant_neg = patterns.get('dominant_negative_emotion')
|
||||
if dominant_neg and language == "zh":
|
||||
emotion_type_zh_map = {
|
||||
'sadness': '悲伤', 'anger': '愤怒', 'fear': '恐惧'
|
||||
}
|
||||
dominant_negative_translated = emotion_type_zh_map.get(dominant_neg, dominant_neg)
|
||||
|
||||
template = prompt_env.get_template("generate_emotion_suggestions.jinja2")
|
||||
rendered_prompt = template.render(
|
||||
health_data=health_data,
|
||||
patterns=patterns,
|
||||
user_profile=user_profile,
|
||||
emotion_distribution_json=emotion_distribution_json,
|
||||
language=language
|
||||
language=language,
|
||||
dominant_negative_translated=dominant_negative_translated
|
||||
)
|
||||
|
||||
# 记录渲染结果到提示日志
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
{% if language == "en" %}
|
||||
You are a professional mental health consultant. Based on the following user's emotional health data and personal information, generate 3-5 personalized emotional improvement suggestions.
|
||||
|
||||
## Core Principle (Highest Priority)
|
||||
|
||||
**You must strictly base your suggestions on the emotion distribution data provided below. As long as any emotion type has a count ≥ 1, that emotion EXISTS and you must acknowledge and address it in your suggestions. You must NEVER claim an emotion is "zero" or "absent" when its count is ≥ 1.**
|
||||
|
||||
Specific rules:
|
||||
1. Carefully check the count for each emotion type in "Emotion Distribution" — count ≥ 1 means the emotion exists
|
||||
2. Even if an emotion appeared only once, you must mention it in health_summary or suggestions and provide targeted advice
|
||||
3. Never state that an emotion is "zero" or "non-existent" unless its count in the distribution data is truly 0
|
||||
4. If positive emotions (e.g., Joy) exist, health_summary must affirm this positive signal
|
||||
5. If negative emotions (e.g., Sadness, Anger, Fear) exist even once, you must provide targeted improvement suggestions
|
||||
6. A high proportion of neutral emotions does NOT mean other emotions are absent — address all non-zero emotions
|
||||
|
||||
## User Emotional Health Data
|
||||
|
||||
Health Score: {{ health_data.health_score }}/100
|
||||
Health Level: {{ health_data.level }}
|
||||
Total Emotion Records: {{ health_data.dimensions.positivity_rate.positive_count + health_data.dimensions.positivity_rate.negative_count + health_data.dimensions.positivity_rate.neutral_count }}
|
||||
|
||||
Dimension Analysis:
|
||||
- Positivity Rate: {{ health_data.dimensions.positivity_rate.score }}/100
|
||||
@@ -18,7 +31,7 @@ Dimension Analysis:
|
||||
- Resilience: {{ health_data.dimensions.resilience.score }}/100
|
||||
- Recovery Rate: {{ health_data.dimensions.resilience.recovery_rate }}
|
||||
|
||||
Emotion Distribution:
|
||||
Emotion Distribution (check each item — every emotion with count ≥ 1 must be reflected in suggestions):
|
||||
{{ emotion_distribution_json }}
|
||||
|
||||
## Emotion Pattern Analysis
|
||||
@@ -41,6 +54,7 @@ Please generate 3-5 personalized suggestions, each containing:
|
||||
5. actionable_steps: 3 specific executable steps
|
||||
|
||||
Also provide a health_summary (no more than 50 words) summarizing the user's overall emotional state.
|
||||
**The health_summary must truthfully reflect ALL non-zero emotions from the distribution data. Do not omit any emotion type that has appeared.**
|
||||
|
||||
Please return in JSON format as follows:
|
||||
{
|
||||
@@ -57,6 +71,7 @@ Please return in JSON format as follows:
|
||||
}
|
||||
|
||||
Notes:
|
||||
- CRITICAL: Any emotion with count ≥ 1 in the distribution MUST be acknowledged and addressed — never ignore or claim it is zero
|
||||
- Suggestions should be specific and actionable, avoid vague advice
|
||||
- Provide personalized suggestions based on user's interests and hobbies
|
||||
- Provide targeted suggestions for main issues (such as dominant negative emotions)
|
||||
@@ -66,10 +81,23 @@ Notes:
|
||||
{% else %}
|
||||
你是一位专业的心理健康顾问。请根据以下用户的情绪健康数据和个人信息,生成3-5条个性化的情绪改善建议。
|
||||
|
||||
## 核心原则(最高优先级)
|
||||
|
||||
**你必须严格基于下方提供的情绪分布数据来生成建议。只要某种情绪的出现次数 ≥ 1,就代表该情绪确实存在,你必须在建议中承认并回应这一情绪,绝对不能说"该情绪为零"或"没有该情绪"。**
|
||||
|
||||
具体规则:
|
||||
1. 仔细查看"情绪分布"中每种情绪的出现次数,次数 ≥ 1 即表示该情绪存在
|
||||
2. 即使某种情绪只出现了1次,也必须在 health_summary 或建议中提及并给出针对性建议
|
||||
3. 严禁在输出中声称某种情绪"为零"或"不存在",除非该情绪在分布数据中确实为0次
|
||||
4. 如果正面情绪(如喜悦)存在,health_summary 中必须肯定这一积极信号
|
||||
5. 如果负面情绪(如悲伤、愤怒、恐惧)存在,即使只有1次,也必须给出针对性的改善建议
|
||||
6. 中性情绪占比高不代表没有其他情绪,必须同时关注所有非零情绪
|
||||
|
||||
## 用户情绪健康数据
|
||||
|
||||
健康分数:{{ health_data.health_score }}/100
|
||||
健康等级:{{ health_data.level }}
|
||||
情绪记录总数:{{ health_data.dimensions.positivity_rate.positive_count + health_data.dimensions.positivity_rate.negative_count + health_data.dimensions.positivity_rate.neutral_count }}条
|
||||
|
||||
维度分析:
|
||||
- 积极率:{{ health_data.dimensions.positivity_rate.score }}/100
|
||||
@@ -83,12 +111,12 @@ Notes:
|
||||
- 恢复力:{{ health_data.dimensions.resilience.score }}/100
|
||||
- 恢复率:{{ health_data.dimensions.resilience.recovery_rate }}
|
||||
|
||||
情绪分布:
|
||||
情绪分布(请逐项检查,次数≥1的情绪都必须在建议中体现):
|
||||
{{ emotion_distribution_json }}
|
||||
|
||||
## 情绪模式分析
|
||||
|
||||
主要负面情绪:{{ patterns.dominant_negative_emotion|default('无') }}
|
||||
主要负面情绪:{{ dominant_negative_translated|default(patterns.dominant_negative_emotion)|default('无') }}
|
||||
情绪波动性:{{ patterns.emotion_volatility|default('未知') }}
|
||||
高强度情绪次数:{{ patterns.high_intensity_emotions|default([])|length }}
|
||||
|
||||
@@ -106,6 +134,7 @@ Notes:
|
||||
5. actionable_steps: 3个可执行的具体步骤
|
||||
|
||||
同时提供一个health_summary(不超过50字),概括用户的整体情绪状态。
|
||||
**health_summary 必须如实反映情绪分布中所有非零情绪的存在,不得遗漏任何已出现的情绪类型。**
|
||||
|
||||
请以JSON格式返回,格式如下:
|
||||
{
|
||||
@@ -122,9 +151,11 @@ Notes:
|
||||
}
|
||||
|
||||
注意事项:
|
||||
- 所有输出内容必须完全使用中文,严禁出现任何英文单词或短语(包括情绪类型名称如fear、sadness、anger等,必须使用对应的中文:恐惧、悲伤、愤怒等)
|
||||
- 再次强调:情绪分布中出现次数≥1的情绪必须在建议中被提及和回应,绝不能忽略或声称为零
|
||||
- 建议要具体、可执行,避免空泛
|
||||
- 结合用户的兴趣爱好提供个性化建议
|
||||
- 针对主要问题(如主要负面情绪)提供针对性建议
|
||||
- 优先级要合理分配(至少1个high,1-2个medium,其余low)
|
||||
- 优先级要合理分配(至少1个高,1-2个中,其余低)
|
||||
- 每个建议的3个步骤要循序渐进、易于实施
|
||||
{% endif %}
|
||||
|
||||
@@ -25,6 +25,31 @@ type_mapping = {
|
||||
"Condition": "条件实体节点",
|
||||
"Numeric": "数值实体节点"
|
||||
}
|
||||
EPISODIC_TYPE_MAPPING = {
|
||||
"conversation": "对话",
|
||||
"project_work": "项目/工作",
|
||||
"learning": "学习",
|
||||
"decision": "决策",
|
||||
"important_event": "重要事件",
|
||||
}
|
||||
|
||||
|
||||
def translate_episodic_type(episodic_type: str, language: str = "zh") -> str:
|
||||
"""
|
||||
根据语言参数翻译情景类型
|
||||
|
||||
Args:
|
||||
episodic_type: 英文枚举值 (conversation, project_work, etc.)
|
||||
language: 语言类型 ("zh" 中文, "en" 英文)
|
||||
|
||||
Returns:
|
||||
翻译后的类型字符串
|
||||
"""
|
||||
if language == "en":
|
||||
return episodic_type
|
||||
return EPISODIC_TYPE_MAPPING.get(episodic_type, episodic_type)
|
||||
|
||||
|
||||
class EmotionType(ABC):
|
||||
JOY_TYPE = "joy"
|
||||
SURPRISE_TYPE = "surprise"
|
||||
@@ -51,7 +76,6 @@ class EpisodicMemoryOverviewRequest(BaseModel):
|
||||
"""情景记忆总览查询请求"""
|
||||
|
||||
end_user_id: str = Field(..., description="终端用户ID")
|
||||
language_type: Optional[str] = Field("zh", description="语言类型(zh/en)")
|
||||
time_range: str = Field(
|
||||
default="all",
|
||||
description="时间范围筛选,可选值:all, today, this_week, this_month"
|
||||
@@ -71,4 +95,3 @@ class EpisodicMemoryDetailsRequest(BaseModel):
|
||||
|
||||
end_user_id: str = Field(..., description="终端用户ID")
|
||||
summary_id: str = Field(..., description="情景记忆摘要ID")
|
||||
language_type: Optional[str] = Field("zh", description="语言类型(zh/en)")
|
||||
|
||||
@@ -220,14 +220,16 @@ class EmotionAnalyticsService:
|
||||
"""计算积极率
|
||||
|
||||
根据情绪类型分类正面、负面和中性情绪,计算积极率。
|
||||
公式:(正面数 / (正面数 + 负面数)) * 100
|
||||
当存在非中性情绪时:(正面数 / (正面数 + 负面数)) * 100
|
||||
当只有中性情绪时:基于中性情绪的存在给出基准分数
|
||||
当完全没有情绪数据时:score 为 None,表示无法计算
|
||||
|
||||
Args:
|
||||
emotions: 情绪数据列表,每个包含 emotion_type 字段
|
||||
|
||||
Returns:
|
||||
Dict: 包含积极率计算结果:
|
||||
- score: 积极率分数(0-100)
|
||||
- score: 积极率分数(0-100),无数据时为 None
|
||||
- positive_count: 正面情绪数量
|
||||
- negative_count: 负面情绪数量
|
||||
- neutral_count: 中性情绪数量
|
||||
@@ -245,14 +247,19 @@ class EmotionAnalyticsService:
|
||||
total_non_neutral = positive_count + negative_count
|
||||
if total_non_neutral > 0:
|
||||
score = (positive_count / total_non_neutral) * 100
|
||||
elif neutral_count > 0:
|
||||
# 只有中性情绪,说明情绪状态平稳,给予基准分 50
|
||||
score = 50.0
|
||||
else:
|
||||
score = 50.0 # 如果没有非中性情绪,默认为50
|
||||
# 完全没有情绪数据,无法计算积极率
|
||||
score = None
|
||||
|
||||
score_display = f"{score:.2f}" if score is not None else "N/A"
|
||||
logger.debug(f"积极率计算: positive={positive_count}, negative={negative_count}, "
|
||||
f"neutral={neutral_count}, score={score:.2f}")
|
||||
f"neutral={neutral_count}, score={score_display}")
|
||||
|
||||
return {
|
||||
"score": round(score, 2),
|
||||
"score": round(score, 2) if score is not None else None,
|
||||
"positive_count": positive_count,
|
||||
"negative_count": negative_count,
|
||||
"neutral_count": neutral_count
|
||||
@@ -385,12 +392,12 @@ class EmotionAnalyticsService:
|
||||
if not emotions:
|
||||
logger.warning(f"用户 {end_user_id} 在时间范围 {time_range} 内没有情绪数据")
|
||||
return {
|
||||
"health_score": 0.0,
|
||||
"health_score": None,
|
||||
"level": "无数据",
|
||||
"dimensions": {
|
||||
"positivity_rate": {"score": 0.0, "positive_count": 0, "negative_count": 0, "neutral_count": 0},
|
||||
"stability": {"score": 0.0, "std_deviation": 0.0},
|
||||
"resilience": {"score": 0.0, "recovery_rate": 0.0}
|
||||
"positivity_rate": {"score": None, "positive_count": 0, "negative_count": 0, "neutral_count": 0},
|
||||
"stability": {"score": None, "std_deviation": 0.0},
|
||||
"resilience": {"score": None, "recovery_rate": 0.0}
|
||||
},
|
||||
"emotion_distribution": {},
|
||||
"time_range": time_range
|
||||
@@ -403,8 +410,10 @@ class EmotionAnalyticsService:
|
||||
|
||||
# 计算综合健康分数
|
||||
# 公式:positivity_rate * 0.4 + stability * 0.3 + resilience * 0.3
|
||||
# 如果积极率无法计算(无数据),视为 0 参与加权
|
||||
positivity_score = positivity_rate["score"] if positivity_rate["score"] is not None else 0.0
|
||||
health_score = (
|
||||
positivity_rate["score"] * 0.4 +
|
||||
positivity_score * 0.4 +
|
||||
stability["score"] * 0.3 +
|
||||
resilience["score"] * 0.3
|
||||
)
|
||||
@@ -700,7 +709,7 @@ class EmotionAnalyticsService:
|
||||
Returns:
|
||||
EmotionSuggestionsResponse: 默认建议
|
||||
"""
|
||||
health_score = health_data.get('health_score', 0)
|
||||
health_score = health_data.get('health_score') or 0
|
||||
|
||||
if language == "en":
|
||||
if health_score >= 80:
|
||||
|
||||
Reference in New Issue
Block a user