mirror of https://github.com/Mai-with-u/MaiBot.git
feat:添加来自困惑内容的主动提问发言
parent
eaf218ff15
commit
35c3fe5b10
|
|
@ -451,7 +451,7 @@ class ExpressionLearner:
|
|||
)
|
||||
|
||||
# print(f"random_msg_str:{random_msg_str}")
|
||||
logger.info(f"学习{type_str}的prompt: {prompt}")
|
||||
# logger.info(f"学习{type_str}的prompt: {prompt}")
|
||||
|
||||
try:
|
||||
response, _ = await self.express_learn_model.generate_response_async(prompt, temperature=0.3)
|
||||
|
|
@ -459,7 +459,7 @@ class ExpressionLearner:
|
|||
logger.error(f"学习{type_str}失败: {e}")
|
||||
return None
|
||||
|
||||
logger.debug(f"学习{type_str}的response: {response}")
|
||||
# logger.debug(f"学习{type_str}的response: {response}")
|
||||
|
||||
expressions: List[Tuple[str, str]] = self.parse_expression_response(response)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ from src.chat.heart_flow.hfc_utils import CycleDetail
|
|||
from src.chat.heart_flow.hfc_utils import send_typing, stop_typing
|
||||
from src.chat.express.expression_learner import expression_learner_manager
|
||||
from src.chat.frequency_control.frequency_control import frequency_control_manager
|
||||
from src.memory_system.question_maker import QuestionMaker
|
||||
from src.memory_system.questions import global_conflict_tracker
|
||||
from src.person_info.person_info import Person
|
||||
from src.plugin_system.base.component_types import EventType, ActionInfo
|
||||
from src.plugin_system.core import events_manager
|
||||
|
|
@ -100,6 +102,8 @@ class HeartFChatting:
|
|||
self.no_reply_until_call = False
|
||||
|
||||
self.is_mute = False
|
||||
|
||||
self.last_active_time = time.time() # 记录上一次非noreply时间
|
||||
|
||||
|
||||
async def start(self):
|
||||
|
|
@ -176,6 +180,18 @@ class HeartFChatting:
|
|||
filter_command=True,
|
||||
)
|
||||
|
||||
force_reply = False
|
||||
if time.time() - self.last_active_time > 1: #长久没有回复,可以试试主动发言
|
||||
print(f"{self.log_prefix} 长久没有回复,可以试试主动发言")
|
||||
if random.random() < 0.01: # 30%概率主动发言
|
||||
print(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题")
|
||||
cycle_timers, thinking_id = self.start_cycle()
|
||||
question_maker = QuestionMaker(self.stream_id)
|
||||
question, conflict_context = await question_maker.make_question()
|
||||
await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id)
|
||||
await self._lift_question_reply(question,cycle_timers,thinking_id)
|
||||
|
||||
|
||||
if len(recent_messages_list) >= 1:
|
||||
# for message in recent_messages_list:
|
||||
# print(message.processed_plain_text)
|
||||
|
|
@ -509,6 +525,89 @@ class HeartFChatting:
|
|||
traceback.print_exc()
|
||||
return False, ""
|
||||
|
||||
async def _lift_question_reply(self, question: str, cycle_timers: Dict[str, float], thinking_id: str):
|
||||
reason = f"你对问题\"{question}\"感到好奇,想要和群友讨论"
|
||||
new_msg = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=self.stream_id,
|
||||
timestamp=time.time(),
|
||||
limit=1,
|
||||
)
|
||||
|
||||
reply_action_info = ActionPlannerInfo(
|
||||
action_type="reply",
|
||||
reasoning= "",
|
||||
action_data={},
|
||||
action_message=new_msg[0],
|
||||
available_actions=None,
|
||||
loop_start_time=time.time(),
|
||||
action_reasoning=reason)
|
||||
self.action_planner.add_plan_log(reasoning=reason, actions=[reply_action_info])
|
||||
|
||||
await database_api.store_action_info(
|
||||
chat_stream=self.chat_stream,
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=reason,
|
||||
action_done=True,
|
||||
thinking_id=thinking_id,
|
||||
action_data=reply_action_info.action_data,
|
||||
action_name="reply",
|
||||
action_reasoning=reason,
|
||||
)
|
||||
|
||||
success, llm_response = await generator_api.rewrite_reply(
|
||||
chat_stream=self.chat_stream,
|
||||
reply_data={
|
||||
"raw_reply": f"我对这个问题感到好奇:{question}",
|
||||
"reason": reason,
|
||||
},
|
||||
)
|
||||
|
||||
if not success or not llm_response or not llm_response.reply_set:
|
||||
logger.info("主动提问发言失败")
|
||||
self.action_planner.add_plan_excute_log(result="主动回复生成失败")
|
||||
return {"action_type": "reply", "success": False, "result": "主动回复生成失败", "loop_info": None}
|
||||
|
||||
if success:
|
||||
for reply_seg in llm_response.reply_set.reply_data:
|
||||
send_data = reply_seg.content
|
||||
await send_api.text_to_stream(
|
||||
text=send_data,
|
||||
stream_id=self.stream_id,
|
||||
)
|
||||
|
||||
await database_api.store_action_info(
|
||||
chat_stream=self.chat_stream,
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=reason,
|
||||
action_done=True,
|
||||
thinking_id=thinking_id,
|
||||
action_data={"reply_text": llm_response.reply_set.reply_data[0].content},
|
||||
action_name="reply",
|
||||
)
|
||||
|
||||
# 构建循环信息
|
||||
loop_info: Dict[str, Any] = {
|
||||
"loop_plan_info": {
|
||||
"action_result": [reply_action_info],
|
||||
},
|
||||
"loop_action_info": {
|
||||
"action_taken": True,
|
||||
"reply_text": llm_response.reply_set.reply_data[0].content,
|
||||
"command": "",
|
||||
"taken_time": time.time(),
|
||||
},
|
||||
}
|
||||
self.last_active_time = time.time()
|
||||
self.action_planner.add_plan_excute_log(result=f"你提问:{question}")
|
||||
|
||||
return {
|
||||
"action_type": "reply",
|
||||
"success": True,
|
||||
"result": f"你提问:{question}",
|
||||
"loop_info": loop_info,
|
||||
}
|
||||
|
||||
|
||||
async def _send_response(
|
||||
self,
|
||||
reply_set: "ReplySetModel",
|
||||
|
|
@ -519,7 +618,7 @@ class HeartFChatting:
|
|||
chat_id=self.chat_stream.stream_id, start_time=self.last_read_time, end_time=time.time()
|
||||
)
|
||||
|
||||
need_reply = new_message_count >= random.randint(2, 4)
|
||||
need_reply = new_message_count >= random.randint(2, 3)
|
||||
|
||||
if need_reply:
|
||||
logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复")
|
||||
|
|
@ -600,8 +699,6 @@ class HeartFChatting:
|
|||
action_name="no_reply_until_call",
|
||||
action_reasoning=reason,
|
||||
)
|
||||
|
||||
|
||||
return {"action_type": "no_reply_until_call", "success": True, "result": "保持沉默,直到有人直接叫的名字", "command": ""}
|
||||
|
||||
elif action_planner_info.action_type == "reply":
|
||||
|
|
@ -650,6 +747,7 @@ class HeartFChatting:
|
|||
actions=chosen_action_plan_infos,
|
||||
selected_expressions=selected_expressions,
|
||||
)
|
||||
self.last_active_time = time.time()
|
||||
return {
|
||||
"action_type": "reply",
|
||||
"success": True,
|
||||
|
|
@ -667,6 +765,8 @@ class HeartFChatting:
|
|||
thinking_id = thinking_id,
|
||||
action_message= action_planner_info.action_message,
|
||||
)
|
||||
|
||||
self.last_active_time = time.time()
|
||||
return {
|
||||
"action_type": action_planner_info.action_type,
|
||||
"success": success,
|
||||
|
|
|
|||
|
|
@ -469,8 +469,8 @@ class ActionPlanner:
|
|||
# 调用LLM
|
||||
llm_content, (reasoning_content, _, _) = await self.planner_llm.generate_response_async(prompt=prompt)
|
||||
|
||||
logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
|
||||
logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}")
|
||||
# logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
|
||||
# logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}")
|
||||
|
||||
if global_config.debug.show_prompt:
|
||||
logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import re
|
|||
from typing import List, Optional, Dict, Any, Tuple
|
||||
from datetime import datetime
|
||||
from src.memory_system.Memory_chest import global_memory_chest
|
||||
from src.memory_system.questions import global_conflict_tracker
|
||||
from src.common.logger import get_logger
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.common.data_models.info_data_model import ActionPlannerInfo
|
||||
|
|
@ -277,6 +278,20 @@ class DefaultReplyer:
|
|||
else:
|
||||
return ""
|
||||
|
||||
async def build_question_block(self) -> str:
|
||||
"""构建问题块"""
|
||||
# if not global_config.question.enable_question:
|
||||
# return ""
|
||||
questions = global_conflict_tracker.get_questions_by_chat_id(self.chat_stream.stream_id)
|
||||
questions_str = ""
|
||||
for question in questions:
|
||||
questions_str += f"- {question.question}\n"
|
||||
if questions_str:
|
||||
return f"你在聊天中,有以下问题想要得到解答:\n{questions_str}"
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
async def build_tool_info(self, chat_history: str, sender: str, target: str, enable_tool: bool = True) -> str:
|
||||
"""构建工具信息块
|
||||
|
||||
|
|
@ -619,6 +634,7 @@ class DefaultReplyer:
|
|||
self._time_and_run_task(self.build_actions_prompt(available_actions, chosen_actions), "actions_info"),
|
||||
self._time_and_run_task(self.build_personality_prompt(), "personality_prompt"),
|
||||
self._time_and_run_task(self.build_mood_state_prompt(), "mood_state_prompt"),
|
||||
self._time_and_run_task(self.build_question_block(), "question_block"),
|
||||
)
|
||||
|
||||
# 任务名称中英文映射
|
||||
|
|
@ -632,6 +648,7 @@ class DefaultReplyer:
|
|||
"actions_info": "动作信息",
|
||||
"personality_prompt": "人格信息",
|
||||
"mood_state_prompt": "情绪状态",
|
||||
"question_block": "问题",
|
||||
}
|
||||
|
||||
# 处理结果
|
||||
|
|
@ -661,6 +678,7 @@ class DefaultReplyer:
|
|||
prompt_info: str = results_dict["prompt_info"] # 直接使用格式化后的结果
|
||||
actions_info: str = results_dict["actions_info"]
|
||||
personality_prompt: str = results_dict["personality_prompt"]
|
||||
question_block: str = results_dict["question_block"]
|
||||
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
||||
mood_state_prompt: str = results_dict["mood_state_prompt"]
|
||||
|
||||
|
|
@ -704,6 +722,7 @@ class DefaultReplyer:
|
|||
target=target,
|
||||
reason=reply_reason,
|
||||
reply_style=global_config.personality.reply_style,
|
||||
question_block=question_block,
|
||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
||||
moderation_prompt=moderation_prompt_block,
|
||||
), selected_expressions
|
||||
|
|
@ -728,6 +747,7 @@ class DefaultReplyer:
|
|||
reply_style=global_config.personality.reply_style,
|
||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
||||
moderation_prompt=moderation_prompt_block,
|
||||
question_block=question_block,
|
||||
), selected_expressions
|
||||
|
||||
async def build_prompt_rewrite_context(
|
||||
|
|
@ -760,7 +780,6 @@ class DefaultReplyer:
|
|||
# 并行执行2个构建任务
|
||||
(expression_habits_block, _), personality_prompt = await asyncio.gather(
|
||||
self.build_expression_habits(chat_talking_prompt_half, target),
|
||||
# self.build_relation_info(chat_talking_prompt_half, sender, []),
|
||||
self.build_personality_prompt(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ class PrivateReplyer:
|
|||
mood_state = await mood_manager.get_mood_by_chat_id(self.chat_stream.stream_id).get_mood()
|
||||
return f"你现在的心情是:{mood_state}"
|
||||
|
||||
|
||||
|
||||
async def build_memory_block(self) -> str:
|
||||
"""构建记忆块
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ def init_replyer_prompt():
|
|||
|
||||
Prompt(
|
||||
"""{knowledge_prompt}{tool_info_block}{extra_info_block}
|
||||
{expression_habits_block}{memory_block}
|
||||
{expression_habits_block}{memory_block}{question_block}
|
||||
|
||||
你正在qq群里聊天,下面是群里正在聊的内容:
|
||||
{time_block}
|
||||
|
|
@ -34,7 +34,7 @@ def init_replyer_prompt():
|
|||
|
||||
Prompt(
|
||||
"""{knowledge_prompt}{tool_info_block}{extra_info_block}
|
||||
{expression_habits_block}{memory_block}
|
||||
{expression_habits_block}{memory_block}{question_block}
|
||||
|
||||
你正在qq群里聊天,下面是群里正在聊的内容:
|
||||
{time_block}
|
||||
|
|
|
|||
|
|
@ -338,44 +338,12 @@ class MemoryConflict(BaseModel):
|
|||
answer = TextField(null=True) # 回答内容
|
||||
create_time = FloatField() # 创建时间
|
||||
update_time = FloatField() # 更新时间
|
||||
context = TextField(null=True) # 上下文
|
||||
|
||||
class Meta:
|
||||
table_name = "memory_conflicts"
|
||||
|
||||
|
||||
|
||||
|
||||
class GraphNodes(BaseModel):
|
||||
"""
|
||||
用于存储记忆图节点的模型
|
||||
"""
|
||||
|
||||
concept = TextField(unique=True, index=True) # 节点概念
|
||||
memory_items = TextField() # JSON格式存储的记忆列表
|
||||
weight = FloatField(default=0.0) # 节点权重
|
||||
hash = TextField() # 节点哈希值
|
||||
created_time = FloatField() # 创建时间戳
|
||||
last_modified = FloatField() # 最后修改时间戳
|
||||
|
||||
class Meta:
|
||||
table_name = "graph_nodes"
|
||||
|
||||
|
||||
class GraphEdges(BaseModel):
|
||||
"""
|
||||
用于存储记忆图边的模型
|
||||
"""
|
||||
|
||||
source = TextField(index=True) # 源节点
|
||||
target = TextField(index=True) # 目标节点
|
||||
strength = IntegerField() # 连接强度
|
||||
hash = TextField() # 边哈希值
|
||||
created_time = FloatField() # 创建时间戳
|
||||
last_modified = FloatField() # 最后修改时间戳
|
||||
|
||||
class Meta:
|
||||
table_name = "graph_edges"
|
||||
|
||||
|
||||
def create_tables():
|
||||
"""
|
||||
|
|
@ -393,8 +361,6 @@ def create_tables():
|
|||
OnlineTime,
|
||||
PersonInfo,
|
||||
Expression,
|
||||
GraphNodes, # 添加图节点表
|
||||
GraphEdges, # 添加图边表
|
||||
ActionRecords, # 添加 ActionRecords 到初始化列表
|
||||
MemoryChest,
|
||||
MemoryConflict, # 添加记忆冲突表
|
||||
|
|
@ -422,8 +388,6 @@ def initialize_database(sync_constraints=False):
|
|||
OnlineTime,
|
||||
PersonInfo,
|
||||
Expression,
|
||||
GraphNodes,
|
||||
GraphEdges,
|
||||
ActionRecords, # 添加 ActionRecords 到初始化列表
|
||||
MemoryChest,
|
||||
MemoryConflict,
|
||||
|
|
@ -521,8 +485,6 @@ def sync_field_constraints():
|
|||
OnlineTime,
|
||||
PersonInfo,
|
||||
Expression,
|
||||
GraphNodes,
|
||||
GraphEdges,
|
||||
ActionRecords,
|
||||
MemoryChest,
|
||||
MemoryConflict,
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
import time
|
||||
import asyncio
|
||||
from src.common.logger import get_logger
|
||||
from src.common.database.database_model import MemoryConflict
|
||||
from src.chat.utils.chat_message_builder import (
|
||||
get_raw_msg_by_timestamp_with_chat,
|
||||
build_readable_messages,
|
||||
)
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.config.config import model_config, global_config
|
||||
|
||||
logger = get_logger("conflict_tracker")
|
||||
|
||||
class QuestionTracker:
|
||||
"""
|
||||
用于跟踪一个问题在后续聊天中的解答情况
|
||||
"""
|
||||
|
||||
def __init__(self, question: str, chat_id: str) -> None:
|
||||
self.question = question
|
||||
self.chat_id = chat_id
|
||||
now = time.time()
|
||||
self.start_time = now
|
||||
self.last_read_time = now
|
||||
self.active = True
|
||||
# 将 LLM 实例作为类属性,使用 utils 模型
|
||||
self.llm_request = LLMRequest(model_set=model_config.model_task_config.utils, request_type="conflict.judge")
|
||||
|
||||
def stop(self) -> None:
|
||||
self.active = False
|
||||
|
||||
async def judge_answer(self, conversation_text: str) -> tuple[bool, str]:
|
||||
"""
|
||||
使用小模型判定问题是否已得到解答。
|
||||
返回 (已解答, 答案)
|
||||
"""
|
||||
prompt = (
|
||||
"你是一个严谨的判定器。下面给出聊天记录以及一个问题。\n"
|
||||
"任务:判断在这段聊天中,该问题是否已经得到明确解答。\n"
|
||||
"如果已解答,请只输出:YES: <简短答案>\n"
|
||||
"如果没有,请只输出:NO\n\n"
|
||||
f"问题:{self.question}\n"
|
||||
"聊天记录如下:\n"
|
||||
f"{conversation_text}"
|
||||
)
|
||||
|
||||
if global_config.debug.show_prompt:
|
||||
logger.info(f"判定提示词: {prompt}")
|
||||
else:
|
||||
logger.debug("已发送判定提示词")
|
||||
|
||||
result_text, _ = await self.llm_request.generate_response_async(prompt, temperature=0.5)
|
||||
|
||||
logger.info(f"判定结果: {prompt}\n{result_text}")
|
||||
|
||||
if not result_text:
|
||||
return False, ""
|
||||
|
||||
text = result_text.strip()
|
||||
if text.upper().startswith("YES:"):
|
||||
answer = text[4:].strip()
|
||||
return True, answer
|
||||
if text.upper().startswith("YES"):
|
||||
# 兼容仅输出 YES 或 YES <answer>
|
||||
answer = text[3:].strip().lstrip(":").strip()
|
||||
return True, answer
|
||||
return False, ""
|
||||
|
||||
class ConflictTracker:
|
||||
"""
|
||||
记忆整合冲突追踪器
|
||||
|
||||
用于记录和存储记忆整合过程中的冲突内容
|
||||
"""
|
||||
|
||||
async def record_conflict(self, conflict_content: str, start_following: bool = False,chat_id: str = "") -> bool:
|
||||
"""
|
||||
记录冲突内容
|
||||
|
||||
Args:k
|
||||
conflict_content: 冲突内容
|
||||
|
||||
Returns:
|
||||
bool: 是否成功记录
|
||||
"""
|
||||
try:
|
||||
if not conflict_content or conflict_content.strip() == "":
|
||||
return False
|
||||
|
||||
# 若需要跟随后续消息以判断是否得到解答,则进入跟踪流程
|
||||
if start_following and chat_id:
|
||||
tracker = QuestionTracker(conflict_content.strip(), chat_id)
|
||||
# 后台启动跟踪任务,避免阻塞
|
||||
asyncio.create_task(self._follow_and_record(tracker, conflict_content.strip()))
|
||||
return True
|
||||
|
||||
# 默认:直接记录,不进行跟踪
|
||||
MemoryConflict.create(
|
||||
conflict_content=conflict_content,
|
||||
create_time=time.time(),
|
||||
update_time=time.time(),
|
||||
answer="",
|
||||
)
|
||||
|
||||
logger.info(f"记录冲突内容: {len(conflict_content)} 字符")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"记录冲突内容时出错: {e}")
|
||||
return False
|
||||
|
||||
async def _follow_and_record(self, tracker: QuestionTracker, original_question: str) -> None:
|
||||
"""
|
||||
后台任务:跟踪问题是否被解答,并写入数据库。
|
||||
"""
|
||||
try:
|
||||
max_duration = 30 * 60 # 30 分钟
|
||||
max_messages = 100 # 最多 100 条消息
|
||||
poll_interval = 2.0 # 秒
|
||||
logger.info(f"开始跟踪问题: {original_question}")
|
||||
while tracker.active:
|
||||
now_ts = time.time()
|
||||
# 终止条件:时长达到上限
|
||||
if now_ts - tracker.start_time >= max_duration:
|
||||
logger.info("问题跟踪达到30分钟上限,判定为未解答")
|
||||
break
|
||||
|
||||
# 统计最近一段是否有新消息(不过滤机器人,过滤命令)
|
||||
recent_msgs = get_raw_msg_by_timestamp_with_chat(
|
||||
chat_id=tracker.chat_id,
|
||||
timestamp_start=tracker.last_read_time,
|
||||
timestamp_end=now_ts,
|
||||
limit=30,
|
||||
limit_mode="latest",
|
||||
filter_bot=False,
|
||||
filter_command=True,
|
||||
)
|
||||
|
||||
if len(recent_msgs) > 0:
|
||||
tracker.last_read_time = now_ts
|
||||
|
||||
# 统计从开始到现在的总消息数(用于触发100条上限)
|
||||
all_msgs = get_raw_msg_by_timestamp_with_chat(
|
||||
chat_id=tracker.chat_id,
|
||||
timestamp_start=tracker.start_time,
|
||||
timestamp_end=now_ts,
|
||||
limit=0,
|
||||
limit_mode="latest",
|
||||
filter_bot=False,
|
||||
filter_command=True,
|
||||
)
|
||||
|
||||
# 构建可读聊天文本
|
||||
chat_text = build_readable_messages(
|
||||
all_msgs,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="relative",
|
||||
read_mark=0.0,
|
||||
truncate=False,
|
||||
show_actions=False,
|
||||
show_pic=False,
|
||||
remove_emoji_stickers=True,
|
||||
)
|
||||
|
||||
# 让小模型判断是否有答案
|
||||
answered, answer_text = await tracker.judge_answer(chat_text)
|
||||
if answered:
|
||||
logger.info("问题已得到解答,结束跟踪并写入答案")
|
||||
tracker.stop()
|
||||
MemoryConflict.create(
|
||||
conflict_content=tracker.question,
|
||||
create_time=tracker.start_time,
|
||||
update_time=time.time(),
|
||||
answer=answer_text or "",
|
||||
)
|
||||
return
|
||||
|
||||
if len(all_msgs) >= max_messages:
|
||||
logger.info("问题跟踪达到100条消息上限,判定为未解答")
|
||||
break
|
||||
|
||||
# 无新消息时稍作等待
|
||||
await asyncio.sleep(poll_interval)
|
||||
|
||||
# 未获取到答案,仅存储问题
|
||||
MemoryConflict.create(
|
||||
conflict_content=original_question,
|
||||
create_time=time.time(),
|
||||
update_time=time.time(),
|
||||
answer="",
|
||||
)
|
||||
logger.info(f"记录冲突内容(未解答): {len(original_question)} 字符")
|
||||
except Exception as e:
|
||||
logger.error(f"后台问题跟踪任务异常: {e}")
|
||||
|
||||
async def record_memory_merge_conflict(self, part2_content: str) -> bool:
|
||||
"""
|
||||
记录记忆整合过程中的冲突内容(part2)
|
||||
|
||||
Args:
|
||||
part2_content: 冲突内容(part2)
|
||||
|
||||
Returns:
|
||||
bool: 是否成功记录
|
||||
"""
|
||||
if not part2_content or part2_content.strip() == "":
|
||||
return False
|
||||
|
||||
return await self.record_conflict(part2_content)
|
||||
|
||||
async def get_all_conflicts(self) -> list:
|
||||
"""
|
||||
获取所有冲突记录
|
||||
|
||||
Returns:
|
||||
list: 冲突记录列表
|
||||
"""
|
||||
try:
|
||||
conflicts = list(MemoryConflict.select())
|
||||
return conflicts
|
||||
except Exception as e:
|
||||
logger.error(f"获取冲突记录时出错: {e}")
|
||||
return []
|
||||
|
||||
async def get_conflict_count(self) -> int:
|
||||
"""
|
||||
获取冲突记录数量
|
||||
|
||||
Returns:
|
||||
int: 记录数量
|
||||
"""
|
||||
try:
|
||||
return MemoryConflict.select().count()
|
||||
except Exception as e:
|
||||
logger.error(f"获取冲突记录数量时出错: {e}")
|
||||
return 0
|
||||
|
||||
# 全局冲突追踪器实例
|
||||
global_conflict_tracker = ConflictTracker()
|
||||
|
|
@ -12,9 +12,12 @@ from src.config.config import global_config
|
|||
from src.plugin_system.apis.message_api import build_readable_messages
|
||||
from src.plugin_system.apis.message_api import get_raw_msg_by_timestamp_with_chat
|
||||
from json_repair import repair_json
|
||||
from src.memory_system.questions import global_conflict_tracker
|
||||
from .memory_utils import (
|
||||
find_best_matching_memory,
|
||||
check_title_exists_fuzzy
|
||||
check_title_exists_fuzzy,
|
||||
get_all_titles,
|
||||
|
||||
)
|
||||
|
||||
logger = get_logger("memory")
|
||||
|
|
@ -186,32 +189,6 @@ class MemoryChest:
|
|||
return running_content
|
||||
|
||||
|
||||
|
||||
|
||||
def get_all_titles(self, exclude_locked: bool = False) -> list[str]:
|
||||
"""
|
||||
获取记忆仓库中的所有标题
|
||||
|
||||
Args:
|
||||
exclude_locked: 是否排除锁定的记忆,默认为 False
|
||||
|
||||
Returns:
|
||||
list: 包含所有标题的列表
|
||||
"""
|
||||
try:
|
||||
# 查询所有记忆记录的标题
|
||||
titles = []
|
||||
for memory in MemoryChestModel.select():
|
||||
if memory.title:
|
||||
# 如果 exclude_locked 为 True 且记忆已锁定,则跳过
|
||||
if exclude_locked and memory.locked:
|
||||
continue
|
||||
titles.append(memory.title)
|
||||
return titles
|
||||
except Exception as e:
|
||||
print(f"获取记忆标题时出错: {e}")
|
||||
return []
|
||||
|
||||
async def get_answer_by_question(self, chat_id: str = "", question: str = "") -> str:
|
||||
"""
|
||||
根据问题获取答案
|
||||
|
|
@ -306,7 +283,7 @@ class MemoryChest:
|
|||
str: 选择的标题
|
||||
"""
|
||||
# 获取所有标题并构建格式化字符串(排除锁定的记忆)
|
||||
titles = self.get_all_titles(exclude_locked=True)
|
||||
titles = get_all_titles(exclude_locked=True)
|
||||
formatted_titles = ""
|
||||
for title in titles:
|
||||
formatted_titles += f"{title}\n"
|
||||
|
|
@ -329,7 +306,7 @@ class MemoryChest:
|
|||
title, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(prompt)
|
||||
|
||||
# 根据 title 获取 titles 里的对应项
|
||||
titles = self.get_all_titles()
|
||||
titles = get_all_titles()
|
||||
selected_title = None
|
||||
|
||||
# 使用模糊查找匹配标题
|
||||
|
|
@ -427,7 +404,7 @@ class MemoryChest:
|
|||
list[str]: 选中的记忆内容列表
|
||||
"""
|
||||
try:
|
||||
all_titles = self.get_all_titles(exclude_locked=True)
|
||||
all_titles = get_all_titles(exclude_locked=True)
|
||||
content = ""
|
||||
display_index = 1
|
||||
for title in all_titles:
|
||||
|
|
@ -462,7 +439,7 @@ class MemoryChest:
|
|||
请输出JSON格式,不要输出其他内容:
|
||||
"""
|
||||
|
||||
logger.info(f"选择合并目标 prompt: {prompt}")
|
||||
# logger.info(f"选择合并目标 prompt: {prompt}")
|
||||
|
||||
if global_config.debug.show_prompt:
|
||||
logger.info(f"选择合并目标 prompt: {prompt}")
|
||||
|
|
@ -692,8 +669,6 @@ class MemoryChest:
|
|||
# 处理part2:独立记录冲突内容(无论part1是否为空)
|
||||
if part2_content and part2_content.strip() != "none":
|
||||
logger.info(f"合并记忆part2记录冲突内容: {len(part2_content)} 字符")
|
||||
# 导入冲突追踪器
|
||||
from src.curiousity.questions import global_conflict_tracker
|
||||
# 记录冲突到数据库
|
||||
await global_conflict_tracker.record_memory_merge_conflict(part2_content)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from src.memory_system.Memory_chest import global_memory_chest
|
|||
from src.common.logger import get_logger
|
||||
from src.common.database.database_model import MemoryChest as MemoryChestModel
|
||||
from src.config.config import global_config
|
||||
from src.memory_system.memory_utils import get_all_titles
|
||||
|
||||
logger = get_logger("memory")
|
||||
|
||||
|
|
@ -130,7 +131,7 @@ class MemoryManagementTask(AsyncTask):
|
|||
"""随机获取一个记忆标题"""
|
||||
try:
|
||||
# 获取所有记忆标题
|
||||
all_titles = global_memory_chest.get_all_titles()
|
||||
all_titles = get_all_titles()
|
||||
if not all_titles:
|
||||
return ""
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,79 @@
|
|||
记忆系统工具函数
|
||||
包含模糊查找、相似度计算等工具函数
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from difflib import SequenceMatcher
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from src.common.database.database_model import MemoryChest as MemoryChestModel
|
||||
from src.common.logger import get_logger
|
||||
from json_repair import repair_json
|
||||
|
||||
|
||||
logger = get_logger("memory_utils")
|
||||
|
||||
def get_all_titles(exclude_locked: bool = False) -> list[str]:
|
||||
"""
|
||||
获取记忆仓库中的所有标题
|
||||
|
||||
Args:
|
||||
exclude_locked: 是否排除锁定的记忆,默认为 False
|
||||
|
||||
Returns:
|
||||
list: 包含所有标题的列表
|
||||
"""
|
||||
try:
|
||||
# 查询所有记忆记录的标题
|
||||
titles = []
|
||||
for memory in MemoryChestModel.select():
|
||||
if memory.title:
|
||||
# 如果 exclude_locked 为 True 且记忆已锁定,则跳过
|
||||
if exclude_locked and memory.locked:
|
||||
continue
|
||||
titles.append(memory.title)
|
||||
return titles
|
||||
except Exception as e:
|
||||
print(f"获取记忆标题时出错: {e}")
|
||||
return []
|
||||
|
||||
def parse_md_json(json_text: str) -> list[str]:
|
||||
"""从Markdown格式的内容中提取JSON对象和推理内容"""
|
||||
json_objects = []
|
||||
reasoning_content = ""
|
||||
|
||||
# 使用正则表达式查找```json包裹的JSON内容
|
||||
json_pattern = r"```json\s*(.*?)\s*```"
|
||||
matches = re.findall(json_pattern, json_text, re.DOTALL)
|
||||
|
||||
# 提取JSON之前的内容作为推理文本
|
||||
if matches:
|
||||
# 找到第一个```json的位置
|
||||
first_json_pos = json_text.find("```json")
|
||||
if first_json_pos > 0:
|
||||
reasoning_content = json_text[:first_json_pos].strip()
|
||||
# 清理推理内容中的注释标记
|
||||
reasoning_content = re.sub(r"^//\s*", "", reasoning_content, flags=re.MULTILINE)
|
||||
reasoning_content = reasoning_content.strip()
|
||||
|
||||
for match in matches:
|
||||
try:
|
||||
# 清理可能的注释和格式问题
|
||||
json_str = re.sub(r"//.*?\n", "\n", match) # 移除单行注释
|
||||
json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) # 移除多行注释
|
||||
if json_str := json_str.strip():
|
||||
json_obj = json.loads(json_str)
|
||||
if isinstance(json_obj, dict):
|
||||
json_objects.append(json_obj)
|
||||
elif isinstance(json_obj, list):
|
||||
for item in json_obj:
|
||||
if isinstance(item, dict):
|
||||
json_objects.append(item)
|
||||
except Exception as e:
|
||||
logger.warning(f"解析JSON块失败: {e}, 块内容: {match[:100]}...")
|
||||
continue
|
||||
|
||||
return json_objects, reasoning_content
|
||||
|
||||
def calculate_similarity(text1: str, text2: str) -> float:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import time
|
||||
import random
|
||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages
|
||||
from src.common.database.database_model import MemoryConflict
|
||||
from src.config.config import global_config
|
||||
|
||||
|
||||
class QuestionMaker:
|
||||
def __init__(self, chat_id: str,context: str = ""):
|
||||
self.chat_id = chat_id
|
||||
self.context = context
|
||||
|
||||
def get_context(self):
|
||||
latest_30_msgs = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=self.chat_id,
|
||||
timestamp=time.time(),
|
||||
limit=30,
|
||||
)
|
||||
|
||||
all_dialogue_prompt_str = build_readable_messages(
|
||||
latest_30_msgs,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
)
|
||||
return all_dialogue_prompt_str
|
||||
|
||||
|
||||
async def get_all_conflicts(self):
|
||||
conflicts = list(MemoryConflict.select())
|
||||
return conflicts
|
||||
|
||||
async def get_un_answered_conflict(self):
|
||||
conflicts = await self.get_all_conflicts()
|
||||
return [conflict for conflict in conflicts if not conflict.answer]
|
||||
|
||||
async def get_random_unanswered_conflict(self):
|
||||
conflicts = await self.get_un_answered_conflict()
|
||||
return random.choice(conflicts)
|
||||
|
||||
async def make_question(self):
|
||||
conflict = await self.get_random_unanswered_conflict()
|
||||
question = conflict.conflict_content
|
||||
conflict_context = conflict.context
|
||||
chat_context = self.get_context()
|
||||
|
||||
return question, conflict_context
|
||||
|
|
@ -0,0 +1,406 @@
|
|||
import time
|
||||
import asyncio
|
||||
from src.common.logger import get_logger
|
||||
from src.common.database.database_model import MemoryConflict
|
||||
from src.chat.utils.chat_message_builder import (
|
||||
get_raw_msg_by_timestamp_with_chat,
|
||||
build_readable_messages,
|
||||
)
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.config.config import model_config, global_config
|
||||
from typing import List
|
||||
from src.memory_system.memory_utils import parse_md_json
|
||||
|
||||
logger = get_logger("conflict_tracker")
|
||||
|
||||
class QuestionTracker:
|
||||
"""
|
||||
用于跟踪一个问题在后续聊天中的解答情况
|
||||
"""
|
||||
|
||||
def __init__(self, question: str, chat_id: str, context: str = "") -> None:
|
||||
self.question = question
|
||||
self.chat_id = chat_id
|
||||
now = time.time()
|
||||
self.context = context
|
||||
self.start_time = now
|
||||
self.last_read_time = now
|
||||
self.last_judge_time = now # 上次判定的时间
|
||||
self.judge_debounce_interval = 10.0 # 判定防抖间隔:10秒
|
||||
self.consecutive_end_count = 0 # 连续END计数
|
||||
self.active = True
|
||||
# 将 LLM 实例作为类属性,使用 utils 模型
|
||||
self.llm_request = LLMRequest(model_set=model_config.model_task_config.utils, request_type="conflict.judge")
|
||||
|
||||
def stop(self) -> None:
|
||||
self.active = False
|
||||
|
||||
def should_judge_now(self) -> bool:
|
||||
"""
|
||||
检查是否应该进行判定(防抖检查)
|
||||
|
||||
Returns:
|
||||
bool: 是否可以判定
|
||||
"""
|
||||
now = time.time()
|
||||
# 检查是否已经过了10秒的防抖间隔
|
||||
return (now - self.last_judge_time) >= self.judge_debounce_interval
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""比较两个追踪器是否相等(基于问题内容和聊天ID)"""
|
||||
if not isinstance(other, QuestionTracker):
|
||||
return False
|
||||
return self.question == other.question and self.chat_id == other.chat_id
|
||||
|
||||
def __hash__(self) -> int:
|
||||
"""为对象提供哈希值,支持集合操作"""
|
||||
return hash((self.question, self.chat_id))
|
||||
|
||||
async def judge_answer(self, conversation_text: str) -> tuple[bool, str, str]:
|
||||
"""
|
||||
使用模型判定问题是否已得到解答。
|
||||
|
||||
Returns:
|
||||
tuple[bool, str, str]: (是否结束跟踪, 结束原因或答案, 判定类型)
|
||||
- True: 结束跟踪(已解答、话题转向等)
|
||||
- False: 继续跟踪
|
||||
判定类型: "ANSWERED", "END", "CONTINUE"
|
||||
"""
|
||||
prompt = f"""你是一个严谨的判定器。下面给出聊天记录以及一个问题。
|
||||
任务:判断在这段聊天中,该问题是否已经得到明确解答。
|
||||
**你必须严格按照聊天记录的内容,不要添加额外的信息**
|
||||
|
||||
输出规则:
|
||||
- 如果聊天记录内容的信息已解答问题,请只输出:YES: <简短答案>
|
||||
- 如果聊天记录内容与问题无关,话题已转向其他方向,请只输出:END
|
||||
- 如果问题尚未解答但聊天仍在相关话题上,请只输出:NO
|
||||
|
||||
**问题**
|
||||
{self.question}
|
||||
|
||||
|
||||
**聊天记录**
|
||||
{conversation_text}
|
||||
"""
|
||||
|
||||
if global_config.debug.show_prompt:
|
||||
logger.info(f"判定提示词: {prompt}")
|
||||
else:
|
||||
logger.debug("已发送判定提示词")
|
||||
|
||||
result_text, _ = await self.llm_request.generate_response_async(prompt, temperature=0.5)
|
||||
|
||||
logger.info(f"判定结果: {prompt}\n{result_text}")
|
||||
|
||||
# 更新上次判定时间
|
||||
self.last_judge_time = time.time()
|
||||
|
||||
if not result_text:
|
||||
return False, "", "CONTINUE"
|
||||
|
||||
text = result_text.strip()
|
||||
if text.upper().startswith("YES:"):
|
||||
answer = text[4:].strip()
|
||||
return True, answer, "ANSWERED"
|
||||
if text.upper().startswith("YES"):
|
||||
# 兼容仅输出 YES 或 YES <answer>
|
||||
answer = text[3:].strip().lstrip(":").strip()
|
||||
return True, answer, "ANSWERED"
|
||||
if text.upper().startswith("END"):
|
||||
# 聊天内容与问题无关,放弃该问题思考
|
||||
return True, "话题已转向其他方向,放弃该问题思考", "END"
|
||||
return False, "", "CONTINUE"
|
||||
|
||||
class ConflictTracker:
|
||||
"""
|
||||
记忆整合冲突追踪器
|
||||
|
||||
用于记录和存储记忆整合过程中的冲突内容
|
||||
"""
|
||||
def __init__(self):
|
||||
self.question_tracker_list:List[QuestionTracker] = []
|
||||
|
||||
self.LLMRequest_tracker = LLMRequest(
|
||||
model_set=model_config.model_task_config.utils,
|
||||
request_type="conflict_tracker",
|
||||
)
|
||||
|
||||
def get_questions_by_chat_id(self, chat_id: str) -> List[QuestionTracker]:
|
||||
return [tracker for tracker in self.question_tracker_list if tracker.chat_id == chat_id]
|
||||
|
||||
async def track_conflict(self, question: str, context: str = "",start_following: bool = False,chat_id: str = "") -> bool:
|
||||
"""
|
||||
跟踪冲突内容
|
||||
"""
|
||||
tracker = QuestionTracker(question.strip(), chat_id, context)
|
||||
self.question_tracker_list.append(tracker)
|
||||
asyncio.create_task(self._follow_and_record(tracker, question.strip()))
|
||||
return True
|
||||
|
||||
async def record_conflict(self, conflict_content: str, context: str = "",start_following: bool = False,chat_id: str = "") -> bool:
|
||||
"""
|
||||
记录冲突内容
|
||||
|
||||
Args:k
|
||||
conflict_content: 冲突内容
|
||||
|
||||
Returns:
|
||||
bool: 是否成功记录
|
||||
"""
|
||||
try:
|
||||
if not conflict_content or conflict_content.strip() == "":
|
||||
return False
|
||||
|
||||
# 若需要跟随后续消息以判断是否得到解答,则进入跟踪流程
|
||||
if start_following and chat_id:
|
||||
tracker = QuestionTracker(conflict_content.strip(), chat_id, context)
|
||||
self.question_tracker_list.append(tracker)
|
||||
# 后台启动跟踪任务,避免阻塞
|
||||
asyncio.create_task(self._follow_and_record(tracker, conflict_content.strip()))
|
||||
return True
|
||||
|
||||
# 默认:直接记录,不进行跟踪
|
||||
MemoryConflict.create(
|
||||
conflict_content=conflict_content,
|
||||
create_time=time.time(),
|
||||
update_time=time.time(),
|
||||
answer="",
|
||||
)
|
||||
|
||||
logger.info(f"记录冲突内容: {len(conflict_content)} 字符")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"记录冲突内容时出错: {e}")
|
||||
return False
|
||||
|
||||
async def _follow_and_record(self, tracker: QuestionTracker, original_question: str) -> None:
|
||||
"""
|
||||
后台任务:跟踪问题是否被解答,并写入数据库。
|
||||
"""
|
||||
try:
|
||||
max_duration = 10 * 60 # 30 分钟
|
||||
max_messages = 50 # 最多 100 条消息
|
||||
poll_interval = 2.0 # 秒
|
||||
logger.info(f"开始跟踪问题: {original_question}")
|
||||
while tracker.active:
|
||||
now_ts = time.time()
|
||||
# 终止条件:时长达到上限
|
||||
if now_ts - tracker.start_time >= max_duration:
|
||||
logger.info("问题跟踪达到10分钟上限,判定为未解答")
|
||||
break
|
||||
|
||||
# 统计最近一段是否有新消息(不过滤机器人,过滤命令)
|
||||
recent_msgs = get_raw_msg_by_timestamp_with_chat(
|
||||
chat_id=tracker.chat_id,
|
||||
timestamp_start=tracker.last_read_time,
|
||||
timestamp_end=now_ts,
|
||||
limit=30,
|
||||
limit_mode="latest",
|
||||
filter_bot=False,
|
||||
filter_command=True,
|
||||
)
|
||||
|
||||
if len(recent_msgs) > 0:
|
||||
tracker.last_read_time = now_ts
|
||||
|
||||
# 统计从开始到现在的总消息数(用于触发100条上限)
|
||||
all_msgs = get_raw_msg_by_timestamp_with_chat(
|
||||
chat_id=tracker.chat_id,
|
||||
timestamp_start=tracker.start_time,
|
||||
timestamp_end=now_ts,
|
||||
limit=0,
|
||||
limit_mode="latest",
|
||||
filter_bot=False,
|
||||
filter_command=True,
|
||||
)
|
||||
|
||||
# 检查是否应该进行判定(防抖检查)
|
||||
if not tracker.should_judge_now():
|
||||
logger.debug(f"判定防抖中,跳过本次判定: {tracker.question}")
|
||||
await asyncio.sleep(poll_interval)
|
||||
continue
|
||||
|
||||
# 构建可读聊天文本
|
||||
chat_text = build_readable_messages(
|
||||
all_msgs,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="relative",
|
||||
read_mark=0.0,
|
||||
truncate=False,
|
||||
show_actions=False,
|
||||
show_pic=False,
|
||||
remove_emoji_stickers=True,
|
||||
)
|
||||
|
||||
# 让小模型判断是否有答案
|
||||
answered, answer_text, judge_type = await tracker.judge_answer(chat_text)
|
||||
|
||||
if judge_type == "ANSWERED":
|
||||
# 问题已解答,直接结束跟踪
|
||||
logger.info("问题已得到解答,结束跟踪并写入答案")
|
||||
await self.add_or_update_conflict(
|
||||
conflict_content=tracker.question,
|
||||
create_time=tracker.start_time,
|
||||
update_time=time.time(),
|
||||
answer=answer_text or "",
|
||||
)
|
||||
return
|
||||
elif judge_type == "END":
|
||||
# 话题转向,增加END计数
|
||||
tracker.consecutive_end_count += 1
|
||||
logger.info(f"话题已转向,连续END次数: {tracker.consecutive_end_count}")
|
||||
|
||||
if tracker.consecutive_end_count >= 2:
|
||||
# 连续两次END,结束跟踪
|
||||
logger.info("连续两次END,结束跟踪")
|
||||
break
|
||||
else:
|
||||
# 第一次END,重置计数器并继续跟踪
|
||||
logger.info("第一次END,继续跟踪")
|
||||
continue
|
||||
elif judge_type == "CONTINUE":
|
||||
# 继续跟踪,重置END计数器
|
||||
tracker.consecutive_end_count = 0
|
||||
continue
|
||||
|
||||
if len(all_msgs) >= max_messages:
|
||||
logger.info("问题跟踪达到100条消息上限,判定为未解答")
|
||||
logger.info(f"追踪结束:{tracker.question}")
|
||||
break
|
||||
|
||||
# 无新消息时稍作等待
|
||||
await asyncio.sleep(poll_interval)
|
||||
|
||||
# 未获取到答案,仅存储问题
|
||||
await self.add_or_update_conflict(
|
||||
conflict_content=original_question,
|
||||
create_time=time.time(),
|
||||
update_time=time.time(),
|
||||
answer="",
|
||||
)
|
||||
logger.info(f"记录冲突内容(未解答): {len(original_question)} 字符")
|
||||
logger.info(f"问题跟踪结束:{original_question}")
|
||||
except Exception as e:
|
||||
logger.error(f"后台问题跟踪任务异常: {e}")
|
||||
finally:
|
||||
# 无论任务成功还是失败,都要从追踪列表中移除
|
||||
tracker.stop()
|
||||
self.remove_tracker(tracker)
|
||||
|
||||
def remove_tracker(self, tracker: QuestionTracker) -> None:
|
||||
"""
|
||||
从追踪列表中移除指定的追踪器
|
||||
|
||||
Args:
|
||||
tracker: 要移除的追踪器对象
|
||||
"""
|
||||
try:
|
||||
if tracker in self.question_tracker_list:
|
||||
self.question_tracker_list.remove(tracker)
|
||||
logger.info(f"已从追踪列表中移除追踪器: {tracker.question}")
|
||||
else:
|
||||
logger.warning(f"尝试移除不存在的追踪器: {tracker.question}")
|
||||
except Exception as e:
|
||||
logger.error(f"移除追踪器时出错: {e}")
|
||||
|
||||
async def add_or_update_conflict(self,conflict_content: str,create_time: float,update_time: float,answer: str = "",context: str = "") -> bool:
|
||||
"""
|
||||
根据conflict_content匹配数据库内容,如果找到相同的就更新update_time和answer,
|
||||
如果没有相同的,就新建一条保存全部内容
|
||||
"""
|
||||
try:
|
||||
# 尝试根据conflict_content查找现有记录
|
||||
existing_conflict = MemoryConflict.get_or_none(
|
||||
MemoryConflict.conflict_content == conflict_content
|
||||
)
|
||||
|
||||
if existing_conflict:
|
||||
# 如果找到相同的conflict_content,更新update_time和answer
|
||||
existing_conflict.update_time = update_time
|
||||
existing_conflict.answer = answer
|
||||
existing_conflict.save()
|
||||
return True
|
||||
else:
|
||||
# 如果没有找到相同的,创建新记录
|
||||
MemoryConflict.create(
|
||||
conflict_content=conflict_content,
|
||||
create_time=create_time,
|
||||
update_time=update_time,
|
||||
answer=answer,
|
||||
context=context,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
# 记录错误并返回False
|
||||
logger.error(f"添加或更新冲突记录时出错: {e}")
|
||||
return False
|
||||
|
||||
async def record_memory_merge_conflict(self, part2_content: str) -> bool:
|
||||
"""
|
||||
记录记忆整合过程中的冲突内容(part2)
|
||||
|
||||
Args:
|
||||
part2_content: 冲突内容(part2)
|
||||
|
||||
Returns:
|
||||
bool: 是否成功记录
|
||||
"""
|
||||
if not part2_content or part2_content.strip() == "":
|
||||
return False
|
||||
|
||||
prompt = f"""以下是一段有冲突的信息,请你根据这些信息总结出几个具体的提问:
|
||||
冲突信息:
|
||||
{part2_content}
|
||||
|
||||
要求:
|
||||
1.提问必须具体,明确
|
||||
2.提问最好涉及指向明确的事物,而不是代称
|
||||
3.如果缺少上下文,不要强行提问,可以忽略
|
||||
|
||||
请用json格式输出,不要输出其他内容,仅输出提问理由和具体提的提问:
|
||||
**示例**
|
||||
// 理由文本
|
||||
```json
|
||||
{{
|
||||
"question":"提问",
|
||||
}}
|
||||
```
|
||||
```json
|
||||
{{
|
||||
"question":"提问"
|
||||
}}
|
||||
```
|
||||
...提问数量在1-3个之间,不要重复,现在请输出:"""
|
||||
|
||||
question_response, (reasoning_content, model_name, tool_calls) = await self.LLMRequest_tracker.generate_response_async(prompt)
|
||||
|
||||
# 解析JSON响应
|
||||
questions, reasoning_content = parse_md_json(question_response)
|
||||
|
||||
print(prompt)
|
||||
print(question_response)
|
||||
|
||||
for question in questions:
|
||||
await self.record_conflict(
|
||||
conflict_content=question["question"],
|
||||
context=reasoning_content,
|
||||
start_following=False,
|
||||
)
|
||||
return True
|
||||
|
||||
async def get_conflict_count(self) -> int:
|
||||
"""
|
||||
获取冲突记录数量
|
||||
|
||||
Returns:
|
||||
int: 记录数量
|
||||
"""
|
||||
try:
|
||||
return MemoryConflict.select().count()
|
||||
except Exception as e:
|
||||
logger.error(f"获取冲突记录数量时出错: {e}")
|
||||
return 0
|
||||
|
||||
# 全局冲突追踪器实例
|
||||
global_conflict_tracker = ConflictTracker()
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import asyncio
|
||||
from src.common.database.database_model import GraphNodes
|
||||
from src.common.logger import get_logger
|
||||
|
||||
logger = get_logger("migrate")
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from src.plugin_system.base.component_types import ActionActivationType
|
|||
from src.plugin_system.apis import config_api
|
||||
from src.plugin_system.apis import frequency_api
|
||||
from src.plugin_system.apis import generator_api
|
||||
from src.curiousity.questions import global_conflict_tracker
|
||||
from src.memory_system.questions import global_conflict_tracker
|
||||
|
||||
logger = get_logger("question_actions")
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ logger = get_logger("question_actions")
|
|||
class CuriousAction(BaseAction):
|
||||
"""频率调节动作 - 调整聊天发言频率"""
|
||||
|
||||
activation_type = ActionActivationType.LLM_JUDGE
|
||||
activation_type = ActionActivationType.ALWAYS
|
||||
parallel_action = True
|
||||
|
||||
# 动作基本信息
|
||||
|
|
@ -37,10 +37,6 @@ class CuriousAction(BaseAction):
|
|||
action_parameters = {
|
||||
"question": "对存在疑问的信息提出一个问题,描述全面",
|
||||
}
|
||||
|
||||
# 动作使用场景
|
||||
bot_name = config_api.get_global_config("bot.nickname")
|
||||
|
||||
|
||||
action_require = [
|
||||
f"当聊天记录中的信息存在逻辑上的矛盾时使用",
|
||||
|
|
@ -56,6 +52,9 @@ class CuriousAction(BaseAction):
|
|||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行频率调节动作"""
|
||||
try:
|
||||
if len(global_conflict_tracker.question_tracker_list) > 3:
|
||||
return False, "当前有太多问题,请先解答完再提问,不要再使用make_question动作"
|
||||
|
||||
question = self.action_data.get("question", "")
|
||||
|
||||
# 存储问题到冲突追踪器
|
||||
|
|
@ -64,10 +63,10 @@ class CuriousAction(BaseAction):
|
|||
logger.info(f"已存储问题到冲突追踪器: {question}")
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=True,
|
||||
action_prompt_display=f"你产生了一个问题:{question},尝试向其他人提问或回忆",
|
||||
action_prompt_display=f"你产生了一个问题:{question}",
|
||||
action_done=True,
|
||||
)
|
||||
return True, "问题已记录"
|
||||
return True, f"问题{question}已记录,不要重复提问该问题"
|
||||
except Exception as e:
|
||||
error_msg = f"问题生成失败: {str(e)}"
|
||||
logger.error(f"{self.log_prefix} {error_msg}", exc_info=True)
|
||||
|
|
@ -103,7 +102,7 @@ class CuriousPlugin(BasePlugin):
|
|||
# 配置Schema定义
|
||||
config_schema: dict = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="3.0.0", description="配置文件版本"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ class EmojiAction(BaseAction):
|
|||
action_prompt_display=f"你发送了表情包,原因:{reason}",
|
||||
action_done=True,
|
||||
)
|
||||
return True, f"成功发送表情包:{emoji_description}"
|
||||
return True, f"成功发送表情包:[表情包:{chosen_emotion}]"
|
||||
else:
|
||||
error_msg = "发送表情包失败"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue