refactor(profile,llm): 提高用户资料的准确性和系统的稳健性。本次提交引入了多项针对用户资料管理和大语言模型交互的优化,目标是实现更高的准确性、更严格的数据验证以及提升系统可靠性。

- **用户资料管理(`user_profile_tool.py`):**
  - `UserProfileTool` 的描述进行了大幅更新,明确定义了严格的使用场景和绝对禁止的行为,防止误用。
  - 对 `preference_keywords` 和 `key_info` 的值实施了更严格的过滤,确保只记录具体、客观的事实和真实兴趣。
  - 减少了用于上下文的最近聊天消息数量,以更关注相关性更高的近期交互。
  - 修改了好感度计算逻辑,使其更加保守,不容易因日常小互动而改变,需要更有意义的交流才会产生变化。
  - 印象生成提示已更新,严格禁止猜测。
  并强调记录事实观察到的特征。- **关系信息显示(`relationship_fetcher.py`):** - 通过过滤掉一般交互术语来增强用户偏好显示,仅展示真实的爱好和兴趣。- 暂时注释了“关键事实”的显示,以防呈现潜在不准确或推测性的信息。- **大型语言模型交互稳定性(`base_action.py`):** - 在 `should_activate` 方法中引入了 7 秒超时的 LLM 判断调用。- 如果 LLM 判断超时,动作现在默认为“激活”,以防止系统阻塞并确保持续运行。
This commit is contained in:
tt-P607
2025-12-09 22:52:36 +08:00
parent 084192843b
commit 44f85c40bf
3 changed files with 145 additions and 88 deletions

View File

@@ -173,24 +173,34 @@ class RelationshipFetcher:
if impression: if impression:
relation_parts.append(f"\n你对{person_name}的印象:\n{impression}") relation_parts.append(f"\n你对{person_name}的印象:\n{impression}")
# 5. 用户偏好关键词 # 5. 用户偏好关键词(仅显示真实兴趣爱好)
if rel_data.get("preference_keywords"): if rel_data.get("preference_keywords"):
keywords_list = [kw.strip() for kw in rel_data["preference_keywords"].split(",") if kw.strip()] keywords_list = [kw.strip() for kw in rel_data["preference_keywords"].split(",") if kw.strip()]
if keywords_list: # 过滤掉明显不是兴趣爱好的词
keywords_str = "".join(keywords_list) filtered_keywords = []
relation_parts.append(f"\n{person_name}的偏好和兴趣:{keywords_str}") 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. 关键信息 # 6. 关键信息 - 暂时隐藏,防止显示不准确的推测信息
if rel_data.get("key_facts"): # if rel_data.get("key_facts"):
try: # try:
import orjson # import orjson
facts = orjson.loads(rel_data["key_facts"]) # facts = orjson.loads(rel_data["key_facts"])
if facts and isinstance(facts, list): # if facts and isinstance(facts, list):
facts_lines = self._format_key_facts(facts, person_name) # facts_lines = self._format_key_facts(facts, person_name)
if facts_lines: # if facts_lines:
relation_parts.append(f"\n你记住的关于{person_name}的重要信息:\n{facts_lines}") # relation_parts.append(f"\n你记住的关于{person_name}的重要信息:\n{facts_lines}")
except Exception: # except Exception:
pass # pass
except Exception as e: except Exception as e:
logger.error(f"查询UserRelationships表失败: {e}") logger.error(f"查询UserRelationships表失败: {e}")

View File

@@ -838,11 +838,20 @@ class BaseAction(ABC):
只需要回答"""",不要有其他内容。 只需要回答"""",不要有其他内容。
""" """
# 调用 LLM 进行判断 # 调用 LLM 进行判断设置7秒超时避免长时间等待
response, _ = await llm_judge_model.generate_response_async(prompt=prompt) import asyncio
response = response.strip().lower() response = "" # 初始化response变量
try:
should_activate = "" in response or "yes" in response or "true" in response 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( logger.debug(
f"{self.log_prefix} LLM 判断结果: 响应='{response}', 结果={'激活' if should_activate else '不激活'}" f"{self.log_prefix} LLM 判断结果: 响应='{response}', 结果={'激活' if should_activate else '不激活'}"

View File

@@ -43,32 +43,34 @@ class UserProfileTool(BaseTool):
""" """
name = "update_user_profile" 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明确要求被称呼的真实昵称 1. TA明确说出具体个人信息"我生日是3月15日""我在北京工作""我是程序员")→ 填 key_info
- 必须是TA主动说"叫我xxx""的昵称是xxx" 2. TA的重要信息发生变化"我搬到上海了""换工作了")→ 更新 key_info
- 聊天中的玩笑称呼、撒娇称呼、临时戏称一律不填 3. TA主动深度自我揭露重大个人经历或核心价值观 → 慎重考虑填 impression_hint
- 你给TA起的爱称不算别名 4. TA明确表达具体的现实兴趣爱好"我喜欢摄影""我在学编程")→ 填 preference
## ⛔ 偏好(preference)规则 ## 🚫 绝对禁止的情况(常见误用)
- 只填可以作为兴趣爱好的名词(如:编程、摄影、音乐、游戏) - 一般性聊天、日常互动、开玩笑 → 绝对不用
- 必须是TA在现实中真正从事或喜欢的活动/领域 - 撒娇、求抱抱、情感表达 → 绝对不用
- 聊天互动方式不是爱好(撒娇、亲亲、被夸奖等不填) - 描述聊天感受、互动方式 → 绝对不用
- 你们之间的私密互动不是爱好 - 状态描述("累了""开心""")→ 绝对不用
- 情感状态不是爱好 - 你的推测或印象 → 绝对不用
- 聊天话题、兴趣讨论 → 绝对不用
## ⛔ 关键信息(key_info)规则 ## ⛔ 关键信息(key_info)严格标准
- 只填客观可验证的事实信息 - job: 必须是具体职业("程序员""医生""学生"),不能是状态("工作很累""上班族"
- 必须是具体的值(日期、地点、职业名称) - birthday: 具体日期("3月15日""1995年"),不能是模糊描述
- 你的主观感受不是TA的信息 - location: 具体地点("北京""上海浦东"),不能是"在家""公司"
- 关系描述不是信息 - 如果不是TA明确说出的具体事实绝对不要记录
## ⛔ 印象更新(impression_hint)超严格标准:
- 只有深度心理揭露、重大人生事件、核心价值观分享才考虑
- 聊天互动方式、日常行为表现、情感表达方式 → 绝对不记录
- 默认策略:当有疑虑时,不要使用此工具
此工具在后台异步执行,不影响回复速度。""" 此工具在后台异步执行,不影响回复速度。"""
parameters = [ parameters = [
@@ -195,7 +197,11 @@ class UserProfileTool(BaseTool):
final_impression = existing_profile.get("relationship_text", "") final_impression = existing_profile.get("relationship_text", "")
affection_change = 0.0 # 好感度变化量 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( impression_result = await self._generate_impression_with_affection(
target_user_name=target_user_name, target_user_name=target_user_name,
impression_hint=impression_hint, impression_hint=impression_hint,
@@ -277,14 +283,31 @@ class UserProfileTool(BaseTool):
if info_type not in valid_types: if info_type not in valid_types:
info_type = "other" info_type = "other"
# 🎯 信息质量判断:过滤掉模糊的描述性内容 # 🎯 信息质量判断:过滤掉模糊的描述性内容和状态描述
low_quality_patterns = [ low_quality_patterns = [
# 原有的模糊描述
"的生日", "的工作", "的位置", "的梦想", "的家人", "的宠物", "的生日", "的工作", "的位置", "的梦想", "的家人", "的宠物",
"birthday", "job", "location", "unknown", "未知", "不知道", "birthday", "job", "location", "unknown", "未知", "不知道",
"affectionate", "friendly", "的信息", "某个", "一个" "affectionate", "friendly", "的信息", "某个", "一个",
# 新增:状态描述而非具体信息
"很累", "累了", "疲惫", "", "很忙", "加班", "休息",
"开心", "难过", "高兴", "沮丧", "烦躁", "焦虑",
"上班", "下班", "工作中", "在家", "出差",
"感觉", "心情", "状态", "最近", "今天", "现在"
] ]
info_value_lower = info_value.lower().strip() 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: if len(info_value_lower) < 2:
logger.warning(f"关键信息值太短,跳过: {info_value}") logger.warning(f"关键信息值太短,跳过: {info_value}")
@@ -362,12 +385,12 @@ class UserProfileTool(BaseTool):
logger.error(f"保存关键信息失败: {e}") 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: Args:
target_user_id: 目标用户ID target_user_id: 目标用户ID
max_messages: 最大消息数量 max_messages: 最大消息数量默认10条避免传递过多历史消息
Returns: Returns:
str: 格式化的聊天记录文本 str: 格式化的聊天记录文本
@@ -499,22 +522,28 @@ class UserProfileTool(BaseTool):
## 当前好感度 ## 当前好感度
{current_score:.2f} (范围0-10.3=普通认识0.5=朋友0.7=好友0.9=挚友) {current_score:.2f} (范围0-10.3=普通认识0.5=朋友0.7=好友0.9=挚友)
## ⚠️⚠️ 最高优先级:保持已确认信息 ⚠️⚠️ ## ⚠️⚠️ 最高优先级:严格控制信息记录 ⚠️⚠️
**旧印象中已经明确的信息必须保持,绝对不能改动** **绝对禁止推测、猜想、脑补任何具体信息**
1. **性别判断规则**(按优先级) 1. **不要推测身份职业**
- 如果旧印象中已经用""→ 这是男生gender="male",继续用"" - 不要根据聊天话题推测工作聊AI ≠ 是程序员)
- 如果旧印象中已经用""→ 这是女生gender="female",继续用"" - 不要根据时间推测身份(深夜聊天 ≠ 是学生)
- 只有旧印象没有性别线索时,才根据聊天内容判断 - 不要根据行为推测背景(会装机 ≠ 从事相关工作)
2. **其他已确认信息** 2. **不要记录未确认信息**
- 旧印象中提到的身份(学生/上班族等)→ 保持 - 只记录TA明确说出的事实
- 旧印象中提到的特点、爱好 → 保持或深化,不要删除 - 你的推测、联想、印象都不是事实
- 你是在**补充和深化**印象,不是**重写** - 模糊的、不确定的信息不要记录
3. **保持旧印象中已确认的信息**
- 如果旧印象中已经用""→ 这是男生,继续用""
- 如果旧印象中已经用""→ 这是女生,继续用""
- 其他已明确的特点、爱好要保持,不要删除
## ⚠️ 区分虚构内容和真实信息 ## ⚠️ 区分虚构内容和真实信息
- 游戏剧情、小说情节、角色扮演等虚构内容 ≠ TA本人的特质 - 游戏剧情、小说情节、角色扮演等虚构内容 ≠ TA本人的特质
- 印象记录的是**这个人本身**TA的性格、TA喜欢什么、TA和你交流的方式 - 印象记录的是**这个人本身**TA的性格、TA和你交流的方式
- 不要将聊天内容当作个人信息记录
## 任务 ## 任务
1. **先看旧印象中的性别**,已确定就沿用,没确定才判断 1. **先看旧印象中的性别**,已确定就沿用,没确定才判断
@@ -552,34 +581,43 @@ class UserProfileTool(BaseTool):
当前好感度:{current_score:.2f} 当前好感度:{current_score:.2f}
**关系阶段与增速:** **关系阶段与增速(更加保守)**
| 阶段 | 分数范围 | 单次变化范围 | 说明 | | 阶段 | 分数范围 | 单次变化范围 | 说明 |
|------|----------|--------------|------| |------|----------|--------------|------|
| 陌生→初识 | 0.0-0.3 | ±0.03~0.05 | 容易建立初步印象 | | 陌生→初识 | 0.0-0.3 | ±0.01~0.03 | 需要重要交流才变化 |
| 初识→熟人 | 0.3-0.5 | ±0.02~0.04 | 逐渐熟悉的阶段 | | 初识→熟人 | 0.3-0.5 | ±0.01~0.025 | 逐渐熟悉的阶段 |
| 熟人→朋友 | 0.5-0.7 | ±0.01~0.03 | 需要更多互动积累 | | 熟人→朋友 | 0.5-0.7 | ±0.01~0.02 | 需要更多深入互动 |
| 朋友→好友 | 0.7-0.85 | ±0.01~0.02 | 关系深化慢 | | 朋友→好友 | 0.7-0.85 | ±0.005~0.015 | 关系深化慢 |
| 好友→挚友 | 0.85-1.0 | ±0.005~0.01 | 极难变化,需要重大事件 | | 好友→挚友 | 0.85-1.0 | ±0.002~0.005 | 极难变化,需要重大事件 |
**加分情况(根据当前阶段选择合适幅度):** **加分情况(根据当前阶段选择合适幅度):**
- 愉快的聊天、有来有往的互动 → 小幅+(低阶段更明显) - 深层情感分享、主动倾诉重要烦恼 → 小幅+(低阶段更明显)
- 分享心情、倾诉烦恼 → 中幅+ - 在你遇到困难时主动关心或提供帮助 → 中幅+
- 主动关心、记得之前聊过的事 → 中幅+ - 记得并主动询问你之前提到的重要事情 → 中幅+
- 深度交流、展现信任 → 较大+ - 深度价值观交流、展现真实的信任 → 较大+
- 在困难时寻求帮助或给予支持 → 大幅+ - 在重大困难时寻求帮助或给予关键支持 → 大幅+
**减分情况:** **减分情况:**
- 敷衍、冷淡回应 → 小幅- - 长时间敷衍、多次冷淡回应 → 小幅-
- 明显的不耐烦忽视 → 中幅- - 明显的不耐烦忽视重要话题 → 中幅-
- 冲突、误解 → 较大- - 直接冲突、严重误解或伤害性言论 → 较大-
- 长期不联系(关系会自然冷却)→ 缓慢- - 长期不联系且无合理原因 → 缓慢-
**不变的情况:** **不变的情况(大部分日常交流都应该是这种)**
- 纯粹的信息询问(问时间、问天气等) - 普通的愉快聊天、日常问候
- 一般性信息交换、轻松互动
- 开玩笑、调侃、日常关心
- 分享日常生活琐事、兴趣爱好
- 寻求一般性建议或提供普通帮助
- 纯粹的信息询问
- 机械式的对话 - 机械式的对话
- 无法判断情感倾向的中性交流 - 无法判断情感倾向的中性交流
**注意:高好感度(>0.8)时要非常谨慎加分,友好互动在这个阶段是常态,不是加分项。** **重要原则:**
- 默认倾向于"不变化",只有真正重大的交流才改变好感度
- 普通的友好互动是维持关系,不是加深关系
- 高好感度(>0.7)时,日常友好交流绝对不加分
- 宁可保守不变,也不要轻易加减分
请严格按照以下JSON格式输出 请严格按照以下JSON格式输出
{{ {{
@@ -613,22 +651,22 @@ class UserProfileTool(BaseTool):
change_reason = result.get("change_reason", "") change_reason = result.get("change_reason", "")
detected_gender = result.get("gender", "unknown") detected_gender = result.get("gender", "unknown")
# 🎯 根据当前好感度阶段限制变化范围 # 🎯 根据当前好感度阶段限制变化范围(更加保守)
if current_score < 0.3: if current_score < 0.3:
# 陌生→初识±0.05 # 陌生→初识±0.03
max_change = 0.05
elif current_score < 0.5:
# 初识→熟人±0.04
max_change = 0.04
elif current_score < 0.7:
# 熟人→朋友±0.03
max_change = 0.03 max_change = 0.03
elif current_score < 0.85: elif current_score < 0.5:
# 朋友→好友±0.02 # 初识→熟人±0.025
max_change = 0.025
elif current_score < 0.7:
# 熟人→朋友±0.02
max_change = 0.02 max_change = 0.02
elif current_score < 0.85:
# 朋友→好友±0.015
max_change = 0.015
else: else:
# 好友→挚友±0.01 # 好友→挚友±0.005
max_change = 0.01 max_change = 0.005
affection_change = max(-max_change, min(max_change, affection_change)) affection_change = max(-max_change, min(max_change, affection_change))