mirror of https://github.com/Mai-with-u/MaiBot.git
better:大幅优化planner消耗 -60%
parent
acf316281b
commit
224af22142
|
|
@ -57,33 +57,27 @@ no_reply
|
||||||
控制聊天频率,不要太过频繁的发言
|
控制聊天频率,不要太过频繁的发言
|
||||||
{{"action":"no_reply"}}
|
{{"action":"no_reply"}}
|
||||||
|
|
||||||
no_reply_until_call
|
{no_reply_until_call_block}
|
||||||
动作描述:
|
|
||||||
保持沉默,直到有人直接叫你的名字
|
|
||||||
当前话题不感兴趣时使用,或有人不喜欢你的发言时使用
|
|
||||||
当你频繁选择no_reply时使用,表示话题暂时与你无关
|
|
||||||
{{"action":"no_reply_until_call"}}
|
|
||||||
|
|
||||||
{action_options_text}
|
{action_options_text}
|
||||||
|
|
||||||
|
|
||||||
**你之前的action执行和思考记录**
|
**你之前的action执行和思考记录**
|
||||||
{actions_before_now_block}
|
{actions_before_now_block}
|
||||||
|
|
||||||
请选择**可选的**且符合使用条件的action,并说明触发action的消息id(消息id格式:m+数字)
|
请选择**可选的**且符合使用条件的action,并说明触发action的消息id(消息id格式:m+数字)
|
||||||
不要回复你自己发送的消息
|
不要回复你自己发送的消息
|
||||||
先输出你的选择思考理由,再输出你选择的action,理由是一段平文本,不要分点,精简。
|
先输出你的简短的选择思考理由,再输出你选择的action,理由不要分点,精简。
|
||||||
**动作选择要求**
|
**动作选择要求**
|
||||||
请你根据聊天内容,用户的最新消息和以下标准选择合适的动作:
|
请你根据聊天内容,用户的最新消息和以下标准选择合适的动作:
|
||||||
{plan_style}
|
{plan_style}
|
||||||
{moderation_prompt}
|
{moderation_prompt}
|
||||||
|
|
||||||
请选择所有符合使用要求的action,动作用json格式输出,如果输出多个json,每个json都要单独用```json包裹,你可以重复使用同一个动作或不同动作:
|
请选择所有符合使用要求的action,动作用json格式输出,用```json包裹,如果输出多个json,每个json都要单独一行放在同一个```json代码块内,你可以重复使用同一个动作或不同动作:
|
||||||
**示例**
|
**示例**
|
||||||
// 理由文本
|
// 理由文本(简短)
|
||||||
```json
|
```json
|
||||||
{{"action":"动作名", "target_message_id":"m123", "reason":"原因"}}
|
{{"action":"动作名", "target_message_id":"m123", "reason":"原因"}}
|
||||||
```
|
|
||||||
```json
|
|
||||||
{{"action":"动作名", "target_message_id":"m456", "reason":"原因"}}
|
{{"action":"动作名", "target_message_id":"m456", "reason":"原因"}}
|
||||||
```""",
|
```""",
|
||||||
"planner_prompt",
|
"planner_prompt",
|
||||||
|
|
@ -285,18 +279,73 @@ class ActionPlanner:
|
||||||
if len(self.plan_log) > 20:
|
if len(self.plan_log) > 20:
|
||||||
self.plan_log.pop(0)
|
self.plan_log.pop(0)
|
||||||
|
|
||||||
def get_plan_log_str(self) -> str:
|
def get_plan_log_str(self, max_action_records: int = 2, max_execution_records: int = 5) -> str:
|
||||||
plan_log_str = ""
|
"""
|
||||||
for reasoning, time, content in self.plan_log:
|
获取计划日志字符串
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_action_records: 显示多少条最新的action记录,默认2
|
||||||
|
max_execution_records: 显示多少条最新执行结果记录,默认8
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式化的日志字符串
|
||||||
|
"""
|
||||||
|
action_records = []
|
||||||
|
execution_records = []
|
||||||
|
|
||||||
|
# 从后往前遍历,收集最新的记录
|
||||||
|
for reasoning, timestamp, content in reversed(self.plan_log):
|
||||||
if isinstance(content, list) and all(isinstance(action, ActionPlannerInfo) for action in content):
|
if isinstance(content, list) and all(isinstance(action, ActionPlannerInfo) for action in content):
|
||||||
time = datetime.fromtimestamp(time).strftime("%H:%M:%S")
|
# 这是action记录
|
||||||
plan_log_str += f"{time}:{reasoning}|你使用了{','.join([action.action_type for action in content])}\n"
|
if len(action_records) < max_action_records:
|
||||||
|
action_records.append((reasoning, timestamp, content, "action"))
|
||||||
else:
|
else:
|
||||||
time = datetime.fromtimestamp(time).strftime("%H:%M:%S")
|
# 这是执行结果记录
|
||||||
plan_log_str += f"{time}:{content}\n"
|
if len(execution_records) < max_execution_records:
|
||||||
|
execution_records.append((reasoning, timestamp, content, "execution"))
|
||||||
|
|
||||||
|
# 合并所有记录并按时间戳排序
|
||||||
|
all_records = action_records + execution_records
|
||||||
|
all_records.sort(key=lambda x: x[1]) # 按时间戳排序
|
||||||
|
|
||||||
|
plan_log_str = ""
|
||||||
|
|
||||||
|
# 按时间顺序添加所有记录
|
||||||
|
for reasoning, timestamp, content, record_type in all_records:
|
||||||
|
time_str = datetime.fromtimestamp(timestamp).strftime("%H:%M:%S")
|
||||||
|
if record_type == "action":
|
||||||
|
# plan_log_str += f"{time_str}:{reasoning}|你使用了{','.join([action.action_type for action in content])}\n"
|
||||||
|
plan_log_str += f"{time_str}:{reasoning}\n"
|
||||||
|
else:
|
||||||
|
plan_log_str += f"{time_str}:你执行了action:{content}\n"
|
||||||
|
|
||||||
return plan_log_str
|
return plan_log_str
|
||||||
|
|
||||||
|
def _has_consecutive_no_reply(self, min_count: int = 3) -> bool:
|
||||||
|
"""
|
||||||
|
检查是否有连续min_count次以上的no_reply
|
||||||
|
|
||||||
|
Args:
|
||||||
|
min_count: 需要连续的最少次数,默认3
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
如果有连续min_count次以上no_reply返回True,否则返回False
|
||||||
|
"""
|
||||||
|
consecutive_count = 0
|
||||||
|
|
||||||
|
# 从后往前遍历plan_log,检查最新的连续记录
|
||||||
|
for _reasoning, _timestamp, content in reversed(self.plan_log):
|
||||||
|
if isinstance(content, list) and all(isinstance(action, ActionPlannerInfo) for action in content):
|
||||||
|
# 检查所有action是否都是no_reply
|
||||||
|
if all(action.action_type == "no_reply" for action in content):
|
||||||
|
consecutive_count += 1
|
||||||
|
if consecutive_count >= min_count:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# 如果遇到非no_reply的action,重置计数
|
||||||
|
break
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
async def build_planner_prompt(
|
async def build_planner_prompt(
|
||||||
self,
|
self,
|
||||||
|
|
@ -318,6 +367,17 @@ class ActionPlanner:
|
||||||
# 构建动作选项块
|
# 构建动作选项块
|
||||||
action_options_block = await self._build_action_options_block(current_available_actions)
|
action_options_block = await self._build_action_options_block(current_available_actions)
|
||||||
|
|
||||||
|
# 检查是否有连续3次以上no_reply,如果有则添加no_reply_until_call选项
|
||||||
|
no_reply_until_call_block = ""
|
||||||
|
if self._has_consecutive_no_reply(min_count=3):
|
||||||
|
no_reply_until_call_block = """no_reply_until_call
|
||||||
|
动作描述:
|
||||||
|
保持沉默,直到有人直接叫你的名字
|
||||||
|
当前话题不感兴趣时使用,或有人不喜欢你的发言时使用
|
||||||
|
当你频繁选择no_reply时使用,表示话题暂时与你无关
|
||||||
|
{{"action":"no_reply_until_call"}}
|
||||||
|
"""
|
||||||
|
|
||||||
# 其他信息
|
# 其他信息
|
||||||
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
|
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
|
||||||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
|
@ -335,6 +395,7 @@ class ActionPlanner:
|
||||||
chat_content_block=chat_content_block,
|
chat_content_block=chat_content_block,
|
||||||
actions_before_now_block=actions_before_now_block,
|
actions_before_now_block=actions_before_now_block,
|
||||||
action_options_text=action_options_block,
|
action_options_text=action_options_block,
|
||||||
|
no_reply_until_call_block=no_reply_until_call_block,
|
||||||
moderation_prompt=moderation_prompt_block,
|
moderation_prompt=moderation_prompt_block,
|
||||||
name_block=name_block,
|
name_block=name_block,
|
||||||
interest=interest,
|
interest=interest,
|
||||||
|
|
@ -532,10 +593,11 @@ class ActionPlanner:
|
||||||
|
|
||||||
# 使用正则表达式查找```json包裹的JSON内容
|
# 使用正则表达式查找```json包裹的JSON内容
|
||||||
json_pattern = r"```json\s*(.*?)\s*```"
|
json_pattern = r"```json\s*(.*?)\s*```"
|
||||||
matches = re.findall(json_pattern, content, re.DOTALL)
|
markdown_matches = re.findall(json_pattern, content, re.DOTALL)
|
||||||
|
|
||||||
# 提取JSON之前的内容作为推理文本
|
# 提取JSON之前的内容作为推理文本
|
||||||
if matches:
|
first_json_pos = len(content)
|
||||||
|
if markdown_matches:
|
||||||
# 找到第一个```json的位置
|
# 找到第一个```json的位置
|
||||||
first_json_pos = content.find("```json")
|
first_json_pos = content.find("```json")
|
||||||
if first_json_pos > 0:
|
if first_json_pos > 0:
|
||||||
|
|
@ -544,19 +606,38 @@ class ActionPlanner:
|
||||||
reasoning_content = re.sub(r"^//\s*", "", reasoning_content, flags=re.MULTILINE)
|
reasoning_content = re.sub(r"^//\s*", "", reasoning_content, flags=re.MULTILINE)
|
||||||
reasoning_content = reasoning_content.strip()
|
reasoning_content = reasoning_content.strip()
|
||||||
|
|
||||||
for match in matches:
|
# 处理```json包裹的JSON
|
||||||
|
for match in markdown_matches:
|
||||||
try:
|
try:
|
||||||
# 清理可能的注释和格式问题
|
# 清理可能的注释和格式问题
|
||||||
json_str = re.sub(r"//.*?\n", "\n", match) # 移除单行注释
|
json_str = re.sub(r"//.*?\n", "\n", match) # 移除单行注释
|
||||||
json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) # 移除多行注释
|
json_str = re.sub(r"/\*.*?\*/", "", json_str, flags=re.DOTALL) # 移除多行注释
|
||||||
if json_str := json_str.strip():
|
if json_str := json_str.strip():
|
||||||
json_obj = json.loads(repair_json(json_str))
|
# 尝试按行分割,每行可能是一个JSON对象
|
||||||
if isinstance(json_obj, dict):
|
lines = [line.strip() for line in json_str.split('\n') if line.strip()]
|
||||||
json_objects.append(json_obj)
|
for line in lines:
|
||||||
elif isinstance(json_obj, list):
|
try:
|
||||||
for item in json_obj:
|
# 尝试解析每一行作为独立的JSON对象
|
||||||
if isinstance(item, dict):
|
json_obj = json.loads(repair_json(line))
|
||||||
json_objects.append(item)
|
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 json.JSONDecodeError:
|
||||||
|
# 如果单行解析失败,尝试将整个块作为一个JSON对象或数组
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 如果按行解析没有成功,尝试将整个块作为一个JSON对象或数组
|
||||||
|
if not json_objects:
|
||||||
|
json_obj = json.loads(repair_json(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:
|
except Exception as e:
|
||||||
logger.warning(f"解析JSON块失败: {e}, 块内容: {match[:100]}...")
|
logger.warning(f"解析JSON块失败: {e}, 块内容: {match[:100]}...")
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue