Files
Mofox-Core/docs/用户画像系统重构规划.md
tt-P607 2671a6e7e5 feat(profile):对用户关系和分析系统进行重构,采用结构化数据和异步更新
此提交完全重写了用户关系和分析系统,创建了一个更强大、详细和响应式的框架。旧系统已被弃用,取而代之的是一个集中式的`UserRelationships`模型。

主要变更:

1.  ‌**增强数据库模型(`UserRelationships`):**‌
    - 添加`impression_text`用于长期、叙述式印象。
    - 引入`key_facts`(JSON)存储结构化数据如生日、工作和位置。
    - 添加`relationship_stage`跟踪关系进展(如陌生人、朋友、挚友)。
    - 添加`first_met_time`和`last_impression_update`的时间戳。

2.  ‌**重设计`UserProfileTool`:**‌
    - 工具的用途被限定为仅捕捉重要新信息,防止用于小聊。
    - 更新现在在后台异步处理,确保机器人回复不被延迟。
    - 引入`key_info_type`和`key_info_value`参数供LLM提交结构化事实。

3.  ‌**复杂的印象和情感逻辑:**‌
    - 关系追踪LLM现在分析最近聊天历史生成更丰富、更上下文的印象。
    - 用渐进的`affection_change`(最大±0.03)取代直接情感分数设置,使关系发展更真实。

4.  ‌**数据源整合:**‌
    - `RelationshipFetcher`重构为仅依赖`UserRelationships`表作为唯一数据源。
    - 简化`get_user_relationship` API并移除其缓存,确保分析的实时数据访问。

破坏性变更:`UserProfileTool`已重设计,新增参数(`key_info_type`、`key_info_value`)并改变用途。移除`affection_score`参数。此外,`get_user_relationship`数据库API签名简化为仅接受`user_id`。
2025-12-03 16:53:40 +08:00

19 KiB
Raw Blame History

用户画像系统重构规划 v2

决策:统一使用 user_relationships 表,废弃 person_info 表的印象相关功能

旧表数据保留但不再调用,作为历史存档。


一、设计理念:像人一样记住别人

1.1 人类是怎么记住一个人的?

当我们认识一个人,我们的记忆不是一堆标签,而是:

  1. 第一印象 - "这个人看起来挺随和的"
  2. 具体片段 - "上次他帮我修电脑,折腾了一晚上"
  3. 情感色彩 - "和他聊天很舒服,不会有压力"
  4. 关键信息 - "他生日是11月23日喜欢吃辣"
  5. 关系定位 - "算是比较聊得来的朋友"
  6. 印象演变 - "一开始觉得他很高冷,后来发现其实很话痨"

1.2 当前系统的问题

❌ 现状:
"该用户性格开朗喜欢编程、游戏、音乐关系分数0.65"

✅ 期望:
"柒柒是个挺有意思的人,说话很有逻辑但偶尔会冒出一些冷笑话。
和他聊天挺舒服的,他不会问让人尴尬的问题。他在游戏公司做后端,
经常加班但从不抱怨。他说以后想开一家咖啡店,感觉是认真的。
生日是11月23日。"

1.3 与记忆系统的分工

系统 负责内容 特点
上下文 当前对话内容 即时、完整
短期记忆 近期聊天片段 几天内、可检索
长期记忆 重要事件、知识 持久、语义检索
用户印象(本系统) 对人的认知 长期、主观、人格化

本系统只关注

  • 对这个人的长期印象(性格、相处感受)
  • 重要的具体信息(生日、职业、理想等)
  • 关系状态(熟悉程度、好感度)

不需要记录

  • 最近聊了什么(短期记忆负责)
  • 具体对话内容(上下文和长期记忆负责)
  • 临时性的事件(记忆系统负责)

1.4 新系统的设计目标

维度 要求
长期 只记稳定的、长期的认知,不记临时的
精炼 200-500字的印象 + 关键信息列表
主观 是"我"眼中的TA不是客观档案
实时 通过工具调用实时更新

二、数据结构重设计

2.1 扩展 user_relationships

-- 保留现有字段
id, user_id, user_name, user_aliases, relationship_score, last_updated, created_at

-- relationship_text 重命名为 impression_text
-- preference_keywords 废弃

-- 新增字段
ALTER TABLE user_relationships ADD COLUMN impression_text TEXT;       -- 长期印象
ALTER TABLE user_relationships ADD COLUMN key_facts TEXT;             -- 关键信息JSON
ALTER TABLE user_relationships ADD COLUMN relationship_stage VARCHAR(50); -- 关系阶段
ALTER TABLE user_relationships ADD COLUMN first_met_time FLOAT;       -- 认识时间
ALTER TABLE user_relationships ADD COLUMN last_impression_update FLOAT; -- 上次更新印象时间

2.2 字段说明

impression_text - 长期印象(核心)

精炼的、稳定的对这个人的认知,不是流水账:

示例200-400字

"柒柒是个典型的理科生,说话很有逻辑,但偶尔会冒出一些冷笑话。
和他聊天挺舒服的,不会有压力,他也不会问让人尴尬的问题。

他在游戏公司做后端开发,虽然经常加班但从不抱怨,感觉是个挺拼的人。
他对技术很感兴趣经常问我一些AI相关的问题虽然有些问题挺刁钻
但能感觉到他是真的好奇。

他说以后想开一家自己的咖啡店,深夜聊天时说的,感觉不是随便说说的。

总的来说他给我的感觉是'表面随意但内心认真'的那种人。"

更新时机

  • 对TA的认知发生明显变化时
  • 关系有明显进展时

key_facts - 关键信息(长期记住)

存储长期稳定、一定要记住的重要信息(生日、职业这种不会变的):

信息类型

类型 说明 示例
birthday 生日 "11月23日"
job 职业/工作 "程序员"
location 所在地/老家 "上海"
dream 理想/目标 "想开咖啡店"
family 家庭 "有个妹妹"
pet 宠物 "养了只橘猫"
other 其他 -

存储格式

[
    {"type": "birthday", "value": "11月23日"},
    {"type": "job", "value": "游戏公司后端程序员"},
    {"type": "dream", "value": "想开咖啡店"},
    {"type": "pet", "value": "养了只橘猫叫橘子"}
]

relationship_stage - 关系阶段

阶段 描述 对应分数范围
stranger 陌生人 0.0-0.2
acquaintance 初识 0.2-0.4
familiar 熟人 0.4-0.6
friend 朋友 0.6-0.75
close_friend 好友 0.75-0.9
bestie 挚友 0.9-1.0

三、工具调用设计(实时更新)

沿用之前的两阶段设计

  1. tool_use 模型决定是否更新
  2. relationship_tracker 模型生成高质量内容

3.1 工具1update_user_impression - 更新印象

name = "update_user_impression"
description = """当你对某人有了新的、长期的认识时使用。
注意:只记录稳定的、长期的印象,临时的事情不用记。

适用场景:
- 发现了TA的性格特点
- 对TA有了新的认知
- 觉得和TA的关系有变化"""

parameters = [
    ("user_id", STRING, "用户ID", True),
    ("user_name", STRING, "怎么称呼TA", True),
    ("impression_update", STRING, "你对TA的新认识简要描述即可系统会用你的语气润色", True),
]

3.2 工具2remember_user_info - 记住重要信息

name = "remember_user_info"
description = """当你知道了关于某人的长期重要信息时使用。
比如:生日、职业、理想、家庭、宠物等不会经常变的信息。"""

parameters = [
    ("user_id", STRING, "用户ID", True),
    ("user_name", STRING, "怎么称呼TA", True),
    ("info_type", STRING, "信息类型birthday/job/location/dream/family/pet/other", True),
    ("info_value", STRING, "具体内容", True),
]

3.3 更新流程

┌─────────────────────────────────────────────────────────────┐
│  tool_use 模型判断:需要更新印象/记住信息吗?                  │
└─────────────────────────────────────────────────────────────┘
                              │ 是
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  调用工具,传入简要信息                                       │
│  - impression_update: "他是个很细心的人"                     │
│  - 或 info_type: "birthday", info_value: "11月23日"         │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  relationship_tracker 模型生成完整内容                       │
│  - 融合现有印象 + 新信息                                     │
│  - 用人设语气润色                                            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  写入 user_relationships 表                                  │
└─────────────────────────────────────────────────────────────┘

四、读取和展示

4.1 构建关系信息时的呈现

修改 RelationshipFetcher.build_relation_info()

async def build_relation_info(self, person_id, points_num=5):
    """构建关于某人的关系信息(新版)"""
    
    person_info_manager = get_person_info_manager()
    person_name = await person_info_manager.get_value(person_id, "person_name")
    
    # 从 user_relationships 表读取所有数据
    relationship = await get_user_relationship(platform, user_id, "bot")
    
    if not relationship:
        return f"你完全不认识{person_name},这是你们第一次交流。"
    
    relation_parts = []
    
    # 1. 基本信息:认识时间
    if relationship.first_met_time:
        from datetime import datetime
        know_time = datetime.fromtimestamp(relationship.first_met_time).strftime("%Y年%m月")
        relation_parts.append(f"你从{know_time}开始认识{person_name},已经聊过很多次了")
    
    # 2. 别名
    if relationship.user_aliases:
        aliases_str = relationship.user_aliases.replace(",", "、")
        relation_parts.append(f"{person_name}的别名:{aliases_str}")
    
    # 3. 关系程度(保留好感度!)
    if relationship.relationship_score is not None:
        score = relationship.relationship_score
        stage_desc = self._get_relationship_stage_description(relationship.relationship_stage or score)
        relation_parts.append(f"你和{person_name}的关系:{stage_desc}(好感度{score:.2f}")
    
    # 4. 核心:长期印象(新字段)
    if relationship.impression_text:
        relation_parts.append(f"\n你对{person_name}的印象:\n{relationship.impression_text}")
    
    # 5. 喜好和兴趣(保留!)
    if relationship.preference_keywords:
        keywords_str = relationship.preference_keywords.replace(",", "、")
        relation_parts.append(f"\n{person_name}的喜好和兴趣:{keywords_str}")
    
    # 6. 关键信息(新字段)
    if relationship.key_facts:
        facts = json.loads(relationship.key_facts)
        if facts:
            facts_lines = [self._format_fact(f) for f in facts]
            relation_parts.append(f"\n你记住的关于{person_name}的重要信息:\n" + "\n".join(facts_lines))
    
    # 构建最终输出
    if relation_parts:
        return f"关于{person_name},你知道以下信息:\n" + "\n".join([f"• {p}" if not p.startswith("\n") else p for p in relation_parts])
    else:
        return f"你完全不认识{person_name},这是你们第一次交流。"

def _format_fact(self, fact: dict) -> str:
    """格式化单条关键信息"""
    type_names = {
        "birthday": "生日",
        "job": "工作", 
        "location": "所在地",
        "dream": "理想",
        "family": "家庭",
        "pet": "宠物",
        "other": "其他"
    }
    type_name = type_names.get(fact.get("type", "other"), "其他")
    return f"• {type_name}{fact.get('value', '')}"

4.2 展示效果示例

完整的提示词插入内容

关于言柒,你知道以下信息:
• 你从2024年6月开始认识言柒已经聊过很多次了
• 言柒的别名:柒柒、小柒
• 你和言柒的关系很亲近的好友好感度0.82

你对言柒的印象:
柒柒是个典型的理科生,说话很有逻辑,但偶尔会冒出一些冷笑话让人忍俊不禁。
和他聊天挺舒服的,不会有压力,他也不会问让人尴尬的问题。

他在游戏公司做后端开发,虽然经常加班但从不抱怨,感觉是个挺拼的人。
他对技术很感兴趣经常问我一些AI相关的问题虽然有些问题挺刁钻
但能感觉到他是真的好奇而不是在刁难我。

他说以后想开一家自己的咖啡店,深夜聊天时说的,感觉不是随便说说的。
总的来说他给我的感觉是"表面随意但内心认真"的那种人。

言柒的喜好和兴趣群内梗文化、AI技术、编程、游戏

你记住的关于言柒的重要信息:
• 生日11月23日
• 工作:游戏公司后端程序员
• 理想:想开咖啡店
• 宠物:养了只橘猫叫橘子

五、迁移计划

5.1 数据库变更

-- 1. 扩展 user_relationships 表
ALTER TABLE user_relationships ADD COLUMN impression_text TEXT;          -- 长期印象
ALTER TABLE user_relationships ADD COLUMN key_facts TEXT;                 -- 关键信息JSON
ALTER TABLE user_relationships ADD COLUMN relationship_stage VARCHAR(50) DEFAULT 'stranger';
ALTER TABLE user_relationships ADD COLUMN first_met_time FLOAT;
ALTER TABLE user_relationships ADD COLUMN last_impression_update FLOAT;

-- 2. 保留的字段
-- relationship_score 保留!很多地方在用
-- preference_keywords 保留!喜好是印象的一部分
-- relationship_text 可作为 impression_text 的初始值迁移

-- 3. person_info 表不删除,但停止写入印象相关字段

5.2 代码变更清单

文件 变更内容
models.py 扩展 UserRelationships 模型
user_profile_tool.py 重构为新的印象更新逻辑
relationship_fetcher.py 修改读取逻辑,只从 user_relationships 读
relationship_manager.py 废弃或精简,只保留必要的统计功能
新增 user_fact_tool.py 记录具体信息的工具

5.3 废弃的功能

以下功能将停止使用(代码保留但不调用):

  • RelationshipManager.update_person_impression() - 停止写入 person_info
  • RelationshipManager._update_impression() - 停止使用
  • person_info 表的以下字段停止更新:
    • impression
    • short_impression
    • points
    • forgotten_points
    • attitude

六、实施步骤

Phase 1数据库扩展 已完成

  • 修改 models.py,添加新字段
    • impression_text: 长期印象
    • key_facts: 关键信息JSON
    • relationship_stage: 关系阶段
    • first_met_time: 认识时间
    • last_impression_update: 上次更新时间

Phase 2核心工具重构 已完成

  • 重构 UserProfileTool
    • 新增 _get_recent_chat_history() 读取聊天记录
    • 新增 _generate_impression_with_affection() 联动生成印象+好感度
    • 新增 _calculate_relationship_stage() 自动计算关系阶段
  • 创建 UserFactTool (remember_user_info 工具)
  • 编写印象生成提示词(包含好感度变化规则)

Phase 3读取层适配 已完成

  • 修改 RelationshipFetcher.build_relation_info()
    • 读取新字段 impression_text, key_facts, relationship_stage
    • 新增辅助方法 _get_stage_description(), _format_key_facts()
  • 优先使用新字段,兼容旧字段

Phase 4清理与优化待后续

  • 标记废弃的代码
  • 添加迁移说明
  • 性能测试

七、新旧系统提示词输出对比

7.1 旧系统输出(当前)

关于言柒,你知道以下信息:
• 用户言柒在QQ平台的昵称是柒柒
• 你从2024年06月21日开始认识言柒
• 你们已经交流过2357次
• 最近一次交流是在08月21日
• 你对言柒的态度是比较有好感
• 你对ta的总体印象一个挺有趣的理科男
• 更详细的了解:这位用户是群里的'真相洞察者'与'梗文化解构师'善于在群聊中发现有趣的点并进行吐槽说话直接但不会让人觉得刻薄。对技术尤其是AI领域比较感兴趣经常会问一些技术相关的问题。
• 你记得关于言柒的一些事情:
  - 他说最近工作很累经常加班2024-08-20
  - 他对AI很感兴趣想学习2024-08-15
  - 他通过了新公司的面试很开心2024-08-10
• 言柒的别名有:柒柒、小柒
• 你对言柒的整体认知:这位用户是群里的'真相洞察者'与'梗文化解构师'...
• 言柒的偏好和兴趣群内梗文化、真相揭露、二次元文化、AI技术、编程
• 你们的关系程度非常亲密的好友0.82

问题

  • 数据来自两个表,有重复("总体印象"和"整体认知"说的差不多)
  • 格式像数据库报表,不像人的认知
  • points 是碎片化的事件记录,不是对人的理解

7.2 新系统输出(改造后)

关于言柒,你知道以下信息:
• 你从2024年6月开始认识言柒已经聊过很多次了
• 言柒的别名:柒柒、小柒
• 你和言柒的关系很亲近的好友好感度0.82

你对言柒的印象:
柒柒是个典型的理科生,说话很有逻辑,但偶尔会冒出一些冷笑话让人忍俊不禁。
和他聊天挺舒服的,不会有压力,他也不会问让人尴尬的问题。

他在游戏公司做后端开发,虽然经常加班但从不抱怨,感觉是个挺拼的人。
他对技术很感兴趣经常问我一些AI相关的问题虽然有些问题挺刁钻
但能感觉到他是真的好奇而不是在刁难我。

他说以后想开一家自己的咖啡店,深夜聊天时说的,感觉不是随便说说的。
总的来说他给我的感觉是"表面随意但内心认真"的那种人。

言柒的喜好和兴趣群内梗文化、AI技术、编程、游戏

你记住的关于言柒的重要信息:
• 生日11月23日
• 工作:游戏公司后端程序员
• 理想:想开咖啡店
• 宠物:养了只橘猫叫橘子

改进点

  • 只从 user_relationships 一个表读取,不再重复
  • 长期印象impression_text是完整的叙事像真的在描述一个认识的人
  • 好感度relationship_score保留给AI参考
  • 喜好preference_keywords保留是印象的一部分
  • 关键信息key_facts是长期稳定的事实不是临时事件
  • 临时事件("最近加班很累")不在这里,交给记忆系统

7.3 数据来源对照

输出内容 旧系统来源 新系统来源
认识时间 person_info.know_since user_relationships.first_met_time
别名 person_info.nickname + user_relationships.user_aliases user_relationships.user_aliases
好感度 person_info.attitude 保留 user_relationships.relationship_score
印象 person_info.impression + user_relationships.relationship_text 合并到 user_relationships.impression_text
喜好 user_relationships.preference_keywords 保留 user_relationships.preference_keywords
记忆点 person_info.points 移除(交给记忆系统)
关键信息 新增 user_relationships.key_facts

附录:相关文件

文件 状态 说明
src/common/database/core/models.py 需修改 扩展 UserRelationships
src/plugins/.../user_profile_tool.py 需重构 新的印象更新逻辑
src/person_info/relationship_fetcher.py 需修改 适配新数据结构
src/person_info/relationship_manager.py 废弃 印象功能停用
src/person_info/person_info.py 保留 只用于基础信息