feat(affinity-flow): 增强关系追踪系统的人设集成和逻辑严谨性

- 在关系追踪器中集成bot人设信息,从性格视角分析用户互动
- 添加严格的关系分数档次定义和现实发展逻辑约束
- 改进提示词工程,要求详细的性格观察和互动记忆记录
- 单次互动加分限制在合理范围内(0.05-0.1),防止跳跃式关系提升
- 优化关系印象描述要求(100-200字),包含用户性格特点和深刻记忆

refactor(planner): 简化消息数据处理流程

- 使用StreamContext对象替代原始的message_data字典
- 移除冗余的消息数据准备步骤,直接从context获取未读消息
- 统一规划器接口,提高代码可读性和维护性

fix(person-info): 添加napcat到qq平台的用户ID迁移机制

- 为qq平台生成person_id时检查是否存在napcat平台的相同用户
- 如果存在则自动迁移记录并更新平台信息
- 确保用户身份在不同平台间的正确识别和延续

fix(plan-executor): 修复自我回复检测逻辑

- 使用action_message.user_info.user_id替代原始字典访问
- 防止因消息格式变化导致的自我回复检测失效

chore(config): 更新默认平台配置为qq

- 将napcat_adapter插件的默认平台名称从napcat改为qq
- 保持与现有部署环境的一致性
This commit is contained in:
Windpicker-owo
2025-09-20 22:11:38 +08:00
parent 10d5fc7202
commit 006f9130b9
10 changed files with 128 additions and 49 deletions

View File

@@ -9,6 +9,7 @@ from typing import Dict, Optional, List
from src.chat.planner_actions.action_manager import ActionManager
from src.chat.planner_actions.planner import ActionPlanner
from src.chat.affinity_flow.chatter import AffinityFlowChatter
from src.common.data_models.message_manager_data_model import StreamContext
from src.common.logger import get_logger
logger = get_logger("afc_manager")
@@ -49,7 +50,7 @@ class AFCManager:
return self.affinity_flow_chatters[stream_id]
async def process_stream_context(self, stream_id: str, context) -> Dict[str, any]:
async def process_stream_context(self, stream_id: str, context: StreamContext) -> Dict[str, any]:
"""处理StreamContext对象"""
try:
# 获取或创建聊天处理器

View File

@@ -9,6 +9,7 @@ from typing import Dict
from src.chat.planner_actions.action_manager import ActionManager
from src.chat.planner_actions.planner import ActionPlanner
from src.common.data_models.message_manager_data_model import StreamContext
from src.plugin_system.base.component_types import ChatMode
from src.common.logger import get_logger
@@ -42,7 +43,7 @@ class AffinityFlowChatter:
}
self.last_activity_time = time.time()
async def process_stream_context(self, context) -> Dict[str, any]:
async def process_stream_context(self, context: StreamContext) -> Dict[str, any]:
"""
处理StreamContext对象
@@ -53,27 +54,18 @@ class AffinityFlowChatter:
处理结果字典
"""
try:
# 获取未读消息和历史消息
unread_messages = context.get_unread_messages()
history_messages = context.get_history_messages()
# 准备消息数据
message_data = {
"unread_messages": unread_messages,
"history_messages": history_messages
}
# 使用增强版规划器处理消息
actions, target_message = await self.planner.plan(
mode=ChatMode.FOCUS,
message_data=message_data
context=context
)
self.stats["plans_created"] += 1
# 执行动作(如果规划器返回了动作)
execution_result = {"executed_count": len(actions) if actions else 0}
if actions:
# 这里可以添加额外的动作执行逻辑
logger.debug(f"聊天流 {self.stream_id} 生成了 {len(actions)} 个动作")
# 更新统计

View File

@@ -114,20 +114,49 @@ class UserRelationshipTracker:
async def _update_user_relationship(self, interaction: Dict) -> Optional[Dict]:
"""更新单个用户的关系"""
try:
# 获取bot人设信息
from src.individuality.individuality import Individuality
individuality = Individuality()
bot_personality = await individuality.get_personality_block()
prompt = f"""
分析以下用户交互,更新用户关系:
你现在是一个有着特定性格和身份的AI助手。你的人设是{bot_personality}
请以你独特的性格视角,严格按现实逻辑分析以下用户交互,更新用户关系:
用户ID: {interaction["user_id"]}
用户名: {interaction["user_name"]}
用户消息: {interaction["user_message"]}
Bot回复: {interaction["bot_reply"]}
你的回复: {interaction["bot_reply"]}
当前关系分: {interaction["current_relationship_score"]}
【重要】关系分数档次定义:
- 0.0-0.2:陌生人/初次认识 - 仅礼貌性交流
- 0.2-0.4:普通网友 - 有基本互动但不熟悉
- 0.4-0.6:熟悉网友 - 经常交流,有一定了解
- 0.6-0.8:朋友 - 可以分享心情,互相关心
- 0.8-1.0:好朋友/知己 - 深度信任,亲密无间
【严格要求】:
1. 加分必须符合现实关系发展逻辑 - 不能因为对方态度好就盲目加分到不符合当前关系档次的分数
2. 关系提升需要足够的互动积累和时间验证
3. 即使是朋友关系单次互动加分通常不超过0.05-0.1
4. 关系描述要详细具体,包括:
- 用户性格特点观察
- 印象深刻的互动记忆
- 你们关系的具体状态描述
根据你的人设性格,思考:
1. 以你的性格,你会如何看待这次互动?
2. 用户的行为是否符合你性格的喜好?
3. 这次互动是否真的让你们的关系提升了一个档次?为什么?
4. 有什么特别值得记住的互动细节?
请以JSON格式返回更新结果
{{
"new_relationship_score": 0.0~1.0的数值,
"reasoning": "更新理由",
"interaction_summary": "交互总结"
"new_relationship_score": 0.0~1.0的数值(必须符合现实逻辑),
"reasoning": "从你的性格角度说明更新理由,重点说明是否符合现实关系发展逻辑",
"interaction_summary": "基于你性格的交互总结,包含印象深刻的互动记忆"
}}
"""
@@ -470,36 +499,59 @@ Bot回复: {interaction["bot_reply"]}
# 构建分析提示
user_reactions_text = "\n".join([f"- {msg.processed_plain_text}" for msg in user_reactions])
# 获取bot人设信息
from src.individuality.individuality import Individuality
individuality = Individuality()
bot_personality = await individuality.get_personality_block()
prompt = f"""
分析以下用户交互,更新用户关系印象和分数:
你现在是一个有着特定性格和身份的AI助手。你的人设是{bot_personality}
请以你独特的性格视角,严格按现实逻辑分析以下用户交互,更新用户关系印象和分数:
用户信息:
- 用户ID: {user_id}
- 用户名: {user_name}
上次Bot回复: {last_bot_reply.processed_plain_text}
上次回复: {last_bot_reply.processed_plain_text}
用户反应消息:
{user_reactions_text}
当前Bot回复: {current_reply}
当前回复: {current_reply}
当前关系印象: {current_text}
当前关系分数: {current_score:.3f}
请根据用户的反应和对话内容分析用户性格特点、与Bot的互动模式然后更新关系印象和分数。
【重要】关系分数档次定义:
- 0.0-0.2:陌生人/初次认识 - 仅礼貌性交流
- 0.2-0.4:普通网友 - 有基本互动但不熟悉
- 0.4-0.6:熟悉网友 - 经常交流,有一定了解
- 0.6-0.8:朋友 - 可以分享心情,互相关心
- 0.8-1.0:好朋友/知己 - 深度信任,亲密无间
分析要点:
1. 用户的情绪态度(积极/消极/中性)
2. 用户对Bot的兴趣程度
3. 用户的交流风格(主动/被动/友好/正式等)
4. 互动的质量和深度
【严格要求】:
1. 加分必须符合现实关系发展逻辑 - 不能因为用户反应好就盲目加分
2. 关系提升需要足够的互动积累和时间验证单次互动加分通常不超过0.05-0.1
3. 必须考虑当前关系档次不能跳跃式提升比如从0.3直接到0.7
4. 关系印象描述要详细具体100-200字包括
- 用户性格特点和交流风格观察
- 印象深刻的互动记忆和对话片段
- 你们关系的具体状态描述和发展阶段
- 根据你的性格,你对用户的真实感受
性格视角深度分析:
1. 以你的性格特点,用户这次的反应给你什么感受?
2. 用户的情绪和行为符合你性格的喜好吗?具体哪些方面?
3. 从现实角度看,这次互动是否足以让关系提升到下一个档次?为什么?
4. 有什么特别值得记住的互动细节或对话内容?
5. 基于你们的互动历史,用户给你留下了哪些深刻印象?
请以JSON格式返回更新结果:
{{
"relationship_text": "更新的关系印象描述(50字以内)",
"relationship_score": 0.0~1.0的新分数,
"analysis_reasoning": "分析理由说明",
"relationship_text": "详细的关系印象描述(100-200字),包含用户性格观察、印象深刻记忆、关系状态描述",
"relationship_score": 0.0~1.0的新分数(必须严格符合现实逻辑),
"analysis_reasoning": "从你性格角度的深度分析,重点说明分数调整的现实合理性",
"interaction_quality": "high/medium/low"
}}
"""

View File

@@ -134,9 +134,7 @@ class MessageManager:
self._clear_all_unread_messages(context)
except Exception as e:
# 发生异常时,清除所有未读消息,防止意外关闭等导致消息一直未读
logger.error(f"处理聊天流 {stream_id} 时发生异常,将清除所有未读消息: {e}")
self._clear_all_unread_messages(context)
raise
logger.debug(f"聊天流 {stream_id} 消息处理完成")

View File

@@ -126,7 +126,7 @@ class PlanExecutor:
try:
logger.info(f"执行回复动作: {action_info.action_type}, 原因: {action_info.reasoning}")
if action_info.action_message.get("user_id", "") == str(global_config.bot.qq_account):
if action_info.action_message.user_info.user_id == str(global_config.bot.qq_account):
logger.warning("尝试回复自己,跳过此动作以防止死循环。")
return {
"action_type": action_info.action_type,

View File

@@ -263,9 +263,6 @@ class PlanFilter:
# 获取聊天流的上下文
stream_context = message_manager.stream_contexts.get(plan.chat_id)
if not stream_context:
# 如果没有找到对应的上下文,使用兼容性处理
return await self._fallback_build_history_blocks(plan)
# 获取真正的已读和未读消息
read_messages = stream_context.history_messages # 已读消息存储在history_messages中

View File

@@ -13,6 +13,7 @@ from src.chat.planner_actions.plan_generator import PlanGenerator
from src.chat.affinity_flow.interest_scoring import InterestScoringSystem
from src.chat.affinity_flow.relationship_tracker import UserRelationshipTracker
from src.common.data_models.info_data_model import Plan
from src.common.data_models.message_manager_data_model import StreamContext
from src.common.logger import get_logger
from src.config.config import global_config
from src.plugin_system.base.component_types import ChatMode
@@ -84,16 +85,14 @@ class ActionPlanner:
}
async def plan(
self, mode: ChatMode = ChatMode.FOCUS, message_data: dict = None
self, mode: ChatMode = ChatMode.FOCUS, context: StreamContext = None
) -> Tuple[List[Dict], Optional[Dict]]:
"""
执行完整的增强版规划流程。
Args:
mode (ChatMode): 当前的聊天模式,默认为 FOCUS。
message_data (dict): 消息数据字典,包含:
- unread_messages: 未读消息列表
- history_messages: 历史消息列表(可选)
context (StreamContext): 包含聊天流消息的上下文对象。
Returns:
Tuple[List[Dict], Optional[Dict]]: 一个元组,包含:
@@ -101,11 +100,9 @@ class ActionPlanner:
- final_target_message_dict (Optional[Dict]): 最终的目标消息(字典格式)。
"""
try:
# 提取未读消息用于兴趣度计算
unread_messages = message_data.get("unread_messages", []) if message_data else []
self.planner_stats["total_plans"] += 1
return await self._enhanced_plan_flow(mode, unread_messages or [])
return await self._enhanced_plan_flow(mode, context)
except Exception as e:
logger.error(f"规划流程出错: {e}")
@@ -113,13 +110,14 @@ class ActionPlanner:
return [], None
async def _enhanced_plan_flow(
self, mode: ChatMode, unread_messages: List[Dict]
self, mode: ChatMode, context: StreamContext
) -> Tuple[List[Dict], Optional[Dict]]:
"""执行增强版规划流程"""
try:
# 1. 生成初始 Plan
initial_plan = await self.generator.generate(mode)
unread_messages = context.get_unread_messages() if context else []
# 2. 兴趣度评分 - 只对未读消息进行评分
if unread_messages:
bot_nickname = global_config.bot.nickname
@@ -135,8 +133,8 @@ class ActionPlanner:
logger.info(f"消息兴趣度不足({latest_score.total_score:.2f})移除reply动作")
reply_not_available = True
base_threshold = self.interest_scoring.reply_threshold
# 检查兴趣度是否达到阈值的0.8
# base_threshold = self.interest_scoring.reply_threshold
# 检查兴趣度是否达到非回复动作阈值
non_reply_action_interest_threshold = global_config.affinity_flow.non_reply_action_interest_threshold
if score < non_reply_action_interest_threshold:
logger.info(f"❌ 兴趣度不足非回复动作阈值: {score:.3f} < {non_reply_action_interest_threshold:.3f}直接返回no_action")

View File

@@ -987,8 +987,9 @@ class DefaultReplyer:
if person_name is None:
# 尝试从reply_message获取用户名
fallback_name = reply_message.get("user_nickname") or reply_message.get("user_id", "未知用户")
logger.warning(f"无法获取person_name使用fallback: {fallback_name}")
logger.warning(f"未知用户,将存储用户信息:{fallback_name}")
person_name = str(fallback_name)
person_info_manager.set_value(person_id, "person_name", fallback_name)
# 检查是否是bot自己的名字如果是则替换为"(你)"
bot_user_id = str(global_config.bot.qq_account)

View File

@@ -93,10 +93,50 @@ class PersonInfoManager:
if "-" in platform:
platform = platform.split("-")[1]
# 在此处打一个补丁如果platform为qq尝试生成id后检查是否存在如果不存在则将平台换为napcat后再次检查如果存在则更新原id为platform为qq的id
components = [platform, str(user_id)]
key = "_".join(components)
return hashlib.md5(key.encode()).hexdigest()
# 如果不是 qq 平台,直接返回计算的 id
if platform != "qq":
return hashlib.md5(key.encode()).hexdigest()
qq_id = hashlib.md5(key.encode()).hexdigest()
# 对于 qq 平台,先检查该 person_id 是否已存在;如果存在直接返回
def _db_check_and_migrate_sync(p_id: str, raw_user_id: str):
try:
with get_db_session() as session:
# 检查 qq_id 是否存在
existing_qq = session.execute(select(PersonInfo).where(PersonInfo.person_id == p_id)).scalar()
if existing_qq:
return p_id
# 如果 qq_id 不存在,尝试使用 napcat 作为平台生成对应 id 并检查
nap_components = ["napcat", str(raw_user_id)]
nap_key = "_".join(nap_components)
nap_id = hashlib.md5(nap_key.encode()).hexdigest()
existing_nap = session.execute(select(PersonInfo).where(PersonInfo.person_id == nap_id)).scalar()
if not existing_nap:
# napcat 也不存在,返回 qq_id未命中
return p_id
# napcat 存在,迁移该记录:更新 person_id 与 platform -> qq
try:
# 更新现有 napcat 记录
existing_nap.person_id = p_id
existing_nap.platform = "qq"
existing_nap.user_id = str(raw_user_id)
session.commit()
return p_id
except Exception:
session.rollback()
return p_id
except Exception as e:
logger.error(f"检查/迁移 napcat->qq 时出错: {e}")
return p_id
return _db_check_and_migrate_sync(qq_id, user_id)
async def is_person_known(self, platform: str, user_id: int):
"""判断是否认识某人"""

View File

@@ -11,7 +11,7 @@ router = None
def create_router(plugin_config: dict):
"""创建路由器实例"""
global router
platform_name = config_api.get_plugin_config(plugin_config, "maibot_server.platform_name", "napcat")
platform_name = config_api.get_plugin_config(plugin_config, "maibot_server.platform_name", "qq")
host = config_api.get_plugin_config(plugin_config, "maibot_server.host", "localhost")
port = config_api.get_plugin_config(plugin_config, "maibot_server.port", 8000)