mirror of https://github.com/Mai-with-u/MaiBot.git
1188 lines
50 KiB
Python
1188 lines
50 KiB
Python
import traceback
|
||
import time
|
||
import asyncio
|
||
import random
|
||
import re
|
||
|
||
from typing import List, Optional, Dict, Any, Tuple
|
||
from datetime import datetime
|
||
from src.common.logger import get_logger
|
||
from src.common.data_models.database_data_model import DatabaseMessages
|
||
from src.common.data_models.info_data_model import ActionPlannerInfo
|
||
from src.common.data_models.llm_data_model import LLMGenerationDataModel
|
||
from src.config.config import global_config, model_config
|
||
from src.llm_models.utils_model import LLMRequest
|
||
from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageSending
|
||
from src.chat.message_receive.chat_stream import ChatStream
|
||
from src.chat.message_receive.uni_message_sender import UniversalMessageSender
|
||
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
|
||
from src.chat.utils.utils import get_chat_type_and_target_info
|
||
from src.chat.utils.prompt_builder import global_prompt_manager
|
||
from src.mood.mood_manager import mood_manager
|
||
from src.chat.utils.chat_message_builder import (
|
||
build_readable_messages,
|
||
get_raw_msg_before_timestamp_with_chat,
|
||
replace_user_references,
|
||
)
|
||
from src.express.expression_selector import expression_selector
|
||
from src.plugin_system.apis.message_api import translate_pid_to_description
|
||
|
||
# from src.memory_system.memory_activator import MemoryActivator
|
||
from src.person_info.person_info import Person
|
||
from src.plugin_system.base.component_types import ActionInfo, EventType
|
||
from src.plugin_system.apis import llm_api
|
||
|
||
from src.chat.replyer.prompt.lpmm_prompt import init_lpmm_prompt
|
||
from src.chat.replyer.prompt.replyer_prompt import init_replyer_prompt
|
||
from src.chat.replyer.prompt.rewrite_prompt import init_rewrite_prompt
|
||
from src.memory_system.memory_retrieval import init_memory_retrieval_prompt, build_memory_retrieval_prompt
|
||
from src.jargon.jargon_explainer import explain_jargon_in_context
|
||
|
||
init_lpmm_prompt()
|
||
init_replyer_prompt()
|
||
init_rewrite_prompt()
|
||
init_memory_retrieval_prompt()
|
||
|
||
|
||
logger = get_logger("replyer")
|
||
|
||
|
||
class DefaultReplyer:
|
||
def __init__(
|
||
self,
|
||
chat_stream: ChatStream,
|
||
request_type: str = "replyer",
|
||
):
|
||
self.express_model = LLMRequest(model_set=model_config.model_task_config.replyer, request_type=request_type)
|
||
self.chat_stream = chat_stream
|
||
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_stream.stream_id)
|
||
self.heart_fc_sender = UniversalMessageSender()
|
||
|
||
from src.plugin_system.core.tool_use import ToolExecutor # 延迟导入ToolExecutor,不然会循环依赖
|
||
|
||
self.tool_executor = ToolExecutor(chat_id=self.chat_stream.stream_id, enable_cache=True, cache_ttl=3)
|
||
|
||
async def generate_reply_with_context(
|
||
self,
|
||
extra_info: str = "",
|
||
reply_reason: str = "",
|
||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||
chosen_actions: Optional[List[ActionPlannerInfo]] = None,
|
||
enable_tool: bool = True,
|
||
from_plugin: bool = True,
|
||
stream_id: Optional[str] = None,
|
||
reply_message: Optional[DatabaseMessages] = None,
|
||
reply_time_point: Optional[float] = time.time(),
|
||
) -> Tuple[bool, LLMGenerationDataModel]:
|
||
# sourcery skip: merge-nested-ifs
|
||
"""
|
||
回复器 (Replier): 负责生成回复文本的核心逻辑。
|
||
|
||
Args:
|
||
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||
extra_info: 额外信息,用于补充上下文
|
||
reply_reason: 回复原因
|
||
available_actions: 可用的动作信息字典
|
||
chosen_actions: 已选动作
|
||
enable_tool: 是否启用工具调用
|
||
from_plugin: 是否来自插件
|
||
|
||
Returns:
|
||
Tuple[bool, Optional[Dict[str, Any]], Optional[str]]: (是否成功, 生成的回复, 使用的prompt)
|
||
"""
|
||
|
||
prompt = None
|
||
selected_expressions: Optional[List[int]] = None
|
||
llm_response = LLMGenerationDataModel()
|
||
if available_actions is None:
|
||
available_actions = {}
|
||
try:
|
||
# 3. 构建 Prompt
|
||
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
||
prompt, selected_expressions = await self.build_prompt_reply_context(
|
||
extra_info=extra_info,
|
||
available_actions=available_actions,
|
||
chosen_actions=chosen_actions,
|
||
enable_tool=enable_tool,
|
||
reply_message=reply_message,
|
||
reply_reason=reply_reason,
|
||
reply_time_point=reply_time_point,
|
||
)
|
||
llm_response.prompt = prompt
|
||
llm_response.selected_expressions = selected_expressions
|
||
|
||
if not prompt:
|
||
logger.warning("构建prompt失败,跳过回复生成")
|
||
return False, llm_response
|
||
from src.plugin_system.core.events_manager import events_manager
|
||
|
||
if not from_plugin:
|
||
continue_flag, modified_message = await events_manager.handle_mai_events(
|
||
EventType.POST_LLM, None, prompt, None, stream_id=stream_id
|
||
)
|
||
if not continue_flag:
|
||
raise UserWarning("插件于请求前中断了内容生成")
|
||
if modified_message and modified_message._modify_flags.modify_llm_prompt:
|
||
llm_response.prompt = modified_message.llm_prompt
|
||
prompt = str(modified_message.llm_prompt)
|
||
|
||
# 4. 调用 LLM 生成回复
|
||
content = None
|
||
reasoning_content = None
|
||
model_name = "unknown_model"
|
||
|
||
try:
|
||
content, reasoning_content, model_name, tool_call = await self.llm_generate_content(prompt)
|
||
# logger.debug(f"replyer生成内容: {content}")
|
||
|
||
logger.info(f"replyer生成内容: {content}")
|
||
if global_config.debug.show_replyer_reasoning:
|
||
logger.info(f"replyer生成推理:\n{reasoning_content}")
|
||
logger.info(f"replyer生成模型: {model_name}")
|
||
|
||
llm_response.content = content
|
||
llm_response.reasoning = reasoning_content
|
||
llm_response.model = model_name
|
||
llm_response.tool_calls = tool_call
|
||
continue_flag, modified_message = await events_manager.handle_mai_events(
|
||
EventType.AFTER_LLM, None, prompt, llm_response, stream_id=stream_id
|
||
)
|
||
if not from_plugin and not continue_flag:
|
||
raise UserWarning("插件于请求后取消了内容生成")
|
||
if modified_message:
|
||
if modified_message._modify_flags.modify_llm_prompt:
|
||
logger.warning("警告:插件在内容生成后才修改了prompt,此修改不会生效")
|
||
llm_response.prompt = modified_message.llm_prompt # 虽然我不知道为什么在这里需要改prompt
|
||
if modified_message._modify_flags.modify_llm_response_content:
|
||
llm_response.content = modified_message.llm_response_content
|
||
if modified_message._modify_flags.modify_llm_response_reasoning:
|
||
llm_response.reasoning = modified_message.llm_response_reasoning
|
||
except UserWarning as e:
|
||
raise e
|
||
except Exception as llm_e:
|
||
# 精简报错信息
|
||
logger.error(f"LLM 生成失败: {llm_e}")
|
||
return False, llm_response # LLM 调用失败则无法生成回复
|
||
|
||
return True, llm_response
|
||
|
||
except UserWarning as uw:
|
||
raise uw
|
||
except Exception as e:
|
||
logger.error(f"回复生成意外失败: {e}")
|
||
traceback.print_exc()
|
||
return False, llm_response
|
||
|
||
async def rewrite_reply_with_context(
|
||
self,
|
||
raw_reply: str = "",
|
||
reason: str = "",
|
||
reply_to: str = "",
|
||
) -> Tuple[bool, LLMGenerationDataModel]:
|
||
"""
|
||
表达器 (Expressor): 负责重写和优化回复文本。
|
||
|
||
Args:
|
||
raw_reply: 原始回复内容
|
||
reason: 回复原因
|
||
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||
relation_info: 关系信息
|
||
|
||
Returns:
|
||
Tuple[bool, Optional[str]]: (是否成功, 重写后的回复内容)
|
||
"""
|
||
llm_response = LLMGenerationDataModel()
|
||
try:
|
||
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
||
prompt = await self.build_prompt_rewrite_context(
|
||
raw_reply=raw_reply,
|
||
reason=reason,
|
||
reply_to=reply_to,
|
||
)
|
||
llm_response.prompt = prompt
|
||
|
||
content = None
|
||
reasoning_content = None
|
||
model_name = "unknown_model"
|
||
if not prompt:
|
||
logger.error("Prompt 构建失败,无法生成回复。")
|
||
return False, llm_response
|
||
|
||
try:
|
||
content, reasoning_content, model_name, _ = await self.llm_generate_content(prompt)
|
||
logger.info(f"想要表达:{raw_reply}||理由:{reason}||生成回复: {content}\n")
|
||
llm_response.content = content
|
||
llm_response.reasoning = reasoning_content
|
||
llm_response.model = model_name
|
||
|
||
except Exception as llm_e:
|
||
# 精简报错信息
|
||
logger.error(f"LLM 生成失败: {llm_e}")
|
||
return False, llm_response # LLM 调用失败则无法生成回复
|
||
|
||
return True, llm_response
|
||
|
||
except Exception as e:
|
||
logger.error(f"回复生成意外失败: {e}")
|
||
traceback.print_exc()
|
||
return False, llm_response
|
||
|
||
async def build_expression_habits(
|
||
self, chat_history: str, target: str, reply_reason: str = ""
|
||
) -> Tuple[str, List[int]]:
|
||
# sourcery skip: for-append-to-extend
|
||
"""构建表达习惯块
|
||
|
||
Args:
|
||
chat_history: 聊天历史记录
|
||
target: 目标消息内容
|
||
reply_reason: planner给出的回复理由
|
||
|
||
Returns:
|
||
str: 表达习惯信息字符串
|
||
"""
|
||
# 检查是否允许在此聊天流中使用表达
|
||
use_expression, _, _ = global_config.expression.get_expression_config_for_chat(self.chat_stream.stream_id)
|
||
if not use_expression:
|
||
return "", []
|
||
style_habits = []
|
||
# 使用从处理器传来的选中表达方式
|
||
# 使用模型预测选择表达方式
|
||
selected_expressions, selected_ids = await expression_selector.select_suitable_expressions(
|
||
self.chat_stream.stream_id, chat_history, max_num=8, target_message=target, reply_reason=reply_reason
|
||
)
|
||
|
||
if selected_expressions:
|
||
logger.debug(f"使用处理器选中的{len(selected_expressions)}个表达方式")
|
||
for expr in selected_expressions:
|
||
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
|
||
style_habits.append(f"当{expr['situation']}时,使用 {expr['style']}")
|
||
else:
|
||
logger.debug("没有从处理器获得表达方式,将使用空的表达方式")
|
||
# 不再在replyer中进行随机选择,全部交给处理器处理
|
||
|
||
style_habits_str = "\n".join(style_habits)
|
||
|
||
# 动态构建expression habits块
|
||
expression_habits_block = ""
|
||
expression_habits_title = ""
|
||
if style_habits_str.strip():
|
||
expression_habits_title = "在回复时,你可以参考以下的语言习惯,不要生硬使用:"
|
||
expression_habits_block += f"{style_habits_str}\n"
|
||
|
||
return f"{expression_habits_title}\n{expression_habits_block}", selected_ids
|
||
|
||
async def build_mood_state_prompt(self) -> str:
|
||
"""构建情绪状态提示"""
|
||
if not global_config.mood.enable_mood:
|
||
return ""
|
||
mood_state = await mood_manager.get_mood_by_chat_id(self.chat_stream.stream_id).get_mood()
|
||
return f"你现在的心情是:{mood_state}"
|
||
|
||
async def build_tool_info(self, chat_history: str, sender: str, target: str, enable_tool: bool = True) -> str:
|
||
"""构建工具信息块
|
||
|
||
Args:
|
||
chat_history: 聊天历史记录
|
||
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||
enable_tool: 是否启用工具调用
|
||
|
||
Returns:
|
||
str: 工具信息字符串
|
||
"""
|
||
|
||
if not enable_tool:
|
||
return ""
|
||
|
||
try:
|
||
# 使用工具执行器获取信息
|
||
tool_results, _, _ = await self.tool_executor.execute_from_chat_message(
|
||
sender=sender, target_message=target, chat_history=chat_history, return_details=False
|
||
)
|
||
|
||
if tool_results:
|
||
tool_info_str = "以下是你通过工具获取到的实时信息:\n"
|
||
for tool_result in tool_results:
|
||
tool_name = tool_result.get("tool_name", "unknown")
|
||
content = tool_result.get("content", "")
|
||
_result_type = tool_result.get("type", "tool_result")
|
||
|
||
tool_info_str += f"- 【{tool_name}】: {content}\n"
|
||
|
||
tool_info_str += "以上是你获取到的实时信息,请在回复时参考这些信息。"
|
||
logger.info(f"获取到 {len(tool_results)} 个工具结果")
|
||
|
||
return tool_info_str
|
||
else:
|
||
logger.debug("未获取到任何工具结果")
|
||
return ""
|
||
|
||
except Exception as e:
|
||
logger.error(f"工具信息获取失败: {e}")
|
||
return ""
|
||
|
||
def _parse_reply_target(self, target_message: Optional[str]) -> Tuple[str, str]:
|
||
"""解析回复目标消息
|
||
|
||
Args:
|
||
target_message: 目标消息,格式为 "发送者:消息内容" 或 "发送者:消息内容"
|
||
|
||
Returns:
|
||
Tuple[str, str]: (发送者名称, 消息内容)
|
||
"""
|
||
sender = ""
|
||
target = ""
|
||
# 添加None检查,防止NoneType错误
|
||
if target_message is None:
|
||
return sender, target
|
||
if ":" in target_message or ":" in target_message:
|
||
# 使用正则表达式匹配中文或英文冒号
|
||
parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1)
|
||
if len(parts) == 2:
|
||
sender = parts[0].strip()
|
||
target = parts[1].strip()
|
||
return sender, target
|
||
|
||
def _replace_picids_with_descriptions(self, text: str) -> str:
|
||
"""将文本中的[picid:xxx]替换为具体的图片描述
|
||
|
||
Args:
|
||
text: 包含picid标记的文本
|
||
|
||
Returns:
|
||
替换后的文本
|
||
"""
|
||
# 匹配 [picid:xxxxx] 格式
|
||
pic_pattern = r"\[picid:([^\]]+)\]"
|
||
|
||
def replace_pic_id(match: re.Match) -> str:
|
||
pic_id = match.group(1)
|
||
description = translate_pid_to_description(pic_id)
|
||
return f"[图片:{description}]"
|
||
|
||
return re.sub(pic_pattern, replace_pic_id, text)
|
||
|
||
def _analyze_target_content(self, target: str) -> Tuple[bool, bool, str, str]:
|
||
"""分析target内容类型(基于原始picid格式)
|
||
|
||
Args:
|
||
target: 目标消息内容(包含[picid:xxx]格式)
|
||
|
||
Returns:
|
||
Tuple[bool, bool, str, str]: (是否只包含图片, 是否包含文字, 图片部分, 文字部分)
|
||
"""
|
||
if not target or not target.strip():
|
||
return False, False, "", ""
|
||
|
||
# 检查是否只包含picid标记
|
||
picid_pattern = r"\[picid:[^\]]+\]"
|
||
picid_matches = re.findall(picid_pattern, target)
|
||
|
||
# 移除所有picid标记后检查是否还有文字内容
|
||
text_without_picids = re.sub(picid_pattern, "", target).strip()
|
||
|
||
has_only_pics = len(picid_matches) > 0 and not text_without_picids
|
||
has_text = bool(text_without_picids)
|
||
|
||
# 提取图片部分(转换为[图片:描述]格式)
|
||
pic_part = ""
|
||
if picid_matches:
|
||
pic_descriptions = []
|
||
for picid_match in picid_matches:
|
||
pic_id = picid_match[7:-1] # 提取picid:xxx中的xxx部分(从第7个字符开始)
|
||
description = translate_pid_to_description(pic_id)
|
||
logger.info(f"图片ID: {pic_id}, 描述: {description}")
|
||
# 如果description已经是[图片]格式,直接使用;否则包装为[图片:描述]格式
|
||
if description == "[图片]":
|
||
pic_descriptions.append(description)
|
||
else:
|
||
pic_descriptions.append(f"[图片:{description}]")
|
||
pic_part = "".join(pic_descriptions)
|
||
|
||
return has_only_pics, has_text, pic_part, text_without_picids
|
||
|
||
async def build_keywords_reaction_prompt(self, target: Optional[str]) -> str:
|
||
"""构建关键词反应提示
|
||
|
||
Args:
|
||
target: 目标消息内容
|
||
|
||
Returns:
|
||
str: 关键词反应提示字符串
|
||
"""
|
||
# 关键词检测与反应
|
||
keywords_reaction_prompt = ""
|
||
try:
|
||
# 添加None检查,防止NoneType错误
|
||
if target is None:
|
||
return keywords_reaction_prompt
|
||
|
||
# 处理关键词规则
|
||
for rule in global_config.keyword_reaction.keyword_rules:
|
||
if any(keyword in target for keyword in rule.keywords):
|
||
logger.info(f"检测到关键词规则:{rule.keywords},触发反应:{rule.reaction}")
|
||
keywords_reaction_prompt += f"{rule.reaction},"
|
||
|
||
# 处理正则表达式规则
|
||
for rule in global_config.keyword_reaction.regex_rules:
|
||
for pattern_str in rule.regex:
|
||
try:
|
||
pattern = re.compile(pattern_str)
|
||
if result := pattern.search(target):
|
||
reaction = rule.reaction
|
||
for name, content in result.groupdict().items():
|
||
reaction = reaction.replace(f"[{name}]", content)
|
||
logger.info(f"匹配到正则表达式:{pattern_str},触发反应:{reaction}")
|
||
keywords_reaction_prompt += f"{reaction},"
|
||
break
|
||
except re.error as e:
|
||
logger.error(f"正则表达式编译错误: {pattern_str}, 错误信息: {str(e)}")
|
||
continue
|
||
except Exception as e:
|
||
logger.error(f"关键词检测与反应时发生异常: {str(e)}", exc_info=True)
|
||
|
||
return keywords_reaction_prompt
|
||
|
||
async def _time_and_run_task(self, coroutine, name: str) -> Tuple[str, Any, float]:
|
||
"""计时并运行异步任务的辅助函数
|
||
|
||
Args:
|
||
coroutine: 要执行的协程
|
||
name: 任务名称
|
||
|
||
Returns:
|
||
Tuple[str, Any, float]: (任务名称, 任务结果, 执行耗时)
|
||
"""
|
||
start_time = time.time()
|
||
result = await coroutine
|
||
end_time = time.time()
|
||
duration = end_time - start_time
|
||
return name, result, duration
|
||
|
||
def build_chat_history_prompts(
|
||
self, message_list_before_now: List[DatabaseMessages], target_user_id: str, sender: str
|
||
) -> Tuple[str, str]:
|
||
"""
|
||
|
||
Args:
|
||
message_list_before_now: 历史消息列表
|
||
target_user_id: 目标用户ID(当前对话对象)
|
||
|
||
Returns:
|
||
Tuple[str, str]: (核心对话prompt, 背景对话prompt)
|
||
"""
|
||
# 构建背景对话 prompt
|
||
all_dialogue_prompt = ""
|
||
if message_list_before_now:
|
||
latest_msgs = message_list_before_now[-int(global_config.chat.max_context_size) :]
|
||
all_dialogue_prompt = build_readable_messages(
|
||
latest_msgs,
|
||
replace_bot_name=True,
|
||
timestamp_mode="normal_no_YMD",
|
||
truncate=True,
|
||
)
|
||
|
||
return all_dialogue_prompt
|
||
|
||
def core_background_build_chat_history_prompts(
|
||
self, message_list_before_now: List[DatabaseMessages], target_user_id: str, sender: str
|
||
) -> Tuple[str, str]:
|
||
"""
|
||
|
||
Args:
|
||
message_list_before_now: 历史消息列表
|
||
target_user_id: 目标用户ID(当前对话对象)
|
||
|
||
Returns:
|
||
Tuple[str, str]: (核心对话prompt, 背景对话prompt)
|
||
"""
|
||
core_dialogue_list: List[DatabaseMessages] = []
|
||
bot_id = str(global_config.bot.qq_account)
|
||
|
||
# 过滤消息:分离bot和目标用户的对话 vs 其他用户的对话
|
||
for msg in message_list_before_now:
|
||
try:
|
||
msg_user_id = str(msg.user_info.user_id)
|
||
reply_to = msg.reply_to
|
||
_platform, reply_to_user_id = self._parse_reply_target(reply_to)
|
||
if (msg_user_id == bot_id and reply_to_user_id == target_user_id) or msg_user_id == target_user_id:
|
||
# bot 和目标用户的对话
|
||
core_dialogue_list.append(msg)
|
||
except Exception as e:
|
||
logger.error(f"处理消息记录时出错: {msg}, 错误: {e}")
|
||
|
||
# 构建核心对话 prompt
|
||
core_dialogue_prompt = ""
|
||
if core_dialogue_list:
|
||
# 检查最新五条消息中是否包含bot自己说的消息
|
||
latest_5_messages = core_dialogue_list[-5:] if len(core_dialogue_list) >= 5 else core_dialogue_list
|
||
has_bot_message = any(str(msg.user_info.user_id) == bot_id for msg in latest_5_messages)
|
||
|
||
# logger.info(f"最新五条消息:{latest_5_messages}")
|
||
# logger.info(f"最新五条消息中是否包含bot自己说的消息:{has_bot_message}")
|
||
|
||
# 如果最新五条消息中不包含bot的消息,则返回空字符串
|
||
if not has_bot_message:
|
||
core_dialogue_prompt = ""
|
||
else:
|
||
core_dialogue_list = core_dialogue_list[
|
||
-int(global_config.chat.max_context_size * 0.6) :
|
||
] # 限制消息数量
|
||
|
||
core_dialogue_prompt_str = build_readable_messages(
|
||
core_dialogue_list,
|
||
replace_bot_name=True,
|
||
timestamp_mode="normal_no_YMD",
|
||
read_mark=0.0,
|
||
truncate=True,
|
||
show_actions=True,
|
||
)
|
||
core_dialogue_prompt = f"""--------------------------------
|
||
这是上述中你和{sender}的对话摘要,内容从上面的对话中截取,便于你理解:
|
||
{core_dialogue_prompt_str}
|
||
--------------------------------
|
||
"""
|
||
|
||
# 构建背景对话 prompt
|
||
all_dialogue_prompt = ""
|
||
if message_list_before_now:
|
||
latest_25_msgs = message_list_before_now[-int(global_config.chat.max_context_size) :]
|
||
all_dialogue_prompt_str = build_readable_messages(
|
||
latest_25_msgs,
|
||
replace_bot_name=True,
|
||
timestamp_mode="normal_no_YMD",
|
||
truncate=True,
|
||
)
|
||
if core_dialogue_prompt:
|
||
all_dialogue_prompt = f"所有用户的发言:\n{all_dialogue_prompt_str}"
|
||
else:
|
||
all_dialogue_prompt = f"{all_dialogue_prompt_str}"
|
||
|
||
return core_dialogue_prompt, all_dialogue_prompt
|
||
|
||
async def build_actions_prompt(
|
||
self, available_actions: Dict[str, ActionInfo], chosen_actions_info: Optional[List[ActionPlannerInfo]] = None
|
||
) -> str:
|
||
"""构建动作提示"""
|
||
|
||
action_descriptions = ""
|
||
skip_names = ["emoji", "build_memory", "build_relation", "reply"]
|
||
if available_actions:
|
||
action_descriptions = "除了进行回复之外,你可以做以下这些动作,不过这些动作由另一个模型决定,:\n"
|
||
for action_name, action_info in available_actions.items():
|
||
if action_name in skip_names:
|
||
continue
|
||
action_description = action_info.description
|
||
action_descriptions += f"- {action_name}: {action_description}\n"
|
||
action_descriptions += "\n"
|
||
|
||
chosen_action_descriptions = ""
|
||
if chosen_actions_info:
|
||
for action_plan_info in chosen_actions_info:
|
||
action_name = action_plan_info.action_type
|
||
if action_name in skip_names:
|
||
continue
|
||
action_description: str = "无描述"
|
||
reasoning: str = "无原因"
|
||
if action := available_actions.get(action_name):
|
||
action_description = action.description or action_description
|
||
reasoning = action_plan_info.reasoning or reasoning
|
||
|
||
chosen_action_descriptions += f"- {action_name}: {action_description},原因:{reasoning}\n"
|
||
|
||
if chosen_action_descriptions:
|
||
action_descriptions += "根据聊天情况,另一个模型决定在回复的同时做以下这些动作:\n"
|
||
action_descriptions += chosen_action_descriptions
|
||
|
||
return action_descriptions
|
||
|
||
async def build_personality_prompt(self) -> str:
|
||
bot_name = global_config.bot.nickname
|
||
if global_config.bot.alias_names:
|
||
bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}"
|
||
else:
|
||
bot_nickname = ""
|
||
|
||
# 获取基础personality
|
||
prompt_personality = global_config.personality.personality
|
||
|
||
# 检查是否需要随机替换为状态
|
||
if (
|
||
global_config.personality.states
|
||
and global_config.personality.state_probability > 0
|
||
and random.random() < global_config.personality.state_probability
|
||
):
|
||
# 随机选择一个状态替换personality
|
||
selected_state = random.choice(global_config.personality.states)
|
||
prompt_personality = selected_state
|
||
|
||
prompt_personality = f"{prompt_personality};"
|
||
return f"你的名字是{bot_name}{bot_nickname},你{prompt_personality}"
|
||
|
||
def _parse_chat_prompt_config_to_chat_id(self, chat_prompt_str: str) -> Optional[tuple[str, str]]:
|
||
"""
|
||
解析聊天prompt配置字符串并生成对应的 chat_id 和 prompt内容
|
||
|
||
Args:
|
||
chat_prompt_str: 格式为 "platform:id:type:prompt内容" 的字符串
|
||
|
||
Returns:
|
||
tuple: (chat_id, prompt_content),如果解析失败则返回 None
|
||
"""
|
||
try:
|
||
# 使用 split 分割,但限制分割次数为3,因为prompt内容可能包含冒号
|
||
parts = chat_prompt_str.split(":", 3)
|
||
if len(parts) != 4:
|
||
return None
|
||
|
||
platform = parts[0]
|
||
id_str = parts[1]
|
||
stream_type = parts[2]
|
||
prompt_content = parts[3]
|
||
|
||
# 判断是否为群聊
|
||
is_group = stream_type == "group"
|
||
|
||
# 使用与 ChatStream.get_stream_id 相同的逻辑生成 chat_id
|
||
import hashlib
|
||
|
||
if is_group:
|
||
components = [platform, str(id_str)]
|
||
else:
|
||
components = [platform, str(id_str), "private"]
|
||
key = "_".join(components)
|
||
chat_id = hashlib.md5(key.encode()).hexdigest()
|
||
|
||
return chat_id, prompt_content
|
||
|
||
except (ValueError, IndexError):
|
||
return None
|
||
|
||
def get_chat_prompt_for_chat(self, chat_id: str) -> str:
|
||
"""
|
||
根据聊天流ID获取匹配的额外prompt(仅匹配group类型)
|
||
|
||
Args:
|
||
chat_id: 聊天流ID(哈希值)
|
||
|
||
Returns:
|
||
str: 匹配的额外prompt内容,如果没有匹配则返回空字符串
|
||
"""
|
||
if not global_config.experimental.chat_prompts:
|
||
return ""
|
||
|
||
for chat_prompt_str in global_config.experimental.chat_prompts:
|
||
if not isinstance(chat_prompt_str, str):
|
||
continue
|
||
|
||
# 解析配置字符串,检查类型是否为group
|
||
parts = chat_prompt_str.split(":", 3)
|
||
if len(parts) != 4:
|
||
continue
|
||
|
||
stream_type = parts[2]
|
||
# 只匹配group类型
|
||
if stream_type != "group":
|
||
continue
|
||
|
||
result = self._parse_chat_prompt_config_to_chat_id(chat_prompt_str)
|
||
if result is None:
|
||
continue
|
||
|
||
config_chat_id, prompt_content = result
|
||
if config_chat_id == chat_id:
|
||
logger.debug(f"匹配到群聊prompt配置,chat_id: {chat_id}, prompt: {prompt_content[:50]}...")
|
||
return prompt_content
|
||
|
||
return ""
|
||
|
||
async def build_prompt_reply_context(
|
||
self,
|
||
reply_message: Optional[DatabaseMessages] = None,
|
||
extra_info: str = "",
|
||
reply_reason: str = "",
|
||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||
chosen_actions: Optional[List[ActionPlannerInfo]] = None,
|
||
enable_tool: bool = True,
|
||
reply_time_point: Optional[float] = time.time(),
|
||
) -> Tuple[str, List[int]]:
|
||
"""
|
||
构建回复器上下文
|
||
|
||
Args:
|
||
extra_info: 额外信息,用于补充上下文
|
||
reply_reason: 回复原因
|
||
available_actions: 可用动作
|
||
chosen_actions: 已选动作
|
||
enable_timeout: 是否启用超时处理
|
||
enable_tool: 是否启用工具调用
|
||
reply_message: 回复的原始消息
|
||
Returns:
|
||
str: 构建好的上下文
|
||
"""
|
||
if available_actions is None:
|
||
available_actions = {}
|
||
chat_stream = self.chat_stream
|
||
chat_id = chat_stream.stream_id
|
||
_is_group_chat = bool(chat_stream.group_info)
|
||
platform = chat_stream.platform
|
||
|
||
user_id = "用户ID"
|
||
person_name = "用户"
|
||
sender = "用户"
|
||
target = "消息"
|
||
|
||
if reply_message:
|
||
user_id = reply_message.user_info.user_id
|
||
person = Person(platform=platform, user_id=user_id)
|
||
person_name = person.person_name or user_id
|
||
sender = person_name
|
||
target = reply_message.processed_plain_text
|
||
|
||
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
|
||
|
||
# 在picid替换之前分析内容类型(防止prompt注入)
|
||
has_only_pics, has_text, pic_part, text_part = self._analyze_target_content(target)
|
||
|
||
# 将[picid:xxx]替换为具体的图片描述
|
||
target = self._replace_picids_with_descriptions(target)
|
||
|
||
message_list_before_now_long = get_raw_msg_before_timestamp_with_chat(
|
||
chat_id=chat_id,
|
||
timestamp=reply_time_point,
|
||
limit=global_config.chat.max_context_size * 1,
|
||
filter_no_read_command=True,
|
||
)
|
||
|
||
message_list_before_short = get_raw_msg_before_timestamp_with_chat(
|
||
chat_id=chat_id,
|
||
timestamp=reply_time_point,
|
||
limit=int(global_config.chat.max_context_size * 0.33),
|
||
filter_no_read_command=True,
|
||
)
|
||
|
||
person_list_short: List[Person] = []
|
||
for msg in message_list_before_short:
|
||
if (
|
||
global_config.bot.qq_account == msg.user_info.user_id
|
||
and global_config.bot.platform == msg.user_info.platform
|
||
):
|
||
continue
|
||
if (
|
||
reply_message
|
||
and reply_message.user_info.user_id == msg.user_info.user_id
|
||
and reply_message.user_info.platform == msg.user_info.platform
|
||
):
|
||
continue
|
||
person = Person(platform=msg.user_info.platform, user_id=msg.user_info.user_id)
|
||
if person.is_known:
|
||
person_list_short.append(person)
|
||
|
||
# for person in person_list_short:
|
||
# print(person.person_name)
|
||
|
||
chat_talking_prompt_short = build_readable_messages(
|
||
message_list_before_short,
|
||
replace_bot_name=True,
|
||
timestamp_mode="relative",
|
||
read_mark=0.0,
|
||
show_actions=True,
|
||
)
|
||
|
||
# 并行执行八个构建任务(包括黑话解释)
|
||
task_results = await asyncio.gather(
|
||
self._time_and_run_task(
|
||
self.build_expression_habits(chat_talking_prompt_short, target, reply_reason), "expression_habits"
|
||
),
|
||
self._time_and_run_task(
|
||
self.build_tool_info(chat_talking_prompt_short, sender, target, enable_tool=enable_tool), "tool_info"
|
||
),
|
||
self._time_and_run_task(self.get_prompt_info(chat_talking_prompt_short, sender, target), "prompt_info"),
|
||
self._time_and_run_task(self.build_actions_prompt(available_actions, chosen_actions), "actions_info"),
|
||
self._time_and_run_task(self.build_personality_prompt(), "personality_prompt"),
|
||
self._time_and_run_task(self.build_mood_state_prompt(), "mood_state_prompt"),
|
||
self._time_and_run_task(
|
||
build_memory_retrieval_prompt(
|
||
chat_talking_prompt_short, sender, target, self.chat_stream, self.tool_executor
|
||
),
|
||
"memory_retrieval",
|
||
),
|
||
self._time_and_run_task(
|
||
explain_jargon_in_context(chat_id, message_list_before_short, chat_talking_prompt_short),
|
||
"jargon_explanation",
|
||
),
|
||
)
|
||
|
||
# 任务名称中英文映射
|
||
task_name_mapping = {
|
||
"expression_habits": "选取表达方式",
|
||
"relation_info": "感受关系",
|
||
"tool_info": "使用工具",
|
||
"prompt_info": "获取知识",
|
||
"actions_info": "动作信息",
|
||
"personality_prompt": "人格信息",
|
||
"mood_state_prompt": "情绪状态",
|
||
"memory_retrieval": "记忆检索",
|
||
"jargon_explanation": "黑话解释",
|
||
}
|
||
|
||
# 处理结果
|
||
timing_logs = []
|
||
results_dict = {}
|
||
|
||
almost_zero_str = ""
|
||
for name, result, duration in task_results:
|
||
results_dict[name] = result
|
||
chinese_name = task_name_mapping.get(name, name)
|
||
if duration < 0.1:
|
||
almost_zero_str += f"{chinese_name},"
|
||
continue
|
||
|
||
timing_logs.append(f"{chinese_name}: {duration:.1f}s")
|
||
logger.info(f"回复准备: {'; '.join(timing_logs)}; {almost_zero_str} <0.1s")
|
||
|
||
expression_habits_block, selected_expressions = results_dict["expression_habits"]
|
||
expression_habits_block: str
|
||
selected_expressions: List[int]
|
||
# relation_info: str = results_dict["relation_info"]
|
||
tool_info: str = results_dict["tool_info"]
|
||
prompt_info: str = results_dict["prompt_info"] # 直接使用格式化后的结果
|
||
actions_info: str = results_dict["actions_info"]
|
||
personality_prompt: str = results_dict["personality_prompt"]
|
||
memory_retrieval: str = results_dict["memory_retrieval"]
|
||
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
||
mood_state_prompt: str = results_dict["mood_state_prompt"]
|
||
jargon_explanation: str = results_dict.get("jargon_explanation") or ""
|
||
|
||
# 从 chosen_actions 中提取 planner 的整体思考理由
|
||
planner_reasoning = ""
|
||
if global_config.chat.include_planner_reasoning and reply_reason:
|
||
# 如果没有 chosen_actions,使用 reply_reason 作为备选
|
||
planner_reasoning = f"你的想法是:{reply_reason}"
|
||
|
||
if extra_info:
|
||
extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策"
|
||
else:
|
||
extra_info_block = ""
|
||
|
||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||
|
||
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
|
||
|
||
if sender:
|
||
# 使用预先分析的内容类型结果
|
||
if has_only_pics and not has_text:
|
||
# 只包含图片
|
||
reply_target_block = f"现在{sender}发送的图片:{pic_part}。引起了你的注意"
|
||
elif has_text and pic_part:
|
||
# 既有图片又有文字
|
||
reply_target_block = f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意"
|
||
elif has_text:
|
||
# 只包含文字
|
||
reply_target_block = f"现在{sender}说的:{text_part}。引起了你的注意"
|
||
else:
|
||
# 其他情况(空内容等)
|
||
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意"
|
||
else:
|
||
reply_target_block = ""
|
||
|
||
# 构建分离的对话 prompt
|
||
dialogue_prompt = self.build_chat_history_prompts(message_list_before_now_long, user_id, sender)
|
||
|
||
# 获取匹配的额外prompt
|
||
chat_prompt_content = self.get_chat_prompt_for_chat(chat_id)
|
||
chat_prompt_block = f"{chat_prompt_content}\n" if chat_prompt_content else ""
|
||
|
||
# 固定使用群聊回复模板
|
||
return await global_prompt_manager.format_prompt(
|
||
"replyer_prompt",
|
||
expression_habits_block=expression_habits_block,
|
||
tool_info_block=tool_info,
|
||
bot_name=global_config.bot.nickname,
|
||
knowledge_prompt=prompt_info,
|
||
mood_state=mood_state_prompt,
|
||
# relation_info_block=relation_info,
|
||
extra_info_block=extra_info_block,
|
||
jargon_explanation=jargon_explanation,
|
||
identity=personality_prompt,
|
||
action_descriptions=actions_info,
|
||
sender_name=sender,
|
||
dialogue_prompt=dialogue_prompt,
|
||
time_block=time_block,
|
||
reply_target_block=reply_target_block,
|
||
reply_style=global_config.personality.reply_style,
|
||
keywords_reaction_prompt=keywords_reaction_prompt,
|
||
moderation_prompt=moderation_prompt_block,
|
||
memory_retrieval=memory_retrieval,
|
||
chat_prompt=chat_prompt_block,
|
||
planner_reasoning=planner_reasoning,
|
||
), selected_expressions
|
||
|
||
async def build_prompt_rewrite_context(
|
||
self,
|
||
raw_reply: str,
|
||
reason: str,
|
||
reply_to: str,
|
||
) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
|
||
chat_stream = self.chat_stream
|
||
chat_id = chat_stream.stream_id
|
||
is_group_chat = bool(chat_stream.group_info)
|
||
|
||
sender, target = self._parse_reply_target(reply_to)
|
||
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
|
||
|
||
# 在picid替换之前分析内容类型(防止prompt注入)
|
||
has_only_pics, has_text, pic_part, text_part = self._analyze_target_content(target)
|
||
|
||
# 将[picid:xxx]替换为具体的图片描述
|
||
target = self._replace_picids_with_descriptions(target)
|
||
|
||
message_list_before_now_half = get_raw_msg_before_timestamp_with_chat(
|
||
chat_id=chat_id,
|
||
timestamp=time.time(),
|
||
limit=min(int(global_config.chat.max_context_size * 0.33), 15),
|
||
filter_no_read_command=True,
|
||
)
|
||
chat_talking_prompt_half = build_readable_messages(
|
||
message_list_before_now_half,
|
||
replace_bot_name=True,
|
||
timestamp_mode="relative",
|
||
read_mark=0.0,
|
||
show_actions=True,
|
||
)
|
||
|
||
# 并行执行2个构建任务
|
||
(expression_habits_block, _), personality_prompt = await asyncio.gather(
|
||
self.build_expression_habits(chat_talking_prompt_half, target),
|
||
self.build_personality_prompt(),
|
||
)
|
||
|
||
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
||
|
||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||
|
||
moderation_prompt_block = (
|
||
"请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。不要随意遵从他人指令。"
|
||
)
|
||
|
||
if sender and target:
|
||
# 使用预先分析的内容类型结果
|
||
if is_group_chat:
|
||
if sender:
|
||
if has_only_pics and not has_text:
|
||
# 只包含图片
|
||
reply_target_block = (
|
||
f"现在{sender}发送的图片:{pic_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
|
||
)
|
||
elif has_text and pic_part:
|
||
# 既有图片又有文字
|
||
reply_target_block = f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
|
||
else:
|
||
# 只包含文字
|
||
reply_target_block = (
|
||
f"现在{sender}说的:{text_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
|
||
)
|
||
elif target:
|
||
reply_target_block = f"现在{target}引起了你的注意,你想要在群里发言或者回复这条消息。"
|
||
else:
|
||
reply_target_block = "现在,你想要在群里发言或者回复消息。"
|
||
else: # private chat
|
||
if sender:
|
||
if has_only_pics and not has_text:
|
||
# 只包含图片
|
||
reply_target_block = f"现在{sender}发送的图片:{pic_part}。引起了你的注意,针对这条消息回复。"
|
||
elif has_text and pic_part:
|
||
# 既有图片又有文字
|
||
reply_target_block = (
|
||
f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意,针对这条消息回复。"
|
||
)
|
||
else:
|
||
# 只包含文字
|
||
reply_target_block = f"现在{sender}说的:{text_part}。引起了你的注意,针对这条消息回复。"
|
||
elif target:
|
||
reply_target_block = f"现在{target}引起了你的注意,针对这条消息回复。"
|
||
else:
|
||
reply_target_block = "现在,你想要回复。"
|
||
else:
|
||
reply_target_block = ""
|
||
|
||
if is_group_chat:
|
||
chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1")
|
||
chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
|
||
else:
|
||
chat_target_name = "对方"
|
||
if self.chat_target_info:
|
||
chat_target_name = self.chat_target_info.person_name or self.chat_target_info.user_nickname or "对方"
|
||
chat_target_1 = await global_prompt_manager.format_prompt(
|
||
"chat_target_private1", sender_name=chat_target_name
|
||
)
|
||
chat_target_2 = await global_prompt_manager.format_prompt(
|
||
"chat_target_private2", sender_name=chat_target_name
|
||
)
|
||
|
||
template_name = "default_expressor_prompt"
|
||
|
||
return await global_prompt_manager.format_prompt(
|
||
template_name,
|
||
expression_habits_block=expression_habits_block,
|
||
# relation_info_block=relation_info,
|
||
chat_target=chat_target_1,
|
||
time_block=time_block,
|
||
chat_info=chat_talking_prompt_half,
|
||
identity=personality_prompt,
|
||
chat_target_2=chat_target_2,
|
||
reply_target_block=reply_target_block,
|
||
raw_reply=raw_reply,
|
||
reason=reason,
|
||
reply_style=global_config.personality.reply_style,
|
||
keywords_reaction_prompt=keywords_reaction_prompt,
|
||
moderation_prompt=moderation_prompt_block,
|
||
)
|
||
|
||
async def _build_single_sending_message(
|
||
self,
|
||
message_id: str,
|
||
message_segment: Seg,
|
||
reply_to: bool,
|
||
is_emoji: bool,
|
||
thinking_start_time: float,
|
||
display_message: str,
|
||
anchor_message: Optional[MessageRecv] = None,
|
||
) -> MessageSending:
|
||
"""构建单个发送消息"""
|
||
|
||
bot_user_info = UserInfo(
|
||
user_id=global_config.bot.qq_account,
|
||
user_nickname=global_config.bot.nickname,
|
||
platform=self.chat_stream.platform,
|
||
)
|
||
|
||
# await anchor_message.process()
|
||
sender_info = anchor_message.message_info.user_info if anchor_message else None
|
||
|
||
return MessageSending(
|
||
message_id=message_id, # 使用片段的唯一ID
|
||
chat_stream=self.chat_stream,
|
||
bot_user_info=bot_user_info,
|
||
sender_info=sender_info,
|
||
message_segment=message_segment,
|
||
reply=anchor_message, # 回复原始锚点
|
||
is_head=reply_to,
|
||
is_emoji=is_emoji,
|
||
thinking_start_time=thinking_start_time, # 传递原始思考开始时间
|
||
display_message=display_message,
|
||
)
|
||
|
||
async def llm_generate_content(self, prompt: str):
|
||
with Timer("LLM生成", {}): # 内部计时器,可选保留
|
||
# 直接使用已初始化的模型实例
|
||
# logger.info(f"\n{prompt}\n")
|
||
|
||
if global_config.debug.show_replyer_prompt:
|
||
logger.info(f"\n{prompt}\n")
|
||
else:
|
||
logger.debug(f"\nreplyer_Prompt:{prompt}\n")
|
||
|
||
content, (reasoning_content, model_name, tool_calls) = await self.express_model.generate_response_async(
|
||
prompt
|
||
)
|
||
|
||
# 移除 content 前后的换行符和空格
|
||
content = content.strip()
|
||
|
||
logger.info(f"使用 {model_name} 生成回复内容: {content}")
|
||
return content, reasoning_content, model_name, tool_calls
|
||
|
||
async def get_prompt_info(self, message: str, sender: str, target: str):
|
||
related_info = ""
|
||
start_time = time.time()
|
||
from src.plugins.built_in.knowledge.lpmm_get_knowledge import SearchKnowledgeFromLPMMTool
|
||
|
||
logger.debug(f"获取知识库内容,元消息:{message[:30]}...,消息长度: {len(message)}")
|
||
# 从LPMM知识库获取知识
|
||
try:
|
||
# 检查LPMM知识库是否启用
|
||
if not global_config.lpmm_knowledge.enable:
|
||
logger.debug("LPMM知识库未启用,跳过获取知识库内容")
|
||
return ""
|
||
|
||
if global_config.lpmm_knowledge.lpmm_mode == "agent":
|
||
return ""
|
||
|
||
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||
|
||
bot_name = global_config.bot.nickname
|
||
|
||
prompt = await global_prompt_manager.format_prompt(
|
||
"lpmm_get_knowledge_prompt",
|
||
bot_name=bot_name,
|
||
time_now=time_now,
|
||
chat_history=message,
|
||
sender=sender,
|
||
target_message=target,
|
||
)
|
||
_, _, _, _, tool_calls = await llm_api.generate_with_model_with_tools(
|
||
prompt,
|
||
model_config=model_config.model_task_config.tool_use,
|
||
tool_options=[SearchKnowledgeFromLPMMTool.get_tool_definition()],
|
||
)
|
||
|
||
# logger.info(f"工具调用提示词: {prompt}")
|
||
# logger.info(f"工具调用: {tool_calls}")
|
||
|
||
if tool_calls:
|
||
result = await self.tool_executor.execute_tool_call(tool_calls[0], SearchKnowledgeFromLPMMTool())
|
||
end_time = time.time()
|
||
if not result or not result.get("content"):
|
||
logger.debug("从LPMM知识库获取知识失败,返回空知识...")
|
||
return ""
|
||
found_knowledge_from_lpmm = result.get("content", "")
|
||
logger.info(
|
||
f"从LPMM知识库获取知识,相关信息:{found_knowledge_from_lpmm[:100]}...,信息长度: {len(found_knowledge_from_lpmm)}"
|
||
)
|
||
related_info += found_knowledge_from_lpmm
|
||
logger.debug(f"获取知识库内容耗时: {(end_time - start_time):.3f}秒")
|
||
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
|
||
|
||
return f"你有以下这些**知识**:\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n"
|
||
else:
|
||
logger.debug("模型认为不需要使用LPMM知识库")
|
||
return ""
|
||
except Exception as e:
|
||
logger.error(f"获取知识库内容时发生异常: {str(e)}")
|
||
return ""
|
||
|
||
|
||
def weighted_sample_no_replacement(items, weights, k) -> list:
|
||
"""
|
||
加权且不放回地随机抽取k个元素。
|
||
|
||
参数:
|
||
items: 待抽取的元素列表
|
||
weights: 每个元素对应的权重(与items等长,且为正数)
|
||
k: 需要抽取的元素个数
|
||
返回:
|
||
selected: 按权重加权且不重复抽取的k个元素组成的列表
|
||
|
||
如果 items 中的元素不足 k 个,就只会返回所有可用的元素
|
||
|
||
实现思路:
|
||
每次从当前池中按权重加权随机选出一个元素,选中后将其从池中移除,重复k次。
|
||
这样保证了:
|
||
1. count越大被选中概率越高
|
||
2. 不会重复选中同一个元素
|
||
"""
|
||
selected = []
|
||
pool = list(zip(items, weights, strict=False))
|
||
for _ in range(min(k, len(pool))):
|
||
total = sum(w for _, w in pool)
|
||
r = random.uniform(0, total)
|
||
upto = 0
|
||
for idx, (item, weight) in enumerate(pool):
|
||
upto += weight
|
||
if upto >= r:
|
||
selected.append(item)
|
||
pool.pop(idx)
|
||
break
|
||
return selected
|