diff --git a/src/person_info/relationship_fetcher.py b/src/person_info/relationship_fetcher.py index e5df2c651..586f7573c 100644 --- a/src/person_info/relationship_fetcher.py +++ b/src/person_info/relationship_fetcher.py @@ -173,24 +173,34 @@ class RelationshipFetcher: if impression: relation_parts.append(f"\n你对{person_name}的印象:\n{impression}") - # 5. 用户偏好关键词 + # 5. 用户偏好关键词(仅显示真实兴趣爱好) if rel_data.get("preference_keywords"): keywords_list = [kw.strip() for kw in rel_data["preference_keywords"].split(",") if kw.strip()] - if keywords_list: - keywords_str = "、".join(keywords_list) - relation_parts.append(f"\n{person_name}的偏好和兴趣:{keywords_str}") + # 过滤掉明显不是兴趣爱好的词 + filtered_keywords = [] + for kw in keywords_list: + kw_lower = kw.lower() + # 排除聊天互动、情感需求等不是真实兴趣的词汇 + if not any(excluded in kw_lower for excluded in [ + '亲亲', '撒娇', '被宠', '被夸', '聊天', '互动', '关心', '专注', '需要' + ]): + filtered_keywords.append(kw) + + if filtered_keywords: + keywords_str = "、".join(filtered_keywords) + relation_parts.append(f"\n{person_name}的兴趣爱好:{keywords_str}") - # 6. 关键信息 - if rel_data.get("key_facts"): - try: - import orjson - facts = orjson.loads(rel_data["key_facts"]) - if facts and isinstance(facts, list): - facts_lines = self._format_key_facts(facts, person_name) - if facts_lines: - relation_parts.append(f"\n你记住的关于{person_name}的重要信息:\n{facts_lines}") - except Exception: - pass + # 6. 关键信息 - 暂时隐藏,防止显示不准确的推测信息 + # if rel_data.get("key_facts"): + # try: + # import orjson + # facts = orjson.loads(rel_data["key_facts"]) + # if facts and isinstance(facts, list): + # facts_lines = self._format_key_facts(facts, person_name) + # if facts_lines: + # relation_parts.append(f"\n你记住的关于{person_name}的重要信息:\n{facts_lines}") + # except Exception: + # pass except Exception as e: logger.error(f"查询UserRelationships表失败: {e}") diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index 44ef212ce..bf36bcc14 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -838,11 +838,20 @@ class BaseAction(ABC): 只需要回答"是"或"否",不要有其他内容。 """ - # 调用 LLM 进行判断 - response, _ = await llm_judge_model.generate_response_async(prompt=prompt) - response = response.strip().lower() - - should_activate = "是" in response or "yes" in response or "true" in response + # 调用 LLM 进行判断,设置7秒超时避免长时间等待 + import asyncio + response = "" # 初始化response变量 + try: + response, _ = await asyncio.wait_for( + llm_judge_model.generate_response_async(prompt=prompt), + timeout=7.0 + ) + response = response.strip().lower() + should_activate = "是" in response or "yes" in response or "true" in response + except asyncio.TimeoutError: + logger.warning(f"{self.log_prefix} LLM 判断激活超时(7秒),默认激活以避免阻塞") + # 超时时默认激活,交给后续决策系统处理 + should_activate = True logger.debug( f"{self.log_prefix} LLM 判断结果: 响应='{response}', 结果={'激活' if should_activate else '不激活'}" diff --git a/src/plugins/built_in/affinity_flow_chatter/tools/user_profile_tool.py b/src/plugins/built_in/affinity_flow_chatter/tools/user_profile_tool.py index dfa7d8f96..9fc7c016b 100644 --- a/src/plugins/built_in/affinity_flow_chatter/tools/user_profile_tool.py +++ b/src/plugins/built_in/affinity_flow_chatter/tools/user_profile_tool.py @@ -43,32 +43,34 @@ class UserProfileTool(BaseTool): """ name = "update_user_profile" - description = """记录或更新你对某个人的认识。 + description = """⚠️ 严格限制使用场景 ⚠️ -使用场景: -1. TA告诉你个人信息(生日、职业、城市等)→ 填 key_info_type 和 key_info_value -2. TA的信息有变化(搬家、换工作等)→ 会自动更新旧信息 -3. 你对TA有了新的认识或感受 → 填 impression_hint -4. 想记录TA真正的兴趣爱好 → 填 preference +记录或更新你对某个人的认识 - 仅限重要信息! -## ⛔ 别名(alias)规则: -- 只填TA明确要求被称呼的真实昵称 -- 必须是TA主动说"叫我xxx"或"我的昵称是xxx" -- 聊天中的玩笑称呼、撒娇称呼、临时戏称一律不填 -- 你给TA起的爱称不算别名 +## 📋 明确的使用场景(必须符合其中一种): +1. TA明确说出具体个人信息("我生日是3月15日"、"我在北京工作"、"我是程序员")→ 填 key_info +2. TA的重要信息发生变化("我搬到上海了"、"我换工作了")→ 更新 key_info +3. TA主动深度自我揭露重大个人经历或核心价值观 → 慎重考虑填 impression_hint +4. TA明确表达具体的现实兴趣爱好("我喜欢摄影"、"我在学编程")→ 填 preference -## ⛔ 偏好(preference)规则: -- 只填可以作为兴趣爱好的名词(如:编程、摄影、音乐、游戏) -- 必须是TA在现实中真正从事或喜欢的活动/领域 -- 聊天互动方式不是爱好(撒娇、亲亲、被夸奖等不填) -- 你们之间的私密互动不是爱好 -- 情感状态不是爱好 +## 🚫 绝对禁止的情况(常见误用): +- 一般性聊天、日常互动、开玩笑 → 绝对不用 +- 撒娇、求抱抱、情感表达 → 绝对不用 +- 描述聊天感受、互动方式 → 绝对不用 +- 状态描述("累了"、"开心"、"忙")→ 绝对不用 +- 你的推测或印象 → 绝对不用 +- 聊天话题、兴趣讨论 → 绝对不用 -## ⛔ 关键信息(key_info)规则: -- 只填客观可验证的事实信息 -- 必须是具体的值(日期、地点、职业名称) -- 你的主观感受不是TA的信息 -- 关系描述不是信息 +## ⛔ 关键信息(key_info)严格标准: +- job: 必须是具体职业("程序员"、"医生"、"学生"),不能是状态("工作很累"、"上班族") +- birthday: 具体日期("3月15日"、"1995年"),不能是模糊描述 +- location: 具体地点("北京"、"上海浦东"),不能是"在家"、"公司" +- 如果不是TA明确说出的具体事实,绝对不要记录 + +## ⛔ 印象更新(impression_hint)超严格标准: +- 只有深度心理揭露、重大人生事件、核心价值观分享才考虑 +- 聊天互动方式、日常行为表现、情感表达方式 → 绝对不记录 +- 默认策略:当有疑虑时,不要使用此工具 此工具在后台异步执行,不影响回复速度。""" parameters = [ @@ -195,7 +197,11 @@ class UserProfileTool(BaseTool): final_impression = existing_profile.get("relationship_text", "") affection_change = 0.0 # 好感度变化量 - if impression_hint or chat_history_text: + # 只有在LLM明确提供impression_hint时才更新印象(更严格) + if impression_hint and impression_hint.strip(): + # 获取最近的聊天记录用于上下文 + chat_history_text = await self._get_recent_chat_history(target_user_id) + impression_result = await self._generate_impression_with_affection( target_user_name=target_user_name, impression_hint=impression_hint, @@ -277,14 +283,31 @@ class UserProfileTool(BaseTool): if info_type not in valid_types: info_type = "other" - # 🎯 信息质量判断:过滤掉模糊的描述性内容 + # 🎯 信息质量判断:过滤掉模糊的描述性内容和状态描述 low_quality_patterns = [ + # 原有的模糊描述 "的生日", "的工作", "的位置", "的梦想", "的家人", "的宠物", "birthday", "job", "location", "unknown", "未知", "不知道", - "affectionate", "friendly", "的信息", "某个", "一个" + "affectionate", "friendly", "的信息", "某个", "一个", + # 新增:状态描述而非具体信息 + "很累", "累了", "疲惫", "忙", "很忙", "加班", "休息", + "开心", "难过", "高兴", "沮丧", "烦躁", "焦虑", + "上班", "下班", "工作中", "在家", "出差", + "感觉", "心情", "状态", "最近", "今天", "现在" ] info_value_lower = info_value.lower().strip() + # 🎯 针对job类型的特殊验证 + if info_type == "job": + job_invalid_patterns = [ + "累", "忙", "班", "工作", "上班族", "打工人", "社畜", + "很", "非常", "特别", "太", "好", "不好" + ] + for pattern in job_invalid_patterns: + if pattern in info_value_lower: + logger.warning(f"职业信息无效(状态描述而非具体职业),跳过: {info_value}") + return + # 如果值太短或包含低质量模式,跳过 if len(info_value_lower) < 2: logger.warning(f"关键信息值太短,跳过: {info_value}") @@ -362,12 +385,12 @@ class UserProfileTool(BaseTool): logger.error(f"保存关键信息失败: {e}") # 不抛出异常,因为这是后台任务 - async def _get_recent_chat_history(self, target_user_id: str, max_messages: int = 50) -> str: + async def _get_recent_chat_history(self, target_user_id: str, max_messages: int = 10) -> str: """获取最近的聊天记录 Args: target_user_id: 目标用户ID - max_messages: 最大消息数量 + max_messages: 最大消息数量(默认10条,避免传递过多历史消息) Returns: str: 格式化的聊天记录文本 @@ -499,22 +522,28 @@ class UserProfileTool(BaseTool): ## 当前好感度 {current_score:.2f} (范围0-1,0.3=普通认识,0.5=朋友,0.7=好友,0.9=挚友) -## ⚠️⚠️ 最高优先级:保持已确认信息 ⚠️⚠️ -**旧印象中已经明确的信息必须保持,绝对不能改动!** +## ⚠️⚠️ 最高优先级:严格控制信息记录 ⚠️⚠️ +**绝对禁止推测、猜想、脑补任何具体信息!** -1. **性别判断规则**(按优先级): - - 如果旧印象中已经用"他"→ 这是男生,gender="male",继续用"他" - - 如果旧印象中已经用"她"→ 这是女生,gender="female",继续用"她" - - 只有旧印象没有性别线索时,才根据聊天内容判断 +1. **不要推测身份职业**: + - 不要根据聊天话题推测工作(聊AI ≠ 是程序员) + - 不要根据时间推测身份(深夜聊天 ≠ 是学生) + - 不要根据行为推测背景(会装机 ≠ 从事相关工作) -2. **其他已确认信息**: - - 旧印象中提到的身份(学生/上班族等)→ 保持 - - 旧印象中提到的特点、爱好 → 保持或深化,不要删除 - - 你是在**补充和深化**印象,不是**重写** +2. **不要记录未确认的信息**: + - 只记录TA明确说出的事实 + - 你的推测、联想、印象都不是事实 + - 模糊的、不确定的信息不要记录 + +3. **保持旧印象中已确认的信息**: + - 如果旧印象中已经用"他"→ 这是男生,继续用"他" + - 如果旧印象中已经用"她"→ 这是女生,继续用"她" + - 其他已明确的特点、爱好要保持,不要删除 ## ⚠️ 区分虚构内容和真实信息 - 游戏剧情、小说情节、角色扮演等虚构内容 ≠ TA本人的特质 -- 印象记录的是**这个人本身**:TA的性格、TA喜欢什么、TA和你交流的方式 +- 印象记录的是**这个人本身**:TA的性格、TA和你交流的方式 +- 不要将聊天内容当作个人信息记录 ## 任务 1. **先看旧印象中的性别**,已确定就沿用,没确定才判断 @@ -552,34 +581,43 @@ class UserProfileTool(BaseTool): 当前好感度:{current_score:.2f} -**关系阶段与增速:** +**关系阶段与增速(更加保守):** | 阶段 | 分数范围 | 单次变化范围 | 说明 | |------|----------|--------------|------| -| 陌生→初识 | 0.0-0.3 | ±0.03~0.05 | 容易建立初步印象 | -| 初识→熟人 | 0.3-0.5 | ±0.02~0.04 | 逐渐熟悉的阶段 | -| 熟人→朋友 | 0.5-0.7 | ±0.01~0.03 | 需要更多互动积累 | -| 朋友→好友 | 0.7-0.85 | ±0.01~0.02 | 关系深化变慢 | -| 好友→挚友 | 0.85-1.0 | ±0.005~0.01 | 极难变化,需要重大事件 | +| 陌生→初识 | 0.0-0.3 | ±0.01~0.03 | 需要重要交流才变化 | +| 初识→熟人 | 0.3-0.5 | ±0.01~0.025 | 逐渐熟悉的阶段 | +| 熟人→朋友 | 0.5-0.7 | ±0.01~0.02 | 需要更多深入互动 | +| 朋友→好友 | 0.7-0.85 | ±0.005~0.015 | 关系深化极慢 | +| 好友→挚友 | 0.85-1.0 | ±0.002~0.005 | 极难变化,需要重大事件 | **加分情况(根据当前阶段选择合适幅度):** -- 愉快的聊天、有来有往的互动 → 小幅+(低阶段更明显) -- 分享心情、倾诉烦恼 → 中幅+ -- 主动关心、记得之前聊过的事 → 中幅+ -- 深度交流、展现信任 → 较大+ -- 在困难时寻求帮助或给予支持 → 大幅+ +- 深层情感分享、主动倾诉重要烦恼 → 小幅+(低阶段更明显) +- 在你遇到困难时主动关心或提供帮助 → 中幅+ +- 记得并主动询问你之前提到的重要事情 → 中幅+ +- 深度价值观交流、展现真实的信任 → 较大+ +- 在重大困难时寻求帮助或给予关键支持 → 大幅+ **减分情况:** -- 敷衍、冷淡的回应 → 小幅- -- 明显的不耐烦或忽视 → 中幅- -- 冲突、误解 → 较大- -- 长期不联系(关系会自然冷却)→ 缓慢- +- 长时间敷衍、多次冷淡回应 → 小幅- +- 明显的不耐烦、忽视重要话题 → 中幅- +- 直接冲突、严重误解或伤害性言论 → 较大- +- 长期不联系且无合理原因 → 缓慢- -**不变的情况:** -- 纯粹的信息询问(问时间、问天气等) +**不变的情况(大部分日常交流都应该是这种):** +- 普通的愉快聊天、日常问候 +- 一般性信息交换、轻松互动 +- 开玩笑、调侃、日常关心 +- 分享日常生活琐事、兴趣爱好 +- 寻求一般性建议或提供普通帮助 +- 纯粹的信息询问 - 机械式的对话 - 无法判断情感倾向的中性交流 -**注意:高好感度(>0.8)时要非常谨慎加分,友好互动在这个阶段是常态,不是加分项。** +**重要原则:** +- 默认倾向于"不变化",只有真正重大的交流才改变好感度 +- 普通的友好互动是维持关系,不是加深关系 +- 高好感度(>0.7)时,日常友好交流绝对不加分 +- 宁可保守不变,也不要轻易加减分 请严格按照以下JSON格式输出: {{ @@ -613,22 +651,22 @@ class UserProfileTool(BaseTool): change_reason = result.get("change_reason", "") detected_gender = result.get("gender", "unknown") - # 🎯 根据当前好感度阶段限制变化范围 + # 🎯 根据当前好感度阶段限制变化范围(更加保守) if current_score < 0.3: - # 陌生→初识:±0.05 - max_change = 0.05 - elif current_score < 0.5: - # 初识→熟人:±0.04 - max_change = 0.04 - elif current_score < 0.7: - # 熟人→朋友:±0.03 + # 陌生→初识:±0.03 max_change = 0.03 - elif current_score < 0.85: - # 朋友→好友:±0.02 + elif current_score < 0.5: + # 初识→熟人:±0.025 + max_change = 0.025 + elif current_score < 0.7: + # 熟人→朋友:±0.02 max_change = 0.02 + elif current_score < 0.85: + # 朋友→好友:±0.015 + max_change = 0.015 else: - # 好友→挚友:±0.01 - max_change = 0.01 + # 好友→挚友:±0.005 + max_change = 0.005 affection_change = max(-max_change, min(max_change, affection_change))