feat:新增记忆唤醒流程

This commit is contained in:
SengokuCola
2025-05-12 12:50:08 +08:00
parent d7c5f7031c
commit 319352294b
34 changed files with 183 additions and 460 deletions

View File

@@ -129,7 +129,8 @@ class CycleAnalyzer:
try:
duration = float(line[3:].strip().split("")[0])
total_duration += duration
except:
except Exception as e:
logger.error(f"解析耗时时出错: {e}")
pass
# 解析工具使用
@@ -174,7 +175,8 @@ class CycleAnalyzer:
timestamp_str = filename.split("_", 2)[2].split(".")[0]
timestamp = time.mktime(time.strptime(timestamp_str, "%Y%m%d_%H%M%S"))
all_cycles.append((timestamp, stream_id, filepath))
except:
except Exception as e:
logger.error(f"从文件名中提取时间戳时出错: {e}")
continue
# 按时间戳排序取最新的count个

View File

@@ -130,9 +130,6 @@ def main():
parser = argparse.ArgumentParser(description="HeartFC循环信息查看工具")
subparsers = parser.add_subparsers(dest="command", help="子命令")
# 列出所有聊天流
list_streams_parser = subparsers.add_parser("list-streams", help="列出所有聊天流")
# 分析聊天流
analyze_parser = subparsers.add_parser("analyze", help="分析指定聊天流的循环信息")
analyze_parser.add_argument("stream_id", help="聊天流ID")

View File

@@ -168,9 +168,6 @@ class CycleDetail:
filename = f"cycle_{cycle_info.cycle_id}_{timestamp}.txt"
filepath = os.path.join(stream_dir, filename)
# 将CycleInfo转换为JSON格式
cycle_data = cycle_info.to_dict()
# 格式化输出成易读的格式
with open(filepath, "w", encoding="utf-8") as f:
# 写入基本信息

View File

@@ -31,6 +31,7 @@ from src.heart_flow.observation.working_observation import WorkingObservation
from src.plugins.heartFC_chat.info_processors.tool_processor import ToolProcessor
from src.plugins.heartFC_chat.expressors.default_expressor import DefaultExpressor
from src.plugins.heartFC_chat.hfc_utils import _create_empty_anchor_message
from src.plugins.heartFC_chat.memory_activator import MemoryActivator
install(extra_lines=3)
@@ -115,39 +116,6 @@ class ActionManager:
self._original_actions_backup = None
# logger.debug("恢复了原始动作集") # 可选日志
def clear_actions(self):
"""清空所有动作"""
self._available_actions.clear()
def reset_to_default(self):
"""重置为默认动作集"""
self._available_actions = DEFAULT_ACTIONS.copy()
# 在文件开头添加自定义异常类
class HeartFCError(Exception):
"""麦麦聊天系统基础异常类"""
pass
class PlannerError(HeartFCError):
"""规划器异常"""
pass
class ReplierError(HeartFCError):
"""回复器异常"""
pass
class SenderError(HeartFCError):
"""发送器异常"""
pass
async def _handle_cycle_delay(action_taken_this_cycle: bool, cycle_start_time: float, log_prefix: str):
"""处理循环延迟"""
@@ -202,6 +170,7 @@ class HeartFChatting:
self.hfcloop_observation = HFCloopObservation(observe_id=self.stream_id)
self.tool_processor = ToolProcessor(subheartflow_id=self.stream_id)
self.working_observation = WorkingObservation(observe_id=self.stream_id)
self.memory_activator = MemoryActivator()
# 日志前缀
self.log_prefix: str = str(chat_id) # Initial default, will be updated
@@ -439,6 +408,8 @@ class HeartFChatting:
for observation in observations:
logger.debug(f"{self.log_prefix} 观察信息: {observation}")
running_memorys = await self.memory_activator.activate_memory(observations)
# 记录并行任务开始时间
parallel_start_time = time.time()
logger.debug(f"{self.log_prefix} 开始信息处理器并行任务")
@@ -446,16 +417,16 @@ class HeartFChatting:
# 并行执行两个任务:思考和工具执行
with Timer("执行 信息处理器", cycle_timers):
# 1. 子思维思考 - 不执行工具调用
think_task = asyncio.create_task(self.mind_processor.process_info(observations=observations))
think_task = asyncio.create_task(self.mind_processor.process_info(observations=observations,running_memorys=running_memorys))
logger.debug(f"{self.log_prefix} 启动子思维思考任务")
# 2. 工具执行器 - 专门处理工具调用
tool_task = asyncio.create_task(self.tool_processor.process_info(observations=observations))
tool_task = asyncio.create_task(self.tool_processor.process_info(observations=observations,running_memorys=running_memorys))
logger.debug(f"{self.log_prefix} 启动工具执行任务")
# 3. 聊天信息处理器
chatting_info_task = asyncio.create_task(
self.chatting_info_processor.process_info(observations=observations)
self.chatting_info_processor.process_info(observations=observations,running_memorys=running_memorys)
)
logger.debug(f"{self.log_prefix} 启动聊天信息处理器任务")
@@ -578,7 +549,7 @@ class HeartFChatting:
return await handler(reasoning, action_data, cycle_timers)
else: # no_reply
return await handler(reasoning, planner_start_db_time, cycle_timers), ""
except HeartFCError as e:
except Exception as e:
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
# 出错时也重置计数器
self._lian_xu_bu_hui_fu_ci_shu = 0
@@ -738,12 +709,6 @@ class HeartFChatting:
history = history[-last_n:]
return [cycle.to_dict() for cycle in history]
def get_last_cycle_info(self) -> Optional[Dict[str, Any]]:
"""获取最近一个循环的信息"""
if self._cycle_history:
return self._cycle_history[-1].to_dict()
return None
async def _planner(self, all_plan_info: List[InfoBase], cycle_timers: dict) -> Dict[str, Any]:
"""
规划器 (Planner): 使用LLM根据上下文决定是否和如何回复。
@@ -989,16 +954,13 @@ class HeartFChatting:
success, reply_set = await self.expressor.deal_reply(
cycle_timers=cycle_timers, action_data=reply_data, anchor_message=anchor_message, reasoning=reasoning
)
reply_text = ""
for reply in reply_set:
reply_text += reply
self._current_cycle.set_response_info(
success=success,
reply_text=reply_text,
anchor_message=anchor_message,
)
return success, reply_text
self._current_cycle.set_response_info(
response_text=reply_text,
)
return success, reply_text

View File

@@ -848,7 +848,6 @@ class PromptBuilder:
for name in action_keys:
desc = current_available_actions[name]
action_options_text += f"- '{name}': {desc}\n"
example_action_key = action_keys[0] if action_keys else "no_reply"
planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt")

View File

@@ -5,6 +5,7 @@ from src.plugins.chat.message import MessageRecv, BaseMessageInfo
from src.plugins.chat.chat_stream import ChatStream
from src.plugins.chat.message import UserInfo
from src.common.logger_manager import get_logger
import json
logger = get_logger(__name__)
@@ -42,3 +43,22 @@ async def _create_empty_anchor_message(
logger.error(f"Error getting/creating anchor message: {e}")
logger.error(traceback.format_exc())
return None
def get_keywords_from_json(json_str: str) -> list[str]:
# 提取JSON内容
start = json_str.find("{")
end = json_str.rfind("}") + 1
if start == -1 or end == 0:
logger.error("未找到有效的JSON内容")
return []
json_content = json_str[start:end]
# 解析JSON
try:
json_data = json.loads(json_content)
return json_data.get("keywords", [])
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}")
return []

View File

@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import List, Any, Optional
from typing import List, Any, Optional, Dict
from src.heart_flow.info.info_base import InfoBase
from src.heart_flow.chatting_observation import Observation
from src.heart_flow.observation.observation import Observation
from src.common.logger_manager import get_logger
logger = get_logger("base_processor")
@@ -21,7 +21,7 @@ class BaseProcessor(ABC):
@abstractmethod
async def process_info(
self, infos: List[InfoBase], observations: Optional[List[Observation]] = None, **kwargs: Any
self, infos: List[InfoBase], observations: Optional[List[Observation]] = None, running_memorys: Optional[List[Dict]] = None, **kwargs: Any
) -> List[InfoBase]:
"""处理信息对象的抽象方法

View File

@@ -7,7 +7,7 @@ from src.common.logger_manager import get_logger
from src.heart_flow.observation.chatting_observation import ChattingObservation
from src.heart_flow.observation.hfcloop_observation import HFCloopObservation
from src.heart_flow.info.cycle_info import CycleInfo
from typing import Dict
logger = get_logger("observation")
@@ -21,7 +21,7 @@ class ChattingInfoProcessor(BaseProcessor):
"""初始化观察处理器"""
super().__init__()
async def process_info(self, observations: Optional[List[Observation]] = None, **kwargs: Any) -> List[InfoBase]:
async def process_info(self, observations: Optional[List[Observation]] = None, running_memorys: Optional[List[Dict]] = None, **kwargs: Any) -> List[InfoBase]:
"""处理Observation对象
Args:

View File

@@ -22,6 +22,7 @@ from src.plugins.heartFC_chat.info_processors.processor_utils import (
calculate_replacement_probability,
get_spark,
)
from typing import Dict
logger = get_logger("sub_heartflow")
@@ -29,6 +30,7 @@ logger = get_logger("sub_heartflow")
def init_prompt():
# --- Group Chat Prompt ---
group_prompt = """
{memory_str}
{extra_info}
{relation_prompt}
你的名字是{bot_name},{prompt_personality},你现在{mood_info}
@@ -50,6 +52,7 @@ def init_prompt():
# --- Private Chat Prompt ---
private_prompt = """
{memory_str}
{extra_info}
{relation_prompt}
你的名字是{bot_name},{prompt_personality},你现在{mood_info}
@@ -121,7 +124,7 @@ class MindProcessor(BaseProcessor):
self.structured_info_str = "\n".join(lines)
logger.debug(f"{self.log_prefix} 更新 structured_info_str: \n{self.structured_info_str}")
async def process_info(self, observations: Optional[List[Observation]] = None, *infos) -> List[dict]:
async def process_info(self, observations: Optional[List[Observation]] = None, running_memorys: Optional[List[Dict]] = None, *infos) -> List[dict]:
"""处理信息对象
Args:
@@ -130,14 +133,14 @@ class MindProcessor(BaseProcessor):
Returns:
List[dict]: 处理后的结构化信息列表
"""
current_mind = await self.do_thinking_before_reply(observations)
current_mind = await self.do_thinking_before_reply(observations,running_memorys)
mind_info = MindInfo()
mind_info.set_current_mind(current_mind)
return [mind_info]
async def do_thinking_before_reply(self, observations: Optional[List[Observation]] = None):
async def do_thinking_before_reply(self, observations: Optional[List[Observation]] = None, running_memorys: Optional[List[Dict]] = None):
"""
在回复前进行思考,生成内心想法并收集工具调用结果
@@ -166,6 +169,12 @@ class MindProcessor(BaseProcessor):
f"{self.log_prefix} 当前完整的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}"
)
memory_str = ""
if running_memorys:
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
for running_memory in running_memorys:
memory_str += f"{running_memory['topic']}: {running_memory['content']}\n"
# ---------- 1. 准备基础数据 ----------
# 获取现有想法和情绪状态
previous_mind = self.current_mind if self.current_mind else ""
@@ -210,6 +219,7 @@ class MindProcessor(BaseProcessor):
logger.debug(f"{self.log_prefix} 使用{'群聊' if is_group_chat else '私聊'}思考模板")
prompt = (await global_prompt_manager.get_prompt_async(template_name)).format(
memory_str=memory_str,
extra_info=self.structured_info_str,
prompt_personality=prompt_personality,
relation_prompt=relation_prompt,

View File

@@ -5,11 +5,11 @@ import time
from src.common.logger_manager import get_logger
from src.individuality.individuality import Individuality
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager
from src.do_tool.tool_use import ToolUser
from src.tools.tool_use import ToolUser
from src.plugins.utils.json_utils import process_llm_tool_calls
from src.plugins.person_info.relationship_manager import relationship_manager
from .base_processor import BaseProcessor
from typing import List, Optional
from typing import List, Optional, Dict
from src.heart_flow.observation.observation import Observation
from src.heart_flow.observation.working_observation import WorkingObservation
from src.heart_flow.info.structured_info import StructuredInfo
@@ -30,6 +30,8 @@ def init_prompt():
你当前的额外信息:
{extra_info}
{memory_str}
你的心情是:{mood_info}
{relation_prompt}
@@ -61,7 +63,7 @@ class ToolProcessor(BaseProcessor):
)
self.structured_info = []
async def process_info(self, observations: Optional[List[Observation]] = None, *infos) -> List[dict]:
async def process_info(self, observations: Optional[List[Observation]] = None, running_memorys: Optional[List[Dict]] = None, *infos) -> List[dict]:
"""处理信息对象
Args:
@@ -74,7 +76,7 @@ class ToolProcessor(BaseProcessor):
if observations:
for observation in observations:
if isinstance(observation, ChattingObservation):
result, used_tools, prompt = await self.execute_tools(observation)
result, used_tools, prompt = await self.execute_tools(observation, running_memorys)
# 更新WorkingObservation中的结构化信息
for observation in observations:
@@ -92,7 +94,7 @@ class ToolProcessor(BaseProcessor):
return [structured_info]
async def execute_tools(self, observation: ChattingObservation):
async def execute_tools(self, observation: ChattingObservation, running_memorys: Optional[List[Dict]] = None):
"""
并行执行工具,返回结构化信息
@@ -112,23 +114,21 @@ class ToolProcessor(BaseProcessor):
tool_instance = ToolUser()
tools = tool_instance._define_tools()
logger.debug(f"observation: {observation}")
logger.debug(f"observation.chat_target_info: {observation.chat_target_info}")
logger.debug(f"observation.is_group_chat: {observation.is_group_chat}")
logger.debug(f"observation.person_list: {observation.person_list}")
# logger.debug(f"observation: {observation}")
# logger.debug(f"observation.chat_target_info: {observation.chat_target_info}")
# logger.debug(f"observation.is_group_chat: {observation.is_group_chat}")
# logger.debug(f"observation.person_list: {observation.person_list}")
is_group_chat = observation.is_group_chat
if not is_group_chat:
chat_target_name = (
observation.chat_target_info.get("person_name")
or observation.chat_target_info.get("user_nickname")
or "对方"
)
else:
chat_target_name = "群聊"
chat_observe_info = observation.get_observe_info()
person_list = observation.person_list
memory_str = ""
if running_memorys:
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
for running_memory in running_memorys:
memory_str += f"{running_memory['topic']}: {running_memory['content']}\n"
# 构建关系信息
relation_prompt = "【关系信息】\n"
@@ -148,6 +148,7 @@ class ToolProcessor(BaseProcessor):
# 构建专用于工具调用的提示词
prompt = await global_prompt_manager.format_prompt(
"tool_executor_prompt",
memory_str=memory_str,
extra_info="extra_structured_info",
chat_observe_info=chat_observe_info,
# chat_target_name=chat_target_name,

View File

@@ -0,0 +1,70 @@
from src.heart_flow.observation.chatting_observation import ChattingObservation
from src.heart_flow.observation.working_observation import WorkingObservation
from src.heart_flow.observation.hfcloop_observation import HFCloopObservation
from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config
from src.common.logger_manager import get_logger
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager
from src.plugins.heartFC_chat.hfc_utils import get_keywords_from_json
from datetime import datetime
from src.plugins.memory_system.Hippocampus import HippocampusManager
from typing import List, Dict
logger = get_logger("memory_activator")
Prompt(
"""
你是一个记忆分析器,你需要根据以下信息来进行会议
以下是一场聊天中的信息,请根据这些信息,总结出几个关键词作为记忆回忆的触发词
{obs_info_text}
请输出一个json格式包含以下字段
{
"keywords": ["关键词1", "关键词2", "关键词3",......]
}
不要输出其他多余内容只输出json格式就好
""",
"memory_activator_prompt",
)
class MemoryActivator:
def __init__(self):
self.summart_model = LLMRequest(
model=global_config.llm_observation, temperature=0.7, max_tokens=300, request_type="chat_observation"
)
self.running_memory = []
async def activate_memory(self, observations) -> List[Dict]:
obs_info_text = ""
for observation in observations:
if isinstance(observation, ChattingObservation):
obs_info_text += observation.get_observe_info()
elif isinstance(observation, WorkingObservation):
working_info = observation.get_observe_info()
for working_info_item in working_info:
obs_info_text += f"{working_info_item['type']}: {working_info_item['content']}\n"
elif isinstance(observation, HFCloopObservation):
obs_info_text += observation.get_observe_info()
prompt = global_prompt_manager.format_prompt("memory_activator_prompt", obs_info_text=obs_info_text)
response = self.summart_model.generate_response(prompt)
keywords = get_keywords_from_json(response)
# 调用记忆系统获取相关记忆
related_memory = await HippocampusManager.get_instance().get_memory_from_topic(
valid_keywords=keywords, max_memory_num=3, max_memory_length=2, max_depth=3
)
logger.debug(f"获取到的记忆: {related_memory}")
if related_memory:
for topic, memory in related_memory:
self.running_memory.append({"topic": topic, "content": memory, "timestamp": datetime.now().isoformat()})
logger.debug(f"添加新记忆: {topic} - {memory}")
return self.running_memory