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:
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)
# 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
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
except Exception as e:
logger.error(f"查询UserRelationships表失败: {e}")

View File

@@ -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 '不激活'}"

View File

@@ -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-10.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))