refactor(core): 优化类型提示与代码风格

本次提交对项目代码进行了广泛的重构,主要集中在以下几个方面:

1.  **类型提示现代化**:
    -   将 `typing` 模块中的 `Optional[T]`、`List[T]`、`Dict[K, V]` 等旧式类型提示更新为现代的 `T | None`、`list[T]`、`dict[K, V]` 语法。
    -   这提高了代码的可读性,并与较新 Python 版本的风格保持一致。

2.  **代码风格统一**:
    -   移除了多余的空行和不必要的空格,使代码更加紧凑和规范。
    -   统一了部分日志输出的格式,增强了日志的可读性。

3.  **导入语句优化**:
    -   调整了部分模块的 `import` 语句顺序,使其符合 PEP 8 规范。

这些更改不涉及任何功能性变动,旨在提升代码库的整体质量、可维护性和开发体验。
This commit is contained in:
minecraft1024a
2025-10-31 20:56:17 +08:00
parent 926adf16dd
commit a29be48091
47 changed files with 923 additions and 933 deletions

View File

@@ -5,7 +5,6 @@
"""
import json
import time
from typing import Any
from sqlalchemy import select
@@ -22,7 +21,7 @@ logger = get_logger("chat_stream_impression_tool")
class ChatStreamImpressionTool(BaseTool):
"""聊天流印象更新工具
使用二步调用机制:
1. LLM决定是否调用工具并传入初步参数stream_id会自动传入
2. 工具内部调用LLM结合现有数据和传入参数决定最终更新内容
@@ -31,27 +30,52 @@ class ChatStreamImpressionTool(BaseTool):
name = "update_chat_stream_impression"
description = "当你通过观察聊天记录对当前聊天环境(群聊或私聊)产生了整体印象或认识时使用此工具,更新对这个聊天流的看法。包括:环境氛围、聊天风格、常见话题、你的兴趣程度。调用时机:当你发现这个聊天环境有明显的氛围特点(如很活跃、很专业、很闲聊)、群成员经常讨论某类话题、或者你对这个环境的感受发生变化时。注意:这是对整个聊天环境的印象,而非对单个用户。"
parameters = [
("impression_description", ToolParamType.STRING, "你对这个聊天环境的整体感受和印象,例如'这是个技术氛围浓厚的群''大家都很友好热情'。当你通过聊天记录感受到环境特点时填写(可选)", False, None),
("chat_style", ToolParamType.STRING, "这个聊天环境的风格特征,如'活跃热闹,互帮互助''严肃专业,深度讨论''轻松闲聊,段子频出'等。当你发现聊天方式有明显特点时填写(可选)", False, None),
("topic_keywords", ToolParamType.STRING, "这个聊天环境中经常出现的话题,如'编程,AI,技术分享''游戏,动漫,娱乐'。当你观察到群里反复讨论某些主题时填写,多个关键词用逗号分隔(可选)", False, None),
("interest_score", ToolParamType.FLOAT, "你对这个聊天环境的兴趣和喜欢程度0.0(无聊/不喜欢)到1.0(很有趣/很喜欢)。当你对这个环境的感觉发生变化时更新(可选)", False, None),
(
"impression_description",
ToolParamType.STRING,
"你对这个聊天环境的整体感受和印象,例如'这是个技术氛围浓厚的群''大家都很友好热情'。当你通过聊天记录感受到环境特点时填写(可选)",
False,
None,
),
(
"chat_style",
ToolParamType.STRING,
"这个聊天环境的风格特征,如'活跃热闹,互帮互助''严肃专业,深度讨论''轻松闲聊,段子频出'等。当你发现聊天方式有明显特点时填写(可选)",
False,
None,
),
(
"topic_keywords",
ToolParamType.STRING,
"这个聊天环境中经常出现的话题,如'编程,AI,技术分享''游戏,动漫,娱乐'。当你观察到群里反复讨论某些主题时填写,多个关键词用逗号分隔(可选)",
False,
None,
),
(
"interest_score",
ToolParamType.FLOAT,
"你对这个聊天环境的兴趣和喜欢程度0.0(无聊/不喜欢)到1.0(很有趣/很喜欢)。当你对这个环境的感觉发生变化时更新(可选)",
False,
None,
),
]
available_for_llm = True
history_ttl = 5
def __init__(self, plugin_config: dict | None = None, chat_stream: Any = None):
super().__init__(plugin_config, chat_stream)
# 初始化用于二步调用的LLM
try:
self.impression_llm = LLMRequest(
model_set=model_config.model_task_config.relationship_tracker,
request_type="chat_stream_impression_update"
request_type="chat_stream_impression_update",
)
except AttributeError:
# 降级处理
available_models = [
attr for attr in dir(model_config.model_task_config)
attr
for attr in dir(model_config.model_task_config)
if not attr.startswith("_") and attr != "model_dump"
]
if available_models:
@@ -59,7 +83,7 @@ class ChatStreamImpressionTool(BaseTool):
logger.warning(f"relationship_tracker配置不存在使用降级模型: {fallback_model}")
self.impression_llm = LLMRequest(
model_set=getattr(model_config.model_task_config, fallback_model),
request_type="chat_stream_impression_update"
request_type="chat_stream_impression_update",
)
else:
logger.error("无可用的模型配置")
@@ -67,17 +91,17 @@ class ChatStreamImpressionTool(BaseTool):
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
"""执行聊天流印象更新
Args:
function_args: 工具参数
Returns:
dict: 执行结果
"""
try:
# 优先从 function_args 获取 stream_id
stream_id = function_args.get("stream_id")
# 如果没有,从 chat_stream 对象获取
if not stream_id and self.chat_stream:
try:
@@ -85,61 +109,49 @@ class ChatStreamImpressionTool(BaseTool):
logger.debug(f"从 chat_stream 获取到 stream_id: {stream_id}")
except AttributeError:
logger.warning("chat_stream 对象没有 stream_id 属性")
# 如果还是没有,返回错误
if not stream_id:
logger.error("无法获取 stream_idfunction_args 和 chat_stream 都没有提供")
return {
"type": "error",
"id": "chat_stream_impression",
"content": "错误无法获取当前聊天流ID"
}
return {"type": "error", "id": "chat_stream_impression", "content": "错误无法获取当前聊天流ID"}
# 从LLM传入的参数
new_impression = function_args.get("impression_description", "")
new_style = function_args.get("chat_style", "")
new_topics = function_args.get("topic_keywords", "")
new_score = function_args.get("interest_score")
# 从数据库获取现有聊天流印象
existing_impression = await self._get_stream_impression(stream_id)
# 如果LLM没有传入任何有效参数返回提示
if not any([new_impression, new_style, new_topics, new_score is not None]):
return {
"type": "info",
"id": stream_id,
"content": "提示:需要提供至少一项更新内容(印象描述、聊天风格、话题关键词或兴趣分数)"
"content": "提示:需要提供至少一项更新内容(印象描述、聊天风格、话题关键词或兴趣分数)",
}
# 调用LLM进行二步决策
if self.impression_llm is None:
logger.error("LLM未正确初始化无法执行二步调用")
return {
"type": "error",
"id": stream_id,
"content": "系统错误LLM未正确初始化"
}
return {"type": "error", "id": stream_id, "content": "系统错误LLM未正确初始化"}
final_impression = await self._llm_decide_final_impression(
stream_id=stream_id,
existing_impression=existing_impression,
new_impression=new_impression,
new_style=new_style,
new_topics=new_topics,
new_score=new_score
new_score=new_score,
)
if not final_impression:
return {
"type": "error",
"id": stream_id,
"content": "LLM决策失败无法更新聊天流印象"
}
return {"type": "error", "id": stream_id, "content": "LLM决策失败无法更新聊天流印象"}
# 更新数据库
await self._update_stream_impression_in_db(stream_id, final_impression)
# 构建返回信息
updates = []
if final_impression.get("stream_impression_text"):
@@ -150,30 +162,26 @@ class ChatStreamImpressionTool(BaseTool):
updates.append(f"话题: {final_impression['stream_topic_keywords']}")
if final_impression.get("stream_interest_score") is not None:
updates.append(f"兴趣分: {final_impression['stream_interest_score']:.2f}")
result_text = f"已更新聊天流 {stream_id} 的印象:\n" + "\n".join(updates)
logger.info(f"聊天流印象更新成功: {stream_id}")
return {
"type": "chat_stream_impression_update",
"id": stream_id,
"content": result_text
}
return {"type": "chat_stream_impression_update", "id": stream_id, "content": result_text}
except Exception as e:
logger.error(f"聊天流印象更新失败: {e}", exc_info=True)
return {
"type": "error",
"id": function_args.get("stream_id", "unknown"),
"content": f"聊天流印象更新失败: {str(e)}"
"content": f"聊天流印象更新失败: {e!s}",
}
async def _get_stream_impression(self, stream_id: str) -> dict[str, Any]:
"""从数据库获取聊天流现有印象
Args:
stream_id: 聊天流ID
Returns:
dict: 聊天流印象数据
"""
@@ -182,13 +190,15 @@ class ChatStreamImpressionTool(BaseTool):
stmt = select(ChatStreams).where(ChatStreams.stream_id == stream_id)
result = await session.execute(stmt)
stream = result.scalar_one_or_none()
if stream:
return {
"stream_impression_text": stream.stream_impression_text or "",
"stream_chat_style": stream.stream_chat_style or "",
"stream_topic_keywords": stream.stream_topic_keywords or "",
"stream_interest_score": float(stream.stream_interest_score) if stream.stream_interest_score is not None else 0.5,
"stream_interest_score": float(stream.stream_interest_score)
if stream.stream_interest_score is not None
else 0.5,
"group_name": stream.group_name or "私聊",
}
else:
@@ -217,10 +227,10 @@ class ChatStreamImpressionTool(BaseTool):
new_impression: str,
new_style: str,
new_topics: str,
new_score: float | None
new_score: float | None,
) -> dict[str, Any] | None:
"""使用LLM决策最终的聊天流印象内容
Args:
stream_id: 聊天流ID
existing_impression: 现有印象数据
@@ -228,33 +238,34 @@ class ChatStreamImpressionTool(BaseTool):
new_style: LLM传入的新风格
new_topics: LLM传入的新话题
new_score: LLM传入的新分数
Returns:
dict: 最终决定的印象数据如果失败返回None
"""
try:
# 获取bot人设
from src.individuality.individuality import Individuality
individuality = Individuality()
bot_personality = await individuality.get_personality_block()
prompt = f"""
你现在是一个有着特定性格和身份的AI助手。你的人设是{bot_personality}
你正在更新对聊天流 {stream_id} 的整体印象。
【当前聊天流信息】
- 聊天环境: {existing_impression.get('group_name', '未知')}
- 当前印象: {existing_impression.get('stream_impression_text', '暂无印象')}
- 聊天风格: {existing_impression.get('stream_chat_style', '未知')}
- 常见话题: {existing_impression.get('stream_topic_keywords', '未知')}
- 当前兴趣分: {existing_impression.get('stream_interest_score', 0.5):.2f}
- 聊天环境: {existing_impression.get("group_name", "未知")}
- 当前印象: {existing_impression.get("stream_impression_text", "暂无印象")}
- 聊天风格: {existing_impression.get("stream_chat_style", "未知")}
- 常见话题: {existing_impression.get("stream_topic_keywords", "未知")}
- 当前兴趣分: {existing_impression.get("stream_interest_score", 0.5):.2f}
【本次想要更新的内容】
- 新的印象描述: {new_impression if new_impression else '不更新'}
- 新的聊天风格: {new_style if new_style else '不更新'}
- 新的话题关键词: {new_topics if new_topics else '不更新'}
- 新的兴趣分数: {new_score if new_score is not None else '不更新'}
- 新的印象描述: {new_impression if new_impression else "不更新"}
- 新的聊天风格: {new_style if new_style else "不更新"}
- 新的话题关键词: {new_topics if new_topics else "不更新"}
- 新的兴趣分数: {new_score if new_score is not None else "不更新"}
请综合考虑现有信息和新信息,决定最终的聊天流印象内容。注意:
1. 印象描述如果提供了新印象应该综合现有印象和新印象形成对这个聊天环境的整体认知100-200字
@@ -271,31 +282,47 @@ class ChatStreamImpressionTool(BaseTool):
"reasoning": "你的决策理由"
}}
"""
# 调用LLM
llm_response, _ = await self.impression_llm.generate_response_async(prompt=prompt)
if not llm_response:
logger.warning("LLM未返回有效响应")
return None
# 清理并解析响应
cleaned_response = self._clean_llm_json_response(llm_response)
response_data = json.loads(cleaned_response)
# 提取最终决定的数据
final_impression = {
"stream_impression_text": response_data.get("stream_impression_text", existing_impression.get("stream_impression_text", "")),
"stream_chat_style": response_data.get("stream_chat_style", existing_impression.get("stream_chat_style", "")),
"stream_topic_keywords": response_data.get("stream_topic_keywords", existing_impression.get("stream_topic_keywords", "")),
"stream_interest_score": max(0.0, min(1.0, float(response_data.get("stream_interest_score", existing_impression.get("stream_interest_score", 0.5))))),
"stream_impression_text": response_data.get(
"stream_impression_text", existing_impression.get("stream_impression_text", "")
),
"stream_chat_style": response_data.get(
"stream_chat_style", existing_impression.get("stream_chat_style", "")
),
"stream_topic_keywords": response_data.get(
"stream_topic_keywords", existing_impression.get("stream_topic_keywords", "")
),
"stream_interest_score": max(
0.0,
min(
1.0,
float(
response_data.get(
"stream_interest_score", existing_impression.get("stream_interest_score", 0.5)
)
),
),
),
}
logger.info(f"LLM决策完成: {stream_id}")
logger.debug(f"决策理由: {response_data.get('reasoning', '')}")
return final_impression
except json.JSONDecodeError as e:
logger.error(f"LLM响应JSON解析失败: {e}")
logger.debug(f"LLM原始响应: {llm_response if 'llm_response' in locals() else 'N/A'}")
@@ -306,7 +333,7 @@ class ChatStreamImpressionTool(BaseTool):
async def _update_stream_impression_in_db(self, stream_id: str, impression: dict[str, Any]):
"""更新数据库中的聊天流印象
Args:
stream_id: 聊天流ID
impression: 印象数据
@@ -316,14 +343,14 @@ class ChatStreamImpressionTool(BaseTool):
stmt = select(ChatStreams).where(ChatStreams.stream_id == stream_id)
result = await session.execute(stmt)
existing = result.scalar_one_or_none()
if existing:
# 更新现有记录
existing.stream_impression_text = impression.get("stream_impression_text", "")
existing.stream_chat_style = impression.get("stream_chat_style", "")
existing.stream_topic_keywords = impression.get("stream_topic_keywords", "")
existing.stream_interest_score = impression.get("stream_interest_score", 0.5)
await session.commit()
logger.info(f"聊天流印象已更新到数据库: {stream_id}")
else:
@@ -331,40 +358,40 @@ class ChatStreamImpressionTool(BaseTool):
logger.error(error_msg)
# 注意:通常聊天流应该在消息处理时就已创建,这里不创建新记录
raise ValueError(error_msg)
except Exception as e:
logger.error(f"更新聊天流印象到数据库失败: {e}", exc_info=True)
raise
def _clean_llm_json_response(self, response: str) -> str:
"""清理LLM响应移除可能的JSON格式标记
Args:
response: LLM原始响应
Returns:
str: 清理后的JSON字符串
"""
try:
import re
cleaned = response.strip()
# 移除 ```json 或 ``` 等标记
cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned, flags=re.MULTILINE | re.IGNORECASE)
cleaned = re.sub(r"\s*```$", "", cleaned, flags=re.MULTILINE)
# 尝试找到JSON对象的开始和结束
json_start = cleaned.find("{")
json_end = cleaned.rfind("}")
if json_start != -1 and json_end != -1 and json_end > json_start:
cleaned = cleaned[json_start:json_end + 1]
cleaned = cleaned[json_start : json_end + 1]
cleaned = cleaned.strip()
return cleaned
except Exception as e:
logger.warning(f"清理LLM响应失败: {e}")
return response

View File

@@ -231,11 +231,11 @@ class ChatterPlanExecutor:
except Exception as e:
error_message = str(e)
logger.error(f"执行回复动作失败: {action_info.action_type}, 错误: {error_message}")
# 将机器人回复添加到已读消息中
if success and action_info.action_message:
await self._add_bot_reply_to_read_messages(action_info, plan, reply_content)
execution_time = time.time() - start_time
self.execution_stats["execution_times"].append(execution_time)
@@ -381,13 +381,11 @@ class ChatterPlanExecutor:
is_picid=False,
is_command=False,
is_notify=False,
# 用户信息
user_id=bot_user_id,
user_nickname=bot_nickname,
user_cardname=bot_nickname,
user_platform="qq",
# 聊天上下文信息
chat_info_user_id=chat_stream.user_info.user_id if chat_stream.user_info else bot_user_id,
chat_info_user_nickname=chat_stream.user_info.user_nickname if chat_stream.user_info else bot_nickname,
@@ -397,23 +395,22 @@ class ChatterPlanExecutor:
chat_info_platform=chat_stream.platform,
chat_info_create_time=chat_stream.create_time,
chat_info_last_active_time=chat_stream.last_active_time,
# 群组信息(如果是群聊)
chat_info_group_id=chat_stream.group_info.group_id if chat_stream.group_info else None,
chat_info_group_name=chat_stream.group_info.group_name if chat_stream.group_info else None,
chat_info_group_platform=getattr(chat_stream.group_info, "platform", None) if chat_stream.group_info else None,
chat_info_group_platform=getattr(chat_stream.group_info, "platform", None)
if chat_stream.group_info
else None,
# 动作信息
actions=["bot_reply"],
should_reply=False,
should_act=False
should_act=False,
)
# 添加到chat_stream的已读消息中
chat_stream.context_manager.context.history_messages.append(bot_message)
logger.debug(f"机器人回复已添加到已读消息: {reply_content[:50]}...")
except Exception as e:
logger.error(f"添加机器人回复到已读消息时出错: {e}")
logger.debug(f"plan.chat_id: {plan.chat_id}")

View File

@@ -60,7 +60,7 @@ class ChatterPlanFilter:
prompt, used_message_id_list = await self._build_prompt(plan)
plan.llm_prompt = prompt
if global_config.debug.show_prompt:
logger.info(f"规划器原始提示词:{prompt}") #叫你不要改你耳朵聋吗😡😡😡😡😡
logger.info(f"规划器原始提示词:{prompt}") # 叫你不要改你耳朵聋吗😡😡😡😡😡
llm_content, _ = await self.planner_llm.generate_response_async(prompt=prompt)
@@ -104,24 +104,26 @@ class ChatterPlanFilter:
# 预解析 action_type 来进行判断
thinking = item.get("thinking", "未提供思考过程")
actions_obj = item.get("actions", {})
# 记录决策历史
if hasattr(global_config.chat, "enable_decision_history") and global_config.chat.enable_decision_history:
if (
hasattr(global_config.chat, "enable_decision_history")
and global_config.chat.enable_decision_history
):
action_types_to_log = []
actions_to_process_for_log = []
if isinstance(actions_obj, dict):
actions_to_process_for_log.append(actions_obj)
elif isinstance(actions_obj, list):
actions_to_process_for_log.extend(actions_obj)
for single_action in actions_to_process_for_log:
if isinstance(single_action, dict):
action_types_to_log.append(single_action.get("action_type", "no_action"))
if thinking != "未提供思考过程" and action_types_to_log:
await self._add_decision_to_history(plan, thinking, ", ".join(action_types_to_log))
# 处理actions字段可能是字典或列表的情况
if isinstance(actions_obj, dict):
action_type = actions_obj.get("action_type", "no_action")
@@ -579,15 +581,15 @@ class ChatterPlanFilter:
):
reasoning = f"LLM 返回了当前不可用的动作 '{action}'。原始理由: {reasoning}"
action = "no_action"
#TODO:把逻辑迁移到DatabaseMessages(如果没人做下个星期我自己来)
#from src.common.data_models.database_data_model import DatabaseMessages
# TODO:把逻辑迁移到DatabaseMessages(如果没人做下个星期我自己来)
# from src.common.data_models.database_data_model import DatabaseMessages
#action_message_obj = None
#if target_message_obj:
#try:
#action_message_obj = DatabaseMessages(**target_message_obj)
#except Exception:
#logger.warning("无法将目标消息转换为DatabaseMessages对象")
# action_message_obj = None
# if target_message_obj:
# try:
# action_message_obj = DatabaseMessages(**target_message_obj)
# except Exception:
# logger.warning("无法将目标消息转换为DatabaseMessages对象")
parsed_actions.append(
ActionPlannerInfo(

View File

@@ -17,7 +17,6 @@ from src.plugins.built_in.affinity_flow_chatter.plan_generator import ChatterPla
if TYPE_CHECKING:
from src.chat.planner_actions.action_manager import ChatterActionManager
from src.common.data_models.database_data_model import DatabaseMessages
from src.common.data_models.info_data_model import Plan
from src.common.data_models.message_manager_data_model import StreamContext
@@ -100,11 +99,11 @@ class ChatterActionPlanner:
if context:
context.chat_mode = ChatMode.FOCUS
await self._sync_chat_mode_to_stream(context)
# Normal模式下使用简化流程
if chat_mode == ChatMode.NORMAL:
return await self._normal_mode_flow(context)
# 在规划前,先进行动作修改
from src.chat.planner_actions.action_modifier import ActionModifier
action_modifier = ActionModifier(self.action_manager, self.chat_id)
@@ -184,12 +183,12 @@ class ChatterActionPlanner:
for action in filtered_plan.decided_actions:
if action.action_type in ["reply", "proactive_reply"] and action.action_message:
# 提取目标消息ID
if hasattr(action.action_message, 'message_id'):
if hasattr(action.action_message, "message_id"):
target_message_id = action.action_message.message_id
elif isinstance(action.action_message, dict):
target_message_id = action.action_message.get('message_id')
target_message_id = action.action_message.get("message_id")
break
# 如果找到目标消息ID检查是否已经在处理中
if target_message_id and context:
if context.processing_message_id == target_message_id:
@@ -215,7 +214,7 @@ class ChatterActionPlanner:
# 6. 根据执行结果更新统计信息
self._update_stats_from_execution_result(execution_result)
# 7. Focus模式下如果执行了reply动作切换到Normal模式
if chat_mode == ChatMode.FOCUS and context:
if filtered_plan.decided_actions:
@@ -233,7 +232,7 @@ class ChatterActionPlanner:
# 8. 清理处理标记
if context:
context.processing_message_id = None
logger.debug(f"已清理处理标记,完成规划流程")
logger.debug("已清理处理标记,完成规划流程")
# 9. 返回结果
return self._build_return_result(filtered_plan)
@@ -262,7 +261,7 @@ class ChatterActionPlanner:
return await self._enhanced_plan_flow(context)
try:
unread_messages = context.get_unread_messages() if context else []
if not unread_messages:
logger.debug("Normal模式: 没有未读消息")
from src.common.data_models.info_data_model import ActionPlannerInfo
@@ -273,11 +272,11 @@ class ChatterActionPlanner:
action_message=None,
)
return [asdict(no_action)], None
# 检查是否有消息达到reply阈值
should_reply = False
target_message = None
for message in unread_messages:
message_should_reply = getattr(message, "should_reply", False)
if message_should_reply:
@@ -285,7 +284,7 @@ class ChatterActionPlanner:
target_message = message
logger.info(f"Normal模式: 消息 {message.message_id} 达到reply阈值")
break
if should_reply and target_message:
# 检查是否正在处理相同的目标消息,防止重复回复
target_message_id = target_message.message_id
@@ -302,26 +301,26 @@ class ChatterActionPlanner:
action_message=None,
)
return [asdict(no_action)], None
# 记录当前正在处理的消息ID
if context:
context.processing_message_id = target_message_id
logger.debug(f"Normal模式: 开始处理目标消息: {target_message_id}")
# 达到reply阈值直接进入回复流程
from src.common.data_models.info_data_model import ActionPlannerInfo, Plan
from src.plugin_system.base.component_types import ChatType
# 构建目标消息字典 - 使用 flatten() 方法获取扁平化的字典
target_message_dict = target_message.flatten()
reply_action = ActionPlannerInfo(
action_type="reply",
reasoning="Normal模式: 兴趣度达到阈值,直接回复",
action_data={"target_message_id": target_message.message_id},
action_message=target_message,
)
# Normal模式下直接构建最小化的Plan跳过generator和action_modifier
# 这样可以显著降低延迟
minimal_plan = Plan(
@@ -330,25 +329,25 @@ class ChatterActionPlanner:
mode=ChatMode.NORMAL,
decided_actions=[reply_action],
)
# 执行reply动作
execution_result = await self.executor.execute(minimal_plan)
self._update_stats_from_execution_result(execution_result)
logger.info("Normal模式: 执行reply动作完成")
# 清理处理标记
if context:
context.processing_message_id = None
logger.debug(f"Normal模式: 已清理处理标记")
logger.debug("Normal模式: 已清理处理标记")
# 无论是否回复都进行退出normal模式的判定
await self._check_exit_normal_mode(context)
return [asdict(reply_action)], target_message_dict
else:
# 未达到reply阈值
logger.debug(f"Normal模式: 未达到reply阈值")
logger.debug("Normal模式: 未达到reply阈值")
from src.common.data_models.info_data_model import ActionPlannerInfo
no_action = ActionPlannerInfo(
action_type="no_action",
@@ -356,12 +355,12 @@ class ChatterActionPlanner:
action_data={},
action_message=None,
)
# 无论是否回复都进行退出normal模式的判定
await self._check_exit_normal_mode(context)
return [asdict(no_action)], None
except Exception as e:
logger.error(f"Normal模式流程出错: {e}")
self.planner_stats["failed_plans"] += 1
@@ -378,16 +377,16 @@ class ChatterActionPlanner:
"""
if not context:
return
try:
from src.chat.message_receive.chat_stream import get_chat_manager
chat_manager = get_chat_manager()
chat_stream = await chat_manager.get_stream(self.chat_id) if chat_manager else None
if not chat_stream:
return
focus_energy = chat_stream.focus_energy
# focus_energy越低退出normal模式的概率越高
# 使用反比例函数: 退出概率 = 1 - focus_energy
@@ -395,7 +394,7 @@ class ChatterActionPlanner:
# 当focus_energy = 0.5时,退出概率 = 50%
# 当focus_energy = 0.9时,退出概率 = 10%
exit_probability = 1.0 - focus_energy
import random
if random.random() < exit_probability:
logger.info(f"Normal模式: focus_energy={focus_energy:.3f}, 退出概率={exit_probability:.3f}, 切换回focus模式")
@@ -404,7 +403,7 @@ class ChatterActionPlanner:
await self._sync_chat_mode_to_stream(context)
else:
logger.debug(f"Normal模式: focus_energy={focus_energy:.3f}, 退出概率={exit_probability:.3f}, 保持normal模式")
except Exception as e:
logger.warning(f"检查退出Normal模式失败: {e}")
@@ -412,7 +411,7 @@ class ChatterActionPlanner:
"""同步chat_mode到ChatStream"""
try:
from src.chat.message_receive.chat_stream import get_chat_manager
chat_manager = get_chat_manager()
if chat_manager:
chat_stream = await chat_manager.get_stream(context.stream_id)

View File

@@ -15,57 +15,57 @@ logger = get_logger("proactive_thinking_event")
class ProactiveThinkingReplyHandler(BaseEventHandler):
"""Reply事件处理器
当bot回复某个聊天流后
1. 如果该聊天流的主动思考被暂停(因为抛出了话题),则恢复它
2. 无论是否暂停,都重置定时任务,重新开始计时
"""
handler_name: str = "proactive_thinking_reply_handler"
handler_description: str = "监听reply事件重置主动思考定时任务"
init_subscribe: list[EventType | str] = [EventType.AFTER_SEND]
async def execute(self, kwargs: dict | None) -> HandlerResult:
"""处理reply事件
Args:
kwargs: 事件参数,应包含 stream_id
Returns:
HandlerResult: 处理结果
"""
logger.debug("[主动思考事件] ProactiveThinkingReplyHandler 开始执行")
logger.debug(f"[主动思考事件] 接收到的参数: {kwargs}")
if not kwargs:
logger.debug("[主动思考事件] kwargs 为空,跳过处理")
return HandlerResult(success=True, continue_process=True, message=None)
stream_id = kwargs.get("stream_id")
if not stream_id:
logger.debug(f"[主动思考事件] Reply事件缺少stream_id参数")
logger.debug("[主动思考事件] Reply事件缺少stream_id参数")
return HandlerResult(success=True, continue_process=True, message=None)
logger.debug(f"[主动思考事件] 收到 AFTER_SEND 事件stream_id={stream_id}")
try:
from src.config.config import global_config
# 检查是否启用reply重置
if not global_config.proactive_thinking.reply_reset_enabled:
logger.debug(f"[主动思考事件] reply_reset_enabled 为 False跳过重置")
logger.debug("[主动思考事件] reply_reset_enabled 为 False跳过重置")
return HandlerResult(success=True, continue_process=True, message=None)
# 检查是否被暂停
was_paused = await proactive_thinking_scheduler.is_paused(stream_id)
logger.debug(f"[主动思考事件] 聊天流 {stream_id} 暂停状态: {was_paused}")
if was_paused:
logger.debug(f"[主动思考事件] 检测到reply事件聊天流 {stream_id} 之前因抛出话题而暂停,现在恢复")
# 重置定时任务(这会自动清除暂停标记并创建新任务)
success = await proactive_thinking_scheduler.schedule_proactive_thinking(stream_id)
if success:
if was_paused:
logger.info(f"✅ 聊天流 {stream_id} 主动思考已恢复并重置")
@@ -73,82 +73,82 @@ class ProactiveThinkingReplyHandler(BaseEventHandler):
logger.debug(f"✅ 聊天流 {stream_id} 主动思考任务已重置")
else:
logger.warning(f"❌ 重置聊天流 {stream_id} 主动思考任务失败")
except Exception as e:
logger.error(f"❌ 处理reply事件时出错: {e}", exc_info=True)
# 总是继续处理其他handler
return HandlerResult(success=True, continue_process=True, message=None)
class ProactiveThinkingMessageHandler(BaseEventHandler):
"""消息事件处理器
当收到消息时,如果该聊天流还没有主动思考任务,则创建一个
这样可以确保新的聊天流也能获得主动思考功能
"""
handler_name: str = "proactive_thinking_message_handler"
handler_description: str = "监听消息事件,为新聊天流创建主动思考任务"
init_subscribe: list[EventType | str] = [EventType.ON_MESSAGE]
async def execute(self, kwargs: dict | None) -> HandlerResult:
"""处理消息事件
Args:
kwargs: 事件参数,格式为 {"message": DatabaseMessages}
Returns:
HandlerResult: 处理结果
"""
if not kwargs:
return HandlerResult(success=True, continue_process=True, message=None)
# 从 kwargs 中获取 DatabaseMessages 对象
message = kwargs.get("message")
if not message or not hasattr(message, "chat_stream"):
return HandlerResult(success=True, continue_process=True, message=None)
# 从 chat_stream 获取 stream_id
chat_stream = message.chat_stream
if not chat_stream or not hasattr(chat_stream, "stream_id"):
return HandlerResult(success=True, continue_process=True, message=None)
stream_id = chat_stream.stream_id
try:
from src.config.config import global_config
# 检查是否启用主动思考
if not global_config.proactive_thinking.enable:
return HandlerResult(success=True, continue_process=True, message=None)
# 检查该聊天流是否已经有任务
task_info = await proactive_thinking_scheduler.get_task_info(stream_id)
if task_info:
# 已经有任务,不需要创建
return HandlerResult(success=True, continue_process=True, message=None)
# 从 message_info 获取平台和聊天ID信息
message_info = message.message_info
platform = message_info.platform
is_group = message_info.group_info is not None
chat_id = message_info.group_info.group_id if is_group else message_info.user_info.user_id # type: ignore
# 构造配置字符串
stream_config = f"{platform}:{chat_id}:{'group' if is_group else 'private'}"
# 检查黑白名单
if not proactive_thinking_scheduler._check_whitelist_blacklist(stream_config):
return HandlerResult(success=True, continue_process=True, message=None)
# 创建主动思考任务
success = await proactive_thinking_scheduler.schedule_proactive_thinking(stream_id)
if success:
logger.info(f"为新聊天流 {stream_id} 创建了主动思考任务")
except Exception as e:
logger.error(f"处理消息事件时出错: {e}", exc_info=True)
# 总是继续处理其他handler
return HandlerResult(success=True, continue_process=True, message=None)

View File

@@ -5,11 +5,10 @@
import json
from datetime import datetime
from typing import Any, Literal, Optional
from typing import Any, Literal
from sqlalchemy import select
from src.chat.express.expression_learner import expression_learner_manager
from src.chat.express.expression_selector import expression_selector
from src.common.database.sqlalchemy_database_api import get_db_session
from src.common.database.sqlalchemy_models import ChatStreams
@@ -17,42 +16,40 @@ from src.common.logger import get_logger
from src.config.config import global_config, model_config
from src.individuality.individuality import Individuality
from src.llm_models.utils_model import LLMRequest
from src.plugin_system.apis import chat_api, message_api, send_api
from src.plugin_system.apis import message_api, send_api
logger = get_logger("proactive_thinking_executor")
class ProactiveThinkingPlanner:
"""主动思考规划器
负责:
1. 搜集信息(聊天流印象、话题关键词、历史聊天记录)
2. 调用LLM决策什么都不做/简单冒泡/抛出话题
3. 根据决策生成回复内容
"""
def __init__(self):
"""初始化规划器"""
try:
self.decision_llm = LLMRequest(
model_set=model_config.model_task_config.utils,
request_type="proactive_thinking_decision"
model_set=model_config.model_task_config.utils, request_type="proactive_thinking_decision"
)
self.reply_llm = LLMRequest(
model_set=model_config.model_task_config.replyer,
request_type="proactive_thinking_reply"
model_set=model_config.model_task_config.replyer, request_type="proactive_thinking_reply"
)
except Exception as e:
logger.error(f"初始化LLM失败: {e}")
self.decision_llm = None
self.reply_llm = None
async def gather_context(self, stream_id: str) -> Optional[dict[str, Any]]:
async def gather_context(self, stream_id: str) -> dict[str, Any] | None:
"""搜集聊天流的上下文信息
Args:
stream_id: 聊天流ID
Returns:
dict: 包含所有上下文信息的字典失败返回None
"""
@@ -62,27 +59,25 @@ class ProactiveThinkingPlanner:
if not stream_data:
logger.warning(f"无法获取聊天流 {stream_id} 的印象数据")
return None
# 2. 获取最近的聊天记录
recent_messages = await message_api.get_recent_messages(
chat_id=stream_id,
limit=20,
limit_mode="latest",
hours=24
chat_id=stream_id, limit=20, limit_mode="latest", hours=24
)
recent_chat_history = ""
if recent_messages:
recent_chat_history = await message_api.build_readable_messages_to_str(recent_messages)
# 3. 获取bot人设
individuality = Individuality()
bot_personality = await individuality.get_personality_block()
# 4. 获取当前心情
current_mood = "感觉很平静" # 默认心情
try:
from src.mood.mood_manager import mood_manager
mood_obj = mood_manager.get_mood_by_chat_id(stream_id)
if mood_obj:
await mood_obj._initialize() # 确保已初始化
@@ -90,19 +85,20 @@ class ProactiveThinkingPlanner:
logger.debug(f"获取到聊天流 {stream_id} 的心情: {current_mood}")
except Exception as e:
logger.warning(f"获取心情失败,使用默认值: {e}")
# 5. 获取上次决策
last_decision = None
try:
from src.plugins.built_in.affinity_flow_chatter.proactive_thinking_scheduler import (
proactive_thinking_scheduler,
)
last_decision = proactive_thinking_scheduler.get_last_decision(stream_id)
if last_decision:
logger.debug(f"获取到聊天流 {stream_id} 的上次决策: {last_decision.get('action')}")
except Exception as e:
logger.warning(f"获取上次决策失败: {e}")
# 6. 构建上下文
context = {
"stream_id": stream_id,
@@ -117,45 +113,45 @@ class ProactiveThinkingPlanner:
"current_mood": current_mood,
"last_decision": last_decision,
}
logger.debug(f"成功搜集聊天流 {stream_id} 的上下文信息")
return context
except Exception as e:
logger.error(f"搜集上下文信息失败: {e}", exc_info=True)
return None
async def _get_stream_impression(self, stream_id: str) -> Optional[dict[str, Any]]:
async def _get_stream_impression(self, stream_id: str) -> dict[str, Any] | None:
"""从数据库获取聊天流印象数据"""
try:
async with get_db_session() as session:
stmt = select(ChatStreams).where(ChatStreams.stream_id == stream_id)
result = await session.execute(stmt)
stream = result.scalar_one_or_none()
if not stream:
return None
return {
"stream_name": stream.group_name or "私聊",
"stream_impression_text": stream.stream_impression_text or "",
"stream_chat_style": stream.stream_chat_style or "",
"stream_topic_keywords": stream.stream_topic_keywords or "",
"stream_interest_score": float(stream.stream_interest_score) if stream.stream_interest_score else 0.5,
"stream_interest_score": float(stream.stream_interest_score)
if stream.stream_interest_score
else 0.5,
}
except Exception as e:
logger.error(f"获取聊天流印象失败: {e}")
return None
async def make_decision(
self, context: dict[str, Any]
) -> Optional[dict[str, Any]]:
async def make_decision(self, context: dict[str, Any]) -> dict[str, Any] | None:
"""使用LLM进行决策
Args:
context: 上下文信息
Returns:
dict: 决策结果,包含:
- action: "do_nothing" | "simple_bubble" | "throw_topic"
@@ -165,30 +161,28 @@ class ProactiveThinkingPlanner:
if not self.decision_llm:
logger.error("决策LLM未初始化")
return None
response = None
try:
decision_prompt = self._build_decision_prompt(context)
if global_config.debug.show_prompt:
logger.info(f"决策提示词:\n{decision_prompt}")
response, _ = await self.decision_llm.generate_response_async(prompt=decision_prompt)
if not response:
logger.warning("LLM未返回有效响应")
return None
# 清理并解析JSON响应
cleaned_response = self._clean_json_response(response)
decision = json.loads(cleaned_response)
logger.info(
f"决策结果: {decision.get('action', 'unknown')} - {decision.get('reasoning', '无理由')}"
)
logger.info(f"决策结果: {decision.get('action', 'unknown')} - {decision.get('reasoning', '无理由')}")
return decision
except json.JSONDecodeError as e:
logger.error(f"解析决策JSON失败: {e}")
if response:
@@ -197,18 +191,18 @@ class ProactiveThinkingPlanner:
except Exception as e:
logger.error(f"决策过程失败: {e}", exc_info=True)
return None
def _build_decision_prompt(self, context: dict[str, Any]) -> str:
"""构建决策提示词"""
# 构建上次决策信息
last_decision_text = ""
if context.get('last_decision'):
last_dec = context['last_decision']
last_action = last_dec.get('action', '未知')
last_reasoning = last_dec.get('reasoning', '')
last_topic = last_dec.get('topic')
last_time = last_dec.get('timestamp', '未知')
if context.get("last_decision"):
last_dec = context["last_decision"]
last_action = last_dec.get("action", "未知")
last_reasoning = last_dec.get("reasoning", "")
last_topic = last_dec.get("topic")
last_time = last_dec.get("timestamp", "未知")
last_decision_text = f"""
【上次主动思考的决策】
- 时间: {last_time}
@@ -216,24 +210,24 @@ class ProactiveThinkingPlanner:
- 理由: {last_reasoning}"""
if last_topic:
last_decision_text += f"\n- 话题: {last_topic}"
return f"""你是一个有着独特个性的AI助手。你的人设是
{context['bot_personality']}
现在是 {context['current_time']},你正在考虑是否要主动在 "{context['stream_name']}" 中说些什么。
return f"""你是一个有着独特个性的AI助手。你的人设是
{context["bot_personality"]}
现在是 {context["current_time"]},你正在考虑是否要主动在 "{context["stream_name"]}" 中说些什么。
【你当前的心情】
{context.get('current_mood', '感觉很平静')}
{context.get("current_mood", "感觉很平静")}
【聊天环境信息】
- 整体印象: {context['stream_impression']}
- 聊天风格: {context['chat_style']}
- 常见话题: {context['topic_keywords'] or '暂无'}
- 你的兴趣程度: {context['interest_score']:.2f}/1.0
- 整体印象: {context["stream_impression"]}
- 聊天风格: {context["chat_style"]}
- 常见话题: {context["topic_keywords"] or "暂无"}
- 你的兴趣程度: {context["interest_score"]:.2f}/1.0
{last_decision_text}
【最近的聊天记录】
{context['recent_chat_history']}
{context["recent_chat_history"]}
请根据以上信息(包括你的心情和上次决策),决定你现在应该做什么:
@@ -267,53 +261,50 @@ class ProactiveThinkingPlanner:
3. 只有在真的有话题想聊时才选择 throw_topic
4. 符合你的人设,不要太过热情或冷淡
"""
async def generate_reply(
self,
context: dict[str, Any],
action: Literal["simple_bubble", "throw_topic"],
topic: Optional[str] = None
) -> Optional[str]:
self, context: dict[str, Any], action: Literal["simple_bubble", "throw_topic"], topic: str | None = None
) -> str | None:
"""生成回复内容
Args:
context: 上下文信息
action: 动作类型
topic: (可选) 话题内容当action=throw_topic时必须提供
Returns:
str: 生成的回复文本失败返回None
"""
if not self.reply_llm:
logger.error("回复LLM未初始化")
return None
try:
reply_prompt = await self._build_reply_prompt(context, action, topic)
if global_config.debug.show_prompt:
logger.info(f"回复提示词:\n{reply_prompt}")
response, _ = await self.reply_llm.generate_response_async(prompt=reply_prompt)
if not response:
logger.warning("LLM未返回有效回复")
return None
logger.info(f"生成回复成功: {response[:50]}...")
return response.strip()
except Exception as e:
logger.error(f"生成回复失败: {e}", exc_info=True)
return None
async def _get_expression_habits(self, stream_id: str, chat_history: str) -> str:
"""获取表达方式参考
Args:
stream_id: 聊天流ID
chat_history: 聊天历史
Returns:
str: 格式化的表达方式参考文本
"""
@@ -324,15 +315,15 @@ class ProactiveThinkingPlanner:
chat_history=chat_history,
target_message=None, # 主动思考没有target message
max_num=6, # 主动思考时使用较少的表达方式
min_num=2
min_num=2,
)
if not selected_expressions:
return ""
style_habits = []
grammar_habits = []
for expr in selected_expressions:
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
expr_type = expr.get("type", "style")
@@ -340,7 +331,7 @@ class ProactiveThinkingPlanner:
grammar_habits.append(f"{expr['situation']}时,使用 {expr['style']}")
else:
style_habits.append(f"{expr['situation']}时,使用 {expr['style']}")
expression_block = ""
if style_habits or grammar_habits:
expression_block = "\n【表达方式参考】\n"
@@ -349,41 +340,37 @@ class ProactiveThinkingPlanner:
if grammar_habits:
expression_block += "句法特点:\n" + "\n".join(grammar_habits) + "\n"
expression_block += "注意:仅在情景合适时自然地使用这些表达,不要生硬套用。\n"
return expression_block
except Exception as e:
logger.warning(f"获取表达方式失败: {e}")
return ""
async def _build_reply_prompt(
self,
context: dict[str, Any],
action: Literal["simple_bubble", "throw_topic"],
topic: Optional[str]
self, context: dict[str, Any], action: Literal["simple_bubble", "throw_topic"], topic: str | None
) -> str:
"""构建回复提示词"""
# 获取表达方式参考
expression_habits = await self._get_expression_habits(
stream_id=context.get('stream_id', ''),
chat_history=context.get('recent_chat_history', '')
stream_id=context.get("stream_id", ""), chat_history=context.get("recent_chat_history", "")
)
if action == "simple_bubble":
return f"""你是一个有着独特个性的AI助手。你的人设是
{context['bot_personality']}
{context["bot_personality"]}
现在是 {context['current_time']},你决定在 "{context['stream_name']}" 中简单冒个泡。
现在是 {context["current_time"]},你决定在 "{context["stream_name"]}" 中简单冒个泡。
【你当前的心情】
{context.get('current_mood', '感觉很平静')}
{context.get("current_mood", "感觉很平静")}
【聊天环境】
- 整体印象: {context['stream_impression']}
- 聊天风格: {context['chat_style']}
- 整体印象: {context["stream_impression"]}
- 聊天风格: {context["chat_style"]}
【最近的聊天记录】
{context['recent_chat_history']}
{context["recent_chat_history"]}
{expression_habits}
请生成一条简短的消息,用于水群。要求:
1. 非常简短5-15字
@@ -394,23 +381,23 @@ class ProactiveThinkingPlanner:
6. 如果有表达方式参考,在合适时自然使用
7. 合理参考历史记录
直接输出消息内容,不要解释:"""
else: # throw_topic
return f"""你是一个有着独特个性的AI助手。你的人设是
{context['bot_personality']}
{context["bot_personality"]}
现在是 {context['current_time']},你决定在 "{context['stream_name']}" 中抛出一个话题。
现在是 {context["current_time"]},你决定在 "{context["stream_name"]}" 中抛出一个话题。
【你当前的心情】
{context.get('current_mood', '感觉很平静')}
{context.get("current_mood", "感觉很平静")}
【聊天环境】
- 整体印象: {context['stream_impression']}
- 聊天风格: {context['chat_style']}
- 常见话题: {context['topic_keywords'] or '暂无'}
- 整体印象: {context["stream_impression"]}
- 聊天风格: {context["chat_style"]}
- 常见话题: {context["topic_keywords"] or "暂无"}
【最近的聊天记录】
{context['recent_chat_history']}
{context["recent_chat_history"]}
【你想抛出的话题】
{topic}
@@ -425,21 +412,21 @@ class ProactiveThinkingPlanner:
7. 如果有表达方式参考,在合适时自然使用
直接输出消息内容,不要解释:"""
def _clean_json_response(self, response: str) -> str:
"""清理LLM响应中的JSON格式标记"""
import re
cleaned = response.strip()
cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned, flags=re.MULTILINE | re.IGNORECASE)
cleaned = re.sub(r"\s*```$", "", cleaned, flags=re.MULTILINE)
json_start = cleaned.find("{")
json_end = cleaned.rfind("}")
if json_start != -1 and json_end != -1 and json_end > json_start:
cleaned = cleaned[json_start:json_end + 1]
cleaned = cleaned[json_start : json_end + 1]
return cleaned.strip()
@@ -452,7 +439,7 @@ _statistics: dict[str, dict[str, Any]] = {}
def _update_statistics(stream_id: str, action: str):
"""更新统计数据
Args:
stream_id: 聊天流ID
action: 执行的动作
@@ -465,18 +452,18 @@ def _update_statistics(stream_id: str, action: str):
"throw_topic_count": 0,
"last_execution_time": None,
}
_statistics[stream_id]["total_executions"] += 1
_statistics[stream_id][f"{action}_count"] += 1
_statistics[stream_id]["last_execution_time"] = datetime.now().isoformat()
def get_statistics(stream_id: Optional[str] = None) -> dict[str, Any]:
def get_statistics(stream_id: str | None = None) -> dict[str, Any]:
"""获取统计数据
Args:
stream_id: 聊天流IDNone表示获取所有统计
Returns:
统计数据字典
"""
@@ -487,7 +474,7 @@ def get_statistics(stream_id: Optional[str] = None) -> dict[str, Any]:
async def execute_proactive_thinking(stream_id: str):
"""执行主动思考(被调度器调用的回调函数)
Args:
stream_id: 聊天流ID
"""
@@ -495,125 +482,125 @@ async def execute_proactive_thinking(stream_id: str):
from src.plugins.built_in.affinity_flow_chatter.proactive_thinking_scheduler import (
proactive_thinking_scheduler,
)
config = global_config.proactive_thinking
logger.debug(f"🤔 开始主动思考 {stream_id}")
try:
# 0. 前置检查
if proactive_thinking_scheduler._is_in_quiet_hours():
logger.debug(f"安静时段,跳过")
logger.debug("安静时段,跳过")
return
if not proactive_thinking_scheduler._check_daily_limit(stream_id):
logger.debug(f"今日发言达上限")
logger.debug("今日发言达上限")
return
# 1. 搜集信息
logger.debug(f"步骤1: 搜集上下文")
logger.debug("步骤1: 搜集上下文")
context = await _planner.gather_context(stream_id)
if not context:
logger.warning(f"无法搜集上下文,跳过")
logger.warning("无法搜集上下文,跳过")
return
# 检查兴趣分数阈值
interest_score = context.get('interest_score', 0.5)
interest_score = context.get("interest_score", 0.5)
if not proactive_thinking_scheduler._check_interest_score_threshold(interest_score):
logger.debug(f"兴趣分数不在阈值范围内")
logger.debug("兴趣分数不在阈值范围内")
return
# 2. 进行决策
logger.debug(f"步骤2: LLM决策")
logger.debug("步骤2: LLM决策")
decision = await _planner.make_decision(context)
if not decision:
logger.warning(f"决策失败,跳过")
logger.warning("决策失败,跳过")
return
action = decision.get("action", "do_nothing")
reasoning = decision.get("reasoning", "")
# 记录决策日志
if config.log_decisions:
logger.debug(f"决策: action={action}, reasoning={reasoning}")
# 3. 根据决策执行相应动作
if action == "do_nothing":
logger.debug(f"决策:什么都不做。理由:{reasoning}")
proactive_thinking_scheduler.record_decision(stream_id, action, reasoning, None)
return
elif action == "simple_bubble":
logger.info(f"💬 决策:冒个泡。理由:{reasoning}")
proactive_thinking_scheduler.record_decision(stream_id, action, reasoning, None)
# 生成简单的消息
logger.debug(f"步骤3: 生成冒泡回复")
logger.debug("步骤3: 生成冒泡回复")
reply = await _planner.generate_reply(context, "simple_bubble")
if reply:
await send_api.text_to_stream(
stream_id=stream_id,
text=reply,
)
logger.info(f"✅ 已发送冒泡消息")
logger.info("✅ 已发送冒泡消息")
# 增加每日计数
proactive_thinking_scheduler._increment_daily_count(stream_id)
# 更新统计
if config.enable_statistics:
_update_statistics(stream_id, action)
# 冒泡后暂停主动思考,等待用户回复
# 使用与 topic_throw 相同的冷却时间配置
if config.topic_throw_cooldown > 0:
logger.info(f"[主动思考] 步骤5暂停任务")
logger.info("[主动思考] 步骤5暂停任务")
await proactive_thinking_scheduler.pause_proactive_thinking(stream_id, reason="已冒泡")
logger.info(f"[主动思考] 已暂停聊天流 {stream_id} 的主动思考,等待用户回复")
logger.info(f"[主动思考] simple_bubble 执行完成")
logger.info("[主动思考] simple_bubble 执行完成")
elif action == "throw_topic":
topic = decision.get("topic", "")
logger.info(f"[主动思考] 决策:抛出话题。理由:{reasoning},话题:{topic}")
# 记录决策
proactive_thinking_scheduler.record_decision(stream_id, action, reasoning, topic)
if not topic:
logger.warning("[主动思考] 选择了抛出话题但未提供话题内容,降级为冒泡")
logger.info(f"[主动思考] 步骤3生成降级冒泡回复")
logger.info("[主动思考] 步骤3生成降级冒泡回复")
reply = await _planner.generate_reply(context, "simple_bubble")
else:
# 生成基于话题的消息
logger.info(f"[主动思考] 步骤3生成话题回复")
logger.info("[主动思考] 步骤3生成话题回复")
reply = await _planner.generate_reply(context, "throw_topic", topic)
if reply:
logger.info(f"[主动思考] 步骤4发送消息")
logger.info("[主动思考] 步骤4发送消息")
await send_api.text_to_stream(
stream_id=stream_id,
text=reply,
)
logger.info(f"[主动思考] 已发送话题消息到 {stream_id}")
# 增加每日计数
proactive_thinking_scheduler._increment_daily_count(stream_id)
# 更新统计
if config.enable_statistics:
_update_statistics(stream_id, action)
# 抛出话题后暂停主动思考(如果配置了冷却时间)
if config.topic_throw_cooldown > 0:
logger.info(f"[主动思考] 步骤5暂停任务")
logger.info("[主动思考] 步骤5暂停任务")
await proactive_thinking_scheduler.pause_proactive_thinking(stream_id, reason="已抛出话题")
logger.info(f"[主动思考] 已暂停聊天流 {stream_id} 的主动思考,等待用户回复")
logger.info(f"[主动思考] throw_topic 执行完成")
logger.info("[主动思考] throw_topic 执行完成")
logger.info(f"[主动思考] 聊天流 {stream_id} 的主动思考执行完成")
except Exception as e:
logger.error(f"[主动思考] 执行主动思考失败: {e}", exc_info=True)

View File

@@ -6,20 +6,17 @@
import asyncio
from datetime import datetime, timedelta
from typing import Any, Optional
from typing import Any
from src.common.database.sqlalchemy_database_api import get_db_session
from src.common.database.sqlalchemy_models import ChatStreams
from src.common.logger import get_logger
from src.schedule.unified_scheduler import TriggerType, unified_scheduler
from sqlalchemy import select
logger = get_logger("proactive_thinking_scheduler")
class ProactiveThinkingScheduler:
"""主动思考调度器
负责为每个聊天流创建和管理主动思考任务。
特点:
1. 根据聊天流的兴趣分数动态计算触发间隔
@@ -32,27 +29,28 @@ class ProactiveThinkingScheduler:
self._stream_schedules: dict[str, str] = {} # stream_id -> schedule_id
self._paused_streams: set[str] = set() # 因抛出话题而暂停的聊天流
self._lock = asyncio.Lock()
# 统计数据
self._statistics: dict[str, dict[str, Any]] = {} # stream_id -> 统计信息
self._daily_counts: dict[str, dict[str, int]] = {} # stream_id -> {date: count}
# 历史决策记录stream_id -> 上次决策信息
self._last_decisions: dict[str, dict[str, Any]] = {}
# 从全局配置加载(延迟导入避免循环依赖)
from src.config.config import global_config
self.config = global_config.proactive_thinking
def _calculate_interval(self, focus_energy: float) -> int:
"""根据 focus_energy 计算触发间隔
Args:
focus_energy: 聊天流的 focus_energy 值 (0.0-1.0)
Returns:
int: 触发间隔(秒)
公式:
- focus_energy 越高,间隔越短(更频繁思考)
- interval = base_interval * (factor - focus_energy)
@@ -63,26 +61,26 @@ class ProactiveThinkingScheduler:
# 如果不使用 focus_energy直接返回基础间隔
if not self.config.use_interest_score:
return self.config.base_interval
# 确保值在有效范围内
focus_energy = max(0.0, min(1.0, focus_energy))
# 计算间隔focus_energy 越高,系数越小,间隔越短
factor = self.config.interest_score_factor - focus_energy
interval = int(self.config.base_interval * factor)
# 限制在最小和最大间隔之间
interval = max(self.config.min_interval, min(self.config.max_interval, interval))
logger.debug(f"Focus Energy {focus_energy:.3f} -> 触发间隔 {interval}秒 ({interval/60:.1f}分钟)")
logger.debug(f"Focus Energy {focus_energy:.3f} -> 触发间隔 {interval}秒 ({interval / 60:.1f}分钟)")
return interval
def _check_whitelist_blacklist(self, stream_config: str) -> bool:
"""检查聊天流是否通过黑白名单验证
Args:
stream_config: 聊天流配置字符串,格式: "platform:id:type"
Returns:
bool: True表示允许主动思考False表示拒绝
"""
@@ -91,148 +89,148 @@ class ProactiveThinkingScheduler:
if len(parts) != 3:
logger.warning(f"无效的stream_config格式: {stream_config}")
return False
is_private = parts[2] == "private"
# 检查基础开关
if is_private and not self.config.enable_in_private:
return False
if not is_private and not self.config.enable_in_group:
return False
# 黑名单检查(优先级高)
if self.config.blacklist_mode:
blacklist = self.config.blacklist_private if is_private else self.config.blacklist_group
if stream_config in blacklist:
logger.debug(f"聊天流 {stream_config} 在黑名单中,拒绝主动思考")
return False
# 白名单检查
if self.config.whitelist_mode:
whitelist = self.config.whitelist_private if is_private else self.config.whitelist_group
if stream_config not in whitelist:
logger.debug(f"聊天流 {stream_config} 不在白名单中,拒绝主动思考")
return False
return True
def _check_interest_score_threshold(self, interest_score: float) -> bool:
"""检查兴趣分数是否在阈值范围内
Args:
interest_score: 兴趣分数
Returns:
bool: True表示在范围内
"""
if interest_score < self.config.min_interest_score:
logger.debug(f"兴趣分数 {interest_score:.2f} 低于最低阈值 {self.config.min_interest_score}")
return False
if interest_score > self.config.max_interest_score:
logger.debug(f"兴趣分数 {interest_score:.2f} 高于最高阈值 {self.config.max_interest_score}")
return False
return True
def _check_daily_limit(self, stream_id: str) -> bool:
"""检查今日主动发言次数是否超限
Args:
stream_id: 聊天流ID
Returns:
bool: True表示未超限
"""
if self.config.max_daily_proactive == 0:
return True # 不限制
today = datetime.now().strftime("%Y-%m-%d")
if stream_id not in self._daily_counts:
self._daily_counts[stream_id] = {}
# 清理过期日期的数据
for date in list(self._daily_counts[stream_id].keys()):
if date != today:
del self._daily_counts[stream_id][date]
count = self._daily_counts[stream_id].get(today, 0)
if count >= self.config.max_daily_proactive:
logger.debug(f"聊天流 {stream_id} 今日主动发言次数已达上限 ({count}/{self.config.max_daily_proactive})")
return False
return True
def _increment_daily_count(self, stream_id: str):
"""增加今日主动发言计数"""
today = datetime.now().strftime("%Y-%m-%d")
if stream_id not in self._daily_counts:
self._daily_counts[stream_id] = {}
self._daily_counts[stream_id][today] = self._daily_counts[stream_id].get(today, 0) + 1
def _is_in_quiet_hours(self) -> bool:
"""检查当前是否在安静时段
Returns:
bool: True表示在安静时段
"""
if not self.config.enable_time_strategy:
return False
now = datetime.now()
current_time = now.strftime("%H:%M")
start = self.config.quiet_hours_start
end = self.config.quiet_hours_end
# 处理跨日的情况如23:00-07:00
if start <= end:
return start <= current_time <= end
else:
return current_time >= start or current_time <= end
async def _get_stream_focus_energy(self, stream_id: str) -> float:
"""获取聊天流的 focus_energy
Args:
stream_id: 聊天流ID
Returns:
float: focus_energy 值默认0.5
"""
try:
# 从聊天管理器获取聊天流
from src.chat.message_receive.chat_stream import get_chat_manager
logger.debug(f"[调度器] 获取聊天管理器")
logger.debug("[调度器] 获取聊天管理器")
chat_manager = get_chat_manager()
logger.debug(f"[调度器] 从聊天管理器获取聊天流 {stream_id}")
chat_stream = await chat_manager.get_stream(stream_id)
if chat_stream:
# 计算并获取最新的 focus_energy
logger.debug(f"[调度器] 找到聊天流,开始计算 focus_energy")
logger.debug("[调度器] 找到聊天流,开始计算 focus_energy")
focus_energy = await chat_stream.calculate_focus_energy()
logger.info(f"[调度器] 聊天流 {stream_id} 的 focus_energy: {focus_energy:.3f}")
return focus_energy
else:
logger.warning(f"[调度器] ⚠️ 未找到聊天流 {stream_id},使用默认 focus_energy=0.5")
return 0.5
except Exception as e:
logger.error(f"[调度器] ❌ 获取聊天流 {stream_id} 的 focus_energy 失败: {e}", exc_info=True)
return 0.5
async def schedule_proactive_thinking(self, stream_id: str) -> bool:
"""为聊天流创建或重置主动思考任务
Args:
stream_id: 聊天流ID
Returns:
bool: 是否成功创建/重置任务
"""
@@ -243,25 +241,25 @@ class ProactiveThinkingScheduler:
if stream_id in self._paused_streams:
logger.debug(f"[调度器] 清除聊天流 {stream_id} 的暂停标记")
self._paused_streams.discard(stream_id)
# 如果已经有任务,先移除
if stream_id in self._stream_schedules:
old_schedule_id = self._stream_schedules[stream_id]
logger.debug(f"[调度器] 移除聊天流 {stream_id} 的旧任务")
await unified_scheduler.remove_schedule(old_schedule_id)
# 获取 focus_energy 并计算间隔
focus_energy = await self._get_stream_focus_energy(stream_id)
logger.debug(f"[调度器] focus_energy={focus_energy:.3f}")
interval_seconds = self._calculate_interval(focus_energy)
logger.debug(f"[调度器] 触发间隔={interval_seconds}秒 ({interval_seconds/60:.1f}分钟)")
logger.debug(f"[调度器] 触发间隔={interval_seconds}秒 ({interval_seconds / 60:.1f}分钟)")
# 导入回调函数(延迟导入避免循环依赖)
from src.plugins.built_in.affinity_flow_chatter.proactive_thinking_executor import (
execute_proactive_thinking,
)
# 创建新任务
schedule_id = await unified_scheduler.create_schedule(
callback=execute_proactive_thinking,
@@ -273,34 +271,34 @@ class ProactiveThinkingScheduler:
task_name=f"ProactiveThinking-{stream_id}",
callback_args=(stream_id,),
)
self._stream_schedules[stream_id] = schedule_id
# 计算下次触发时间
next_run_time = datetime.now() + timedelta(seconds=interval_seconds)
logger.info(
f"✅ 聊天流 {stream_id} 主动思考任务已创建 | "
f"Focus: {focus_energy:.3f} | "
f"间隔: {interval_seconds/60:.1f}分钟 | "
f"间隔: {interval_seconds / 60:.1f}分钟 | "
f"下次: {next_run_time.strftime('%H:%M:%S')}"
)
return True
except Exception as e:
logger.error(f"❌ 创建主动思考任务失败 {stream_id}: {e}", exc_info=True)
return False
async def pause_proactive_thinking(self, stream_id: str, reason: str = "抛出话题") -> bool:
"""暂停聊天流的主动思考任务
当选择"抛出话题"后,应该暂停该聊天流的主动思考,
直到bot至少执行过一次reply后才恢复。
Args:
stream_id: 聊天流ID
reason: 暂停原因
Returns:
bool: 是否成功暂停
"""
@@ -309,26 +307,26 @@ class ProactiveThinkingScheduler:
if stream_id not in self._stream_schedules:
logger.warning(f"尝试暂停不存在的任务: {stream_id}")
return False
schedule_id = self._stream_schedules[stream_id]
success = await unified_scheduler.pause_schedule(schedule_id)
if success:
self._paused_streams.add(stream_id)
logger.info(f"⏸️ 暂停主动思考 {stream_id},原因: {reason}")
return success
except Exception as e:
except Exception:
# 错误日志已在上面记录
return False
async def resume_proactive_thinking(self, stream_id: str) -> bool:
"""恢复聊天流的主动思考任务
Args:
stream_id: 聊天流ID
Returns:
bool: 是否成功恢复
"""
@@ -337,26 +335,26 @@ class ProactiveThinkingScheduler:
if stream_id not in self._stream_schedules:
logger.warning(f"尝试恢复不存在的任务: {stream_id}")
return False
schedule_id = self._stream_schedules[stream_id]
success = await unified_scheduler.resume_schedule(schedule_id)
if success:
self._paused_streams.discard(stream_id)
logger.info(f"▶️ 恢复主动思考 {stream_id}")
return success
except Exception as e:
logger.error(f"❌ 恢复主动思考失败 {stream_id}: {e}", exc_info=True)
return False
async def cancel_proactive_thinking(self, stream_id: str) -> bool:
"""取消聊天流的主动思考任务
Args:
stream_id: 聊天流ID
Returns:
bool: 是否成功取消
"""
@@ -364,55 +362,55 @@ class ProactiveThinkingScheduler:
async with self._lock:
if stream_id not in self._stream_schedules:
return True # 已经不存在,视为成功
schedule_id = self._stream_schedules.pop(stream_id)
self._paused_streams.discard(stream_id)
success = await unified_scheduler.remove_schedule(schedule_id)
logger.debug(f"⏹️ 取消主动思考 {stream_id}")
return success
except Exception as e:
logger.error(f"❌ 取消主动思考失败 {stream_id}: {e}", exc_info=True)
return False
async def is_paused(self, stream_id: str) -> bool:
"""检查聊天流的主动思考是否被暂停
Args:
stream_id: 聊天流ID
Returns:
bool: 是否暂停中
"""
async with self._lock:
return stream_id in self._paused_streams
async def get_task_info(self, stream_id: str) -> Optional[dict[str, Any]]:
async def get_task_info(self, stream_id: str) -> dict[str, Any] | None:
"""获取聊天流的主动思考任务信息
Args:
stream_id: 聊天流ID
Returns:
dict: 任务信息如果不存在返回None
"""
async with self._lock:
if stream_id not in self._stream_schedules:
return None
schedule_id = self._stream_schedules[stream_id]
task_info = await unified_scheduler.get_task_info(schedule_id)
if task_info:
task_info["is_paused_for_topic"] = stream_id in self._paused_streams
return task_info
async def list_all_tasks(self) -> list[dict[str, Any]]:
"""列出所有主动思考任务
Returns:
list: 任务信息列表
"""
@@ -425,10 +423,10 @@ class ProactiveThinkingScheduler:
task_info["is_paused_for_topic"] = stream_id in self._paused_streams
tasks.append(task_info)
return tasks
def get_statistics(self) -> dict[str, Any]:
"""获取调度器统计信息
Returns:
dict: 统计信息
"""
@@ -437,51 +435,48 @@ class ProactiveThinkingScheduler:
"paused_for_topic": len(self._paused_streams),
"active_tasks": len(self._stream_schedules) - len(self._paused_streams),
}
async def log_next_trigger_times(self, max_streams: int = 10):
"""在日志中输出聊天流的下次触发时间
Args:
max_streams: 最多显示多少个聊天流0表示全部
"""
logger.info("=" * 60)
logger.info("主动思考任务状态")
logger.info("=" * 60)
tasks = await self.list_all_tasks()
if not tasks:
logger.info("当前没有活跃的主动思考任务")
logger.info("=" * 60)
return
# 按下次触发时间排序
tasks_sorted = sorted(
tasks,
key=lambda x: x.get("next_run_time", datetime.max) or datetime.max
)
tasks_sorted = sorted(tasks, key=lambda x: x.get("next_run_time", datetime.max) or datetime.max)
# 限制显示数量
if max_streams > 0:
tasks_sorted = tasks_sorted[:max_streams]
logger.info(f"共有 {len(self._stream_schedules)} 个任务,显示前 {len(tasks_sorted)}")
logger.info("")
for i, task in enumerate(tasks_sorted, 1):
stream_id = task.get("stream_id", "Unknown")
next_run = task.get("next_run_time")
is_paused = task.get("is_paused_for_topic", False)
# 获取聊天流名称(如果可能)
stream_name = stream_id[:16] + "..." if len(stream_id) > 16 else stream_id
if next_run:
# 计算剩余时间
now = datetime.now()
remaining = next_run - now
remaining_seconds = int(remaining.total_seconds())
if remaining_seconds < 0:
time_str = "已过期(待执行)"
elif remaining_seconds < 60:
@@ -492,28 +487,25 @@ class ProactiveThinkingScheduler:
hours = remaining_seconds // 3600
minutes = (remaining_seconds % 3600) // 60
time_str = f"{hours}小时{minutes}分钟后"
status = "⏸️ 暂停中" if is_paused else "✅ 活跃"
logger.info(
f"[{i:2d}] {status} | {stream_name}\n"
f" 下次触发: {next_run.strftime('%Y-%m-%d %H:%M:%S')} ({time_str})"
)
else:
logger.info(
f"[{i:2d}] ⚠️ 未知 | {stream_name}\n"
f" 下次触发: 未设置"
)
logger.info(f"[{i:2d}] ⚠️ 未知 | {stream_name}\n 下次触发: 未设置")
logger.info("")
logger.info("=" * 60)
def get_last_decision(self, stream_id: str) -> Optional[dict[str, Any]]:
def get_last_decision(self, stream_id: str) -> dict[str, Any] | None:
"""获取聊天流的上次主动思考决策
Args:
stream_id: 聊天流ID
Returns:
dict: 上次决策信息,包含:
- action: "do_nothing" | "simple_bubble" | "throw_topic"
@@ -523,16 +515,10 @@ class ProactiveThinkingScheduler:
None: 如果没有历史决策
"""
return self._last_decisions.get(stream_id)
def record_decision(
self,
stream_id: str,
action: str,
reasoning: str,
topic: Optional[str] = None
) -> None:
def record_decision(self, stream_id: str, action: str, reasoning: str, topic: str | None = None) -> None:
"""记录聊天流的主动思考决策
Args:
stream_id: 聊天流ID
action: 决策动作

View File

@@ -4,10 +4,10 @@
通过LLM二步调用机制更新用户画像信息包括别名、主观印象、偏好关键词和好感分数
"""
import orjson
import time
from typing import Any
import orjson
from sqlalchemy import select
from src.common.database.sqlalchemy_database_api import get_db_session
@@ -42,7 +42,7 @@ class UserProfileTool(BaseTool):
def __init__(self, plugin_config: dict | None = None, chat_stream: Any = None):
super().__init__(plugin_config, chat_stream)
# 初始化用于二步调用的LLM
try:
self.profile_llm = LLMRequest(
@@ -84,24 +84,24 @@ class UserProfileTool(BaseTool):
"id": "user_profile_update",
"content": "错误必须提供目标用户ID"
}
# 从LLM传入的参数
new_aliases = function_args.get("user_aliases", "")
new_impression = function_args.get("impression_description", "")
new_keywords = function_args.get("preference_keywords", "")
new_score = function_args.get("affection_score")
# 从数据库获取现有用户画像
existing_profile = await self._get_user_profile(target_user_id)
# 如果LLM没有传入任何有效参数返回提示
if not any([new_aliases, new_impression, new_keywords, new_score is not None]):
return {
"type": "info",
"id": target_user_id,
"content": f"提示:需要提供至少一项更新内容(别名、印象描述、偏好关键词或好感分数)"
"content": "提示:需要提供至少一项更新内容(别名、印象描述、偏好关键词或好感分数)"
}
# 调用LLM进行二步决策
if self.profile_llm is None:
logger.error("LLM未正确初始化无法执行二步调用")
@@ -110,7 +110,7 @@ class UserProfileTool(BaseTool):
"id": target_user_id,
"content": "系统错误LLM未正确初始化"
}
final_profile = await self._llm_decide_final_profile(
target_user_id=target_user_id,
existing_profile=existing_profile,
@@ -119,17 +119,17 @@ class UserProfileTool(BaseTool):
new_keywords=new_keywords,
new_score=new_score
)
if not final_profile:
return {
"type": "error",
"id": target_user_id,
"content": "LLM决策失败无法更新用户画像"
}
# 更新数据库
await self._update_user_profile_in_db(target_user_id, final_profile)
# 构建返回信息
updates = []
if final_profile.get("user_aliases"):
@@ -140,22 +140,22 @@ class UserProfileTool(BaseTool):
updates.append(f"偏好: {final_profile['preference_keywords']}")
if final_profile.get("relationship_score") is not None:
updates.append(f"好感分: {final_profile['relationship_score']:.2f}")
result_text = f"已更新用户 {target_user_id} 的画像:\n" + "\n".join(updates)
logger.info(f"用户画像更新成功: {target_user_id}")
return {
"type": "user_profile_update",
"id": target_user_id,
"content": result_text
}
except Exception as e:
logger.error(f"用户画像更新失败: {e}", exc_info=True)
return {
"type": "error",
"id": function_args.get("target_user_id", "unknown"),
"content": f"用户画像更新失败: {str(e)}"
"content": f"用户画像更新失败: {e!s}"
}
async def _get_user_profile(self, user_id: str) -> dict[str, Any]:
@@ -172,7 +172,7 @@ class UserProfileTool(BaseTool):
stmt = select(UserRelationships).where(UserRelationships.user_id == user_id)
result = await session.execute(stmt)
profile = result.scalar_one_or_none()
if profile:
return {
"user_name": profile.user_name or user_id,
@@ -227,7 +227,7 @@ class UserProfileTool(BaseTool):
from src.individuality.individuality import Individuality
individuality = Individuality()
bot_personality = await individuality.get_personality_block()
prompt = f"""
你现在是一个有着特定性格和身份的AI助手。你的人设是{bot_personality}
@@ -261,18 +261,18 @@ class UserProfileTool(BaseTool):
"reasoning": "你的决策理由"
}}
"""
# 调用LLM
llm_response, _ = await self.profile_llm.generate_response_async(prompt=prompt)
if not llm_response:
logger.warning("LLM未返回有效响应")
return None
# 清理并解析响应
cleaned_response = self._clean_llm_json_response(llm_response)
response_data = orjson.loads(cleaned_response)
# 提取最终决定的数据
final_profile = {
"user_aliases": response_data.get("user_aliases", existing_profile.get("user_aliases", "")),
@@ -280,12 +280,12 @@ class UserProfileTool(BaseTool):
"preference_keywords": response_data.get("preference_keywords", existing_profile.get("preference_keywords", "")),
"relationship_score": max(0.0, min(1.0, float(response_data.get("relationship_score", existing_profile.get("relationship_score", 0.3))))),
}
logger.info(f"LLM决策完成: {target_user_id}")
logger.debug(f"决策理由: {response_data.get('reasoning', '')}")
return final_profile
except orjson.JSONDecodeError as e:
logger.error(f"LLM响应JSON解析失败: {e}")
logger.debug(f"LLM原始响应: {llm_response if 'llm_response' in locals() else 'N/A'}")
@@ -303,12 +303,12 @@ class UserProfileTool(BaseTool):
"""
try:
current_time = time.time()
async with get_db_session() as session:
stmt = select(UserRelationships).where(UserRelationships.user_id == user_id)
result = await session.execute(stmt)
existing = result.scalar_one_or_none()
if existing:
# 更新现有记录
existing.user_aliases = profile.get("user_aliases", "")
@@ -328,10 +328,10 @@ class UserProfileTool(BaseTool):
last_updated=current_time
)
session.add(new_profile)
await session.commit()
logger.info(f"用户画像已更新到数据库: {user_id}")
except Exception as e:
logger.error(f"更新用户画像到数据库失败: {e}", exc_info=True)
raise
@@ -347,24 +347,24 @@ class UserProfileTool(BaseTool):
"""
try:
import re
cleaned = response.strip()
# 移除 ```json 或 ``` 等标记
cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned, flags=re.MULTILINE | re.IGNORECASE)
cleaned = re.sub(r"\s*```$", "", cleaned, flags=re.MULTILINE)
# 尝试找到JSON对象的开始和结束
json_start = cleaned.find("{")
json_end = cleaned.rfind("}")
if json_start != -1 and json_end != -1 and json_end > json_start:
cleaned = cleaned[json_start:json_end + 1]
cleaned = cleaned.strip()
return cleaned
except Exception as e:
logger.warning(f"清理LLM响应失败: {e}")
return response

View File

@@ -261,7 +261,7 @@ class SetEmojiLikeAction(BaseAction):
elif isinstance(self.action_message, dict):
message_id = self.action_message.get("message_id")
logger.info(f"获取到的消息ID: {message_id}")
if not message_id:
logger.error("未提供有效的消息或消息ID")
await self.store_action_info(action_prompt_display="贴表情失败: 未提供消息ID", action_done=False)
@@ -279,7 +279,7 @@ class SetEmojiLikeAction(BaseAction):
context_text = self.action_message.processed_plain_text or ""
else:
context_text = self.action_message.get("processed_plain_text", "")
if not context_text:
logger.error("无法找到动作选择的原始消息文本")
return False, "无法找到动作选择的原始消息文本"

View File

@@ -5,7 +5,7 @@ Web Search Tool Plugin
"""
from src.common.logger import get_logger
from src.plugin_system import BasePlugin, ComponentInfo, ConfigField, PythonDependency, register_plugin
from src.plugin_system import BasePlugin, ComponentInfo, ConfigField, register_plugin
from src.plugin_system.apis import config_api
from .tools.url_parser import URLParserTool