Skip to content

Commit 751a46d

Browse files
committed
feat:为normal加入了关系的构建和使用
1 parent 733f76b commit 751a46d

4 files changed

Lines changed: 303 additions & 20 deletions

File tree

src/chat/normal_chat/normal_chat.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
from src.chat.normal_chat.normal_chat_action_modifier import NormalChatActionModifier
2222
from src.chat.normal_chat.normal_chat_expressor import NormalChatExpressor
2323
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
24+
from src.person_info.person_info import PersonInfoManager
25+
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat
26+
from src.person_info.relationship_manager import get_relationship_manager
2427

2528
willing_manager = get_willing_manager()
2629

@@ -64,6 +67,9 @@ def __init__(self, chat_stream: ChatStream, interest_dict: dict = None, on_switc
6467
self.recent_replies = []
6568
self.max_replies_history = 20 # 最多保存最近20条回复记录
6669

70+
# 添加engaging_person统计
71+
self.engaging_persons = {} # person_id -> {first_time, last_time, receive_count, reply_count, relation_built}
72+
6773
# 添加回调函数,用于在满足条件时通知切换到focus_chat模式
6874
self.on_switch_to_focus_callback = on_switch_to_focus_callback
6975

@@ -176,6 +182,8 @@ async def process_single_message(msg_id, message, interest_value, is_mentioned):
176182
else:
177183
self.adjust_reply_frequency(duration=(time.time() - self.start_time) / 60)
178184

185+
# print(self.engaging_persons)
186+
179187
await self.normal_response(
180188
message=message,
181189
is_mentioned=is_mentioned,
@@ -219,6 +227,12 @@ async def normal_response(self, message: MessageRecv, is_mentioned: bool, intere
219227
logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。")
220228
return
221229

230+
# 更新engaging_persons统计信息
231+
self._update_engaging_person_stats(message, is_reply=False)
232+
233+
# 检查是否有用户满足关系构建条件
234+
asyncio.create_task(self._check_relation_building_conditions())
235+
222236
timing_results = {}
223237
reply_probability = (
224238
1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0
@@ -410,6 +424,9 @@ async def plan_and_execute_actions():
410424

411425
# 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况)
412426
if first_bot_msg:
427+
# 更新engaging_persons统计信息 - 标记为回复
428+
self._update_engaging_person_stats(message, is_reply=True)
429+
413430
# 记录回复信息到最近回复列表中
414431
reply_info = {
415432
"time": time.time(),
@@ -646,3 +663,255 @@ def set_planner_enabled(self, enabled: bool):
646663
def get_action_manager(self) -> ActionManager:
647664
"""获取动作管理器实例"""
648665
return self.action_manager
666+
667+
def _update_engaging_person_stats(self, message: MessageRecv, is_reply: bool):
668+
"""更新engaging_persons统计信息"""
669+
# 通过platform和user_id计算person_id
670+
platform = message.message_info.platform
671+
user_id = message.message_info.user_info.user_id
672+
person_id = PersonInfoManager.get_person_id(platform, user_id)
673+
current_time = time.time()
674+
675+
if person_id not in self.engaging_persons:
676+
self.engaging_persons[person_id] = {
677+
"first_time": current_time,
678+
"last_time": current_time,
679+
"receive_count": 0,
680+
"reply_count": 0,
681+
"relation_built": False
682+
}
683+
684+
if is_reply:
685+
self.engaging_persons[person_id]["reply_count"] += 1
686+
logger.debug(f"[{self.stream_name}] 用户 {person_id} 回复次数更新: {self.engaging_persons[person_id]['reply_count']}")
687+
else:
688+
self.engaging_persons[person_id]["receive_count"] += 1
689+
self.engaging_persons[person_id]["last_time"] = current_time
690+
logger.debug(f"[{self.stream_name}] 用户 {person_id} 消息次数更新: {self.engaging_persons[person_id]['receive_count']}")
691+
692+
def get_engaging_persons(self) -> dict:
693+
"""获取所有engaging_persons统计信息
694+
695+
Returns:
696+
dict: person_id -> {first_time, last_time, receive_count, reply_count}
697+
"""
698+
return self.engaging_persons.copy()
699+
700+
def get_engaging_person_stats(self, person_id: str) -> dict:
701+
"""获取特定用户的统计信息
702+
703+
Args:
704+
person_id: 用户ID
705+
706+
Returns:
707+
dict: 用户统计信息,如果用户不存在则返回None
708+
"""
709+
return self.engaging_persons.get(person_id)
710+
711+
def get_top_engaging_persons(self, limit: int = 10, sort_by: str = "receive_count") -> list:
712+
"""获取最活跃的用户列表
713+
714+
Args:
715+
limit: 返回的用户数量限制
716+
sort_by: 排序依据,可选值: "receive_count", "reply_count", "last_time"
717+
718+
Returns:
719+
list: 按指定条件排序的用户列表
720+
"""
721+
if sort_by not in ["receive_count", "reply_count", "last_time"]:
722+
sort_by = "receive_count"
723+
724+
sorted_persons = sorted(
725+
self.engaging_persons.items(),
726+
key=lambda x: x[1][sort_by],
727+
reverse=True
728+
)
729+
730+
return sorted_persons[:limit]
731+
732+
def clear_engaging_persons_stats(self):
733+
"""清空engaging_persons统计信息"""
734+
self.engaging_persons.clear()
735+
logger.info(f"[{self.stream_name}] 已清空engaging_persons统计信息")
736+
737+
def get_relation_building_stats(self) -> dict:
738+
"""获取关系构建相关统计信息
739+
740+
Returns:
741+
dict: 关系构建统计信息
742+
"""
743+
total_persons = len(self.engaging_persons)
744+
relation_built_count = sum(1 for stats in self.engaging_persons.values()
745+
if stats.get("relation_built", False))
746+
pending_persons = []
747+
748+
current_time = time.time()
749+
for person_id, stats in self.engaging_persons.items():
750+
if not stats.get("relation_built", False):
751+
time_elapsed = current_time - stats["first_time"]
752+
total_messages = self._get_total_messages_in_timerange(
753+
stats["first_time"], stats["last_time"]
754+
)
755+
756+
# 检查是否接近满足条件
757+
progress_info = {
758+
"person_id": person_id,
759+
"time_elapsed": time_elapsed,
760+
"total_messages": total_messages,
761+
"receive_count": stats["receive_count"],
762+
"reply_count": stats["reply_count"],
763+
"progress": {
764+
"50_messages": f"{total_messages}/50 ({total_messages/50*100:.1f}%)",
765+
"35_msg_10min": f"{total_messages}/35 + {time_elapsed}/600s",
766+
"25_msg_30min": f"{total_messages}/25 + {time_elapsed}/1800s",
767+
"10_msg_1hour": f"{total_messages}/10 + {time_elapsed}/3600s"
768+
}
769+
}
770+
pending_persons.append(progress_info)
771+
772+
return {
773+
"total_persons": total_persons,
774+
"relation_built_count": relation_built_count,
775+
"pending_count": len(pending_persons),
776+
"pending_persons": pending_persons
777+
}
778+
779+
def get_engaging_persons_summary(self) -> dict:
780+
"""获取engaging_persons统计摘要
781+
782+
Returns:
783+
dict: 包含总用户数、总消息数、总回复数等统计信息
784+
"""
785+
if not self.engaging_persons:
786+
return {
787+
"total_persons": 0,
788+
"total_messages": 0,
789+
"total_replies": 0,
790+
"most_active_person": None,
791+
"most_replied_person": None
792+
}
793+
794+
total_messages = sum(stats["receive_count"] for stats in self.engaging_persons.values())
795+
total_replies = sum(stats["reply_count"] for stats in self.engaging_persons.values())
796+
797+
most_active = max(self.engaging_persons.items(), key=lambda x: x[1]["receive_count"])
798+
most_replied = max(self.engaging_persons.items(), key=lambda x: x[1]["reply_count"])
799+
800+
return {
801+
"total_persons": len(self.engaging_persons),
802+
"total_messages": total_messages,
803+
"total_replies": total_replies,
804+
"most_active_person": {
805+
"person_id": most_active[0],
806+
"message_count": most_active[1]["receive_count"]
807+
},
808+
"most_replied_person": {
809+
"person_id": most_replied[0],
810+
"reply_count": most_replied[1]["reply_count"]
811+
}
812+
}
813+
814+
async def _check_relation_building_conditions(self):
815+
"""检查engaging_persons中是否有满足关系构建条件的用户"""
816+
current_time = time.time()
817+
818+
for person_id, stats in list(self.engaging_persons.items()):
819+
# 跳过已经进行过关系构建的用户
820+
if stats.get("relation_built", False):
821+
continue
822+
823+
# 计算时间差和消息数量
824+
time_elapsed = current_time - stats["first_time"]
825+
total_messages = self._get_total_messages_in_timerange(
826+
stats["first_time"], stats["last_time"]
827+
)
828+
829+
# 检查是否满足关系构建条件
830+
should_build_relation = (
831+
total_messages >= 50 # 50条消息必定满足
832+
or (total_messages >= 35 and time_elapsed >= 600) # 35条且10分钟
833+
or (total_messages >= 25 and time_elapsed >= 1800) # 25条且30分钟
834+
or (total_messages >= 10 and time_elapsed >= 3600) # 10条且1小时
835+
)
836+
837+
if should_build_relation:
838+
logger.info(
839+
f"[{self.stream_name}] 用户 {person_id} 满足关系构建条件。"
840+
f"消息数:{total_messages},时长:{time_elapsed:.0f}秒,"
841+
f"收到消息:{stats['receive_count']},回复次数:{stats['reply_count']}"
842+
)
843+
844+
# 计算构建概率并决定是否构建
845+
await self._evaluate_and_build_relation(person_id, stats, total_messages)
846+
847+
848+
def _get_total_messages_in_timerange(self, start_time: float, end_time: float) -> int:
849+
"""获取指定时间范围内的总消息数量"""
850+
try:
851+
messages = get_raw_msg_by_timestamp_with_chat(self.stream_id, start_time, end_time)
852+
return len(messages) if messages else 0
853+
except Exception as e:
854+
logger.error(f"[{self.stream_name}] 获取时间范围内消息数量失败: {e}")
855+
return 0
856+
857+
async def _evaluate_and_build_relation(self, person_id: str, stats: dict, total_messages: int):
858+
"""评估并执行关系构建"""
859+
receive_count = stats["receive_count"]
860+
reply_count = stats["reply_count"]
861+
862+
# 计算回复概率(reply_count在总消息中的比值)
863+
reply_ratio = reply_count / total_messages if total_messages > 0 else 0
864+
reply_build_probability = reply_ratio # 100%回复则100%构建
865+
866+
# 计算接收概率(receive_count的影响)
867+
receive_ratio = receive_count / total_messages if total_messages > 0 else 0
868+
receive_build_probability = receive_ratio * 0.25 # 100%接收则25%构建
869+
870+
# 取最高概率
871+
final_probability = max(reply_build_probability, receive_build_probability)
872+
873+
logger.info(
874+
f"[{self.stream_name}] 用户 {person_id} 关系构建概率评估:"
875+
f"回复比例:{reply_ratio:.2f}({reply_build_probability:.2f})"
876+
f",接收比例:{receive_ratio:.2f}({receive_build_probability:.2f})"
877+
f",最终概率:{final_probability:.2f}"
878+
)
879+
880+
# 使用随机数决定是否构建关系
881+
if random() < final_probability:
882+
logger.info(f"[{self.stream_name}] 决定为用户 {person_id} 构建关系")
883+
await self._build_relation_for_person(person_id, stats)
884+
# 标记已构建
885+
stats["relation_built"] = True
886+
else:
887+
logger.info(f"[{self.stream_name}] 用户 {person_id} 未通过关系构建概率判定")
888+
# 即使未构建,也标记为已处理,避免重复判定
889+
stats["relation_built"] = True
890+
891+
async def _build_relation_for_person(self, person_id: str, stats: dict):
892+
"""为特定用户构建关系"""
893+
try:
894+
start_time = stats["first_time"]
895+
end_time = stats["last_time"]
896+
897+
# 获取该时间段的所有消息用于关系构建
898+
messages = get_raw_msg_by_timestamp_with_chat(self.stream_id, start_time, end_time)
899+
900+
if messages:
901+
logger.info(f"[{self.stream_name}] 为用户 {person_id} 获取到 {len(messages)} 条消息用于关系构建")
902+
903+
# 调用关系管理器更新印象
904+
relationship_manager = get_relationship_manager()
905+
await relationship_manager.update_person_impression(
906+
person_id=person_id,
907+
timestamp=end_time,
908+
bot_engaged_messages=messages
909+
)
910+
911+
logger.info(f"[{self.stream_name}] 用户 {person_id} 关系构建完成")
912+
else:
913+
logger.warning(f"[{self.stream_name}] 未找到用户 {person_id} 的消息,关系构建跳过")
914+
915+
except Exception as e:
916+
logger.error(f"[{self.stream_name}] 为用户 {person_id} 构建关系时出错: {e}")
917+
traceback.print_exc()

src/common/database/database_model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ class PersonInfo(BaseModel):
242242
user_id = TextField(index=True) # 用户ID
243243
nickname = TextField() # 用户昵称
244244
impression = TextField(null=True) # 个人印象
245+
short_impression = TextField(null=True) # 个人印象的简短描述
245246
points = TextField(null=True) # 个人印象的点
246247
forgotten_points = TextField(null=True) # 被遗忘的点
247248
info_list = TextField(null=True) # 与Bot的互动

src/person_info/person_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
# "user_cardname": None, # This field is not in Peewee model PersonInfo
4444
# "user_avatar": None, # This field is not in Peewee model PersonInfo
4545
"impression": None, # Corrected from persion_impression
46+
"short_impression": None,
4647
"info_list": None,
4748
"points": None,
4849
"forgotten_points": None,

src/person_info/relationship_manager.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -124,31 +124,14 @@ async def build_relationship_info(self, person, is_id: bool = False) -> str:
124124
person_name = await person_info_manager.get_value(person_id, "person_name")
125125
if not person_name or person_name == "none":
126126
return ""
127-
# impression = await person_info_manager.get_value(person_id, "impression")
128-
points = await person_info_manager.get_value(person_id, "points") or []
129-
130-
if isinstance(points, str):
131-
try:
132-
points = ast.literal_eval(points)
133-
except (SyntaxError, ValueError):
134-
points = []
135-
136-
random_points = random.sample(points, min(5, len(points))) if points else []
127+
short_impression = await person_info_manager.get_value(person_id, "short_impression")
137128

138129
nickname_str = await person_info_manager.get_value(person_id, "nickname")
139130
platform = await person_info_manager.get_value(person_id, "platform")
140131
relation_prompt = f"'{person_name}' ,ta在{platform}上的昵称是{nickname_str}。"
141132

142-
# if impression:
143-
# relation_prompt += f"你对ta的印象是:{impression}。"
144-
145-
if random_points:
146-
for point in random_points:
147-
# print(f"point: {point}")
148-
# print(f"point[2]: {point[2]}")
149-
# print(f"point[0]: {point[0]}")
150-
point_str = f"时间:{point[2]}。内容:{point[0]}"
151-
relation_prompt += f"你记得{person_name}最近的点是:{point_str}。"
133+
if short_impression:
134+
relation_prompt += f"你对ta的印象是:{short_impression}。"
152135

153136
return relation_prompt
154137

@@ -448,6 +431,35 @@ async def update_person_impression(self, person_id, timestamp, bot_engaged_messa
448431

449432
await person_info_manager.update_one_field(person_id, "impression", compressed_summary)
450433

434+
435+
436+
437+
compress_short_prompt = f"""
438+
你的名字是{global_config.bot.nickname}{global_config.bot.nickname}的别名是{alias_str}
439+
请不要混淆你自己和{global_config.bot.nickname}{person_name}
440+
441+
你对{person_name}的了解是:
442+
{compressed_summary}
443+
444+
请你用一句话概括你对{person_name}的了解。突出:
445+
1.对{person_name}的直观印象
446+
2.{global_config.bot.nickname}{person_name}的关系
447+
3.{person_name}的关键信息
448+
请输出一段平文本,以陈诉自白的语气,输出你对{person_name}的概括,不要输出任何其他内容。
449+
"""
450+
compressed_short_summary, _ = await self.relationship_llm.generate_response_async(prompt=compress_short_prompt)
451+
452+
# current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
453+
# compressed_short_summary = f"截至{current_time},你对{person_name}的了解:{compressed_short_summary}"
454+
455+
await person_info_manager.update_one_field(person_id, "short_impression", compressed_short_summary)
456+
457+
458+
459+
460+
461+
462+
451463
forgotten_points = []
452464

453465
# 这句代码的作用是:将更新后的 forgotten_points(遗忘的记忆点)列表,序列化为 JSON 字符串后,写回到数据库中的 forgotten_points 字段

0 commit comments

Comments
 (0)