mirror of https://github.com/Mai-with-u/MaiBot.git
Merge branch 'dev' of https://github.com/SnowindMe/MaiBot into dev
commit
549eba968c
|
|
@ -6,6 +6,7 @@ log/
|
|||
logs/
|
||||
tool_call_benchmark.py
|
||||
run_ad.bat
|
||||
llm_tool_benchmark_results.json
|
||||
MaiBot-Napcat-Adapter-main
|
||||
MaiBot-Napcat-Adapter
|
||||
/test
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
def 计算字符串长度(输入字符串: str) -> int:
|
||||
"""计算输入字符串的长度
|
||||
|
||||
参数:
|
||||
输入字符串: 要计算长度的字符串
|
||||
|
||||
返回:
|
||||
字符串的长度(整数)
|
||||
"""
|
||||
return len(输入字符串)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试代码
|
||||
测试字符串 = """你。"""
|
||||
print(f"字符串 '{测试字符串}' 的长度是: {计算字符串长度(测试字符串)}")
|
||||
|
|
@ -19,6 +19,7 @@ REFRESH_INTERVAL_MS = 200 # 刷新间隔 (毫秒) - 可以适当调长,因为
|
|||
WINDOW_TITLE = "Interest Monitor (Live History)"
|
||||
MAX_HISTORY_POINTS = 1000 # 图表上显示的最大历史点数 (可以增加)
|
||||
MAX_STREAMS_TO_DISPLAY = 15 # 最多显示多少个聊天流的折线图 (可以增加)
|
||||
MAX_QUEUE_SIZE = 30 # 新增:历史想法队列最大长度
|
||||
|
||||
# *** 添加 Matplotlib 中文字体配置 ***
|
||||
# 尝试使用 'SimHei' 或 'Microsoft YaHei',如果找不到,matplotlib 会回退到默认字体
|
||||
|
|
@ -61,6 +62,10 @@ class InterestMonitorApp:
|
|||
self.single_stream_last_active = tk.StringVar(value="活跃: N/A")
|
||||
self.single_stream_last_interaction = tk.StringVar(value="交互: N/A")
|
||||
|
||||
# 新增:历史想法队列
|
||||
self.main_mind_history = deque(maxlen=MAX_QUEUE_SIZE)
|
||||
self.last_main_mind_timestamp = 0 # 记录最后一条main_mind的时间戳
|
||||
|
||||
# --- UI 元素 ---
|
||||
|
||||
# --- 新增:顶部全局信息框架 ---
|
||||
|
|
@ -143,6 +148,24 @@ class InterestMonitorApp:
|
|||
self.canvas_widget_single = self.canvas_single.get_tk_widget()
|
||||
self.canvas_widget_single.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
|
||||
|
||||
# --- 新增第三个选项卡:麦麦历史想法 ---
|
||||
self.frame_mind_history = ttk.Frame(self.notebook, padding="5 5 5 5")
|
||||
self.notebook.add(self.frame_mind_history, text="麦麦历史想法")
|
||||
|
||||
# 聊天框样式的文本框(只读)+ 滚动条
|
||||
self.mind_text_scroll = tk.Scrollbar(self.frame_mind_history)
|
||||
self.mind_text_scroll.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
self.mind_text = tk.Text(
|
||||
self.frame_mind_history,
|
||||
height=25,
|
||||
state="disabled",
|
||||
wrap="word",
|
||||
font=("微软雅黑", 12),
|
||||
yscrollcommand=self.mind_text_scroll.set,
|
||||
)
|
||||
self.mind_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5, pady=5)
|
||||
self.mind_text_scroll.config(command=self.mind_text.yview)
|
||||
|
||||
# --- 初始化和启动刷新 ---
|
||||
self.update_display() # 首次加载并开始刷新循环
|
||||
|
||||
|
|
@ -154,6 +177,78 @@ class InterestMonitorApp:
|
|||
"""生成随机颜色用于区分线条"""
|
||||
return "#{:06x}".format(random.randint(0, 0xFFFFFF))
|
||||
|
||||
def load_main_mind_history(self):
|
||||
"""只读取包含main_mind的日志行,维护历史想法队列"""
|
||||
if not os.path.exists(LOG_FILE_PATH):
|
||||
return
|
||||
|
||||
main_mind_entries = []
|
||||
try:
|
||||
with open(LOG_FILE_PATH, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
try:
|
||||
log_entry = json.loads(line.strip())
|
||||
if "main_mind" in log_entry:
|
||||
ts = log_entry.get("timestamp", 0)
|
||||
main_mind_entries.append((ts, log_entry))
|
||||
except Exception:
|
||||
continue
|
||||
main_mind_entries.sort(key=lambda x: x[0])
|
||||
recent_entries = main_mind_entries[-MAX_QUEUE_SIZE:]
|
||||
self.main_mind_history.clear()
|
||||
for _ts, entry in recent_entries:
|
||||
self.main_mind_history.append(entry)
|
||||
if recent_entries:
|
||||
self.last_main_mind_timestamp = recent_entries[-1][0]
|
||||
# 首次加载时刷新
|
||||
self.refresh_mind_text()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def update_main_mind_history(self):
|
||||
"""实时监控log文件,发现新main_mind数据则更新队列和展示(仅有新数据时刷新)"""
|
||||
if not os.path.exists(LOG_FILE_PATH):
|
||||
return
|
||||
|
||||
new_entries = []
|
||||
try:
|
||||
with open(LOG_FILE_PATH, "r", encoding="utf-8") as f:
|
||||
for line in reversed(list(f)):
|
||||
try:
|
||||
log_entry = json.loads(line.strip())
|
||||
if "main_mind" in log_entry:
|
||||
ts = log_entry.get("timestamp", 0)
|
||||
if ts > self.last_main_mind_timestamp:
|
||||
new_entries.append((ts, log_entry))
|
||||
else:
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if new_entries:
|
||||
for ts, entry in sorted(new_entries):
|
||||
if len(self.main_mind_history) >= MAX_QUEUE_SIZE:
|
||||
self.main_mind_history.popleft()
|
||||
self.main_mind_history.append(entry)
|
||||
self.last_main_mind_timestamp = ts
|
||||
self.refresh_mind_text() # 只有有新数据时才刷新
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def refresh_mind_text(self):
|
||||
"""刷新聊天框样式的历史想法展示"""
|
||||
self.mind_text.config(state="normal")
|
||||
self.mind_text.delete(1.0, tk.END)
|
||||
for entry in self.main_mind_history:
|
||||
ts = entry.get("timestamp", 0)
|
||||
dt_str = datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else ""
|
||||
main_mind = entry.get("main_mind", "")
|
||||
mai_state = entry.get("mai_state", "")
|
||||
subflow_count = entry.get("subflow_count", "")
|
||||
msg = f"[{dt_str}] 状态:{mai_state} 子流:{subflow_count}\n{main_mind}\n\n"
|
||||
self.mind_text.insert(tk.END, msg)
|
||||
self.mind_text.see(tk.END)
|
||||
self.mind_text.config(state="disabled")
|
||||
|
||||
def load_and_update_history(self):
|
||||
"""从 history log 文件加载数据并更新历史记录"""
|
||||
if not os.path.exists(LOG_FILE_PATH):
|
||||
|
|
@ -537,8 +632,14 @@ class InterestMonitorApp:
|
|||
def update_display(self):
|
||||
"""主更新循环"""
|
||||
try:
|
||||
self.load_and_update_history() # 从文件加载数据并更新内部状态
|
||||
# --- 新增:首次加载历史想法 ---
|
||||
if not hasattr(self, "_main_mind_loaded"):
|
||||
self.load_main_mind_history()
|
||||
self._main_mind_loaded = True
|
||||
else:
|
||||
self.update_main_mind_history() # 只有有新main_mind数据时才刷新界面
|
||||
# *** 修改:分别调用两个图表的更新方法 ***
|
||||
self.load_and_update_history() # 从文件加载数据并更新内部状态
|
||||
self.update_all_streams_plot() # 更新所有流的图表
|
||||
self.update_single_stream_plot() # 更新单个流的图表
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ logger = get_logger("mai_state")
|
|||
|
||||
|
||||
# -- 状态相关的可配置参数 (可以从 glocal_config 加载) --
|
||||
enable_unlimited_hfc_chat = True # 调试用:无限专注聊天
|
||||
enable_unlimited_hfc_chat = True # 调试用:无限专注聊天
|
||||
# enable_unlimited_hfc_chat = False
|
||||
prevent_offline_state = True # 调试用:防止进入离线状态
|
||||
prevent_offline_state = True # 调试用:防止进入离线状态
|
||||
|
||||
# 不同状态下普通聊天的最大消息数
|
||||
MAX_NORMAL_CHAT_NUM_PEEKING = 30
|
||||
|
|
@ -25,6 +25,7 @@ MAX_FOCUSED_CHAT_NUM_FOCUSED = 40
|
|||
|
||||
# -- 状态定义 --
|
||||
|
||||
|
||||
class MaiState(enum.Enum):
|
||||
"""
|
||||
聊天状态:
|
||||
|
|
@ -144,7 +145,7 @@ class MaiStateManager:
|
|||
# 辅助函数:根据 prevent_offline_state 标志调整目标状态
|
||||
def _resolve_offline(candidate_state: MaiState) -> MaiState:
|
||||
if prevent_offline_state and candidate_state == MaiState.OFFLINE:
|
||||
logger.debug(f"阻止进入 OFFLINE,改为 PEEKING")
|
||||
logger.debug("阻止进入 OFFLINE,改为 PEEKING")
|
||||
return MaiState.PEEKING
|
||||
return candidate_state
|
||||
|
||||
|
|
@ -204,8 +205,10 @@ class MaiStateManager:
|
|||
if time_limit_exceeded:
|
||||
next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0]
|
||||
resolved_candidate = _resolve_offline(next_state_candidate)
|
||||
logger.debug(f"规则{rule_id}:时间到,随机选择 {next_state_candidate.value},resolve 为 {resolved_candidate.value}")
|
||||
next_state = resolved_candidate # 直接使用解析后的状态
|
||||
logger.debug(
|
||||
f"规则{rule_id}:时间到,随机选择 {next_state_candidate.value},resolve 为 {resolved_candidate.value}"
|
||||
)
|
||||
next_state = resolved_candidate # 直接使用解析后的状态
|
||||
|
||||
# 注意:enable_unlimited_hfc_chat 优先级高于 prevent_offline_state
|
||||
# 如果触发了这个,它会覆盖上面规则2设置的 next_state
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,
|
|||
|
||||
【当前对话目标】
|
||||
{goals_str}
|
||||
{knowledge_info_str}
|
||||
|
||||
【最近行动历史概要】
|
||||
{action_history_summary}
|
||||
|
|
@ -33,7 +34,7 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,
|
|||
|
||||
------
|
||||
可选行动类型以及解释:
|
||||
fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择
|
||||
fetch_knowledge: 需要调取知识或记忆,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择
|
||||
listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择
|
||||
direct_reply: 直接回复对方
|
||||
rethink_goal: 思考一个对话目标,当你觉得目前对话需要目标,或当前目标不再适用,或话题卡住时选择。注意私聊的环境是灵活的,有可能需要经常选择
|
||||
|
|
@ -53,6 +54,7 @@ PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊,刚刚
|
|||
|
||||
【当前对话目标】
|
||||
{goals_str}
|
||||
{knowledge_info_str}
|
||||
|
||||
【最近行动历史概要】
|
||||
{action_history_summary}
|
||||
|
|
@ -224,6 +226,41 @@ class ActionPlanner:
|
|||
logger.error(f"[私聊][{self.private_name}]构建对话目标字符串时出错: {e}")
|
||||
goals_str = "- 构建对话目标时出错。\n"
|
||||
|
||||
# --- 知识信息字符串构建开始 ---
|
||||
knowledge_info_str = "【已获取的相关知识和记忆】\n"
|
||||
try:
|
||||
# 检查 conversation_info 是否有 knowledge_list 并且不为空
|
||||
if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list:
|
||||
# 最多只显示最近的 5 条知识,防止 Prompt 过长
|
||||
recent_knowledge = conversation_info.knowledge_list[-5:]
|
||||
for i, knowledge_item in enumerate(recent_knowledge):
|
||||
if isinstance(knowledge_item, dict):
|
||||
query = knowledge_item.get("query", "未知查询")
|
||||
knowledge = knowledge_item.get("knowledge", "无知识内容")
|
||||
source = knowledge_item.get("source", "未知来源")
|
||||
# 只取知识内容的前 2000 个字,避免太长
|
||||
knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge
|
||||
knowledge_info_str += (
|
||||
f"{i + 1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n"
|
||||
)
|
||||
else:
|
||||
# 处理列表里不是字典的异常情况
|
||||
knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n"
|
||||
|
||||
if not recent_knowledge: # 如果 knowledge_list 存在但为空
|
||||
knowledge_info_str += "- 暂无相关知识和记忆。\n"
|
||||
|
||||
else:
|
||||
# 如果 conversation_info 没有 knowledge_list 属性,或者列表为空
|
||||
knowledge_info_str += "- 暂无相关知识记忆。\n"
|
||||
except AttributeError:
|
||||
logger.warning(f"[私聊][{self.private_name}]ConversationInfo 对象可能缺少 knowledge_list 属性。")
|
||||
knowledge_info_str += "- 获取知识列表时出错。\n"
|
||||
except Exception as e:
|
||||
logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}")
|
||||
knowledge_info_str += "- 处理知识列表时出错。\n"
|
||||
# --- 知识信息字符串构建结束 ---
|
||||
|
||||
# 获取聊天历史记录 (chat_history_text)
|
||||
chat_history_text = ""
|
||||
try:
|
||||
|
|
@ -349,6 +386,7 @@ class ActionPlanner:
|
|||
time_since_last_bot_message_info=time_since_last_bot_message_info,
|
||||
timeout_context=timeout_context,
|
||||
chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。",
|
||||
knowledge_info_str=knowledge_info_str,
|
||||
)
|
||||
|
||||
logger.debug(f"[私聊][{self.private_name}]发送到LLM的最终提示词:\n------\n{prompt}\n------")
|
||||
|
|
|
|||
|
|
@ -525,9 +525,9 @@ class Conversation:
|
|||
)
|
||||
action_successful = True
|
||||
except Exception as fetch_err:
|
||||
logger.error(f"[私聊][{self.private_name}]获取知识时出错: {fetch_err}")
|
||||
logger.error(f"[私聊][{self.private_name}]获取知识时出错: {str(fetch_err)}")
|
||||
conversation_info.done_action[action_index].update(
|
||||
{"status": "recall", "final_reason": f"获取知识失败: {fetch_err}"}
|
||||
{"status": "recall", "final_reason": f"获取知识失败: {str(fetch_err)}"}
|
||||
)
|
||||
self.conversation_info.last_successful_reply_action = None # 重置状态
|
||||
|
||||
|
|
|
|||
|
|
@ -68,16 +68,18 @@ class KnowledgeFetcher:
|
|||
max_depth=3,
|
||||
fast_retrieval=False,
|
||||
)
|
||||
knowledge = ""
|
||||
knowledge_text = ""
|
||||
sources_text = "无记忆匹配" # 默认值
|
||||
if related_memory:
|
||||
sources = []
|
||||
for memory in related_memory:
|
||||
knowledge += memory[1] + "\n"
|
||||
knowledge_text += memory[1] + "\n"
|
||||
sources.append(f"记忆片段{memory[0]}")
|
||||
knowledge = knowledge.strip(), ",".join(sources)
|
||||
knowledge_text = knowledge_text.strip()
|
||||
sources_text = ",".join(sources)
|
||||
|
||||
knowledge += "现在有以下**知识**可供参考:\n "
|
||||
knowledge += self._lpmm_get_knowledge(query)
|
||||
knowledge += "请记住这些**知识**,并根据**知识**回答问题。\n"
|
||||
knowledge_text += "\n现在有以下**知识**可供参考:\n "
|
||||
knowledge_text += self._lpmm_get_knowledge(query)
|
||||
knowledge_text += "\n请记住这些**知识**,并根据**知识**回答问题。\n"
|
||||
|
||||
return "未找到相关知识", "无记忆匹配"
|
||||
return knowledge_text or "未找到相关知识", sources_text or "无记忆匹配"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ logger = get_module_logger("reply_generator")
|
|||
PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复:
|
||||
|
||||
当前对话目标:{goals_str}
|
||||
|
||||
{knowledge_info_str}
|
||||
|
||||
最近的聊天记录:
|
||||
{chat_history_text}
|
||||
|
||||
|
|
@ -25,7 +28,7 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请
|
|||
1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!)
|
||||
2. 符合你的性格特征和身份细节
|
||||
3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况)
|
||||
4. 适当利用相关知识,但不要生硬引用
|
||||
4. 可以适当利用相关知识,但不要生硬引用
|
||||
5. 自然、得体,结合聊天记录逻辑合理,且没有重复表达同质内容
|
||||
|
||||
请注意把握聊天内容,不要回复的太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。
|
||||
|
|
@ -39,6 +42,9 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请
|
|||
PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊,**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息:
|
||||
|
||||
当前对话目标:{goals_str}
|
||||
|
||||
{knowledge_info_str}
|
||||
|
||||
最近的聊天记录:
|
||||
{chat_history_text}
|
||||
|
||||
|
|
@ -47,7 +53,7 @@ PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊
|
|||
1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!)
|
||||
2. 符合你的性格特征和身份细节
|
||||
3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况)
|
||||
4. 适当利用相关知识,但不要生硬引用
|
||||
4. 可以适当利用相关知识,但不要生硬引用
|
||||
5. 跟之前你发的消息自然的衔接,逻辑合理,且没有重复表达同质内容或部分重叠内容
|
||||
|
||||
请注意把握聊天内容,不用太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。
|
||||
|
|
@ -131,6 +137,38 @@ class ReplyGenerator:
|
|||
else:
|
||||
goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况
|
||||
|
||||
# --- 新增:构建知识信息字符串 ---
|
||||
knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考
|
||||
try:
|
||||
# 检查 conversation_info 是否有 knowledge_list 并且不为空
|
||||
if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list:
|
||||
# 最多只显示最近的 5 条知识
|
||||
recent_knowledge = conversation_info.knowledge_list[-5:]
|
||||
for i, knowledge_item in enumerate(recent_knowledge):
|
||||
if isinstance(knowledge_item, dict):
|
||||
query = knowledge_item.get("query", "未知查询")
|
||||
knowledge = knowledge_item.get("knowledge", "无知识内容")
|
||||
source = knowledge_item.get("source", "未知来源")
|
||||
# 只取知识内容的前 2000 个字
|
||||
knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge
|
||||
knowledge_info_str += (
|
||||
f"{i + 1}. 关于 '{query}' (来源: {source}): {knowledge_snippet}\n" # 格式微调,更简洁
|
||||
)
|
||||
else:
|
||||
knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n"
|
||||
|
||||
if not recent_knowledge:
|
||||
knowledge_info_str += "- 暂无。\n" # 更简洁的提示
|
||||
|
||||
else:
|
||||
knowledge_info_str += "- 暂无。\n"
|
||||
except AttributeError:
|
||||
logger.warning(f"[私聊][{self.private_name}]ConversationInfo 对象可能缺少 knowledge_list 属性。")
|
||||
knowledge_info_str += "- 获取知识列表时出错。\n"
|
||||
except Exception as e:
|
||||
logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}")
|
||||
knowledge_info_str += "- 处理知识列表时出错。\n"
|
||||
|
||||
# 获取聊天历史记录 (chat_history_text)
|
||||
chat_history_text = observation_info.chat_history_str
|
||||
if observation_info.new_messages_count > 0 and observation_info.unprocessed_messages:
|
||||
|
|
@ -162,7 +200,10 @@ class ReplyGenerator:
|
|||
|
||||
# --- 格式化最终的 Prompt ---
|
||||
prompt = prompt_template.format(
|
||||
persona_text=persona_text, goals_str=goals_str, chat_history_text=chat_history_text
|
||||
persona_text=persona_text,
|
||||
goals_str=goals_str,
|
||||
chat_history_text=chat_history_text,
|
||||
knowledge_info_str=knowledge_info_str,
|
||||
)
|
||||
|
||||
# --- 调用 LLM 生成 ---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
import time
|
||||
import traceback
|
||||
import random # <--- 添加导入
|
||||
import random # <--- 添加导入
|
||||
from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
|
||||
from collections import deque
|
||||
from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending
|
||||
|
|
@ -32,7 +32,7 @@ from src.individuality.individuality import Individuality
|
|||
|
||||
INITIAL_DURATION = 60.0
|
||||
|
||||
WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒
|
||||
WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒
|
||||
|
||||
|
||||
logger = get_logger("interest") # Logger Name Changed
|
||||
|
|
@ -48,11 +48,11 @@ class ActionManager:
|
|||
def __init__(self):
|
||||
# 初始化为默认动作集
|
||||
self._available_actions: Dict[str, str] = DEFAULT_ACTIONS.copy()
|
||||
self._original_actions_backup: Optional[Dict[str, str]] = None # 用于临时移除时的备份
|
||||
self._original_actions_backup: Optional[Dict[str, str]] = None # 用于临时移除时的备份
|
||||
|
||||
def get_available_actions(self) -> Dict[str, str]:
|
||||
"""获取当前可用的动作集"""
|
||||
return self._available_actions.copy() # 返回副本以防外部修改
|
||||
return self._available_actions.copy() # 返回副本以防外部修改
|
||||
|
||||
def add_action(self, action_name: str, description: str) -> bool:
|
||||
"""
|
||||
|
|
@ -518,7 +518,7 @@ class HeartFChatting:
|
|||
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
|
||||
# 出错时也重置计数器
|
||||
self._lian_xu_bu_hui_fu_ci_shu = 0
|
||||
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间
|
||||
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间
|
||||
return False, ""
|
||||
|
||||
async def _handle_text_reply(self, reasoning: str, emoji_query: str, cycle_timers: dict) -> tuple[bool, str]:
|
||||
|
|
@ -541,7 +541,7 @@ class HeartFChatting:
|
|||
"""
|
||||
# 重置连续不回复计数器
|
||||
self._lian_xu_bu_hui_fu_ci_shu = 0
|
||||
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间
|
||||
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间
|
||||
|
||||
# 获取锚点消息
|
||||
anchor_message = await self._get_anchor_message()
|
||||
|
|
@ -597,7 +597,7 @@ class HeartFChatting:
|
|||
bool: 是否发送成功
|
||||
"""
|
||||
logger.info(f"{self.log_prefix} 决定回复表情({emoji_query}): {reasoning}")
|
||||
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间(即使不计数也保持一致性)
|
||||
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间(即使不计数也保持一致性)
|
||||
|
||||
try:
|
||||
anchor = await self._get_anchor_message()
|
||||
|
|
@ -633,17 +633,16 @@ class HeartFChatting:
|
|||
observation = self.observations[0] if self.observations else None
|
||||
|
||||
try:
|
||||
dang_qian_deng_dai = 0.0 # 初始化本次等待时间
|
||||
dang_qian_deng_dai = 0.0 # 初始化本次等待时间
|
||||
with Timer("等待新消息", cycle_timers):
|
||||
# 等待新消息、超时或关闭信号,并获取结果
|
||||
await self._wait_for_new_message(observation, planner_start_db_time, self.log_prefix)
|
||||
# 从计时器获取实际等待时间
|
||||
dang_qian_deng_dai = cycle_timers.get("等待新消息", 0.0)
|
||||
|
||||
|
||||
if not self._shutting_down:
|
||||
self._lian_xu_bu_hui_fu_ci_shu += 1
|
||||
self._lian_xu_deng_dai_shi_jian += dang_qian_deng_dai # 累加等待时间
|
||||
self._lian_xu_deng_dai_shi_jian += dang_qian_deng_dai # 累加等待时间
|
||||
logger.debug(
|
||||
f"{self.log_prefix} 连续不回复计数增加: {self._lian_xu_bu_hui_fu_ci_shu}/{self.CONSECUTIVE_NO_REPLY_THRESHOLD}, "
|
||||
f"本次等待: {dang_qian_deng_dai:.2f}秒, 累计等待: {self._lian_xu_deng_dai_shi_jian:.2f}秒"
|
||||
|
|
@ -651,8 +650,10 @@ class HeartFChatting:
|
|||
|
||||
# 检查是否同时达到次数和时间阈值
|
||||
time_threshold = 0.66 * WAITING_TIME_THRESHOLD * self.CONSECUTIVE_NO_REPLY_THRESHOLD
|
||||
if (self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD and
|
||||
self._lian_xu_deng_dai_shi_jian >= time_threshold):
|
||||
if (
|
||||
self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD
|
||||
and self._lian_xu_deng_dai_shi_jian >= time_threshold
|
||||
):
|
||||
logger.info(
|
||||
f"{self.log_prefix} 连续不回复达到阈值 ({self._lian_xu_bu_hui_fu_ci_shu}次) "
|
||||
f"且累计等待时间达到 {self._lian_xu_deng_dai_shi_jian:.2f}秒 (阈值 {time_threshold}秒),"
|
||||
|
|
@ -668,7 +669,6 @@ class HeartFChatting:
|
|||
)
|
||||
# else: 次数和时间都未达到阈值,不做处理
|
||||
|
||||
|
||||
return True
|
||||
|
||||
except asyncio.CancelledError:
|
||||
|
|
@ -793,9 +793,9 @@ class HeartFChatting:
|
|||
logger.info(f"{self.log_prefix}[Planner] 开始{'重新' if is_re_planned else ''}执行规划器")
|
||||
|
||||
# --- 新增:检查历史动作并调整可用动作 ---
|
||||
lian_xu_wen_ben_hui_fu = 0 # 连续文本回复次数
|
||||
lian_xu_wen_ben_hui_fu = 0 # 连续文本回复次数
|
||||
actions_to_remove_temporarily = []
|
||||
probability_roll = random.random() # 在循环外掷骰子一次,用于概率判断
|
||||
probability_roll = random.random() # 在循环外掷骰子一次,用于概率判断
|
||||
|
||||
# 反向遍历最近的循环历史
|
||||
for cycle in reversed(self._cycle_history):
|
||||
|
|
@ -804,9 +804,11 @@ class HeartFChatting:
|
|||
if cycle.action_type == "text_reply":
|
||||
lian_xu_wen_ben_hui_fu += 1
|
||||
else:
|
||||
break # 遇到非文本回复,中断计数
|
||||
break # 遇到非文本回复,中断计数
|
||||
# 检查最近的3个循环即可,避免检查过多历史 (如果历史很长)
|
||||
if len(self._cycle_history) > 0 and cycle.cycle_id <= self._cycle_history[0].cycle_id + (len(self._cycle_history) - 4):
|
||||
if len(self._cycle_history) > 0 and cycle.cycle_id <= self._cycle_history[0].cycle_id + (
|
||||
len(self._cycle_history) - 4
|
||||
):
|
||||
break
|
||||
|
||||
logger.debug(f"{self.log_prefix}[Planner] 检测到连续文本回复次数: {lian_xu_wen_ben_hui_fu}")
|
||||
|
|
@ -816,13 +818,15 @@ class HeartFChatting:
|
|||
logger.info(f"{self.log_prefix}[Planner] 连续回复 >= 3 次,强制移除 text_reply 和 emoji_reply")
|
||||
actions_to_remove_temporarily.extend(["text_reply", "emoji_reply"])
|
||||
elif lian_xu_wen_ben_hui_fu == 2:
|
||||
if probability_roll < 0.8: # 80% 概率
|
||||
if probability_roll < 0.8: # 80% 概率
|
||||
logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次,80% 概率移除 text_reply 和 emoji_reply (触发)")
|
||||
actions_to_remove_temporarily.extend(["text_reply", "emoji_reply"])
|
||||
else:
|
||||
logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次,80% 概率移除 text_reply 和 emoji_reply (未触发)")
|
||||
logger.info(
|
||||
f"{self.log_prefix}[Planner] 连续回复 2 次,80% 概率移除 text_reply 和 emoji_reply (未触发)"
|
||||
)
|
||||
elif lian_xu_wen_ben_hui_fu == 1:
|
||||
if probability_roll < 0.4: # 40% 概率
|
||||
if probability_roll < 0.4: # 40% 概率
|
||||
logger.info(f"{self.log_prefix}[Planner] 连续回复 1 次,40% 概率移除 text_reply (触发)")
|
||||
actions_to_remove_temporarily.append("text_reply")
|
||||
else:
|
||||
|
|
@ -847,7 +851,9 @@ class HeartFChatting:
|
|||
# --- 新增:应用临时动作移除 ---
|
||||
if actions_to_remove_temporarily:
|
||||
self.action_manager.temporarily_remove_actions(actions_to_remove_temporarily)
|
||||
logger.debug(f"{self.log_prefix}[Planner] 临时移除的动作: {actions_to_remove_temporarily}, 当前可用: {list(self.action_manager.get_available_actions().keys())}")
|
||||
logger.debug(
|
||||
f"{self.log_prefix}[Planner] 临时移除的动作: {actions_to_remove_temporarily}, 当前可用: {list(self.action_manager.get_available_actions().keys())}"
|
||||
)
|
||||
|
||||
# --- 构建提示词 ---
|
||||
replan_prompt_str = ""
|
||||
|
|
@ -862,7 +868,7 @@ class HeartFChatting:
|
|||
# --- 调用 LLM ---
|
||||
try:
|
||||
planner_tools = self.action_manager.get_planner_tool_definition()
|
||||
logger.debug(f"{self.log_prefix}[Planner] 本次使用的工具定义: {planner_tools}") # 记录本次使用的工具
|
||||
logger.debug(f"{self.log_prefix}[Planner] 本次使用的工具定义: {planner_tools}") # 记录本次使用的工具
|
||||
_response_text, _reasoning_content, tool_calls = await self.planner_llm.generate_response_tool_async(
|
||||
prompt=prompt,
|
||||
tools=planner_tools,
|
||||
|
|
@ -914,13 +920,15 @@ class HeartFChatting:
|
|||
action = "no_reply"
|
||||
reasoning = f"LLM返回了当前不可用的动作: {extracted_action}"
|
||||
emoji_query = ""
|
||||
llm_error = False # 视为逻辑修正而非 LLM 错误
|
||||
llm_error = False # 视为逻辑修正而非 LLM 错误
|
||||
# --- 检查 'no_reply' 是否也恰好被移除了 (极端情况) ---
|
||||
if "no_reply" not in self.action_manager.get_available_actions():
|
||||
logger.error(f"{self.log_prefix}[Planner] 严重错误:'no_reply' 动作也不可用!无法执行任何动作。")
|
||||
action = "error" # 回退到错误状态
|
||||
reasoning = "无法执行任何有效动作,包括 no_reply"
|
||||
llm_error = True
|
||||
logger.error(
|
||||
f"{self.log_prefix}[Planner] 严重错误:'no_reply' 动作也不可用!无法执行任何动作。"
|
||||
)
|
||||
action = "error" # 回退到错误状态
|
||||
reasoning = "无法执行任何有效动作,包括 no_reply"
|
||||
llm_error = True
|
||||
else:
|
||||
# 动作有效且可用,使用提取的值
|
||||
action = extracted_action
|
||||
|
|
@ -944,11 +952,13 @@ class HeartFChatting:
|
|||
# 如果没有有效的工具调用,我们需要检查 'no_reply' 是否是当前唯一可用的动作
|
||||
available_actions = list(self.action_manager.get_available_actions().keys())
|
||||
if available_actions == ["no_reply"]:
|
||||
logger.info(f"{self.log_prefix}[Planner] LLM未返回工具调用,但当前唯一可用动作是 'no_reply',将执行 'no_reply'")
|
||||
logger.info(
|
||||
f"{self.log_prefix}[Planner] LLM未返回工具调用,但当前唯一可用动作是 'no_reply',将执行 'no_reply'"
|
||||
)
|
||||
action = "no_reply"
|
||||
reasoning = "LLM未返回工具调用,且当前仅 'no_reply' 可用"
|
||||
emoji_query = ""
|
||||
llm_error = False # 视为逻辑选择而非错误
|
||||
llm_error = False # 视为逻辑选择而非错误
|
||||
else:
|
||||
reasoning = "LLM未返回有效的工具调用"
|
||||
logger.warning(f"{self.log_prefix}[Planner] {reasoning}")
|
||||
|
|
@ -963,9 +973,11 @@ class HeartFChatting:
|
|||
llm_error = True
|
||||
# --- 新增:确保动作恢复 ---
|
||||
finally:
|
||||
if actions_to_remove_temporarily: # 只有当确实移除了动作时才需要恢复
|
||||
self.action_manager.restore_actions()
|
||||
logger.debug(f"{self.log_prefix}[Planner] 恢复了原始动作集, 当前可用: {list(self.action_manager.get_available_actions().keys())}")
|
||||
if actions_to_remove_temporarily: # 只有当确实移除了动作时才需要恢复
|
||||
self.action_manager.restore_actions()
|
||||
logger.debug(
|
||||
f"{self.log_prefix}[Planner] 恢复了原始动作集, 当前可用: {list(self.action_manager.get_available_actions().keys())}"
|
||||
)
|
||||
# --- 结束:确保动作恢复 ---
|
||||
# --- 结束 LLM 决策 --- #
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue