feat(person-info): 增强人物关系信息构建功能

重构关系信息构建逻辑,从数据库查询更多维度数据生成详细关系描述:
- 增加认识时间、交流频率、态度评分等基本信息
- 整合UserRelationships表的额外关系数据
- 添加态度和关系分数的描述性文字转换方法
- 优化特征点选择策略,按权重和时效性综合排序
- 提供更结构化的关系信息输出格式
This commit is contained in:
Windpicker-owo
2025-09-19 18:24:30 +08:00
parent b905320c07
commit b6753f2de4
2 changed files with 147 additions and 54 deletions

View File

@@ -31,7 +31,6 @@ from src.chat.express.expression_selector import expression_selector
from src.chat.memory_system.memory_activator import MemoryActivator from src.chat.memory_system.memory_activator import MemoryActivator
from src.chat.memory_system.vector_instant_memory import VectorInstantMemoryV2 from src.chat.memory_system.vector_instant_memory import VectorInstantMemoryV2
from src.mood.mood_manager import mood_manager from src.mood.mood_manager import mood_manager
from src.person_info.relationship_fetcher import relationship_fetcher_manager
from src.person_info.person_info import get_person_info_manager from src.person_info.person_info import get_person_info_manager
from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.base.component_types import ActionInfo, EventType
from src.plugin_system.apis import llm_api from src.plugin_system.apis import llm_api
@@ -1478,8 +1477,6 @@ class DefaultReplyer:
if not global_config.relationship.enable_relationship: if not global_config.relationship.enable_relationship:
return "" return ""
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
# 获取用户ID # 获取用户ID
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id_by_person_name(sender) person_id = person_info_manager.get_person_id_by_person_name(sender)
@@ -1487,7 +1484,48 @@ class DefaultReplyer:
logger.warning(f"未找到用户 {sender} 的ID跳过信息提取") logger.warning(f"未找到用户 {sender} 的ID跳过信息提取")
return f"你完全不认识{sender}不理解ta的相关信息。" return f"你完全不认识{sender}不理解ta的相关信息。"
return await relationship_fetcher.build_relation_info(person_id, points_num=5) # 使用AFC关系追踪器获取关系信息
try:
from src.chat.affinity_flow.relationship_integration import get_relationship_tracker
relationship_tracker = get_relationship_tracker()
if relationship_tracker:
# 获取用户信息以获取真实的user_id
user_info = await person_info_manager.get_values(person_id, ["user_id", "platform"])
user_id = user_info.get("user_id", "unknown")
# 从数据库获取关系数据
relationship_data = relationship_tracker._get_user_relationship_from_db(user_id)
if relationship_data:
relationship_text = relationship_data.get("relationship_text", "")
relationship_score = relationship_data.get("relationship_score", 0.3)
# 构建丰富的关系信息描述
if relationship_text:
# 转换关系分数为描述性文本
if relationship_score >= 0.8:
relationship_level = "非常亲密的朋友"
elif relationship_score >= 0.6:
relationship_level = "好朋友"
elif relationship_score >= 0.4:
relationship_level = "普通朋友"
elif relationship_score >= 0.2:
relationship_level = "认识的人"
else:
relationship_level = "陌生人"
return f"你与{sender}的关系:{relationship_level}(关系分:{relationship_score:.2f}/1.0)。{relationship_text}"
else:
return f"你与{sender}是初次见面,关系分:{relationship_score:.2f}/1.0。"
else:
return f"你完全不认识{sender},这是第一次互动。"
else:
logger.warning("AFC关系追踪器未初始化使用默认关系信息")
return f"你与{sender}是普通朋友关系。"
except Exception as e:
logger.error(f"获取AFC关系信息失败: {e}")
return f"你与{sender}是普通朋友关系。"
def weighted_sample_no_replacement(items, weights, k) -> list: def weighted_sample_no_replacement(items, weights, k) -> list:

View File

@@ -94,78 +94,133 @@ class RelationshipFetcher:
if not self.info_fetched_cache[person_id]: if not self.info_fetched_cache[person_id]:
del self.info_fetched_cache[person_id] del self.info_fetched_cache[person_id]
async def build_relation_info(self, person_id, points_num=3): async def build_relation_info(self, person_id, points_num=5):
"""构建详细的人物关系信息,包含从数据库中查询的丰富关系描述"""
# 清理过期的信息缓存 # 清理过期的信息缓存
self._cleanup_expired_cache() self._cleanup_expired_cache()
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
person_name = await person_info_manager.get_value(person_id, "person_name") person_name = await person_info_manager.get_value(person_id, "person_name")
short_impression = await person_info_manager.get_value(person_id, "short_impression") short_impression = await person_info_manager.get_value(person_id, "short_impression")
full_impression = await person_info_manager.get_value(person_id, "impression")
attitude = await person_info_manager.get_value(person_id, "attitude") or 50
nickname_str = await person_info_manager.get_value(person_id, "nickname") nickname_str = await person_info_manager.get_value(person_id, "nickname")
platform = await person_info_manager.get_value(person_id, "platform") platform = await person_info_manager.get_value(person_id, "platform")
know_times = await person_info_manager.get_value(person_id, "know_times") or 0
know_since = await person_info_manager.get_value(person_id, "know_since")
last_know = await person_info_manager.get_value(person_id, "last_know")
if person_name == nickname_str and not short_impression: # 如果用户没有基本信息,返回默认描述
return "" if person_name == nickname_str and not short_impression and not full_impression:
return f"你完全不认识{person_name},这是你们第一次交流。"
# 获取用户特征点
current_points = await person_info_manager.get_value(person_id, "points") or [] current_points = await person_info_manager.get_value(person_id, "points") or []
forgotten_points = await person_info_manager.get_value(person_id, "forgotten_points") or []
# 按时间排序forgotten_points # 按时间排序并选择最有代表性的特征点
current_points.sort(key=lambda x: x[2]) all_points = current_points + forgotten_points
# 按权重加权随机抽取最多3个不重复的pointspoint[1]的值在1-10之间权重越高被抽到概率越大 if all_points:
if len(current_points) > points_num: # 按权重和时效性综合排序
# point[1] 取值范围1-10直接作为权重 all_points.sort(key=lambda x: (float(x[1]) if len(x) > 1 else 0, float(x[2]) if len(x) > 2 else 0), reverse=True)
weights = [max(1, min(10, int(point[1]))) for point in current_points] selected_points = all_points[:points_num]
# 使用加权采样不放回,保证不重复 points_text = "\n".join([f"- {point[0]}{point[2]}" for point in selected_points if len(point) > 2])
indices = list(range(len(current_points)))
points = []
for _ in range(points_num):
if not indices:
break
sub_weights = [weights[i] for i in indices]
chosen_idx = random.choices(indices, weights=sub_weights, k=1)[0]
points.append(current_points[chosen_idx])
indices.remove(chosen_idx)
else: else:
points = current_points points_text = ""
# 构建points文本 # 构建详细的关系描述
points_text = "\n".join([f"{point[2]}{point[0]}" for point in points]) relation_parts = []
nickname_str = "" # 1. 基本信息
if person_name != nickname_str: if nickname_str and person_name != nickname_str:
nickname_str = f"(ta{platform}的昵称是{nickname_str})" relation_parts.append(f"用户{person_name}{platform}平台的昵称是{nickname_str}")
relation_info = "" # 2. 认识时间和频率
if know_since:
from datetime import datetime
know_time = datetime.fromtimestamp(know_since).strftime('%Y年%m月%d')
relation_parts.append(f"你从{know_time}开始认识{person_name}")
if know_times > 0:
relation_parts.append(f"你们已经交流过{int(know_times)}")
if last_know:
from datetime import datetime
last_time = datetime.fromtimestamp(last_know).strftime('%m月%d')
relation_parts.append(f"最近一次交流是在{last_time}")
if short_impression and relation_info: # 3. 态度和印象
if points_text: attitude_desc = self._get_attitude_description(attitude)
relation_info = f"你对{person_name}印象{nickname_str}{short_impression}。具体来说:{relation_info}。你还记得ta最近做的事{points_text}" relation_parts.append(f"你对{person_name}态度{attitude_desc}")
else:
relation_info = ( if short_impression:
f"你对{person_name}的印象是{nickname_str}{short_impression}。具体来说:{relation_info}" relation_parts.append(f"你对ta的总体印象{short_impression}")
)
elif short_impression: if full_impression:
if points_text: relation_parts.append(f"更详细的了解:{full_impression}")
relation_info = (
f"你对{person_name}的印象是{nickname_str}{short_impression}。你还记得ta最近做的事{points_text}" # 4. 特征点和记忆
) if points_text:
else: relation_parts.append(f"你记得关于{person_name}的一些事情:\n{points_text}")
relation_info = f"你对{person_name}的印象是{nickname_str}{short_impression}"
elif relation_info: # 5. 从UserRelationships表获取额外关系信息
if points_text: try:
relation_info = ( from src.common.database.sqlalchemy_database_api import db_query
f"你对{person_name}的了解{nickname_str}{relation_info}。你还记得ta最近做的事{points_text}" from src.common.database.sqlalchemy_models import UserRelationships
)
else: # 查询用户关系数据
relation_info = f"你对{person_name}的了解{nickname_str}{relation_info}" relationships = await db_query(
elif points_text: UserRelationships,
relation_info = f"你记得{person_name}{nickname_str}最近做的事:{points_text}" filters=[UserRelationships.user_id == str(person_info_manager.get_value_sync(person_id, "user_id"))],
limit=1
)
if relationships:
rel_data = relationships[0]
if rel_data.relationship_text:
relation_parts.append(f"关系记录:{rel_data.relationship_text}")
if rel_data.relationship_score:
score_desc = self._get_relationship_score_description(rel_data.relationship_score)
relation_parts.append(f"关系亲密程度:{score_desc}")
except Exception as e:
logger.debug(f"查询UserRelationships表失败: {e}")
# 构建最终的关系信息字符串
if relation_parts:
relation_info = f"关于{person_name},你知道以下信息:\n" + "\n".join([f"{part}" for part in relation_parts])
else: else:
relation_info = "" relation_info = f"你对{person_name}了解不多,这是比较初步的交流。"
return relation_info return relation_info
def _get_attitude_description(self, attitude: int) -> str:
"""根据态度分数返回描述性文字"""
if attitude >= 80:
return "非常喜欢和欣赏"
elif attitude >= 60:
return "比较有好感"
elif attitude >= 40:
return "中立态度"
elif attitude >= 20:
return "有些反感"
else:
return "非常厌恶"
def _get_relationship_score_description(self, score: float) -> str:
"""根据关系分数返回描述性文字"""
if score >= 0.8:
return "非常亲密的好友"
elif score >= 0.6:
return "关系不错的朋友"
elif score >= 0.4:
return "普通熟人"
elif score >= 0.2:
return "认识但不熟悉"
else:
return "陌生人"
async def _build_fetch_query(self, person_id, target_message, chat_history): async def _build_fetch_query(self, person_id, target_message, chat_history):
nickname_str = ",".join(global_config.bot.alias_names) nickname_str = ",".join(global_config.bot.alias_names)
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。" name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"