Skip to content

Commit 851a559

Browse files
committed
feat:将关系改为及时构建
1 parent 2df83f0 commit 851a559

8 files changed

Lines changed: 193 additions & 93 deletions

File tree

src/chat/focus_chat/info_processors/relationship_processor.py

Lines changed: 160 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
from json_repair import repair_json
1717
from src.person_info.person_info import person_info_manager
1818
import json
19+
import asyncio
20+
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat
1921

2022
logger = get_logger("processor")
2123

@@ -37,6 +39,7 @@ def init_prompt():
3739
1. 根据聊天记录的需求,如果需要你和某个人的信息,请输出你和这个人之间精简的信息
3840
2. 如果没有特别需要提及的信息,就不用输出这个人的信息
3941
3. 如果有人问你对他的看法或者关系,请输出你和这个人之间的信息
42+
4. 你可以完全不输出任何信息,或者不输出某个人
4043
4144
请从这些信息中提取出你对某人的了解信息,信息提取成一串文本:
4245
@@ -58,6 +61,11 @@ def __init__(self, subheartflow_id: str):
5861
super().__init__()
5962

6063
self.subheartflow_id = subheartflow_id
64+
self.person_cache: Dict[str, Dict[str, any]] = {} # {person_id: {"info": str, "ttl": int, "start_time": float}}
65+
self.pending_updates: Dict[str, Dict[str, any]] = (
66+
{}
67+
) # {person_id: {"start_time": float, "end_time": float, "grace_period_ttl": int, "chat_id": str}}
68+
self.grace_period_rounds = 5
6169

6270
self.llm_model = LLMRequest(
6371
model=global_config.model.relation,
@@ -91,54 +99,109 @@ async def process_info(
9199
return [relation_info]
92100

93101
async def relation_identify(
94-
self, observations: Optional[List[Observation]] = None,
102+
self,
103+
observations: Optional[List[Observation]] = None,
95104
):
96105
"""
97106
在回复前进行思考,生成内心想法并收集工具调用结果
98-
99-
参数:
100-
observations: 观察信息
101-
102-
返回:
103-
如果return_prompt为False:
104-
tuple: (current_mind, past_mind) 当前想法和过去的想法列表
105-
如果return_prompt为True:
106-
tuple: (current_mind, past_mind, prompt) 当前想法、过去的想法列表和使用的prompt
107107
"""
108-
109-
if observations is None:
110-
observations = []
111-
for observation in observations:
112-
if isinstance(observation, ChattingObservation):
113-
# 获取聊天元信息
114-
is_group_chat = observation.is_group_chat
115-
chat_target_info = observation.chat_target_info
116-
chat_target_name = "对方" # 私聊默认名称
117-
if not is_group_chat and chat_target_info:
118-
# 优先使用person_name,其次user_nickname,最后回退到默认值
119-
chat_target_name = (
120-
chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or chat_target_name
108+
# 0. 从观察信息中提取所需数据
109+
person_list = []
110+
chat_observe_info = ""
111+
is_group_chat = False
112+
if observations:
113+
for observation in observations:
114+
if isinstance(observation, ChattingObservation):
115+
is_group_chat = observation.is_group_chat
116+
chat_observe_info = observation.get_observe_info()
117+
person_list = observation.person_list
118+
break
119+
120+
# 1. 处理等待更新的条目(仅检查TTL,不检查是否被重提)
121+
persons_to_update_now = [] # 等待期结束,需要立即更新的用户
122+
for person_id, data in list(self.pending_updates.items()):
123+
data["grace_period_ttl"] -= 1
124+
if data["grace_period_ttl"] <= 0:
125+
persons_to_update_now.append(person_id)
126+
127+
# 触发等待期结束的更新任务
128+
for person_id in persons_to_update_now:
129+
if person_id in self.pending_updates:
130+
update_data = self.pending_updates.pop(person_id)
131+
logger.info(f"{self.log_prefix} 用户 {person_id} 等待期结束,开始印象更新。")
132+
asyncio.create_task(
133+
self.update_impression_on_cache_expiry(
134+
person_id, update_data["chat_id"], update_data["start_time"], update_data["end_time"]
121135
)
122-
# 获取聊天内容
123-
chat_observe_info = observation.get_observe_info()
124-
person_list = observation.person_list
125-
126-
nickname_str = ""
127-
for nicknames in global_config.bot.alias_names:
128-
nickname_str += f"{nicknames},"
136+
)
137+
138+
# 2. 维护活动缓存,并将过期条目移至等待区或立即更新
139+
persons_moved_to_pending = []
140+
for person_id, cache_data in self.person_cache.items():
141+
cache_data["ttl"] -= 1
142+
if cache_data["ttl"] <= 0:
143+
persons_moved_to_pending.append(person_id)
144+
145+
for person_id in persons_moved_to_pending:
146+
if person_id in self.person_cache:
147+
cache_item = self.person_cache.pop(person_id)
148+
start_time = cache_item.get("start_time")
149+
end_time = time.time()
150+
time_elapsed = end_time - start_time
151+
152+
impression_messages = get_raw_msg_by_timestamp_with_chat(self.subheartflow_id, start_time, end_time)
153+
message_count = len(impression_messages)
154+
155+
if message_count > 50 or (time_elapsed > 600 and message_count > 20):
156+
logger.info(
157+
f"{self.log_prefix} 用户 {person_id} 缓存过期,满足立即更新条件 (消息数: {message_count}, 持续时间: {time_elapsed:.0f}s),立即更新。"
158+
)
159+
asyncio.create_task(
160+
self.update_impression_on_cache_expiry(person_id, self.subheartflow_id, start_time, end_time)
161+
)
162+
else:
163+
logger.info(f"{self.log_prefix} 用户 {person_id} 缓存过期,进入更新等待区。")
164+
self.pending_updates[person_id] = {
165+
"start_time": start_time,
166+
"end_time": end_time,
167+
"grace_period_ttl": self.grace_period_rounds,
168+
"chat_id": self.subheartflow_id,
169+
}
170+
171+
# 3. 准备LLM输入和直接使用缓存
172+
if not person_list:
173+
return ""
174+
175+
cached_person_info_str = ""
176+
persons_to_process = []
177+
person_name_list_for_llm = []
178+
179+
for person_id in person_list:
180+
if person_id in self.person_cache:
181+
logger.info(f"{self.log_prefix} 关系识别 (缓存): {person_id}")
182+
person_name = await person_info_manager.get_value(person_id, "person_name")
183+
info = self.person_cache[person_id]["info"]
184+
cached_person_info_str += f"你对 {person_name} 的了解:{info}\n"
185+
else:
186+
# 所有不在活动缓存中的用户(包括等待区的)都将由LLM处理
187+
persons_to_process.append(person_id)
188+
person_name_list_for_llm.append(await person_info_manager.get_value(person_id, "person_name"))
189+
190+
# 4. 如果没有需要LLM处理的人员,直接返回缓存信息
191+
if not persons_to_process:
192+
final_result = cached_person_info_str.strip()
193+
if final_result:
194+
logger.info(f"{self.log_prefix} 关系识别 (全部缓存): {final_result}")
195+
return final_result
196+
197+
# 5. 为需要处理的人员准备LLM prompt
198+
nickname_str = ",".join(global_config.bot.alias_names)
129199
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
130-
131-
if is_group_chat:
132-
relation_prompt_init = "你对群聊里的人的印象是:\n"
133-
else:
134-
relation_prompt_init = "你对对方的印象是:\n"
135-
200+
relation_prompt_init = "你对群聊里的人的印象是:\n" if is_group_chat else "你对对方的印象是:\n"
136201
relation_prompt = ""
137-
person_name_list = []
138-
for person in person_list:
139-
relation_prompt += f"{await relationship_manager.build_relationship_info(person, is_id=True)}\n\n"
140-
person_name_list.append(await person_info_manager.get_value(person, "person_name"))
141-
202+
for person_id in persons_to_process:
203+
relation_prompt += f"{await relationship_manager.build_relationship_info(person_id, is_id=True)}\n\n"
204+
142205
if relation_prompt:
143206
relation_prompt = relation_prompt_init + relation_prompt
144207
else:
@@ -151,45 +214,76 @@ async def relation_identify(
151214
chat_observe_info=chat_observe_info,
152215
)
153216

154-
# print(prompt)
155-
156-
content = ""
217+
# 6. 调用LLM并处理结果
218+
newly_processed_info_str = ""
157219
try:
158220
logger.info(f"{self.log_prefix} 关系识别prompt: \n{prompt}\n")
159221
content, _ = await self.llm_model.generate_response_async(prompt=prompt)
160-
if not content:
222+
if content:
223+
print(f"content: {content}")
224+
content_json = json.loads(repair_json(content))
225+
226+
for person_name, person_info in content_json.items():
227+
if person_name in person_name_list_for_llm:
228+
try:
229+
idx = person_name_list_for_llm.index(person_name)
230+
person_id = persons_to_process[idx]
231+
232+
# 关键:检查此人是否在等待区,如果是,则为"唤醒"
233+
start_time = time.time() # 新用户的默认start_time
234+
if person_id in self.pending_updates:
235+
logger.info(f"{self.log_prefix} 用户 {person_id} 在等待期被LLM重提,重新激活缓存。")
236+
revived_item = self.pending_updates.pop(person_id)
237+
start_time = revived_item["start_time"]
238+
239+
self.person_cache[person_id] = {
240+
"info": person_info,
241+
"ttl": 5,
242+
"start_time": start_time,
243+
}
244+
newly_processed_info_str += f"你对 {person_name} 的了解:{person_info}\n"
245+
except (ValueError, IndexError):
246+
continue
247+
else:
161248
logger.warning(f"{self.log_prefix} LLM返回空结果,关系识别失败。")
162-
163-
print(f"content: {content}")
164-
165-
content = repair_json(content)
166-
content = json.loads(content)
167-
168-
person_info_str = ""
169-
170-
for person_name, person_info in content.items():
171-
# print(f"person_name: {person_name}, person_info: {person_info}")
172-
# print(f"person_list: {person_name_list}")
173-
if person_name not in person_name_list:
174-
continue
175-
person_str = f"你对 {person_name} 的了解:{person_info}\n"
176-
person_info_str += person_str
177-
178-
249+
179250
except Exception as e:
180-
# 处理总体异常
181251
logger.error(f"{self.log_prefix} 执行LLM请求或处理响应时出错: {e}")
182252
logger.error(traceback.format_exc())
183-
person_info_str = "关系识别过程中出现错误"
253+
newly_processed_info_str = "关系识别过程中出现错误"
254+
255+
# 7. 合并缓存和新处理的信息
256+
person_info_str = (cached_person_info_str + newly_processed_info_str).strip()
184257

185258
if person_info_str == "None":
186259
person_info_str = ""
187-
188-
# 记录初步思考结果
189260

190261
logger.info(f"{self.log_prefix} 关系识别: {person_info_str}")
191262

192263
return person_info_str
193264

265+
async def update_impression_on_cache_expiry(
266+
self, person_id: str, chat_id: str, start_time: float, end_time: float
267+
):
268+
"""
269+
在缓存过期时,获取聊天记录并更新用户印象
270+
"""
271+
logger.info(f"缓存过期,开始为 {person_id} 更新印象。时间范围:{start_time} -> {end_time}")
272+
try:
273+
274+
275+
impression_messages = get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time)
276+
if impression_messages:
277+
logger.info(f"为 {person_id} 获取到 {len(impression_messages)} 条消息用于印象更新。")
278+
await relationship_manager.update_person_impression(
279+
person_id=person_id, timestamp=end_time, bot_engaged_messages=impression_messages
280+
)
281+
else:
282+
logger.info(f"在指定时间范围内没有找到 {person_id} 的消息,不更新印象。")
283+
284+
except Exception as e:
285+
logger.error(f"为 {person_id} 更新印象时发生错误: {e}")
286+
logger.error(traceback.format_exc())
287+
194288

195289
init_prompt()

src/chat/focus_chat/info_processors/self_processor.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@
2020

2121
def init_prompt():
2222
indentify_prompt = """
23+
<聊天记录>
24+
{chat_observe_info}
25+
</聊天记录>
26+
27+
<人格>
2328
{name_block}
24-
你是一个AI,但是你伪装成了一个人类,不要让别人发现这一点
25-
请参考以下人格,不要被当前聊天内容中的内容误导:
29+
你是一个AI,但是你伪装成了一个人类,不要让别人发现这一点,你的性格是:
2630
{prompt_personality}
2731
{indentify_block}
32+
</人格>
2833
29-
以下是正在进行的聊天内容:
30-
现在是{time_now},你正在参与聊天
31-
{chat_observe_info}
34+
请区分聊天记录的内容和你稳定的人格,聊天记录是现在发生的事情,人格是你稳定的独特的特质。
3235
33-
现在请你输出对自己的描述:请严格遵守以下规则
36+
{name_block}
37+
现在请你提取你人格的关键信息,提取成一串文本:
3438
1. 根据聊天记录,输出与聊天记录相关的自我描述,包括人格,形象等等,对人格形象进行精简
3539
2. 思考有没有内容与你的描述相关
3640
3. 如果没有明显相关内容,请输出十几个字的简短自我描述
3741
38-
现在请输出你的自我描述,请注意不要输出多余内容(包括前后缀,括号(),表情包,at或 @等 ):
42+
现在请输出你的自我描述,格式是:“你是.....,你.................(描述)”
43+
请注意不要输出多余内容(包括前后缀,括号(),表情包,at或 @等 ):
3944
4045
"""
4146
Prompt(indentify_prompt, "indentify_prompt")

src/chat/focus_chat/replyer/default_replyer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ def init_prompt():
3737
3838
{extra_info_block}
3939
40+
{relation_info_block}
41+
4042
{time_block}
4143
你现在正在群里聊天,以下是群里正在进行的聊天内容:
4244
{chat_info}
4345
44-
{relation_info_block}
46+
4547
4648
以上是聊天内容,你需要了解聊天记录中的内容
4749
@@ -605,6 +607,8 @@ async def _build_single_sending_message(
605607
platform=self.chat_stream.platform,
606608
)
607609

610+
# await anchor_message.process()
611+
608612
bot_message = MessageSending(
609613
message_id=message_id, # 使用片段的唯一ID
610614
chat_stream=self.chat_stream,

src/chat/heart_flow/observation/chatting_observation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ def search_message_by_text(self, text: str) -> Optional[MessageRecv]:
132132
# logger.debug(f"找到的锚定消息:find_msg: {find_msg}")
133133
break
134134
else:
135-
similarity = difflib.SequenceMatcher(None, text, message["processed_plain_text"]).ratio()
135+
similarity = difflib.SequenceMatcher(None, text, message["raw_message"]).ratio()
136136
msg_list.append({"message": message, "similarity": similarity})
137137
# logger.debug(f"对锚定消息检查:message: {message['processed_plain_text']},similarity: {similarity}")
138138
if not find_msg:
139139
if msg_list:
140140
msg_list.sort(key=lambda x: x["similarity"], reverse=True)
141-
if msg_list[0]["similarity"] >= 0.5: # 只返回相似度大于等于0.5的消息
141+
if msg_list[0]["similarity"] >= 0.9: # 只返回相似度大于等于0.5的消息
142142
find_msg = msg_list[0]["message"]
143143
else:
144144
logger.debug("没有找到锚定消息,相似度低")
@@ -191,6 +191,7 @@ def search_message_by_text(self, text: str) -> Optional[MessageRecv]:
191191
"detailed_plain_text": find_msg.get("processed_plain_text"),
192192
"processed_plain_text": find_msg.get("processed_plain_text"),
193193
}
194+
# print(f"message_dict: {message_dict}")
194195
find_rec_msg = MessageRecv(message_dict)
195196
# logger.debug(f"锚定消息处理后:find_rec_msg: {find_rec_msg}")
196197
return find_rec_msg

src/main.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from rich.traceback import install
2121
from .chat.focus_chat.expressors.exprssion_learner import expression_learner
2222
from .api.main import start_api_server
23-
from .person_info.impression_update_task import impression_update_task
2423

2524
install(extra_lines=3)
2625

@@ -60,9 +59,6 @@ async def _init_components(self):
6059
# 添加遥测心跳任务
6160
await async_task_manager.add_task(TelemetryHeartBeatTask())
6261

63-
# 添加印象更新任务
64-
await async_task_manager.add_task(impression_update_task)
65-
6662
# 启动API服务器
6763
start_api_server()
6864
logger.success("API服务器启动成功")

0 commit comments

Comments
 (0)