This commit is contained in:
SengokuCola
2025-06-01 18:40:51 +08:00
16 changed files with 326 additions and 202 deletions

View File

@@ -8,6 +8,7 @@ import threading
import time
import sys
class ConfigEditor:
def __init__(self, root):
self.root = root
@@ -120,33 +121,33 @@ class ConfigEditor:
return
# 读取环境文件
with open(env_path, 'r', encoding='utf-8') as f:
with open(env_path, "r", encoding="utf-8") as f:
env_content = f.read()
# 解析环境变量
env_vars = {}
for line in env_content.split('\n'):
for line in env_content.split("\n"):
line = line.strip()
if not line or line.startswith('#'):
if not line or line.startswith("#"):
continue
if '=' in line:
key, value = line.split('=', 1)
if "=" in line:
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
# 检查是否是目标变量
if key.endswith('_BASE_URL') or key.endswith('_KEY'):
if key.endswith("_BASE_URL") or key.endswith("_KEY"):
# 提取前缀去掉_BASE_URL或_KEY
prefix = key[:-9] if key.endswith('_BASE_URL') else key[:-4]
prefix = key[:-9] if key.endswith("_BASE_URL") else key[:-4]
if prefix not in env_vars:
env_vars[prefix] = {}
env_vars[prefix][key] = value
# 将解析的环境变量添加到配置中
if 'env_vars' not in self.config:
self.config['env_vars'] = {}
self.config['env_vars'].update(env_vars)
if "env_vars" not in self.config:
self.config["env_vars"] = {}
self.config["env_vars"].update(env_vars)
except Exception as e:
print(f"加载环境变量失败: {str(e)}")
@@ -181,7 +182,16 @@ class ConfigEditor:
# 只显示bot_config.toml实际存在的section
for section in self.config:
if section not in ("inner", "env_vars", "telemetry", "experimental", "maim_message", "keyword_reaction", "message_receive", "relationship"):
if section not in (
"inner",
"env_vars",
"telemetry",
"experimental",
"maim_message",
"keyword_reaction",
"message_receive",
"relationship",
):
section_trans = self.translations.get("sections", {}).get(section, {})
section_name = section_trans.get("name", section)
self.tree.insert("", "end", text=section_name, values=(section,))
@@ -251,7 +261,7 @@ class ConfigEditor:
"provider": ("模型提供商", "模型API的提供商"),
"pri_in": ("输入价格", "模型输入的价格/消耗"),
"pri_out": ("输出价格", "模型输出的价格/消耗"),
"temp": ("模型温度", "控制模型输出的多样性")
"temp": ("模型温度", "控制模型输出的多样性"),
}
item_name_to_display = key # 默认显示原始键名
@@ -294,9 +304,15 @@ class ConfigEditor:
# 判断parent是不是self.content_frame
if parent == self.content_frame:
# 主界面
if hasattr(self, 'current_section') and self.current_section and self.current_section != "quick_settings":
self.create_section_widgets(parent, self.current_section, self.config[self.current_section], [self.current_section])
elif hasattr(self, 'current_section') and self.current_section == "quick_settings":
if (
hasattr(self, "current_section")
and self.current_section
and self.current_section != "quick_settings"
):
self.create_section_widgets(
parent, self.current_section, self.config[self.current_section], [self.current_section]
)
elif hasattr(self, "current_section") and self.current_section == "quick_settings":
self.create_quick_settings_widgets()
else:
# 弹窗Tab
@@ -318,7 +334,9 @@ class ConfigEditor:
desc_row = 1
if item_desc_to_display:
desc_label = ttk.Label(frame, text=item_desc_to_display, foreground="gray", font=("微软雅黑", 10))
desc_label.grid(row=desc_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W, padx=5, pady=(0, 4))
desc_label.grid(
row=desc_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W, padx=5, pady=(0, 4)
)
widget_row = desc_row + 1 # 内容控件在描述下方
else:
widget_row = desc_row # 内容控件直接在第二行
@@ -401,22 +419,22 @@ class ConfigEditor:
# print(f"最终providers列表: {providers}")
if providers:
# 创建模型名称标签(大字体)
model_name = var.get() if var.get() else providers[0]
section_translations = {
"model.utils": "麦麦组件模型",
"model.utils_small": "小型麦麦组件模型",
"model.memory_summary": "记忆概括模型",
"model.vlm": "图像识别模型",
"model.embedding": "嵌入模型",
"model.normal_chat_1": "普通聊天:主要聊天模型",
"model.normal_chat_2": "普通聊天:次要聊天模型",
"model.focus_working_memory": "专注模式:工作记忆模型",
"model.focus_chat_mind": "专注模式:聊天思考模型",
"model.focus_tool_use": "专注模式:工具调用模型",
"model.focus_planner": "专注模式:决策模型",
"model.focus_expressor": "专注模式:表达器模型",
"model.focus_self_recognize": "专注模式:自我识别模型"
}
# model_name = var.get() if var.get() else providers[0]
# section_translations = {
# "model.utils": "麦麦组件模型",
# "model.utils_small": "小型麦麦组件模型",
# "model.memory_summary": "记忆概括模型",
# "model.vlm": "图像识别模型",
# "model.embedding": "嵌入模型",
# "model.normal_chat_1": "普通聊天:主要聊天模型",
# "model.normal_chat_2": "普通聊天:次要聊天模型",
# "model.focus_working_memory": "专注模式:工作记忆模型",
# "model.focus_chat_mind": "专注模式:聊天思考模型",
# "model.focus_tool_use": "专注模式:工具调用模型",
# "model.focus_planner": "专注模式:决策模型",
# "model.focus_expressor": "专注模式:表达器模型",
# "model.focus_self_recognize": "专注模式:自我识别模型"
# }
# 获取当前节的名称
# current_section = ".".join(path[:-1]) # 去掉最后一个key
# section_name = section_translations.get(current_section, current_section)
@@ -426,8 +444,16 @@ class ConfigEditor:
# section_label.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W, padx=5, pady=(0, 5))
# 创建下拉菜单(小字体)
combo = ttk.Combobox(frame, textvariable=var, values=providers, font=("微软雅黑", 12), state="readonly")
combo.grid(row=widget_row + 1, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
combo = ttk.Combobox(
frame, textvariable=var, values=providers, font=("微软雅黑", 12), state="readonly"
)
combo.grid(
row=widget_row + 1,
column=0,
columnspan=content_col_offset_for_star + 1,
sticky=tk.W + tk.E,
padx=5,
)
combo.bind("<<ComboboxSelected>>", lambda e: self.on_value_changed())
self.widgets[tuple(path + [key])] = var
widget_type = "provider"
@@ -436,14 +462,18 @@ class ConfigEditor:
# 如果没有可用的provider使用普通文本框
# print(f"没有可用的provider使用普通文本框")
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
entry.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
entry.grid(
row=widget_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W + tk.E, padx=5
)
var.trace_add("write", lambda *args: self.on_value_changed())
self.widgets[tuple(path + [key])] = var
widget_type = "text"
else:
# 普通文本框
entry = ttk.Entry(frame, textvariable=var, font=("微软雅黑", 16))
entry.grid(row=widget_row, column=0, columnspan=content_col_offset_for_star +1, sticky=tk.W+tk.E, padx=5)
entry.grid(
row=widget_row, column=0, columnspan=content_col_offset_for_star + 1, sticky=tk.W + tk.E, padx=5
)
var.trace_add("write", lambda *args: self.on_value_changed())
self.widgets[tuple(path + [key])] = var
widget_type = "text"
@@ -468,7 +498,7 @@ class ConfigEditor:
"model.focus_tool_use": "工具调用模型",
"model.focus_planner": "决策模型",
"model.focus_expressor": "表达器模型",
"model.focus_self_recognize": "自我识别模型"
"model.focus_self_recognize": "自我识别模型",
}
section_trans = self.translations.get("sections", {}).get(full_section_path, {})
section_name = section_trans.get("name") or section_translations.get(full_section_path) or section
@@ -557,7 +587,7 @@ class ConfigEditor:
# 创建描述标签
if setting.get("description"):
desc_label = ttk.Label(frame, text=setting['description'], foreground="gray", font=("微软雅黑", 10))
desc_label = ttk.Label(frame, text=setting["description"], foreground="gray", font=("微软雅黑", 10))
desc_label.pack(fill=tk.X, padx=5, pady=(0, 2))
# 根据类型创建不同的控件
@@ -659,7 +689,7 @@ class ConfigEditor:
# 获取所有控件的值
for path, widget in self.widgets.items():
# 跳过 env_vars 的控件赋值(只用于.env不写回config
if len(path) >= 2 and path[0] == 'env_vars':
if len(path) >= 2 and path[0] == "env_vars":
continue
value = self.get_widget_value(widget)
current = self.config
@@ -669,11 +699,11 @@ class ConfigEditor:
current[final_key] = value
# === 只保存 TOML不包含 env_vars ===
env_vars = self.config.pop('env_vars', None)
env_vars = self.config.pop("env_vars", None)
with open(self.config_path, "wb") as f:
tomli_w.dump(self.config, f)
if env_vars is not None:
self.config['env_vars'] = env_vars
self.config["env_vars"] = env_vars
# === 保存 env_vars 到 .env 文件只覆盖特定key其他内容保留 ===
env_path = self.editor_config["config"].get("env_file", ".env")
@@ -687,7 +717,7 @@ class ConfigEditor:
# 2. 收集所有目标key的新值直接从widgets取
new_env_dict = {}
for path, widget in self.widgets.items():
if len(path) == 2 and path[0] == 'env_vars':
if len(path) == 2 and path[0] == "env_vars":
k = path[1]
if k.endswith("_BASE_URL") or k.endswith("_KEY"):
new_env_dict[k] = self.get_widget_value(widget)
@@ -715,15 +745,15 @@ class ConfigEditor:
# === 保存完 .env 后,同步 widgets 的值回 self.config['env_vars'] ===
for path, widget in self.widgets.items():
if len(path) == 2 and path[0] == 'env_vars':
if len(path) == 2 and path[0] == "env_vars":
prefix_key = path[1]
if prefix_key.endswith("_BASE_URL") or prefix_key.endswith("_KEY"):
prefix = prefix_key[:-9] if prefix_key.endswith("_BASE_URL") else prefix_key[:-4]
if 'env_vars' not in self.config:
self.config['env_vars'] = {}
if prefix not in self.config['env_vars']:
self.config['env_vars'][prefix] = {}
self.config['env_vars'][prefix][prefix_key] = self.get_widget_value(widget)
if "env_vars" not in self.config:
self.config["env_vars"] = {}
if prefix not in self.config["env_vars"]:
self.config["env_vars"][prefix] = {}
self.config["env_vars"][prefix][prefix_key] = self.get_widget_value(widget)
self.last_save_time = time.time()
self.pending_save = False
@@ -871,8 +901,7 @@ class ConfigEditor:
title_label.pack(side=tk.LEFT, padx=5)
# 删除按钮
del_button = ttk.Button(title_frame, text="删除组",
command=lambda: self.delete_env_var_group(prefix))
del_button = ttk.Button(title_frame, text="删除组", command=lambda: self.delete_env_var_group(prefix))
del_button.pack(side=tk.RIGHT, padx=5)
# 创建BASE_URL输入框
@@ -904,20 +933,19 @@ class ConfigEditor:
self.widgets[tuple(path + [f"{prefix}_KEY"])] = key_var
# 添加分隔线
separator = ttk.Separator(frame, orient='horizontal')
separator = ttk.Separator(frame, orient="horizontal")
separator.pack(fill=tk.X, pady=5)
def create_env_vars_section(self, parent: ttk.Frame) -> None:
"""创建环境变量编辑区"""
# 创建添加新组的按钮
add_button = ttk.Button(parent, text="添加新的API配置组",
command=self.add_new_env_var_group)
add_button = ttk.Button(parent, text="添加新的API配置组", command=self.add_new_env_var_group)
add_button.pack(pady=10)
# 创建现有组的编辑区
if 'env_vars' in self.config:
for prefix, values in self.config['env_vars'].items():
self.create_env_var_group(parent, prefix, values, ['env_vars'])
if "env_vars" in self.config:
for prefix, values in self.config["env_vars"].items():
self.create_env_var_group(parent, prefix, values, ["env_vars"])
def add_new_env_var_group(self):
"""添加新的环境变量组"""
@@ -942,12 +970,9 @@ class ConfigEditor:
def on_confirm():
prefix = prefix_var.get().strip()
if prefix:
if 'env_vars' not in self.config:
self.config['env_vars'] = {}
self.config['env_vars'][prefix] = {
f"{prefix}_BASE_URL": "",
f"{prefix}_KEY": ""
}
if "env_vars" not in self.config:
self.config["env_vars"] = {}
self.config["env_vars"][prefix] = {f"{prefix}_BASE_URL": "", f"{prefix}_KEY": ""}
# 刷新显示
self.refresh_env_vars_section()
self.on_value_changed()
@@ -959,8 +984,8 @@ class ConfigEditor:
def delete_env_var_group(self, prefix: str):
"""删除环境变量组"""
if messagebox.askyesno("确认", f"确定要删除 {prefix} 配置组吗?"):
if 'env_vars' in self.config:
del self.config['env_vars'][prefix]
if "env_vars" in self.config:
del self.config["env_vars"][prefix]
# 刷新显示
self.refresh_env_vars_section()
self.on_value_changed()
@@ -1003,7 +1028,9 @@ class ConfigEditor:
if "message_receive" in self.config:
recv_frame = ttk.Frame(notebook)
notebook.add(recv_frame, text="消息接收")
self.create_section_widgets(recv_frame, "message_receive", self.config["message_receive"], ["message_receive"])
self.create_section_widgets(
recv_frame, "message_receive", self.config["message_receive"], ["message_receive"]
)
# 关系栏
if "relationship" in self.config:
rel_frame = ttk.Frame(notebook)
@@ -1065,12 +1092,11 @@ class ConfigEditor:
# 重启程序
self.root.quit()
os.execv(sys.executable, ['python'] + sys.argv)
os.execv(sys.executable, ["python"] + sys.argv)
def browse_bot_config():
file_path = filedialog.askopenfilename(
title="选择bot_config.toml文件",
filetypes=[("TOML文件", "*.toml"), ("所有文件", "*.*")]
title="选择bot_config.toml文件", filetypes=[("TOML文件", "*.toml"), ("所有文件", "*.*")]
)
if file_path:
bot_config_var.set(file_path)
@@ -1095,8 +1121,7 @@ class ConfigEditor:
def browse_env():
file_path = filedialog.askopenfilename(
title="选择.env文件",
filetypes=[("环境变量文件", "*.env"), ("所有文件", "*.*")]
title="选择.env文件", filetypes=[("环境变量文件", "*.env"), ("所有文件", "*.*")]
)
if file_path:
env_var.set(file_path)
@@ -1105,6 +1130,7 @@ class ConfigEditor:
browse_env_btn = ttk.Button(env_frame, text="浏览", command=browse_env)
browse_env_btn.pack(side=tk.LEFT, padx=5)
def main():
root = tk.Tk()
_app = ConfigEditor(root)

View File

@@ -1,9 +1,10 @@
import time
import os
from typing import List, Optional, Dict, Any
from typing import Optional, Dict, Any
log_dir = "log/log_cycle_debug/"
class CycleDetail:
"""循环信息记录类"""
@@ -23,6 +24,7 @@ class CycleDetail:
def to_dict(self) -> Dict[str, Any]:
"""将循环信息转换为字典格式"""
def convert_to_serializable(obj, depth=0, seen=None):
if seen is None:
seen = set()
@@ -38,20 +40,24 @@ class CycleDetail:
seen.add(obj_id)
try:
if hasattr(obj, 'to_dict'):
if hasattr(obj, "to_dict"):
# 对于有to_dict方法的对象直接调用其to_dict方法
return obj.to_dict()
elif isinstance(obj, dict):
# 对于字典,只保留基本类型和可序列化的值
return {k: convert_to_serializable(v, depth + 1, seen)
return {
k: convert_to_serializable(v, depth + 1, seen)
for k, v in obj.items()
if isinstance(k, (str, int, float, bool))}
if isinstance(k, (str, int, float, bool))
}
elif isinstance(obj, (list, tuple)):
# 对于列表和元组,只保留可序列化的元素
return [convert_to_serializable(item, depth + 1, seen)
return [
convert_to_serializable(item, depth + 1, seen)
for item in obj
if not isinstance(item, (dict, list, tuple)) or
isinstance(item, (str, int, float, bool, type(None)))]
if not isinstance(item, (dict, list, tuple))
or isinstance(item, (str, int, float, bool, type(None)))
]
elif isinstance(obj, (str, int, float, bool, type(None))):
return obj
else:
@@ -80,7 +86,7 @@ class CycleDetail:
self.prefix = "group"
else:
# 只保留中文和英文字符
self.prefix = ''.join(char for char in self.prefix if '\u4e00' <= char <= '\u9fff' or char.isascii())
self.prefix = "".join(char for char in self.prefix if "\u4e00" <= char <= "\u9fff" or char.isascii())
if not self.prefix:
self.prefix = "group"
@@ -95,6 +101,7 @@ class CycleDetail:
os.makedirs(dir_name, exist_ok=True)
# 写入文件
import json
with open(file_path, "a", encoding="utf-8") as f:
f.write(json.dumps(self.to_dict(), ensure_ascii=False) + "\n")

View File

@@ -418,7 +418,9 @@ class HeartFChatting:
# 记录耗时
processor_time_costs[processor_name] = duration_since_parallel_start
except asyncio.TimeoutError:
logger.info(f"{self.log_prefix} 处理器 {processor_name} 超时(>{global_config.focus_chat.processor_max_time}s已跳过")
logger.info(
f"{self.log_prefix} 处理器 {processor_name} 超时(>{global_config.focus_chat.processor_max_time}s已跳过"
)
processor_time_costs[processor_name] = global_config.focus_chat.processor_max_time
except Exception as e:
logger.error(
@@ -478,24 +480,22 @@ class HeartFChatting:
processor_task = asyncio.create_task(self._process_processors(observations, []))
# 等待两个任务完成
running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(memory_task, processor_task)
running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(
memory_task, processor_task
)
else:
# 串行执行
with Timer("回忆", cycle_timers):
running_memorys = await self.memory_activator.activate_memory(observations)
with Timer("执行 信息处理器", cycle_timers):
all_plan_info, processor_time_costs = await self._process_processors(
observations, running_memorys
)
all_plan_info, processor_time_costs = await self._process_processors(observations, running_memorys)
loop_processor_info = {
"all_plan_info": all_plan_info,
"processor_time_costs": processor_time_costs,
}
with Timer("规划器", cycle_timers):
plan_result = await self.action_planner.plan(all_plan_info, running_memorys)

View File

@@ -30,7 +30,6 @@ class ActionModifier:
observations: Optional[List[Observation]] = None,
**kwargs: Any,
):
# 处理Observation对象
if observations:
# action_info = ActionInfo()
@@ -163,22 +162,34 @@ class ActionModifier:
if len(last_max_reply_num) >= max_reply_num and all(last_max_reply_num):
# 如果最近max_reply_num次都是reply直接移除
result["remove"].append("reply")
logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply直接移除")
logger.info(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply直接移除"
)
elif len(last_max_reply_num) >= sec_thres_reply_num and all(last_max_reply_num[-sec_thres_reply_num:]):
# 如果最近sec_thres_reply_num次都是reply40%概率移除
if random.random() < 0.4 / global_config.focus_chat.consecutive_replies:
result["remove"].append("reply")
logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.4 / global_config.focus_chat.consecutive_replies}概率移除,移除")
logger.info(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.4 / global_config.focus_chat.consecutive_replies}概率移除,移除"
)
else:
logger.debug(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.4 / global_config.focus_chat.consecutive_replies}概率移除,不移除")
logger.debug(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.4 / global_config.focus_chat.consecutive_replies}概率移除,不移除"
)
elif len(last_max_reply_num) >= one_thres_reply_num and all(last_max_reply_num[-one_thres_reply_num:]):
# 如果最近one_thres_reply_num次都是reply20%概率移除
if random.random() < 0.2 / global_config.focus_chat.consecutive_replies:
result["remove"].append("reply")
logger.info(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.2 / global_config.focus_chat.consecutive_replies}概率移除,移除")
logger.info(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.2 / global_config.focus_chat.consecutive_replies}概率移除,移除"
)
else:
logger.debug(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.2 / global_config.focus_chat.consecutive_replies}概率移除,不移除")
logger.debug(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.2 / global_config.focus_chat.consecutive_replies}概率移除,不移除"
)
else:
logger.debug(f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply无需移除")
logger.debug(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply无需移除"
)
return result

View File

@@ -29,14 +29,6 @@ def init_prompt():
{self_info_block}
{extra_info_block}
{memory_str}
你需要基于以下信息决定如何参与对话
这些信息可能会有冲突请你整合这些信息并选择一个最合适的action
{chat_content_block}
{mind_info_block}
{cycle_info_block}
请综合分析聊天内容和你看到的新消息参考聊天规划选择合适的action:
注意除了下面动作选项之外你在群聊里不能做其他任何事情这是你能力的边界现在请你选择合适的action:
{action_options_text}
@@ -46,6 +38,15 @@ def init_prompt():
{moderation_prompt}
你需要基于以下信息决定如何参与对话
这些信息可能会有冲突请你整合这些信息并选择一个最合适的action
{chat_content_block}
{mind_info_block}
{cycle_info_block}
请综合分析聊天内容和你看到的新消息参考聊天规划选择合适的action:
请你以下面格式输出你选择的action
{{
"action": "action_name",
@@ -270,7 +271,6 @@ class ActionPlanner:
) -> str:
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
try:
memory_str = ""
if global_config.focus_chat.parallel_processing:
memory_str = ""
@@ -279,9 +279,6 @@ class ActionPlanner:
for running_memory in running_memorys:
memory_str += f"{running_memory['topic']}: {running_memory['content']}\n"
chat_context_description = "你现在正在一个群聊中"
chat_target_name = None # Only relevant for private
if not is_group_chat and chat_target_info:

View File

@@ -42,5 +42,5 @@ class ActionObservation:
"observe_id": self.observe_id,
"last_observe_time": self.last_observe_time,
"all_actions": self.all_actions,
"all_using_actions": self.all_using_actions
"all_using_actions": self.all_using_actions,
}

View File

@@ -81,7 +81,7 @@ class ChattingObservation(Observation):
"person_list": self.person_list,
"oldest_messages_str": self.oldest_messages_str,
"compressor_prompt": self.compressor_prompt,
"last_observe_time": self.last_observe_time
"last_observe_time": self.last_observe_time,
}
async def initialize(self):
@@ -89,6 +89,8 @@ class ChattingObservation(Observation):
logger.debug(f"初始化observation: self.is_group_chat: {self.is_group_chat}")
logger.debug(f"初始化observation: self.chat_target_info: {self.chat_target_info}")
initial_messages = get_raw_msg_before_timestamp_with_chat(self.chat_id, self.last_observe_time, 10)
self.last_observe_time = initial_messages[-1]["time"] if initial_messages else self.last_observe_time
# logger.error(f"初始化observation: initial_messages: {initial_messages}\n\n\n\n{self.last_observe_time}")
self.talking_message = initial_messages
self.talking_message_str = await build_readable_messages(self.talking_message)

View File

@@ -68,10 +68,14 @@ class HFCloopObservation:
else:
action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n"
elif action_type == "no_reply":
action_detailed_str += f"{action_taken_time_str}时,你选择不回复(action:{action_type}){action_reasoning_str}\n"
action_detailed_str += (
f"{action_taken_time_str}时,你选择不回复(action:{action_type}){action_reasoning_str}\n"
)
else:
if is_taken:
action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}){action_reasoning_str}\n"
action_detailed_str += (
f"{action_taken_time_str}时,你选择执行了(action:{action_type}){action_reasoning_str}\n"
)
else:
action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n"
@@ -116,5 +120,5 @@ class HFCloopObservation:
"observe_id": self.observe_id,
"last_observe_time": self.last_observe_time,
# 不序列化history_loop避免循环引用
"history_loop_count": len(self.history_loop)
"history_loop_count": len(self.history_loop),
}

View File

@@ -18,7 +18,7 @@ class Observation:
return {
"observe_info": self.observe_info,
"observe_id": self.observe_id,
"last_observe_time": self.last_observe_time
"last_observe_time": self.last_observe_time,
}
async def observe(self):

View File

@@ -22,7 +22,7 @@ class StructureObservation:
"observe_id": self.observe_id,
"last_observe_time": self.last_observe_time,
"history_loop": self.history_loop,
"structured_info": self.structured_info
"structured_info": self.structured_info,
}
def get_observe_info(self):

View File

@@ -39,6 +39,10 @@ class WorkingMemoryObservation:
"observe_info": self.observe_info,
"observe_id": self.observe_id,
"last_observe_time": self.last_observe_time,
"working_memory": self.working_memory.to_dict() if hasattr(self.working_memory, 'to_dict') else str(self.working_memory),
"retrieved_working_memory": [item.to_dict() if hasattr(item, 'to_dict') else str(item) for item in self.retrieved_working_memory]
"working_memory": self.working_memory.to_dict()
if hasattr(self.working_memory, "to_dict")
else str(self.working_memory),
"retrieved_working_memory": [
item.to_dict() if hasattr(item, "to_dict") else str(item) for item in self.retrieved_working_memory
],
}

View File

@@ -171,8 +171,6 @@ class FocusChatProcessorConfig(ConfigBase):
"""是否启用轻量级聊天思维处理器可以节省token消耗和时间"""
@dataclass
class ExpressionConfig(ConfigBase):
"""表达配置类"""

View File

View File

@@ -0,0 +1 @@
from . import vtb_action # noqa

View File

@@ -0,0 +1,74 @@
from src.common.logger_manager import get_logger
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action
from typing import Tuple
logger = get_logger("vtb_action")
@register_action
class VTBAction(PluginAction):
"""VTB虚拟主播动作处理类"""
action_name = "vtb_action"
action_description = "使用虚拟主播预设动作表达心情或感觉,适用于需要生动表达情感的场景"
action_parameters = {
"text": "描述想要表达的心情或感觉的文本内容,必填,应当是对情感状态的自然描述",
}
action_require = [
"当需要表达特定情感或心情时使用",
"当用户明确要求使用虚拟主播动作时使用",
"当回应内容需要更生动的情感表达时使用",
"当想要通过预设动作增强互动体验时使用",
]
default = True # 设为默认动作
associated_types = ["vtb_text"]
async def process(self) -> Tuple[bool, str]:
"""处理VTB虚拟主播动作"""
logger.info(f"{self.log_prefix} 执行VTB动作: {self.reasoning}")
# 获取要表达的心情或感觉文本
text = self.action_data.get("text")
if not text:
logger.error(f"{self.log_prefix} 执行VTB动作时未提供文本内容")
return False, "执行VTB动作失败未提供文本内容"
# 处理文本使其更适合VTB动作表达
processed_text = self._process_text_for_vtb(text)
try:
# 发送VTB动作消息
await self.send_message(type="vtb_text", data=processed_text)
logger.info(f"{self.log_prefix} VTB动作执行成功文本内容: {processed_text}")
return True, "VTB动作执行成功"
except Exception as e:
logger.error(f"{self.log_prefix} 执行VTB动作时出错: {e}")
return False, f"执行VTB动作时出错: {e}"
def _process_text_for_vtb(self, text: str) -> str:
"""
处理文本使其更适合VTB动作表达
- 优化情感表达的准确性
- 规范化心情描述格式
- 确保文本适合虚拟主播动作系统理解
"""
# 简单示例实现
processed_text = text.strip()
# 移除多余的空格和换行
import re
processed_text = re.sub(r"\s+", " ", processed_text)
# 确保文本长度适中,避免过长的描述
if len(processed_text) > 100:
processed_text = processed_text[:100] + "..."
# 如果文本为空,提供默认的情感描述
if not processed_text:
processed_text = "平静"
return processed_text