This commit is contained in:
春河晴
2025-06-10 16:13:31 +09:00
parent 440e8bf7f3
commit 8d9a88a903
70 changed files with 1598 additions and 1642 deletions

View File

@@ -227,8 +227,6 @@ class DefaultExpressor:
logger.info(f"想要表达:{in_mind_reply}||理由:{reason}")
logger.info(f"最终回复: {content}\n")
except Exception as llm_e:
# 精简报错信息
logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}")

View File

@@ -113,25 +113,25 @@ class ExpressionLearner:
同时对所有已存储的表达方式进行全局衰减
"""
current_time = time.time()
# 全局衰减所有已存储的表达方式
for type in ["style", "grammar"]:
base_dir = os.path.join("data", "expression", f"learnt_{type}")
if not os.path.exists(base_dir):
continue
for chat_id in os.listdir(base_dir):
file_path = os.path.join(base_dir, chat_id, "expressions.json")
if not os.path.exists(file_path):
continue
try:
with open(file_path, "r", encoding="utf-8") as f:
expressions = json.load(f)
# 应用全局衰减
decayed_expressions = self.apply_decay_to_expressions(expressions, current_time)
# 保存衰减后的结果
with open(file_path, "w", encoding="utf-8") as f:
json.dump(decayed_expressions, f, ensure_ascii=False, indent=2)
@@ -162,23 +162,25 @@ class ExpressionLearner:
"""
if time_diff_days <= 0 or time_diff_days >= DECAY_DAYS:
return 0.001
# 使用二次函数进行插值
# 将7天作为顶点0天和30天作为两个端点
# 使用顶点式y = a(x-h)^2 + k其中(h,k)为顶点
h = 7.0 # 顶点x坐标
k = 0.001 # 顶点y坐标
# 计算a值使得x=0和x=30时y=0.001
# 0.001 = a(0-7)^2 + 0.001
# 解得a = 0
a = 0
# 计算衰减值
decay = a * (time_diff_days - h) ** 2 + k
return min(0.001, decay)
def apply_decay_to_expressions(self, expressions: List[Dict[str, Any]], current_time: float) -> List[Dict[str, Any]]:
def apply_decay_to_expressions(
self, expressions: List[Dict[str, Any]], current_time: float
) -> List[Dict[str, Any]]:
"""
对表达式列表应用衰减
返回衰减后的表达式列表移除count小于0的项
@@ -188,16 +190,16 @@ class ExpressionLearner:
# 确保last_active_time存在如果不存在则使用current_time
if "last_active_time" not in expr:
expr["last_active_time"] = current_time
last_active = expr["last_active_time"]
time_diff_days = (current_time - last_active) / (24 * 3600) # 转换为天
decay_value = self.calculate_decay_factor(time_diff_days)
expr["count"] = max(0.01, expr.get("count", 1) - decay_value)
if expr["count"] > 0:
result.append(expr)
return result
async def learn_and_store(self, type: str, num: int = 10) -> List[Tuple[str, str, str]]:
@@ -211,7 +213,7 @@ class ExpressionLearner:
type_str = "句法特点"
else:
raise ValueError(f"Invalid type: {type}")
res = await self.learn_expression(type, num)
if res is None:
@@ -238,15 +240,15 @@ class ExpressionLearner:
if chat_id not in chat_dict:
chat_dict[chat_id] = []
chat_dict[chat_id].append({"situation": situation, "style": style})
current_time = time.time()
# 存储到/data/expression/对应chat_id/expressions.json
for chat_id, expr_list in chat_dict.items():
dir_path = os.path.join("data", "expression", f"learnt_{type}", str(chat_id))
os.makedirs(dir_path, exist_ok=True)
file_path = os.path.join(dir_path, "expressions.json")
# 若已存在,先读出合并
old_data: List[Dict[str, Any]] = []
if os.path.exists(file_path):
@@ -255,10 +257,10 @@ class ExpressionLearner:
old_data = json.load(f)
except Exception:
old_data = []
# 应用衰减
# old_data = self.apply_decay_to_expressions(old_data, current_time)
# 合并逻辑
for new_expr in expr_list:
found = False
@@ -278,43 +280,43 @@ class ExpressionLearner:
new_expr["count"] = 1
new_expr["last_active_time"] = current_time
old_data.append(new_expr)
# 处理超限问题
if len(old_data) > MAX_EXPRESSION_COUNT:
# 计算每个表达方式的权重count的倒数这样count越小的越容易被选中
weights = [1 / (expr.get("count", 1) + 0.1) for expr in old_data]
# 随机选择要移除的表达方式,避免重复索引
remove_count = len(old_data) - MAX_EXPRESSION_COUNT
# 使用一种不会选到重复索引的方法
indices = list(range(len(old_data)))
# 方法1使用numpy.random.choice
# 把列表转成一个映射字典,保证不会有重复
remove_set = set()
total_attempts = 0
# 尝试按权重随机选择,直到选够数量
while len(remove_set) < remove_count and total_attempts < len(old_data) * 2:
idx = random.choices(indices, weights=weights, k=1)[0]
remove_set.add(idx)
total_attempts += 1
# 如果没选够,随机补充
if len(remove_set) < remove_count:
remaining = set(indices) - remove_set
remove_set.update(random.sample(list(remaining), remove_count - len(remove_set)))
remove_indices = list(remove_set)
# 从后往前删除,避免索引变化
for idx in sorted(remove_indices, reverse=True):
old_data.pop(idx)
with open(file_path, "w", encoding="utf-8") as f:
json.dump(old_data, f, ensure_ascii=False, indent=2)
return learnt_expressions
async def learn_expression(self, type: str, num: int = 10) -> Optional[Tuple[List[Tuple[str, str, str]], str]]:

View File

@@ -97,7 +97,7 @@ class CycleDetail:
)
# current_time_minute = time.strftime("%Y%m%d_%H%M", time.localtime())
# try:
# self.log_cycle_to_file(
# log_dir + self.prefix + f"/{current_time_minute}_cycle_" + str(self.cycle_id) + ".json"
@@ -117,7 +117,6 @@ class CycleDetail:
if dir_name and not os.path.exists(dir_name):
os.makedirs(dir_name, exist_ok=True)
# 写入文件
file_path = os.path.join(dir_name, os.path.basename(file_path))
# print("file_path:", file_path)

View File

@@ -99,22 +99,23 @@ class HeartFChatting:
self.stream_id: str = chat_id # 聊天流ID
self.chat_stream = chat_manager.get_stream(self.stream_id)
self.log_prefix = f"[{chat_manager.get_stream_name(self.stream_id) or self.stream_id}]"
self.memory_activator = MemoryActivator()
# 初始化观察器
self.observations: List[Observation] = []
self._register_observations()
# 根据配置文件和默认规则确定启用的处理器
config_processor_settings = global_config.focus_chat_processor
self.enabled_processor_names = []
for proc_name, (_proc_class, config_key) in PROCESSOR_CLASSES.items():
# 对于关系处理器,需要同时检查两个配置项
if proc_name == "RelationshipProcessor":
if (global_config.relationship.enable_relationship and
getattr(config_processor_settings, config_key, True)):
if global_config.relationship.enable_relationship and getattr(
config_processor_settings, config_key, True
):
self.enabled_processor_names.append(proc_name)
else:
# 其他处理器的原有逻辑
@@ -122,14 +123,13 @@ class HeartFChatting:
self.enabled_processor_names.append(proc_name)
# logger.info(f"{self.log_prefix} 将启用的处理器: {self.enabled_processor_names}")
self.processors: List[BaseProcessor] = []
self._register_default_processors()
self.expressor = DefaultExpressor(chat_stream=self.chat_stream)
self.replyer = DefaultReplyer(chat_stream=self.chat_stream)
self.action_manager = ActionManager()
self.action_planner = PlannerFactory.create_planner(
log_prefix=self.log_prefix, action_manager=self.action_manager
@@ -138,7 +138,6 @@ class HeartFChatting:
self.action_observation = ActionObservation(observe_id=self.stream_id)
self.action_observation.set_action_manager(self.action_manager)
self._processing_lock = asyncio.Lock()
# 循环控制内部状态
@@ -182,7 +181,13 @@ class HeartFChatting:
if processor_info:
processor_actual_class = processor_info[0] # 获取实际的类定义
# 根据处理器类名判断是否需要 subheartflow_id
if name in ["MindProcessor", "ToolProcessor", "WorkingMemoryProcessor", "SelfProcessor", "RelationshipProcessor"]:
if name in [
"MindProcessor",
"ToolProcessor",
"WorkingMemoryProcessor",
"SelfProcessor",
"RelationshipProcessor",
]:
self.processors.append(processor_actual_class(subheartflow_id=self.stream_id))
elif name == "ChattingInfoProcessor":
self.processors.append(processor_actual_class())
@@ -203,9 +208,7 @@ class HeartFChatting:
)
if self.processors:
logger.info(
f"{self.log_prefix} 已注册处理器: {[p.__class__.__name__ for p in self.processors]}"
)
logger.info(f"{self.log_prefix} 已注册处理器: {[p.__class__.__name__ for p in self.processors]}")
else:
logger.warning(f"{self.log_prefix} 没有注册任何处理器。这可能是由于配置错误或所有处理器都被禁用了。")
@@ -292,7 +295,9 @@ class HeartFChatting:
self._current_cycle_detail.set_loop_info(loop_info)
# 从observations列表中获取HFCloopObservation
hfcloop_observation = next((obs for obs in self.observations if isinstance(obs, HFCloopObservation)), None)
hfcloop_observation = next(
(obs for obs in self.observations if isinstance(obs, HFCloopObservation)), None
)
if hfcloop_observation:
hfcloop_observation.add_loop_info(self._current_cycle_detail)
else:
@@ -451,19 +456,19 @@ class HeartFChatting:
# 根据配置决定是否并行执行调整动作、回忆和处理器阶段
# 并行执行调整动作、回忆和处理器阶段
# 并行执行调整动作、回忆和处理器阶段
with Timer("并行调整动作、处理", cycle_timers):
# 创建并行任务
async def modify_actions_task():
async def modify_actions_task():
# 调用完整的动作修改流程
await self.action_modifier.modify_actions(
observations=self.observations,
)
await self.action_observation.observe()
self.observations.append(self.action_observation)
return True
# 创建三个并行任务
action_modify_task = asyncio.create_task(modify_actions_task())
memory_task = asyncio.create_task(self.memory_activator.activate_memory(self.observations))
@@ -474,9 +479,6 @@ class HeartFChatting:
action_modify_task, memory_task, processor_task
)
loop_processor_info = {
"all_plan_info": all_plan_info,
"processor_time_costs": processor_time_costs,
@@ -594,9 +596,7 @@ class HeartFChatting:
else:
success, reply_text = result
command = ""
logger.debug(
f"{self.log_prefix} 麦麦执行了'{action}', 返回结果'{success}', '{reply_text}', '{command}'"
)
logger.debug(f"{self.log_prefix} 麦麦执行了'{action}', 返回结果'{success}', '{reply_text}', '{command}'")
return success, reply_text, command

View File

@@ -51,8 +51,8 @@ async def _process_relationship(message: MessageRecv) -> None:
logger.info(f"首次认识用户: {nickname}")
await relationship_manager.first_knowing_some_one(platform, user_id, nickname, cardname)
# elif not await relationship_manager.is_qved_name(platform, user_id):
# logger.info(f"给用户({nickname},{cardname})取名: {nickname}")
# await relationship_manager.first_knowing_some_one(platform, user_id, nickname, cardname, "")
# logger.info(f"给用户({nickname},{cardname})取名: {nickname}")
# await relationship_manager.first_knowing_some_one(platform, user_id, nickname, cardname, "")
async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]:
@@ -74,7 +74,7 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]:
fast_retrieval=True,
)
logger.trace(f"记忆激活率: {interested_rate:.2f}")
text_len = len(message.processed_plain_text)
# 根据文本长度调整兴趣度长度越大兴趣度越高但增长率递减最低0.01最高0.05
# 采用对数函数实现递减增长
@@ -181,7 +181,6 @@ class HeartFCMessageReceiver:
userinfo = message.message_info.user_info
messageinfo = message.message_info
chat = await chat_manager.get_or_create_stream(
platform=messageinfo.platform,
user_info=userinfo,

View File

@@ -11,7 +11,6 @@ from datetime import datetime
from typing import Dict
from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config
import asyncio
logger = get_logger("processor")

View File

@@ -22,7 +22,7 @@ from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_ch
# 配置常量:是否启用小模型即时信息提取
# 开启时:使用小模型并行即时提取,速度更快,但精度可能略低
# 关闭时:使用原来的异步模式,精度更高但速度较慢
ENABLE_INSTANT_INFO_EXTRACTION = True
ENABLE_INSTANT_INFO_EXTRACTION = True
logger = get_logger("processor")
@@ -63,7 +63,7 @@ def init_prompt():
"""
Prompt(relationship_prompt, "relationship_prompt")
fetch_info_prompt = """
{name_block}
@@ -84,7 +84,6 @@ def init_prompt():
Prompt(fetch_info_prompt, "fetch_info_prompt")
class RelationshipProcessor(BaseProcessor):
log_prefix = "关系"
@@ -92,8 +91,10 @@ class RelationshipProcessor(BaseProcessor):
super().__init__()
self.subheartflow_id = subheartflow_id
self.info_fetching_cache: List[Dict[str, any]] = []
self.info_fetched_cache: Dict[str, Dict[str, any]] = {} # {person_id: {"info": str, "ttl": int, "start_time": float}}
self.info_fetching_cache: List[Dict[str, any]] = []
self.info_fetched_cache: Dict[
str, Dict[str, any]
] = {} # {person_id: {"info": str, "ttl": int, "start_time": float}}
self.person_engaged_cache: List[Dict[str, any]] = [] # [{person_id: str, start_time: float, rounds: int}]
self.grace_period_rounds = 5
@@ -101,7 +102,7 @@ class RelationshipProcessor(BaseProcessor):
model=global_config.model.relation,
request_type="focus.relationship",
)
# 小模型用于即时信息提取
if ENABLE_INSTANT_INFO_EXTRACTION:
self.instant_llm_model = LLMRequest(
@@ -156,26 +157,27 @@ class RelationshipProcessor(BaseProcessor):
for record in list(self.person_engaged_cache):
record["rounds"] += 1
time_elapsed = current_time - record["start_time"]
message_count = len(get_raw_msg_by_timestamp_with_chat(self.subheartflow_id, record["start_time"], current_time))
message_count = len(
get_raw_msg_by_timestamp_with_chat(self.subheartflow_id, record["start_time"], current_time)
)
print(record)
# 根据消息数量和时间设置不同的触发条件
should_trigger = (
message_count >= 50 or # 50条消息必定满足
(message_count >= 35 and time_elapsed >= 300) or # 35条且10分钟
(message_count >= 25 and time_elapsed >= 900) or # 25条且30分钟
(message_count >= 10 and time_elapsed >= 2000) # 10条且1小时
message_count >= 50 # 50条消息必定满足
or (message_count >= 35 and time_elapsed >= 300) # 35条且10分钟
or (message_count >= 25 and time_elapsed >= 900) # 25条且30分钟
or (message_count >= 10 and time_elapsed >= 2000) # 10条且1小时
)
if should_trigger:
logger.info(f"{self.log_prefix} 用户 {record['person_id']} 满足关系构建条件,开始构建关系。消息数:{message_count},时长:{time_elapsed:.0f}")
logger.info(
f"{self.log_prefix} 用户 {record['person_id']} 满足关系构建条件,开始构建关系。消息数:{message_count},时长:{time_elapsed:.0f}"
)
asyncio.create_task(
self.update_impression_on_cache_expiry(
record["person_id"],
self.subheartflow_id,
record["start_time"],
current_time
record["person_id"], self.subheartflow_id, record["start_time"], current_time
)
)
self.person_engaged_cache.remove(record)
@@ -187,20 +189,24 @@ class RelationshipProcessor(BaseProcessor):
if self.info_fetched_cache[person_id][info_type]["ttl"] <= 0:
# 在删除前查找匹配的info_fetching_cache记录
matched_record = None
min_time_diff = float('inf')
min_time_diff = float("inf")
for record in self.info_fetching_cache:
if (record["person_id"] == person_id and
record["info_type"] == info_type and
not record["forget"]):
time_diff = abs(record["start_time"] - self.info_fetched_cache[person_id][info_type]["start_time"])
if (
record["person_id"] == person_id
and record["info_type"] == info_type
and not record["forget"]
):
time_diff = abs(
record["start_time"] - self.info_fetched_cache[person_id][info_type]["start_time"]
)
if time_diff < min_time_diff:
min_time_diff = time_diff
matched_record = record
if matched_record:
matched_record["forget"] = True
logger.info(f"{self.log_prefix} 用户 {person_id}{info_type} 信息已过期,标记为遗忘。")
del self.info_fetched_cache[person_id][info_type]
if not self.info_fetched_cache[person_id]:
del self.info_fetched_cache[person_id]
@@ -208,7 +214,7 @@ class RelationshipProcessor(BaseProcessor):
# 5. 为需要处理的人员准备LLM prompt
nickname_str = ",".join(global_config.bot.alias_names)
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
info_cache_block = ""
if self.info_fetching_cache:
for info_fetching in self.info_fetching_cache:
@@ -223,7 +229,7 @@ class RelationshipProcessor(BaseProcessor):
chat_observe_info=chat_observe_info,
info_cache_block=info_cache_block,
)
try:
logger.debug(f"{self.log_prefix} 人物信息prompt: \n{prompt}\n")
content, _ = await self.llm_model.generate_response_async(prompt=prompt)
@@ -234,45 +240,47 @@ class RelationshipProcessor(BaseProcessor):
# 收集即时提取任务
instant_tasks = []
async_tasks = []
for person_name, info_type in content_json.items():
person_id = person_info_manager.get_person_id_by_person_name(person_name)
if person_id:
self.info_fetching_cache.append({
"person_id": person_id,
"person_name": person_name,
"info_type": info_type,
"start_time": time.time(),
"forget": False,
})
self.info_fetching_cache.append(
{
"person_id": person_id,
"person_name": person_name,
"info_type": info_type,
"start_time": time.time(),
"forget": False,
}
)
if len(self.info_fetching_cache) > 20:
self.info_fetching_cache.pop(0)
else:
logger.warning(f"{self.log_prefix} 未找到用户 {person_name} 的ID跳过调取信息。")
continue
logger.info(f"{self.log_prefix} 调取用户 {person_name}{info_type} 信息。")
# 检查person_engaged_cache中是否已存在该person_id
person_exists = any(record["person_id"] == person_id for record in self.person_engaged_cache)
if not person_exists:
self.person_engaged_cache.append({
"person_id": person_id,
"start_time": time.time(),
"rounds": 0
})
self.person_engaged_cache.append(
{"person_id": person_id, "start_time": time.time(), "rounds": 0}
)
if ENABLE_INSTANT_INFO_EXTRACTION:
# 收集即时提取任务
instant_tasks.append((person_id, info_type, time.time()))
else:
# 使用原来的异步模式
async_tasks.append(asyncio.create_task(self.fetch_person_info(person_id, [info_type], start_time=time.time())))
async_tasks.append(
asyncio.create_task(self.fetch_person_info(person_id, [info_type], start_time=time.time()))
)
# 执行即时提取任务
if ENABLE_INSTANT_INFO_EXTRACTION and instant_tasks:
await self._execute_instant_extraction_batch(instant_tasks)
# 启动异步任务(如果不是即时模式)
if async_tasks:
# 异步任务不需要等待完成
@@ -300,7 +308,7 @@ class RelationshipProcessor(BaseProcessor):
person_infos_str += f"你不了解{person_name}有关[{info_type}]的信息,不要胡乱回答,你可以直接说你不知道,或者你忘记了;"
if person_infos_str:
persons_infos_str += f"你对 {person_name} 的了解:{person_infos_str}\n"
# 处理正在调取但还没有结果的项目(只在非即时提取模式下显示)
if not ENABLE_INSTANT_INFO_EXTRACTION:
pending_info_dict = {}
@@ -312,50 +320,47 @@ class RelationshipProcessor(BaseProcessor):
person_id = record["person_id"]
person_name = record["person_name"]
info_type = record["info_type"]
# 检查是否已经在info_fetched_cache中有结果
if (person_id in self.info_fetched_cache and
info_type in self.info_fetched_cache[person_id]):
if person_id in self.info_fetched_cache and info_type in self.info_fetched_cache[person_id]:
continue
# 按人物组织正在调取的信息
if person_name not in pending_info_dict:
pending_info_dict[person_name] = []
pending_info_dict[person_name].append(info_type)
# 添加正在调取的信息到返回字符串
for person_name, info_types in pending_info_dict.items():
info_types_str = "".join(info_types)
persons_infos_str += f"你正在识图回忆有关 {person_name}{info_types_str} 信息,稍等一下再回答...\n"
return persons_infos_str
async def _execute_instant_extraction_batch(self, instant_tasks: list):
"""
批量执行即时提取任务
"""
if not instant_tasks:
return
logger.info(f"{self.log_prefix} [即时提取] 开始批量提取 {len(instant_tasks)} 个信息")
# 创建所有提取任务
extraction_tasks = []
for person_id, info_type, start_time in instant_tasks:
# 检查缓存中是否已存在且未过期的信息
if (person_id in self.info_fetched_cache and
info_type in self.info_fetched_cache[person_id]):
if person_id in self.info_fetched_cache and info_type in self.info_fetched_cache[person_id]:
logger.info(f"{self.log_prefix} 用户 {person_id}{info_type} 信息已存在且未过期,跳过调取。")
continue
task = asyncio.create_task(self._fetch_single_info_instant(person_id, info_type, start_time))
extraction_tasks.append(task)
# 并行执行所有提取任务并等待完成
if extraction_tasks:
await asyncio.gather(*extraction_tasks, return_exceptions=True)
logger.info(f"{self.log_prefix} [即时提取] 批量提取完成")
async def _fetch_single_info_instant(self, person_id: str, info_type: str, start_time: float):
"""
@@ -363,24 +368,21 @@ class RelationshipProcessor(BaseProcessor):
"""
nickname_str = ",".join(global_config.bot.alias_names)
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
person_name = await person_info_manager.get_value(person_id, "person_name")
person_impression = await person_info_manager.get_value(person_id, "impression")
if not person_impression:
impression_block = "你对ta没有什么深刻的印象"
else:
impression_block = f"{person_impression}"
points = await person_info_manager.get_value(person_id, "points")
if points:
points_text = "\n".join([
f"{point[2]}:{point[0]}"
for point in points
])
points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points])
else:
points_text = "你不记得ta最近发生了什么"
prompt = (await global_prompt_manager.get_prompt_async("fetch_info_prompt")).format(
name_block=name_block,
info_type=info_type,
@@ -393,9 +395,9 @@ class RelationshipProcessor(BaseProcessor):
try:
# 使用小模型进行即时提取
content, _ = await self.instant_llm_model.generate_response_async(prompt=prompt)
logger.info(f"{self.log_prefix} [即时提取] {person_name}{info_type} 结果: {content}")
if content:
content_json = json.loads(repair_json(content))
if info_type in content_json:
@@ -410,7 +412,9 @@ class RelationshipProcessor(BaseProcessor):
"person_name": person_name,
"unknow": False,
}
logger.info(f"{self.log_prefix} [即时提取] 成功获取 {person_name}{info_type}: {info_content}")
logger.info(
f"{self.log_prefix} [即时提取] 成功获取 {person_name}{info_type}: {info_content}"
)
else:
if person_id not in self.info_fetched_cache:
self.info_fetched_cache[person_id] = {}
@@ -423,59 +427,55 @@ class RelationshipProcessor(BaseProcessor):
}
logger.info(f"{self.log_prefix} [即时提取] {person_name}{info_type} 信息不明确")
else:
logger.warning(f"{self.log_prefix} [即时提取] 小模型返回空结果,获取 {person_name}{info_type} 信息失败。")
logger.warning(
f"{self.log_prefix} [即时提取] 小模型返回空结果,获取 {person_name}{info_type} 信息失败。"
)
except Exception as e:
logger.error(f"{self.log_prefix} [即时提取] 执行小模型请求获取用户信息时出错: {e}")
logger.error(traceback.format_exc())
async def fetch_person_info(self, person_id: str, info_types: list[str], start_time: float):
"""
获取某个人的信息
"""
# 检查缓存中是否已存在且未过期的信息
info_types_to_fetch = []
for info_type in info_types:
if (person_id in self.info_fetched_cache and
info_type in self.info_fetched_cache[person_id]):
if person_id in self.info_fetched_cache and info_type in self.info_fetched_cache[person_id]:
logger.info(f"{self.log_prefix} 用户 {person_id}{info_type} 信息已存在且未过期,跳过调取。")
continue
info_types_to_fetch.append(info_type)
if not info_types_to_fetch:
return
nickname_str = ",".join(global_config.bot.alias_names)
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
person_name = await person_info_manager.get_value(person_id, "person_name")
info_type_str = ""
info_json_str = ""
for info_type in info_types_to_fetch:
info_type_str += f"{info_type},"
info_json_str += f"\"{info_type}\": \"信息内容\","
info_json_str += f'"{info_type}": "信息内容",'
info_type_str = info_type_str[:-1]
info_json_str = info_json_str[:-1]
person_impression = await person_info_manager.get_value(person_id, "impression")
if not person_impression:
impression_block = "你对ta没有什么深刻的印象"
else:
impression_block = f"{person_impression}"
points = await person_info_manager.get_value(person_id, "points")
if points:
points_text = "\n".join([
f"{point[2]}:{point[0]}"
for point in points
])
points_text = "\n".join([f"{point[2]}:{point[0]}" for point in points])
else:
points_text = "你不记得ta最近发生了什么"
prompt = (await global_prompt_manager.get_prompt_async("fetch_info_prompt")).format(
name_block=name_block,
info_type=info_type_str,
@@ -487,10 +487,10 @@ class RelationshipProcessor(BaseProcessor):
try:
content, _ = await self.llm_model.generate_response_async(prompt=prompt)
# logger.info(f"{self.log_prefix} fetch_person_info prompt: \n{prompt}\n")
logger.info(f"{self.log_prefix} fetch_person_info 结果: {content}")
if content:
try:
content_json = json.loads(repair_json(content))
@@ -508,9 +508,9 @@ class RelationshipProcessor(BaseProcessor):
else:
if person_id not in self.info_fetched_cache:
self.info_fetched_cache[person_id] = {}
self.info_fetched_cache[person_id][info_type] = {
"info":"unknow",
"info": "unknow",
"ttl": 10,
"start_time": start_time,
"person_name": person_name,
@@ -525,16 +525,12 @@ class RelationshipProcessor(BaseProcessor):
logger.error(f"{self.log_prefix} 执行LLM请求获取用户信息时出错: {e}")
logger.error(traceback.format_exc())
async def update_impression_on_cache_expiry(
self, person_id: str, chat_id: str, start_time: float, end_time: float
):
async def update_impression_on_cache_expiry(self, person_id: str, chat_id: str, start_time: float, end_time: float):
"""
在缓存过期时,获取聊天记录并更新用户印象
"""
logger.info(f"缓存过期,开始为 {person_id} 更新印象。时间范围:{start_time} -> {end_time}")
try:
impression_messages = get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time)
if impression_messages:
logger.info(f"{person_id} 获取到 {len(impression_messages)} 条消息用于印象更新。")

View File

@@ -122,9 +122,7 @@ class SelfProcessor(BaseProcessor):
)
# 获取聊天内容
chat_observe_info = observation.get_observe_info()
person_list = observation.person_list
if isinstance(observation, HFCloopObservation):
# hfcloop_observe_info = observation.get_observe_info()
pass
nickname_str = ""
@@ -133,9 +131,7 @@ class SelfProcessor(BaseProcessor):
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
personality_block = individuality.get_personality_prompt(x_person=2, level=2)
identity_block = individuality.get_identity_prompt(x_person=2, level=2)
prompt = (await global_prompt_manager.get_prompt_async("indentify_prompt")).format(

View File

@@ -118,7 +118,7 @@ class ToolProcessor(BaseProcessor):
is_group_chat = observation.is_group_chat
chat_observe_info = observation.get_observe_info()
person_list = observation.person_list
# person_list = observation.person_list
memory_str = ""
if running_memorys:
@@ -141,9 +141,7 @@ class ToolProcessor(BaseProcessor):
# 调用LLM专注于工具使用
# logger.info(f"开始执行工具调用{prompt}")
response, other_info = await self.llm_model.generate_response_async(
prompt=prompt, tools=tools
)
response, other_info = await self.llm_model.generate_response_async(prompt=prompt, tools=tools)
if len(other_info) == 3:
reasoning_content, model_name, tool_calls = other_info

View File

@@ -118,9 +118,7 @@ class WorkingMemoryProcessor(BaseProcessor):
memory_str=memory_choose_str,
)
# print(f"prompt: {prompt}")
# 调用LLM处理记忆
content = ""

View File

@@ -90,7 +90,7 @@ class MemoryActivator:
# 如果记忆系统被禁用,直接返回空列表
if not global_config.memory.enable_memory:
return []
obs_info_text = ""
for observation in observations:
if isinstance(observation, ChattingObservation):

View File

@@ -5,9 +5,6 @@ from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor
from src.chat.message_receive.chat_stream import ChatStream
from src.common.logger_manager import get_logger
import importlib
import pkgutil
import os
# 不再需要导入动作类因为已经在main.py中导入
# import src.chat.actions.default_actions # noqa
@@ -41,7 +38,7 @@ class ActionManager:
# 初始化时将默认动作加载到使用中的动作
self._using_actions = self._default_actions.copy()
# 添加系统核心动作
self._add_system_core_actions()
@@ -63,19 +60,19 @@ class ActionManager:
action_require: list[str] = getattr(action_class, "action_require", [])
associated_types: list[str] = getattr(action_class, "associated_types", [])
is_enabled: bool = getattr(action_class, "enable_plugin", True)
# 获取激活类型相关属性
focus_activation_type: str = getattr(action_class, "focus_activation_type", "always")
normal_activation_type: str = getattr(action_class, "normal_activation_type", "always")
random_probability: float = getattr(action_class, "random_activation_probability", 0.3)
llm_judge_prompt: str = getattr(action_class, "llm_judge_prompt", "")
activation_keywords: list[str] = getattr(action_class, "activation_keywords", [])
keyword_case_sensitive: bool = getattr(action_class, "keyword_case_sensitive", False)
# 获取模式启用属性
mode_enable: str = getattr(action_class, "mode_enable", "all")
# 获取并行执行属性
parallel_action: bool = getattr(action_class, "parallel_action", False)
@@ -114,13 +111,13 @@ class ActionManager:
def _load_plugin_actions(self) -> None:
"""
加载所有插件目录中的动作
注意插件动作的实际导入已经在main.py中完成这里只需要从_ACTION_REGISTRY获取
"""
try:
# 插件动作已在main.py中加载这里只需要从_ACTION_REGISTRY获取
self._load_registered_actions()
logger.info(f"从注册表加载插件动作成功")
logger.info("从注册表加载插件动作成功")
except Exception as e:
logger.error(f"加载插件动作失败: {e}")
@@ -203,25 +200,25 @@ class ActionManager:
def get_using_actions_for_mode(self, mode: str) -> Dict[str, ActionInfo]:
"""
根据聊天模式获取可用的动作集合
Args:
mode: 聊天模式 ("focus", "normal", "all")
Returns:
Dict[str, ActionInfo]: 在指定模式下可用的动作集合
"""
filtered_actions = {}
for action_name, action_info in self._using_actions.items():
action_mode = action_info.get("mode_enable", "all")
# 检查动作是否在当前模式下启用
if action_mode == "all" or action_mode == mode:
filtered_actions[action_name] = action_info
logger.debug(f"动作 {action_name} 在模式 {mode} 下可用 (mode_enable: {action_mode})")
else:
logger.debug(f"动作 {action_name} 在模式 {mode} 下不可用 (mode_enable: {action_mode})")
logger.debug(f"模式 {mode} 下可用动作: {list(filtered_actions.keys())}")
return filtered_actions
@@ -325,7 +322,7 @@ class ActionManager:
系统核心动作是那些enable_plugin为False但是系统必需的动作
"""
system_core_actions = ["exit_focus_chat"] # 可以根据需要扩展
for action_name in system_core_actions:
if action_name in self._registered_actions and action_name not in self._using_actions:
self._using_actions[action_name] = self._registered_actions[action_name]
@@ -334,10 +331,10 @@ class ActionManager:
def add_system_action_if_needed(self, action_name: str) -> bool:
"""
根据需要添加系统动作到使用集
Args:
action_name: 动作名称
Returns:
bool: 是否成功添加
"""

View File

@@ -30,13 +30,13 @@ class ActionModifier:
"""初始化动作处理器"""
self.action_manager = action_manager
self.all_actions = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
# 用于LLM判定的小模型
self.llm_judge = LLMRequest(
model=global_config.model.utils_small,
request_type="action.judge",
)
# 缓存相关属性
self._llm_judge_cache = {} # 缓存LLM判定结果
self._cache_expiry_time = 30 # 缓存过期时间(秒)
@@ -49,15 +49,15 @@ class ActionModifier:
):
"""
完整的动作修改流程,整合传统观察处理和新的激活类型判定
这个方法处理完整的动作管理流程:
1. 基于观察的传统动作修改(循环历史分析、类型匹配等)
2. 基于激活类型的智能动作判定,最终确定可用动作集
处理后ActionManager 将包含最终的可用动作集,供规划器直接使用
"""
logger.debug(f"{self.log_prefix}开始完整动作修改流程")
# === 第一阶段:传统观察处理 ===
if observations:
hfc_obs = None
@@ -86,7 +86,7 @@ class ActionModifier:
merged_action_changes["add"].extend(action_changes["add"])
merged_action_changes["remove"].extend(action_changes["remove"])
reasons.append("基于循环历史分析")
# 详细记录循环历史分析的变更原因
for action_name in action_changes["add"]:
logger.info(f"{self.log_prefix}添加动作: {action_name},原因: 循环历史分析建议添加")
@@ -106,7 +106,9 @@ class ActionModifier:
if not chat_context.check_types(data["associated_types"]):
type_mismatched_actions.append(action_name)
associated_types_str = ", ".join(data["associated_types"])
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: 关联类型不匹配(需要: {associated_types_str}")
logger.info(
f"{self.log_prefix}移除动作: {action_name},原因: 关联类型不匹配(需要: {associated_types_str}"
)
if type_mismatched_actions:
# 合并到移除列表中
@@ -123,17 +125,19 @@ class ActionModifier:
self.action_manager.remove_action_from_using(action_name)
logger.debug(f"{self.log_prefix}应用移除动作: {action_name},原因集合: {reasons}")
logger.info(f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}")
logger.info(
f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}"
)
# === 第二阶段:激活类型判定 ===
# 如果提供了聊天上下文,则进行激活类型判定
if chat_content is not None:
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
# 获取当前使用的动作集经过第一阶段处理且适用于FOCUS模式
current_using_actions = self.action_manager.get_using_actions()
all_registered_actions = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
# 构建完整的动作信息
current_actions_with_info = {}
for action_name in current_using_actions.keys():
@@ -141,17 +145,17 @@ class ActionModifier:
current_actions_with_info[action_name] = all_registered_actions[action_name]
else:
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
# 应用激活类型判定
final_activated_actions = await self._apply_activation_type_filtering(
current_actions_with_info,
chat_content,
)
# 更新ActionManager移除未激活的动作
actions_to_remove = []
removal_reasons = {}
for action_name in current_using_actions.keys():
if action_name not in final_activated_actions:
actions_to_remove.append(action_name)
@@ -159,7 +163,7 @@ class ActionModifier:
if action_name in all_registered_actions:
action_info = all_registered_actions[action_name]
activation_type = action_info.get("focus_activation_type", ActionActivationType.ALWAYS)
if activation_type == ActionActivationType.RANDOM:
probability = action_info.get("random_probability", 0.3)
removal_reasons[action_name] = f"RANDOM类型未触发概率{probability}"
@@ -172,15 +176,17 @@ class ActionModifier:
removal_reasons[action_name] = "激活判定未通过"
else:
removal_reasons[action_name] = "动作信息不完整"
for action_name in actions_to_remove:
self.action_manager.remove_action_from_using(action_name)
reason = removal_reasons.get(action_name, "未知原因")
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: {reason}")
logger.info(f"{self.log_prefix}激活类型判定完成,最终可用动作: {list(final_activated_actions.keys())}")
logger.info(f"{self.log_prefix}完整动作修改流程结束,最终动作集: {list(self.action_manager.get_using_actions().keys())}")
logger.info(
f"{self.log_prefix}完整动作修改流程结束,最终动作集: {list(self.action_manager.get_using_actions().keys())}"
)
async def _apply_activation_type_filtering(
self,
@@ -189,27 +195,27 @@ class ActionModifier:
) -> Dict[str, Any]:
"""
应用激活类型过滤逻辑,支持四种激活类型的并行处理
Args:
actions_with_info: 带完整信息的动作字典
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文信息
extra_context: 额外的上下文信息
Returns:
Dict[str, Any]: 过滤后激活的actions字典
"""
activated_actions = {}
# 分类处理不同激活类型的actions
always_actions = {}
random_actions = {}
llm_judge_actions = {}
keyword_actions = {}
for action_name, action_info in actions_with_info.items():
activation_type = action_info.get("focus_activation_type", ActionActivationType.ALWAYS)
if activation_type == ActionActivationType.ALWAYS:
always_actions[action_name] = action_info
elif activation_type == ActionActivationType.RANDOM:
@@ -220,12 +226,12 @@ class ActionModifier:
keyword_actions[action_name] = action_info
else:
logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理")
# 1. 处理ALWAYS类型直接激活
for action_name, action_info in always_actions.items():
activated_actions[action_name] = action_info
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: ALWAYS类型直接激活")
# 2. 处理RANDOM类型
for action_name, action_info in random_actions.items():
probability = action_info.get("random_probability", 0.3)
@@ -235,7 +241,7 @@ class ActionModifier:
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: RANDOM类型触发概率{probability}")
else:
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: RANDOM类型未触发概率{probability}")
# 3. 处理KEYWORD类型快速判定
for action_name, action_info in keyword_actions.items():
should_activate = self._check_keyword_activation(
@@ -250,7 +256,7 @@ class ActionModifier:
else:
keywords = action_info.get("activation_keywords", [])
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: KEYWORD类型未匹配关键词{keywords}")
# 4. 处理LLM_JUDGE类型并行判定
if llm_judge_actions:
# 直接并行处理所有LLM判定actions
@@ -258,7 +264,7 @@ class ActionModifier:
llm_judge_actions,
chat_content,
)
# 添加激活的LLM判定actions
for action_name, should_activate in llm_results.items():
if should_activate:
@@ -266,46 +272,43 @@ class ActionModifier:
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: LLM_JUDGE类型判定通过")
else:
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: LLM_JUDGE类型判定未通过")
logger.debug(f"{self.log_prefix}激活类型过滤完成: {list(activated_actions.keys())}")
return activated_actions
async def process_actions_for_planner(
self,
observed_messages_str: str = "",
chat_context: Optional[str] = None,
extra_context: Optional[str] = None
self, observed_messages_str: str = "", chat_context: Optional[str] = None, extra_context: Optional[str] = None
) -> Dict[str, Any]:
"""
[已废弃] 此方法现在已被整合到 modify_actions() 中
为了保持向后兼容性而保留,但建议直接使用 ActionManager.get_using_actions()
规划器应该直接从 ActionManager 获取最终的可用动作集,而不是调用此方法
新的架构:
1. 主循环调用 modify_actions() 处理完整的动作管理流程
2. 规划器直接使用 ActionManager.get_using_actions() 获取最终动作集
"""
logger.warning(f"{self.log_prefix}process_actions_for_planner() 已废弃,建议规划器直接使用 ActionManager.get_using_actions()")
logger.warning(
f"{self.log_prefix}process_actions_for_planner() 已废弃,建议规划器直接使用 ActionManager.get_using_actions()"
)
# 为了向后兼容,仍然返回当前使用的动作集
current_using_actions = self.action_manager.get_using_actions()
all_registered_actions = self.action_manager.get_registered_actions()
# 构建完整的动作信息
result = {}
for action_name in current_using_actions.keys():
if action_name in all_registered_actions:
result[action_name] = all_registered_actions[action_name]
return result
def _generate_context_hash(self, chat_content: str) -> str:
"""生成上下文的哈希值用于缓存"""
context_content = f"{chat_content}"
return hashlib.md5(context_content.encode('utf-8')).hexdigest()
return hashlib.md5(context_content.encode("utf-8")).hexdigest()
async def _process_llm_judge_actions_parallel(
self,
@@ -314,85 +317,85 @@ class ActionModifier:
) -> Dict[str, bool]:
"""
并行处理LLM判定actions支持智能缓存
Args:
llm_judge_actions: 需要LLM判定的actions
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文
extra_context: 额外上下文
Returns:
Dict[str, bool]: action名称到激活结果的映射
"""
# 生成当前上下文的哈希值
current_context_hash = self._generate_context_hash(chat_content)
current_time = time.time()
results = {}
tasks_to_run = {}
# 检查缓存
for action_name, action_info in llm_judge_actions.items():
cache_key = f"{action_name}_{current_context_hash}"
# 检查是否有有效的缓存
if (cache_key in self._llm_judge_cache and
current_time - self._llm_judge_cache[cache_key]["timestamp"] < self._cache_expiry_time):
if (
cache_key in self._llm_judge_cache
and current_time - self._llm_judge_cache[cache_key]["timestamp"] < self._cache_expiry_time
):
results[action_name] = self._llm_judge_cache[cache_key]["result"]
logger.debug(f"{self.log_prefix}使用缓存结果 {action_name}: {'激活' if results[action_name] else '未激活'}")
logger.debug(
f"{self.log_prefix}使用缓存结果 {action_name}: {'激活' if results[action_name] else '未激活'}"
)
else:
# 需要进行LLM判定
tasks_to_run[action_name] = action_info
# 如果有需要运行的任务,并行执行
if tasks_to_run:
logger.debug(f"{self.log_prefix}并行执行LLM判定任务数: {len(tasks_to_run)}")
# 创建并行任务
tasks = []
task_names = []
for action_name, action_info in tasks_to_run.items():
task = self._llm_judge_action(
action_name,
action_info,
chat_content,
action_name,
action_info,
chat_content,
)
tasks.append(task)
task_names.append(action_name)
# 并行执行所有任务
try:
task_results = await asyncio.gather(*tasks, return_exceptions=True)
# 处理结果并更新缓存
for i, (action_name, result) in enumerate(zip(task_names, task_results)):
for _, (action_name, result) in enumerate(zip(task_names, task_results)):
if isinstance(result, Exception):
logger.error(f"{self.log_prefix}LLM判定action {action_name} 时出错: {result}")
results[action_name] = False
else:
results[action_name] = result
# 更新缓存
cache_key = f"{action_name}_{current_context_hash}"
self._llm_judge_cache[cache_key] = {
"result": result,
"timestamp": current_time
}
self._llm_judge_cache[cache_key] = {"result": result, "timestamp": current_time}
logger.debug(f"{self.log_prefix}并行LLM判定完成耗时: {time.time() - current_time:.2f}s")
except Exception as e:
logger.error(f"{self.log_prefix}并行LLM判定失败: {e}")
# 如果并行执行失败为所有任务返回False
for action_name in tasks_to_run.keys():
results[action_name] = False
# 清理过期缓存
self._cleanup_expired_cache(current_time)
return results
def _cleanup_expired_cache(self, current_time: float):
@@ -401,40 +404,39 @@ class ActionModifier:
for cache_key, cache_data in self._llm_judge_cache.items():
if current_time - cache_data["timestamp"] > self._cache_expiry_time:
expired_keys.append(cache_key)
for key in expired_keys:
del self._llm_judge_cache[key]
if expired_keys:
logger.debug(f"{self.log_prefix}清理了 {len(expired_keys)} 个过期缓存条目")
async def _llm_judge_action(
self,
action_name: str,
self,
action_name: str,
action_info: Dict[str, Any],
chat_content: str = "",
) -> bool:
"""
使用LLM判定是否应该激活某个action
Args:
action_name: 动作名称
action_info: 动作信息
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文
extra_context: 额外上下文
Returns:
bool: 是否应该激活此action
"""
try:
# 构建判定提示词
action_description = action_info.get("description", "")
action_require = action_info.get("require", [])
custom_prompt = action_info.get("llm_judge_prompt", "")
# 构建基础判定提示词
base_prompt = f"""
你需要判断在当前聊天情况下,是否应该激活名为"{action_name}"的动作。
@@ -445,34 +447,34 @@ class ActionModifier:
"""
for req in action_require:
base_prompt += f"- {req}\n"
if custom_prompt:
base_prompt += f"\n额外判定条件:\n{custom_prompt}\n"
if chat_content:
base_prompt += f"\n当前聊天记录:\n{chat_content}\n"
base_prompt += """
请根据以上信息判断是否应该激活这个动作。
只需要回答"""",不要有其他内容。
"""
# 调用LLM进行判定
response, _ = await self.llm_judge.generate_response_async(prompt=base_prompt)
# 解析响应
response = response.strip().lower()
# print(base_prompt)
print(f"LLM判定动作 {action_name}:响应='{response}'")
should_activate = "" in response or "yes" in response or "true" in response
logger.debug(f"{self.log_prefix}LLM判定动作 {action_name}:响应='{response}',结果={'激活' if should_activate else '不激活'}")
logger.debug(
f"{self.log_prefix}LLM判定动作 {action_name}:响应='{response}',结果={'激活' if should_activate else '不激活'}"
)
return should_activate
except Exception as e:
logger.error(f"{self.log_prefix}LLM判定动作 {action_name} 时出错: {e}")
# 出错时默认不激活
@@ -486,45 +488,45 @@ class ActionModifier:
) -> bool:
"""
检查是否匹配关键词触发条件
Args:
action_name: 动作名称
action_info: 动作信息
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文
extra_context: 额外上下文
Returns:
bool: 是否应该激活此action
"""
activation_keywords = action_info.get("activation_keywords", [])
case_sensitive = action_info.get("keyword_case_sensitive", False)
if not activation_keywords:
logger.warning(f"{self.log_prefix}动作 {action_name} 设置为关键词触发但未配置关键词")
return False
# 构建检索文本
search_text = ""
if chat_content:
search_text += chat_content
# if chat_context:
# search_text += f" {chat_context}"
# search_text += f" {chat_context}"
# if extra_context:
# search_text += f" {extra_context}"
# search_text += f" {extra_context}"
# 如果不区分大小写,转换为小写
if not case_sensitive:
search_text = search_text.lower()
# 检查每个关键词
matched_keywords = []
for keyword in activation_keywords:
check_keyword = keyword if case_sensitive else keyword.lower()
if check_keyword in search_text:
matched_keywords.append(keyword)
if matched_keywords:
logger.debug(f"{self.log_prefix}动作 {action_name} 匹配到关键词: {matched_keywords}")
return True
@@ -568,7 +570,9 @@ class ActionModifier:
result["remove"].append("no_reply")
result["remove"].append("reply")
no_reply_ratio = no_reply_count / len(recent_cycles)
logger.info(f"{self.log_prefix}检测到高no_reply比例: {no_reply_ratio:.2f}达到退出聊天阈值将添加exit_focus_chat并移除no_reply/reply动作")
logger.info(
f"{self.log_prefix}检测到高no_reply比例: {no_reply_ratio:.2f}达到退出聊天阈值将添加exit_focus_chat并移除no_reply/reply动作"
)
# 计算连续回复的相关阈值
@@ -593,7 +597,7 @@ 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")
reply_count = len(last_max_reply_num) - no_reply_count
# reply_count = len(last_max_reply_num) - no_reply_count
logger.info(
f"{self.log_prefix}移除reply动作原因: 连续回复过多(最近{len(last_max_reply_num)}次全是reply超过阈值{max_reply_num}"
)
@@ -622,8 +626,6 @@ class ActionModifier:
f"{self.log_prefix}连续回复检测:最近{one_thres_reply_num}次全是reply{removal_probability:.2f}概率移除,未触发"
)
else:
logger.debug(
f"{self.log_prefix}连续回复检测无需移除reply动作最近回复模式正常"
)
logger.debug(f"{self.log_prefix}连续回复检测无需移除reply动作最近回复模式正常")
return result

View File

@@ -146,7 +146,7 @@ class ActionPlanner(BasePlanner):
# 注意动作的激活判定现在在主循环的modify_actions中完成
# 使用Focus模式过滤动作
current_available_actions_dict = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
# 获取完整的动作信息
all_registered_actions = self.action_manager.get_registered_actions()
current_available_actions = {}
@@ -192,12 +192,11 @@ class ActionPlanner(BasePlanner):
try:
prompt = f"{prompt}"
llm_content, (reasoning_content, _) = await self.planner_llm.generate_response_async(prompt=prompt)
logger.info(f"{self.log_prefix}规划器原始提示词: {prompt}")
logger.info(f"{self.log_prefix}规划器原始响应: {llm_content}")
logger.info(f"{self.log_prefix}规划器推理: {reasoning_content}")
except Exception as req_e:
logger.error(f"{self.log_prefix}LLM 请求执行失败: {req_e}")
reasoning = f"LLM 请求失败,你的模型出现问题: {req_e}"
@@ -237,10 +236,10 @@ class ActionPlanner(BasePlanner):
extra_info_block = ""
action_data["extra_info_block"] = extra_info_block
if relation_info:
action_data["relation_info_block"] = relation_info
# 对于reply动作不需要额外处理因为相关字段已经在上面的循环中添加到action_data
if extracted_action not in current_available_actions:
@@ -303,12 +302,11 @@ class ActionPlanner(BasePlanner):
) -> str:
"""构建 Planner LLM 的提示词 (获取模板并填充数据)"""
try:
if relation_info_block:
relation_info_block = f"以下是你和别人的关系描述:\n{relation_info_block}"
else:
relation_info_block = ""
memory_str = ""
if running_memorys:
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
@@ -331,9 +329,9 @@ class ActionPlanner(BasePlanner):
# mind_info_block = ""
# if current_mind:
# mind_info_block = f"对聊天的规划:{current_mind}"
# mind_info_block = f"对聊天的规划:{current_mind}"
# else:
# mind_info_block = "你刚参与聊天"
# mind_info_block = "你刚参与聊天"
personality_block = individuality.get_prompt(x_person=2, level=2)
@@ -351,16 +349,14 @@ class ActionPlanner(BasePlanner):
param_text = "\n"
for param_name, param_description in using_actions_info["parameters"].items():
param_text += f' "{param_name}":"{param_description}"\n'
param_text = param_text.rstrip('\n')
param_text = param_text.rstrip("\n")
else:
param_text = ""
require_text = ""
for require_item in using_actions_info["require"]:
require_text += f"- {require_item}\n"
require_text = require_text.rstrip('\n')
require_text = require_text.rstrip("\n")
using_action_prompt = using_action_prompt.format(
action_name=using_actions_name,

View File

@@ -93,7 +93,7 @@ class DefaultReplyer:
self.chat_id = chat_stream.stream_id
self.chat_stream = chat_stream
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id)
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id)
async def _create_thinking_message(self, anchor_message: Optional[MessageRecv], thinking_id: str):
"""创建思考消息 (尝试锚定到 anchor_message)"""
@@ -141,7 +141,7 @@ class DefaultReplyer:
# text_part = action_data.get("text", [])
# if text_part:
sent_msg_list = []
with Timer("生成回复", cycle_timers):
# 可以保留原有的文本处理逻辑或进行适当调整
reply = await self.reply(
@@ -240,22 +240,21 @@ class DefaultReplyer:
# current_temp = float(global_config.model.normal["temp"]) * arousal_multiplier
# self.express_model.params["temperature"] = current_temp # 动态调整温度
reply_to = action_data.get("reply_to", "none")
sender = ""
targer = ""
if ":" in reply_to or "" in reply_to:
# 使用正则表达式匹配中文或英文冒号
parts = re.split(pattern=r'[:]', string=reply_to, maxsplit=1)
parts = re.split(pattern=r"[:]", string=reply_to, maxsplit=1)
if len(parts) == 2:
sender = parts[0].strip()
targer = parts[1].strip()
identity = action_data.get("identity", "")
extra_info_block = action_data.get("extra_info_block", "")
relation_info_block = action_data.get("relation_info_block", "")
# 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_focus(
@@ -374,8 +373,6 @@ class DefaultReplyer:
style_habbits_str = "\n".join(style_habbits)
grammar_habbits_str = "\n".join(grammar_habbits)
# 关键词检测与反应
keywords_reaction_prompt = ""
@@ -407,16 +404,15 @@ class DefaultReplyer:
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
# logger.debug("开始构建 focus prompt")
if sender_name:
reply_target_block = f"现在{sender_name}说的:{target_message}。引起了你的注意,你想要在群里发言或者回复这条消息。"
reply_target_block = (
f"现在{sender_name}说的:{target_message}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif target_message:
reply_target_block = f"现在{target_message}引起了你的注意,你想要在群里发言或者回复这条消息。"
else:
reply_target_block = "现在,你想要在群里发言或者回复消息。"
# --- Choose template based on chat type ---
if is_group_chat:
@@ -665,30 +661,30 @@ def find_similar_expressions(input_text: str, expressions: List[Dict], top_k: in
"""使用TF-IDF和余弦相似度找出与输入文本最相似的top_k个表达方式"""
if not expressions:
return []
# 准备文本数据
texts = [expr['situation'] for expr in expressions]
texts = [expr["situation"] for expr in expressions]
texts.append(input_text) # 添加输入文本
# 使用TF-IDF向量化
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(texts)
# 计算余弦相似度
similarity_matrix = cosine_similarity(tfidf_matrix)
# 获取输入文本的相似度分数(最后一行)
scores = similarity_matrix[-1][:-1] # 排除与自身的相似度
# 获取top_k的索引
top_indices = np.argsort(scores)[::-1][:top_k]
# 获取相似表达
similar_exprs = []
for idx in top_indices:
if scores[idx] > 0: # 只保留有相似度的
similar_exprs.append(expressions[idx])
return similar_exprs