Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
@@ -19,6 +19,7 @@ from src.plugin_system import (
|
|||||||
ToolParamType,
|
ToolParamType,
|
||||||
register_plugin,
|
register_plugin,
|
||||||
)
|
)
|
||||||
|
from src.plugin_system.base.component_types import InjectionRule,InjectionType
|
||||||
from src.plugin_system.base.base_event import HandlerResult
|
from src.plugin_system.base.base_event import HandlerResult
|
||||||
|
|
||||||
logger = get_logger("hello_world_plugin")
|
logger = get_logger("hello_world_plugin")
|
||||||
@@ -188,7 +189,7 @@ class WeatherPrompt(BasePrompt):
|
|||||||
|
|
||||||
prompt_name = "weather_info_prompt"
|
prompt_name = "weather_info_prompt"
|
||||||
prompt_description = "向Planner注入当前天气信息,以丰富对话上下文。"
|
prompt_description = "向Planner注入当前天气信息,以丰富对话上下文。"
|
||||||
injection_point = "planner_prompt"
|
injection_rules = [InjectionRule(target_prompt="planner_prompt", injection_type=InjectionType.REPLACE, target_content="## 可用动作列表")]
|
||||||
|
|
||||||
async def execute(self) -> str:
|
async def execute(self) -> str:
|
||||||
# 在实际应用中,这里可以调用天气API
|
# 在实际应用中,这里可以调用天气API
|
||||||
|
|||||||
@@ -196,19 +196,19 @@ class PromptManager:
|
|||||||
# 确保我们有有效的parameters实例用于注入逻辑
|
# 确保我们有有效的parameters实例用于注入逻辑
|
||||||
params_for_injection = parameters or original_prompt.parameters
|
params_for_injection = parameters or original_prompt.parameters
|
||||||
|
|
||||||
# 从组件管理器获取需要注入的内容
|
# 应用所有匹配的注入规则,获取修改后的模板
|
||||||
components_prefix = await prompt_component_manager.execute_components_for(
|
modified_template = await prompt_component_manager.apply_injections(
|
||||||
injection_point=original_prompt.name, params=params_for_injection
|
target_prompt_name=original_prompt.name,
|
||||||
|
original_template=original_prompt.template,
|
||||||
|
params=params_for_injection,
|
||||||
)
|
)
|
||||||
# 如果有内容需要注入
|
|
||||||
if components_prefix:
|
# 如果模板被修改了,就创建一个新的临时Prompt实例
|
||||||
logger.info(f"为'{name}'注入插件内容: \n{components_prefix}")
|
if modified_template != original_prompt.template:
|
||||||
# 将注入内容与原始模板拼接
|
logger.info(f"为'{name}'应用了Prompt注入规则")
|
||||||
new_template = f"{components_prefix}\n\n{original_prompt.template}"
|
# 创建一个新的临时Prompt实例,不进行注册
|
||||||
# 创建一个新的、临时的Prompt实例。`should_register=False`是关键,
|
|
||||||
# 它防止了这个临时版本污染全局或上下文注册表。
|
|
||||||
temp_prompt = Prompt(
|
temp_prompt = Prompt(
|
||||||
template=new_template,
|
template=modified_template,
|
||||||
name=original_prompt.name,
|
name=original_prompt.name,
|
||||||
parameters=original_prompt.parameters,
|
parameters=original_prompt.parameters,
|
||||||
should_register=False, # 确保不重新注册
|
should_register=False, # 确保不重新注册
|
||||||
@@ -1238,13 +1238,12 @@ async def create_prompt_async(
|
|||||||
|
|
||||||
# 如果提供了名称,就尝试为它注入插件内容
|
# 如果提供了名称,就尝试为它注入插件内容
|
||||||
if name:
|
if name:
|
||||||
components_prefix = await prompt_component_manager.execute_components_for(
|
modified_template = await prompt_component_manager.apply_injections(
|
||||||
injection_point=name, params=final_params
|
target_prompt_name=name, original_template=template, params=final_params
|
||||||
)
|
)
|
||||||
if components_prefix:
|
if modified_template != template:
|
||||||
logger.debug(f"为'{name}'注入插件内容: \n{components_prefix}")
|
logger.debug(f"为'{name}'应用了Prompt注入规则")
|
||||||
# 将注入内容拼接到原始模板的前面
|
template = modified_template
|
||||||
template = f"{components_prefix}\n\n{template}"
|
|
||||||
|
|
||||||
# 使用可能已被修改的模板来创建最终的Prompt实例
|
# 使用可能已被修改的模板来创建最终的Prompt实例
|
||||||
prompt = create_prompt(template, name, final_params)
|
prompt = create_prompt(template, name, final_params)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import re
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from src.chat.utils.prompt_params import PromptParameters
|
from src.chat.utils.prompt_params import PromptParameters
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.base.base_prompt import BasePrompt
|
from src.plugin_system.base.base_prompt import BasePrompt
|
||||||
from src.plugin_system.base.component_types import ComponentType, PromptInfo
|
from src.plugin_system.base.component_types import ComponentType, InjectionRule, InjectionType, PromptInfo
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
|
|
||||||
logger = get_logger("prompt_component_manager")
|
logger = get_logger("prompt_component_manager")
|
||||||
@@ -19,89 +21,143 @@ class PromptComponentManager:
|
|||||||
3. 提供一个接口,以便在构建核心Prompt时,能够获取并执行所有相关的组件。
|
3. 提供一个接口,以便在构建核心Prompt时,能够获取并执行所有相关的组件。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_components_for(self, injection_point: str) -> list[type[BasePrompt]]:
|
def _get_rules_for(self, target_prompt_name: str) -> list[tuple[InjectionRule, Type[BasePrompt]]]:
|
||||||
"""
|
"""
|
||||||
获取指定注入点的所有已注册组件类。
|
获取指定目标Prompt的所有注入规则及其关联的组件类。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
injection_point: 目标Prompt的名称。
|
target_prompt_name (str): 目标 Prompt 的名称。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Type[BasePrompt]]: 与该注入点关联的组件类列表。
|
list[tuple[InjectionRule, Type[BasePrompt]]]: 一个元组列表,
|
||||||
|
每个元组包含一个注入规则和其对应的 Prompt 组件类,并已根据优先级排序。
|
||||||
"""
|
"""
|
||||||
# 从组件注册中心获取所有启用的Prompt组件
|
# 从注册表中获取所有已启用的 PROMPT 类型的组件
|
||||||
enabled_prompts = component_registry.get_enabled_components_by_type(ComponentType.PROMPT)
|
enabled_prompts = component_registry.get_enabled_components_by_type(ComponentType.PROMPT)
|
||||||
|
matching_rules = []
|
||||||
|
|
||||||
matching_components: list[type[BasePrompt]] = []
|
# 遍历所有启用的 Prompt 组件,查找与目标 Prompt 相关的注入规则
|
||||||
|
|
||||||
for prompt_name, prompt_info in enabled_prompts.items():
|
for prompt_name, prompt_info in enabled_prompts.items():
|
||||||
# 确保 prompt_info 是 PromptInfo 类型
|
|
||||||
if not isinstance(prompt_info, PromptInfo):
|
if not isinstance(prompt_info, PromptInfo):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 获取注入点信息
|
# prompt_info.injection_rules 已经经过了后向兼容处理,确保总是列表
|
||||||
injection_points = prompt_info.injection_point
|
for rule in prompt_info.injection_rules:
|
||||||
if isinstance(injection_points, str):
|
# 如果规则的目标是当前指定的 Prompt
|
||||||
injection_points = [injection_points]
|
if rule.target_prompt == target_prompt_name:
|
||||||
|
# 获取该规则对应的组件类
|
||||||
|
component_class = component_registry.get_component_class(prompt_name, ComponentType.PROMPT)
|
||||||
|
# 确保获取到的确实是一个 BasePrompt 的子类
|
||||||
|
if component_class and issubclass(component_class, BasePrompt):
|
||||||
|
matching_rules.append((rule, component_class))
|
||||||
|
|
||||||
# 检查当前注入点是否匹配
|
# 根据规则的优先级进行排序,数字越小,优先级越高,越先应用
|
||||||
if injection_point in injection_points:
|
matching_rules.sort(key=lambda x: x[0].priority)
|
||||||
# 获取组件类
|
return matching_rules
|
||||||
component_class = component_registry.get_component_class(prompt_name, ComponentType.PROMPT)
|
|
||||||
if component_class and issubclass(component_class, BasePrompt):
|
|
||||||
matching_components.append(component_class)
|
|
||||||
|
|
||||||
return matching_components
|
async def apply_injections(
|
||||||
|
self, target_prompt_name: str, original_template: str, params: PromptParameters
|
||||||
async def execute_components_for(self, injection_point: str, params: PromptParameters) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
实例化并执行指定注入点的所有组件,然后将它们的输出拼接成一个字符串。
|
获取、实例化并执行所有相关组件,然后根据注入规则修改原始模板。
|
||||||
|
|
||||||
|
这是一个三步走的过程:
|
||||||
|
1. 实例化所有需要执行的组件。
|
||||||
|
2. 并行执行它们的 `execute` 方法以获取注入内容。
|
||||||
|
3. 按照优先级顺序,将内容注入到原始模板中。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
injection_point: 目标Prompt的名称。
|
target_prompt_name (str): 目标 Prompt 的名称。
|
||||||
params: 用于初始化组件的 PromptParameters 对象。
|
original_template (str): 原始的、未经修改的 Prompt 模板字符串。
|
||||||
|
params (PromptParameters): 传递给 Prompt 组件实例的参数。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 所有相关组件生成的、用换行符连接的文本内容。
|
str: 应用了所有注入规则后,修改过的 Prompt 模板字符串。
|
||||||
"""
|
"""
|
||||||
component_classes = self.get_components_for(injection_point)
|
rules_with_classes = self._get_rules_for(target_prompt_name)
|
||||||
if not component_classes:
|
# 如果没有找到任何匹配的规则,就直接返回原始模板,啥也不干
|
||||||
return ""
|
if not rules_with_classes:
|
||||||
|
return original_template
|
||||||
|
|
||||||
tasks = []
|
# --- 第一步: 实例化所有需要执行的组件 ---
|
||||||
for component_class in component_classes:
|
instance_map = {} # 存储组件实例,虽然目前没直接用,但留着总没错
|
||||||
try:
|
tasks = [] # 存放所有需要并行执行的 execute 异步任务
|
||||||
# 从注册中心获取组件信息
|
components_to_execute = [] # 存放需要执行的组件类,用于后续结果映射
|
||||||
prompt_info = component_registry.get_component_info(
|
|
||||||
component_class.prompt_name, ComponentType.PROMPT
|
|
||||||
)
|
|
||||||
if not isinstance(prompt_info, PromptInfo):
|
|
||||||
logger.warning(f"找不到 Prompt 组件 '{component_class.prompt_name}' 的信息,无法获取插件配置")
|
|
||||||
plugin_config = {}
|
|
||||||
else:
|
|
||||||
plugin_config = component_registry.get_plugin_config(prompt_info.plugin_name)
|
|
||||||
|
|
||||||
instance = component_class(params=params, plugin_config=plugin_config)
|
for rule, component_class in rules_with_classes:
|
||||||
tasks.append(instance.execute())
|
# 如果注入类型是 REMOVE,那就不需要执行组件了,因为它不产生内容
|
||||||
except Exception as e:
|
if rule.injection_type != InjectionType.REMOVE:
|
||||||
logger.error(f"实例化 Prompt 组件 '{component_class.prompt_name}' 失败: {e}")
|
try:
|
||||||
|
# 获取组件的元信息,主要是为了拿到插件名称来读取插件配置
|
||||||
|
prompt_info = component_registry.get_component_info(
|
||||||
|
component_class.prompt_name, ComponentType.PROMPT
|
||||||
|
)
|
||||||
|
if not isinstance(prompt_info, PromptInfo):
|
||||||
|
plugin_config = {}
|
||||||
|
else:
|
||||||
|
# 从注册表获取该组件所属插件的配置
|
||||||
|
plugin_config = component_registry.get_plugin_config(prompt_info.plugin_name)
|
||||||
|
|
||||||
if not tasks:
|
# 实例化组件,并传入参数和插件配置
|
||||||
return ""
|
instance = component_class(params=params, plugin_config=plugin_config)
|
||||||
|
instance_map[component_class.prompt_name] = instance
|
||||||
|
# 将组件的 execute 方法作为一个任务添加到列表中
|
||||||
|
tasks.append(instance.execute())
|
||||||
|
components_to_execute.append(component_class)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"实例化 Prompt 组件 '{component_class.prompt_name}' 失败: {e}")
|
||||||
|
# 即使失败,也添加一个立即完成的空任务,以保持与其他任务的索引同步
|
||||||
|
tasks.append(asyncio.create_task(asyncio.sleep(0, result=e))) # type: ignore
|
||||||
|
|
||||||
# 并行执行所有组件
|
# --- 第二步: 并行执行所有组件的 execute 方法 ---
|
||||||
|
# 使用 asyncio.gather 来同时运行所有任务,提高效率
|
||||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
# 创建一个从组件名到执行结果的映射,方便后续查找
|
||||||
|
result_map = {
|
||||||
|
components_to_execute[i].prompt_name: res
|
||||||
|
for i, res in enumerate(results)
|
||||||
|
if not isinstance(res, Exception) # 只包含成功的结果
|
||||||
|
}
|
||||||
|
# 单独处理并记录执行失败的组件
|
||||||
|
for i, res in enumerate(results):
|
||||||
|
if isinstance(res, Exception):
|
||||||
|
logger.error(f"执行 Prompt 组件 '{components_to_execute[i].prompt_name}' 失败: {res}")
|
||||||
|
|
||||||
# 过滤掉执行失败的结果和空字符串
|
# --- 第三步: 按优先级顺序应用注入规则 ---
|
||||||
valid_results = []
|
modified_template = original_template
|
||||||
for i, result in enumerate(results):
|
for rule, component_class in rules_with_classes:
|
||||||
if isinstance(result, Exception):
|
# 从结果映射中获取该组件生成的内容
|
||||||
logger.error(f"执行 Prompt 组件 '{component_classes[i].prompt_name}' 失败: {result}")
|
content = result_map.get(component_class.prompt_name)
|
||||||
elif result and isinstance(result, str) and result.strip():
|
|
||||||
valid_results.append(result.strip())
|
|
||||||
|
|
||||||
# 使用换行符拼接所有有效结果
|
try:
|
||||||
return "\n".join(valid_results)
|
if rule.injection_type == InjectionType.PREPEND:
|
||||||
|
if content:
|
||||||
|
modified_template = f"{content}\n{modified_template}"
|
||||||
|
elif rule.injection_type == InjectionType.APPEND:
|
||||||
|
if content:
|
||||||
|
modified_template = f"{modified_template}\n{content}"
|
||||||
|
elif rule.injection_type == InjectionType.REPLACE:
|
||||||
|
# 使用正则表达式替换目标内容
|
||||||
|
if content and rule.target_content:
|
||||||
|
modified_template = re.sub(rule.target_content, str(content), modified_template)
|
||||||
|
elif rule.injection_type == InjectionType.INSERT_AFTER:
|
||||||
|
# 在匹配到的内容后面插入
|
||||||
|
if content and rule.target_content:
|
||||||
|
# re.sub a little trick: \g<0> represents the entire matched string
|
||||||
|
replacement = f"\\g<0>\n{content}"
|
||||||
|
modified_template = re.sub(rule.target_content, replacement, modified_template)
|
||||||
|
elif rule.injection_type == InjectionType.REMOVE:
|
||||||
|
# 使用正则表达式移除目标内容
|
||||||
|
if rule.target_content:
|
||||||
|
modified_template = re.sub(rule.target_content, "", modified_template)
|
||||||
|
except re.error as e:
|
||||||
|
logger.error(
|
||||||
|
f"在为 '{component_class.prompt_name}' 应用规则时发生正则错误: {e} (pattern: '{rule.target_content}')"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"应用 Prompt 注入规则 '{rule}' 失败: {e}")
|
||||||
|
|
||||||
|
return modified_template
|
||||||
|
|
||||||
|
|
||||||
# 创建全局单例
|
# 创建全局单例
|
||||||
|
|||||||
@@ -135,11 +135,6 @@ class BasePlugin(PluginBase):
|
|||||||
|
|
||||||
components = self.get_plugin_components()
|
components = self.get_plugin_components()
|
||||||
|
|
||||||
# 检查依赖
|
|
||||||
if not self._check_dependencies():
|
|
||||||
logger.error(f"{self.log_prefix} 依赖检查失败,跳过注册")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 注册所有组件
|
# 注册所有组件
|
||||||
registered_components = []
|
registered_components = []
|
||||||
for component_info, component_class in components:
|
for component_info, component_class in components:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Any
|
|||||||
|
|
||||||
from src.chat.utils.prompt_params import PromptParameters
|
from src.chat.utils.prompt_params import PromptParameters
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.base.component_types import ComponentType, PromptInfo
|
from src.plugin_system.base.component_types import ComponentType, InjectionRule, PromptInfo
|
||||||
|
|
||||||
logger = get_logger("base_prompt")
|
logger = get_logger("base_prompt")
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class BasePrompt(ABC):
|
|||||||
|
|
||||||
子类可以通过类属性定义其行为:
|
子类可以通过类属性定义其行为:
|
||||||
- prompt_name: Prompt组件的唯一名称。
|
- prompt_name: Prompt组件的唯一名称。
|
||||||
- injection_point: 指定要注入的目标Prompt名称(或名称列表)。
|
- injection_rules: 定义注入规则的列表。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prompt_name: str = ""
|
prompt_name: str = ""
|
||||||
@@ -24,11 +24,15 @@ class BasePrompt(ABC):
|
|||||||
prompt_description: str = ""
|
prompt_description: str = ""
|
||||||
"""Prompt组件的描述"""
|
"""Prompt组件的描述"""
|
||||||
|
|
||||||
# 定义此组件希望注入到哪个或哪些核心Prompt中
|
# 定义此组件希望如何注入到核心Prompt中
|
||||||
# 可以是一个字符串(单个目标)或字符串列表(多个目标)
|
# 这是一个 InjectionRule 对象的列表,可以实现复杂的注入逻辑
|
||||||
# 例如: "planner_prompt" 或 ["s4u_style_prompt", "normal_style_prompt"]
|
# 例如: [InjectionRule(target_prompt="planner_prompt", injection_type=InjectionType.APPEND, priority=50)]
|
||||||
injection_point: str | list[str] = ""
|
injection_rules: list[InjectionRule] = []
|
||||||
"""要注入的目标Prompt名称或列表"""
|
"""定义注入规则的列表"""
|
||||||
|
|
||||||
|
# 旧的注入点定义,用于向后兼容。如果定义了这个,它将被自动转换为 injection_rules。
|
||||||
|
injection_point: str | list[str] | None = None
|
||||||
|
"""[已废弃] 要注入的目标Prompt名称或列表,请使用 injection_rules"""
|
||||||
|
|
||||||
def __init__(self, params: PromptParameters, plugin_config: dict | None = None):
|
def __init__(self, params: PromptParameters, plugin_config: dict | None = None):
|
||||||
"""初始化Prompt组件
|
"""初始化Prompt组件
|
||||||
@@ -87,9 +91,11 @@ class BasePrompt(ABC):
|
|||||||
if not cls.prompt_name:
|
if not cls.prompt_name:
|
||||||
raise ValueError("Prompt组件必须定义 'prompt_name' 类属性。")
|
raise ValueError("Prompt组件必须定义 'prompt_name' 类属性。")
|
||||||
|
|
||||||
|
# 同时传递新旧两种定义,PromptInfo的__post_init__将处理兼容性问题
|
||||||
return PromptInfo(
|
return PromptInfo(
|
||||||
name=cls.prompt_name,
|
name=cls.prompt_name,
|
||||||
component_type=ComponentType.PROMPT,
|
component_type=ComponentType.PROMPT,
|
||||||
description=cls.prompt_description,
|
description=cls.prompt_description,
|
||||||
|
injection_rules=cls.injection_rules,
|
||||||
injection_point=cls.injection_point,
|
injection_point=cls.injection_point,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,38 @@ from dataclasses import dataclass, field
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
class InjectionType(Enum):
|
||||||
|
"""Prompt注入类型枚举"""
|
||||||
|
|
||||||
|
PREPEND = "prepend" # 在开头添加
|
||||||
|
APPEND = "append" # 在末尾添加
|
||||||
|
REPLACE = "replace" # 替换指定内容
|
||||||
|
REMOVE = "remove" # 删除指定内容
|
||||||
|
INSERT_AFTER = "insert_after" # 在指定内容之后插入
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InjectionRule:
|
||||||
|
"""Prompt注入规则"""
|
||||||
|
|
||||||
|
target_prompt: str # 目标Prompt的名称
|
||||||
|
injection_type: InjectionType = InjectionType.PREPEND # 注入类型
|
||||||
|
priority: int = 100 # 优先级,数字越小越先执行
|
||||||
|
target_content: str | None = None # 用于REPLACE、REMOVE和INSERT_AFTER操作的目标内容(支持正则表达式)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.injection_type in [
|
||||||
|
InjectionType.REPLACE,
|
||||||
|
InjectionType.REMOVE,
|
||||||
|
InjectionType.INSERT_AFTER,
|
||||||
|
] and self.target_content is None:
|
||||||
|
raise ValueError(f"'{self.injection_type.value}'类型的注入规则必须提供 'target_content'。")
|
||||||
|
|
||||||
|
|
||||||
from maim_message import Seg
|
from maim_message import Seg
|
||||||
|
|
||||||
from src.llm_models.payload_content.tool_option import ToolCall as ToolCall
|
from src.llm_models.payload_content.tool_option import ToolCall as ToolCall
|
||||||
@@ -134,7 +166,7 @@ class ComponentInfo:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class ActionInfo(ComponentInfo):
|
class ActionInfo(ComponentInfo):
|
||||||
"""动作组件信息
|
"""动作组件信息
|
||||||
|
|
||||||
注意:激活类型相关字段已废弃,推荐使用 Action 类的 go_activate() 方法来自定义激活逻辑。
|
注意:激活类型相关字段已废弃,推荐使用 Action 类的 go_activate() 方法来自定义激活逻辑。
|
||||||
这些字段将继续保留以提供向后兼容性,BaseAction.go_activate() 的默认实现会使用这些字段。
|
这些字段将继续保留以提供向后兼容性,BaseAction.go_activate() 的默认实现会使用这些字段。
|
||||||
"""
|
"""
|
||||||
@@ -271,13 +303,30 @@ class EventInfo(ComponentInfo):
|
|||||||
class PromptInfo(ComponentInfo):
|
class PromptInfo(ComponentInfo):
|
||||||
"""Prompt组件信息"""
|
"""Prompt组件信息"""
|
||||||
|
|
||||||
injection_point: str | list[str] = ""
|
injection_rules: list[InjectionRule] = field(default_factory=list)
|
||||||
"""要注入的目标Prompt名称或列表"""
|
"""定义此组件如何注入到其他Prompt中"""
|
||||||
|
|
||||||
|
# 旧的injection_point,用于向后兼容
|
||||||
|
injection_point: str | list[str] | None = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
self.component_type = ComponentType.PROMPT
|
self.component_type = ComponentType.PROMPT
|
||||||
|
|
||||||
|
# 向后兼容逻辑:如果定义了旧的 injection_point,则自动转换为新的 injection_rules
|
||||||
|
if self.injection_point:
|
||||||
|
if not self.injection_rules: # 仅当rules为空时转换
|
||||||
|
points = []
|
||||||
|
if isinstance(self.injection_point, str):
|
||||||
|
points.append(self.injection_point)
|
||||||
|
elif isinstance(self.injection_point, list):
|
||||||
|
points = self.injection_point
|
||||||
|
|
||||||
|
for point in points:
|
||||||
|
self.injection_rules.append(InjectionRule(target_prompt=point))
|
||||||
|
# 转换后可以清空旧字段,避免混淆
|
||||||
|
self.injection_point = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PluginInfo:
|
class PluginInfo:
|
||||||
@@ -292,7 +341,7 @@ class PluginInfo:
|
|||||||
is_built_in: bool = False # 是否为内置插件
|
is_built_in: bool = False # 是否为内置插件
|
||||||
components: list[ComponentInfo] = field(default_factory=list) # 包含的组件列表
|
components: list[ComponentInfo] = field(default_factory=list) # 包含的组件列表
|
||||||
dependencies: list[str] = field(default_factory=list) # 依赖的其他插件
|
dependencies: list[str] = field(default_factory=list) # 依赖的其他插件
|
||||||
python_dependencies: list[PythonDependency] = field(default_factory=list) # Python包依赖
|
python_dependencies: list[str | PythonDependency] = field(default_factory=list) # Python包依赖
|
||||||
config_file: str = "" # 配置文件路径
|
config_file: str = "" # 配置文件路径
|
||||||
metadata: dict[str, Any] = field(default_factory=dict) # 额外元数据
|
metadata: dict[str, Any] = field(default_factory=dict) # 额外元数据
|
||||||
# 新增:manifest相关信息
|
# 新增:manifest相关信息
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from src.config.config import CONFIG_DIR
|
|||||||
from src.plugin_system.base.component_types import (
|
from src.plugin_system.base.component_types import (
|
||||||
PermissionNodeField,
|
PermissionNodeField,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
PythonDependency,
|
|
||||||
)
|
)
|
||||||
from src.plugin_system.base.config_types import ConfigField
|
from src.plugin_system.base.config_types import ConfigField
|
||||||
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
||||||
@@ -30,8 +29,6 @@ class PluginBase(ABC):
|
|||||||
plugin_name: str
|
plugin_name: str
|
||||||
config_file_name: str
|
config_file_name: str
|
||||||
enable_plugin: bool = True
|
enable_plugin: bool = True
|
||||||
dependencies: list[str] = []
|
|
||||||
python_dependencies: list[str | PythonDependency] = []
|
|
||||||
|
|
||||||
config_schema: dict[str, dict[str, ConfigField] | str] = {}
|
config_schema: dict[str, dict[str, ConfigField] | str] = {}
|
||||||
|
|
||||||
@@ -64,12 +61,6 @@ class PluginBase(ABC):
|
|||||||
self.plugin_description = self.plugin_meta.description
|
self.plugin_description = self.plugin_meta.description
|
||||||
self.plugin_author = self.plugin_meta.author
|
self.plugin_author = self.plugin_meta.author
|
||||||
|
|
||||||
# 标准化Python依赖为PythonDependency对象
|
|
||||||
normalized_python_deps = self._normalize_python_dependencies(self.python_dependencies)
|
|
||||||
|
|
||||||
# 检查Python依赖
|
|
||||||
self._check_python_dependencies(normalized_python_deps)
|
|
||||||
|
|
||||||
# 创建插件信息对象
|
# 创建插件信息对象
|
||||||
self.plugin_info = PluginInfo(
|
self.plugin_info = PluginInfo(
|
||||||
name=self.plugin_name,
|
name=self.plugin_name,
|
||||||
@@ -80,8 +71,8 @@ class PluginBase(ABC):
|
|||||||
enabled=self._is_enabled,
|
enabled=self._is_enabled,
|
||||||
is_built_in=False,
|
is_built_in=False,
|
||||||
config_file=self.config_file_name or "",
|
config_file=self.config_file_name or "",
|
||||||
dependencies=self.dependencies.copy(),
|
dependencies=self.plugin_meta.dependencies.copy(),
|
||||||
python_dependencies=normalized_python_deps,
|
python_dependencies=self.plugin_meta.python_dependencies.copy(),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"{self.log_prefix} 插件基类初始化完成")
|
logger.debug(f"{self.log_prefix} 插件基类初始化完成")
|
||||||
@@ -367,20 +358,6 @@ class PluginBase(ABC):
|
|||||||
self._is_enabled = self.config["plugin"]["enabled"]
|
self._is_enabled = self.config["plugin"]["enabled"]
|
||||||
logger.info(f"{self.log_prefix} 从配置更新插件启用状态: {self._is_enabled}")
|
logger.info(f"{self.log_prefix} 从配置更新插件启用状态: {self._is_enabled}")
|
||||||
|
|
||||||
def _check_dependencies(self) -> bool:
|
|
||||||
"""检查插件依赖"""
|
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
|
||||||
|
|
||||||
if not self.dependencies:
|
|
||||||
return True
|
|
||||||
|
|
||||||
for dep in self.dependencies:
|
|
||||||
if not component_registry.get_plugin_info(dep):
|
|
||||||
logger.error(f"{self.log_prefix} 缺少依赖插件: {dep}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_config(self, key: str, default: Any = None) -> Any:
|
def get_config(self, key: str, default: Any = None) -> Any:
|
||||||
"""获取插件配置值,支持嵌套键访问
|
"""获取插件配置值,支持嵌套键访问
|
||||||
|
|
||||||
@@ -403,61 +380,6 @@ class PluginBase(ABC):
|
|||||||
|
|
||||||
return current
|
return current
|
||||||
|
|
||||||
def _normalize_python_dependencies(self, dependencies: Any) -> list[PythonDependency]:
|
|
||||||
"""将依赖列表标准化为PythonDependency对象"""
|
|
||||||
from packaging.requirements import Requirement
|
|
||||||
|
|
||||||
normalized = []
|
|
||||||
for dep in dependencies:
|
|
||||||
if isinstance(dep, str):
|
|
||||||
try:
|
|
||||||
# 尝试解析为requirement格式 (如 "package>=1.0.0")
|
|
||||||
req = Requirement(dep)
|
|
||||||
version_spec = str(req.specifier) if req.specifier else ""
|
|
||||||
|
|
||||||
normalized.append(
|
|
||||||
PythonDependency(
|
|
||||||
package_name=req.name,
|
|
||||||
version=version_spec,
|
|
||||||
install_name=dep, # 保持原始的安装名称
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
# 如果解析失败,作为简单包名处理
|
|
||||||
normalized.append(PythonDependency(package_name=dep, install_name=dep))
|
|
||||||
elif isinstance(dep, PythonDependency):
|
|
||||||
normalized.append(dep)
|
|
||||||
else:
|
|
||||||
logger.warning(f"{self.log_prefix} 未知的依赖格式: {dep}")
|
|
||||||
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
def _check_python_dependencies(self, dependencies: list[PythonDependency]) -> bool:
|
|
||||||
"""检查Python依赖并尝试自动安装"""
|
|
||||||
if not dependencies:
|
|
||||||
logger.info(f"{self.log_prefix} 无Python依赖需要检查")
|
|
||||||
return True
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 延迟导入以避免循环依赖
|
|
||||||
from src.plugin_system.utils.dependency_manager import get_dependency_manager
|
|
||||||
|
|
||||||
dependency_manager = get_dependency_manager()
|
|
||||||
success, errors = dependency_manager.check_and_install_dependencies(dependencies, self.plugin_name)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
logger.info(f"{self.log_prefix} Python依赖检查通过")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(f"{self.log_prefix} Python依赖检查失败:")
|
|
||||||
for error in errors:
|
|
||||||
logger.error(f"{self.log_prefix} - {error}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{self.log_prefix} Python依赖检查时发生异常: {e}", exc_info=True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def register_plugin(self) -> bool:
|
def register_plugin(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from src.plugin_system.base.component_types import PythonDependency
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PluginMetadata:
|
class PluginMetadata:
|
||||||
@@ -23,5 +25,9 @@ class PluginMetadata:
|
|||||||
keywords: list[str] = field(default_factory=list) # 关键词
|
keywords: list[str] = field(default_factory=list) # 关键词
|
||||||
categories: list[str] = field(default_factory=list) # 分类
|
categories: list[str] = field(default_factory=list) # 分类
|
||||||
|
|
||||||
|
# 依赖关系
|
||||||
|
dependencies: list[str] = field(default_factory=list) # 插件依赖
|
||||||
|
python_dependencies: list[str | PythonDependency] = field(default_factory=list) # Python包依赖
|
||||||
|
|
||||||
# 扩展字段
|
# 扩展字段
|
||||||
extra: dict[str, Any] = field(default_factory=dict) # 其他任意信息
|
extra: dict[str, Any] = field(default_factory=dict) # 其他任意信息
|
||||||
|
|||||||
@@ -323,6 +323,33 @@ class PluginManager:
|
|||||||
init_module = module_from_spec(init_spec)
|
init_module = module_from_spec(init_spec)
|
||||||
init_spec.loader.exec_module(init_module)
|
init_spec.loader.exec_module(init_module)
|
||||||
|
|
||||||
|
# --- 在这里进行依赖检查 ---
|
||||||
|
if hasattr(init_module, "__plugin_meta__"):
|
||||||
|
metadata = getattr(init_module, "__plugin_meta__")
|
||||||
|
from src.plugin_system.utils.dependency_manager import get_dependency_manager
|
||||||
|
|
||||||
|
dependency_manager = get_dependency_manager()
|
||||||
|
|
||||||
|
# 1. 检查Python依赖
|
||||||
|
if metadata.python_dependencies:
|
||||||
|
success, errors = dependency_manager.check_and_install_dependencies(
|
||||||
|
metadata.python_dependencies, metadata.name
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
error_msg = f"Python依赖检查失败: {', '.join(errors)}"
|
||||||
|
self.failed_plugins[plugin_name] = error_msg
|
||||||
|
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
|
||||||
|
return None # 依赖检查失败,不加载该模块
|
||||||
|
|
||||||
|
# 2. 检查插件依赖
|
||||||
|
if not self._check_plugin_dependencies(metadata):
|
||||||
|
error_msg = f"插件依赖检查失败: 请确保依赖 {metadata.dependencies} 已正确安装并加载。"
|
||||||
|
self.failed_plugins[plugin_name] = error_msg
|
||||||
|
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
|
||||||
|
return None # 插件依赖检查失败
|
||||||
|
|
||||||
|
# --- 依赖检查逻辑结束 ---
|
||||||
|
|
||||||
# 然后加载 plugin.py
|
# 然后加载 plugin.py
|
||||||
spec = spec_from_file_location(module_name, plugin_file)
|
spec = spec_from_file_location(module_name, plugin_file)
|
||||||
if spec is None or spec.loader is None:
|
if spec is None or spec.loader is None:
|
||||||
@@ -335,7 +362,8 @@ class PluginManager:
|
|||||||
|
|
||||||
# 将 __plugin_meta__ 从 init_module 附加到主模块
|
# 将 __plugin_meta__ 从 init_module 附加到主模块
|
||||||
if init_module and hasattr(init_module, "__plugin_meta__"):
|
if init_module and hasattr(init_module, "__plugin_meta__"):
|
||||||
setattr(module, "__plugin_meta__", getattr(init_module, "__plugin_meta__"))
|
metadata = getattr(init_module, "__plugin_meta__")
|
||||||
|
setattr(module, "__plugin_meta__", metadata)
|
||||||
|
|
||||||
logger.debug(f"插件模块加载成功: {plugin_file} -> {plugin_name} ({plugin_dir})")
|
logger.debug(f"插件模块加载成功: {plugin_file} -> {plugin_name} ({plugin_dir})")
|
||||||
return module
|
return module
|
||||||
@@ -346,6 +374,20 @@ class PluginManager:
|
|||||||
self.failed_plugins[plugin_name if "plugin_name" in locals() else module_name] = error_msg
|
self.failed_plugins[plugin_name if "plugin_name" in locals() else module_name] = error_msg
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _check_plugin_dependencies(self, plugin_meta: PluginMetadata) -> bool:
|
||||||
|
"""检查插件的插件依赖"""
|
||||||
|
dependencies = plugin_meta.dependencies
|
||||||
|
if not dependencies:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for dep_name in dependencies:
|
||||||
|
# 检查依赖的插件类是否已注册
|
||||||
|
if dep_name not in self.plugin_classes:
|
||||||
|
logger.error(f"插件 '{plugin_meta.name}' 缺少依赖: 插件 '{dep_name}' 未找到或加载失败。")
|
||||||
|
return False
|
||||||
|
logger.debug(f"插件 '{plugin_meta.name}' 的所有依赖都已找到。")
|
||||||
|
return True
|
||||||
|
|
||||||
# == 显示统计与插件信息 ==
|
# == 显示统计与插件信息 ==
|
||||||
|
|
||||||
def _show_stats(self, total_registered: int, total_failed_registration: int):
|
def _show_stats(self, total_registered: int, total_failed_registration: int):
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ChatterPlanFilter:
|
|||||||
prompt, used_message_id_list = await self._build_prompt(plan)
|
prompt, used_message_id_list = await self._build_prompt(plan)
|
||||||
plan.llm_prompt = prompt
|
plan.llm_prompt = prompt
|
||||||
if global_config.debug.show_prompt:
|
if global_config.debug.show_prompt:
|
||||||
logger.debug(f"规划器原始提示词:{prompt}")
|
logger.info(f"规划器原始提示词:{prompt}") #叫你不要改你耳朵聋吗😡😡😡😡😡
|
||||||
|
|
||||||
llm_content, _ = await self.planner_llm.generate_response_async(prompt=prompt)
|
llm_content, _ = await self.planner_llm.generate_response_async(prompt=prompt)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
||||||
|
|
||||||
|
# 定义插件元数据
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="MoFox-Bot工具箱",
|
name="MoFox-Bot工具箱",
|
||||||
description="一个集合多种实用功能的插件,旨在提升聊天体验和效率。",
|
description="一个集合多种实用功能的插件,旨在提升聊天体验和效率。",
|
||||||
@@ -11,4 +12,6 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
keywords=["emoji", "reaction", "like", "表情", "回应", "点赞"],
|
keywords=["emoji", "reaction", "like", "表情", "回应", "点赞"],
|
||||||
categories=["Chat", "Integration"],
|
categories=["Chat", "Integration"],
|
||||||
extra={"is_built_in": "true", "plugin_type": "functional"},
|
extra={"is_built_in": "true", "plugin_type": "functional"},
|
||||||
|
dependencies=[],
|
||||||
|
python_dependencies=["httpx", "Pillow"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
extra={
|
extra={
|
||||||
"is_built_in": False,
|
"is_built_in": False,
|
||||||
"plugin_type": "tools",
|
"plugin_type": "tools",
|
||||||
}
|
},
|
||||||
|
python_dependencies = ["aiohttp", "soundfile", "pedalboard"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class TTSVoicePlugin(BasePlugin):
|
|||||||
plugin_author = "Kilo Code & 靚仔"
|
plugin_author = "Kilo Code & 靚仔"
|
||||||
config_file_name = "config.toml"
|
config_file_name = "config.toml"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
python_dependencies = ["aiohttp", "soundfile", "pedalboard"]
|
|
||||||
|
|
||||||
permission_nodes: list[PermissionNodeField] = [
|
permission_nodes: list[PermissionNodeField] = [
|
||||||
PermissionNodeField(node_name="command.use", description="是否可以使用 /tts 命令"),
|
PermissionNodeField(node_name="command.use", description="是否可以使用 /tts 命令"),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from src.plugin_system.base.component_types import PythonDependency
|
||||||
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
@@ -13,4 +14,26 @@ __plugin_meta__ = PluginMetadata(
|
|||||||
extra={
|
extra={
|
||||||
"is_built_in": True,
|
"is_built_in": True,
|
||||||
},
|
},
|
||||||
|
# Python包依赖列表
|
||||||
|
python_dependencies = [ # noqa: RUF012
|
||||||
|
PythonDependency(package_name="asyncddgs", description="异步DuckDuckGo搜索库", optional=False),
|
||||||
|
PythonDependency(
|
||||||
|
package_name="exa_py",
|
||||||
|
description="Exa搜索API客户端库",
|
||||||
|
optional=True, # 如果没有API密钥,这个是可选的
|
||||||
|
),
|
||||||
|
PythonDependency(
|
||||||
|
package_name="tavily",
|
||||||
|
install_name="tavily-python", # 安装时使用这个名称
|
||||||
|
description="Tavily搜索API客户端库",
|
||||||
|
optional=True, # 如果没有API密钥,这个是可选的
|
||||||
|
),
|
||||||
|
PythonDependency(
|
||||||
|
package_name="httpx",
|
||||||
|
version=">=0.20.0",
|
||||||
|
install_name="httpx[socks]", # 安装时使用这个名称(包含可选依赖)
|
||||||
|
description="支持SOCKS代理的HTTP客户端库",
|
||||||
|
optional=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -74,29 +74,6 @@ class WEBSEARCHPLUGIN(BasePlugin):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ 搜索引擎初始化失败: {e}", exc_info=True)
|
logger.error(f"❌ 搜索引擎初始化失败: {e}", exc_info=True)
|
||||||
|
|
||||||
# Python包依赖列表
|
|
||||||
python_dependencies: list[PythonDependency] = [ # noqa: RUF012
|
|
||||||
PythonDependency(package_name="asyncddgs", description="异步DuckDuckGo搜索库", optional=False),
|
|
||||||
PythonDependency(
|
|
||||||
package_name="exa_py",
|
|
||||||
description="Exa搜索API客户端库",
|
|
||||||
optional=True, # 如果没有API密钥,这个是可选的
|
|
||||||
),
|
|
||||||
PythonDependency(
|
|
||||||
package_name="tavily",
|
|
||||||
install_name="tavily-python", # 安装时使用这个名称
|
|
||||||
description="Tavily搜索API客户端库",
|
|
||||||
optional=True, # 如果没有API密钥,这个是可选的
|
|
||||||
),
|
|
||||||
PythonDependency(
|
|
||||||
package_name="httpx",
|
|
||||||
version=">=0.20.0",
|
|
||||||
install_name="httpx[socks]", # 安装时使用这个名称(包含可选依赖)
|
|
||||||
description="支持SOCKS代理的HTTP客户端库",
|
|
||||||
optional=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
config_file_name: str = "config.toml" # 配置文件名
|
config_file_name: str = "config.toml" # 配置文件名
|
||||||
|
|
||||||
# 配置节描述
|
# 配置节描述
|
||||||
|
|||||||
Reference in New Issue
Block a user