docs(prompt): 为 PromptComponentManager 植入详尽的代码文档与注释

为了让提示词注入系统的核心 `PromptComponentManager` 变得更加透明和易于理解,本次提交对代码进行了全面的文档化。此举旨在降低新接手者的认知门槛,并为未来的维护工作铺平道路。

具体而言,我们为类和所有公开方法添加了详细的文档字符串,阐明了其设计哲学、职责边界和参数约定。同时,在关键代码块(如规则数据结构、内容提供者闭包、并发锁的使用)旁增加了内联注释,揭示了其背后的实现考量和工作机制。
This commit is contained in:
minecraft1024a
2025-11-12 12:46:07 +08:00
parent c76e1ff0e6
commit 623cebf728

View File

@@ -13,34 +13,51 @@ from src.plugin_system.core.component_registry import component_registry
logger = get_logger("prompt_component_manager")
class PromptComponentManager:
"""
统一的、动态的、可观测的提示词管理中心。
一个统一的、动态的、可观测的提示词组件管理中心。
该管理器负责:
1. 在启动时,将所有 `BasePrompt` 组件的静态 `injection_rules` 加载为默认的动态规则。
2. 提供 API 以在运行时动态地添加、更新、移除注入规则。
3. 提供查询 API 以观测系统当前的完整注入状态。
4. 在构建核心 Prompt 时,根据统一的规则集应用注入,修改模板
该管理器是整个提示词动态注入系统的核心,它负责:
1. **规则加载**: 在系统启动时,自动扫描所有已注册的 `BasePrompt` 组件,
并将其静态定义的 `injection_rules` 加载为默认的动态规则。
2. **动态管理**: 提供线程安全的 API允许在运行时动态地添加、更新或移除注入规则
使得提示词的结构可以被实时调整
3. **状态观测**: 提供丰富的查询 API用于观测系统当前完整的注入状态
例如查询所有注入到特定目标的规则、或查询某个组件定义的所有规则。
4. **注入应用**: 在构建核心 Prompt 时,根据统一的、按优先级排序的规则集,
动态地修改和装配提示词模板,实现灵活的提示词组合。
"""
def __init__(self):
"""初始化管理器。"""
# _dynamic_rules 存储统一的注入规则
# 结构: { "target_prompt_name": { "prompt_component_name": (InjectionRule, content_provider, source) } }
"""初始化管理器实例"""
# _dynamic_rules 是管理器的核心状态,存储所有注入规则
# 结构: {
# "target_prompt_name": {
# "prompt_component_name": (InjectionRule, content_provider, source)
# }
# }
# content_provider 是一个异步函数,用于在应用规则时动态生成注入内容。
# source 记录了规则的来源(例如 "static_default" 或 "runtime")。
self._dynamic_rules: dict[str, dict[str, tuple[InjectionRule, Callable[..., Awaitable[str]], str]]] = {}
self._lock = asyncio.Lock()
self._initialized = False
self._lock = asyncio.Lock() # 使用异步锁确保对 _dynamic_rules 的并发访问安全。
self._initialized = False # 标记静态规则是否已加载,防止重复加载。
# --- 核心生命周期与初始化 ---
def load_static_rules(self):
"""
在系统启动时被调用,扫描所有已注册的 Prompt 组件,
将其静态的 `injection_rules` 转换为动态规则并加载到管理器中。
在系统启动时加载所有静态注入规则。
该方法会扫描所有已在 `component_registry` 中注册并启用的 Prompt 组件,
将其类变量 `injection_rules` 转换为管理器的动态规则。
这确保了所有插件定义的默认注入行为在系统启动时就能生效。
此操作是幂等的,一旦初始化完成就不会重复执行。
"""
if self._initialized:
return
logger.info("正在加载静态 Prompt 注入规则...")
# 从组件注册表中获取所有已启用的 Prompt 组件
enabled_prompts = component_registry.get_enabled_components_by_type(ComponentType.PROMPT)
for prompt_name, prompt_info in enabled_prompts.items():
@@ -53,65 +70,95 @@ class PromptComponentManager:
continue
def create_provider(cls: type[BasePrompt]) -> Callable[[PromptParameters], Awaitable[str]]:
"""为静态组件创建一个内容提供者闭包。"""
"""
为静态组件创建一个内容提供者闭包 (Content Provider Closure)。
这个闭包捕获了组件的类 `cls`,并返回一个标准的 `content_provider` 异步函数。
当 `apply_injections` 需要内容时,它会调用这个函数。
函数内部会实例化组件,并执行其 `execute` 方法来获取注入内容。
Args:
cls (type[BasePrompt]): 需要为其创建提供者的 Prompt 组件类。
Returns:
Callable[[PromptParameters], Awaitable[str]]: 一个符合管理器标准的异步内容提供者。
"""
async def content_provider(params: PromptParameters) -> str:
"""实际执行内容生成的异步函数。"""
try:
# 从注册表获取最新的组件信息,包括插件配置
p_info = component_registry.get_component_info(cls.prompt_name, ComponentType.PROMPT)
plugin_config = {}
if isinstance(p_info, PromptInfo):
plugin_config = component_registry.get_plugin_config(p_info.plugin_name)
# 实例化组件并执行
instance = cls(params=params, plugin_config=plugin_config)
result = await instance.execute()
return str(result) if result is not None else ""
except Exception as e:
logger.error(f"执行静态规则提供者 '{cls.prompt_name}' 时出错: {e}", exc_info=True)
return ""
return "" # 出错时返回空字符串,避免影响主流程
return content_provider
# 为该组件的每条静态注入规则创建并注册一个动态规则
for rule in prompt_info.injection_rules:
provider = create_provider(component_class)
target_rules = self._dynamic_rules.setdefault(rule.target_prompt, {})
target_rules[prompt_name] = (rule, provider, "static_default")
self._initialized = True
logger.info("静态 Prompt 注入规则加载完成。")
self._initialized = True
logger.info(f"静态 Prompt 注入规则加载完成,共处理 {len(enabled_prompts)} 个组件。")
# --- 运行时规则管理 API ---
async def add_injection_rule(
self,
prompt_name: str,
rule: InjectionRule,
content_provider: Callable[..., Awaitable[str]] | None = None,
content_provider: Callable[..., Awaitable[str]],
source: str = "runtime",
) -> bool:
"""
动态添加或更新一条注入规则。
Args:
prompt_name (str): 动态注入组件名称
rule (InjectionRule): 注入规则。
content_provider (Callable | None, optional): 动态内容提供者。
如果提供apply_injections 时会调用此函数获取注入内容。
函数签名应为: async def provider(params: "PromptParameters") -> str
source (str, optional): 规则来源,默认为 "runtime"
"""
if not content_provider:
logger.error(f"'{prompt_name}' 添加动态注入规则失败:必须提供 content_provider。")
return False
此方法允许在系统运行时,由外部逻辑(如插件、命令)向管理器中添加新的注入行为。
如果已存在同名组件针对同一目标的规则,此方法会覆盖旧规则
Args:
prompt_name (str): 动态注入组件的唯一名称。
rule (InjectionRule): 描述注入行为的规则对象。
content_provider (Callable[..., Awaitable[str]]):
一个异步函数,用于在应用注入时动态生成内容。
函数签名应为: `async def provider(params: "PromptParameters") -> str`
source (str, optional): 规则的来源标识,默认为 "runtime"
Returns:
bool: 如果成功添加或更新,则返回 True。
"""
async with self._lock:
target_rules = self._dynamic_rules.setdefault(rule.target_prompt, {})
target_rules[prompt_name] = (rule, content_provider, source)
logger.info(f"成功添加/更新注入规则: '{prompt_name}' -> '{rule.target_prompt}'")
logger.info(f"成功添加/更新注入规则: '{prompt_name}' -> '{rule.target_prompt}' (来源: {source})")
return True
async def remove_injection_rule(self, prompt_name: str, target_prompt: str) -> bool:
"""移除一条动态注入规则。"""
"""
移除一条动态注入规则。
Args:
prompt_name (str): 要移除的注入组件的名称。
target_prompt (str): 该组件注入的目标核心提示词名称。
Returns:
bool: 如果成功移除,则返回 True如果规则不存在则返回 False。
"""
async with self._lock:
if target_prompt in self._dynamic_rules and prompt_name in self._dynamic_rules[target_prompt]:
del self._dynamic_rules[target_prompt][prompt_name]
# 如果目标下已无任何规则,则清理掉这个键
if not self._dynamic_rules[target_prompt]:
del self._dynamic_rules[target_prompt]
logger.info(f"成功移除注入规则: '{prompt_name}' from '{target_prompt}'")
@@ -119,34 +166,54 @@ class PromptComponentManager:
logger.warning(f"尝试移除注入规则失败: 未找到 '{prompt_name}' on '{target_prompt}'")
return False
# --- 核心注入逻辑 ---
async def apply_injections(
self, target_prompt_name: str, original_template: str, params: PromptParameters
) -> str:
"""
【核心方法】根据目标名称,应用所有匹配的注入规则,返回修改后的模板。
这是提示词构建流程中的关键步骤。它会执行以下操作:
1. 检查并确保静态规则已加载。
2. 获取所有注入到 `target_prompt_name` 的规则。
3. 按照规则的 `priority` 属性进行升序排序,优先级数字越小越先应用。
4. 依次执行每个规则的 `content_provider` 来异步获取注入内容。
5. 根据规则的 `injection_type` (如 PREPEND, APPEND, REPLACE 等) 将内容应用到模板上。
Args:
target_prompt_name (str): 目标核心提示词的名称。
original_template (str): 未经修改的原始提示词模板。
params (PromptParameters): 当前请求的参数,会传递给 `content_provider`。
Returns:
str: 应用了所有注入规则后,最终生成的提示词模板字符串。
"""
if not self._initialized:
self.load_static_rules()
# 1. 从 _dynamic_rules 中获取所有相关的规则
# 步骤 1: 获取所有指向当前目标的规则
# 使用 .values() 获取 (rule, provider, source) 元组列表
rules_for_target = list(self._dynamic_rules.get(target_prompt_name, {}).values())
if not rules_for_target:
return original_template
# 2. 按优先级排序
# 步骤 2: 按优先级排序,数字越小越优先
rules_for_target.sort(key=lambda x: x[0].priority)
# 3. 依次执行内容提供者 (content_provider) 并拼接模板
# 步骤 3: 依次执行内容提供者并根据注入类型修改模板
modified_template = original_template
for rule, provider, source in rules_for_target:
content = ""
# 对于非 REMOVE 类型的注入,需要先获取内容
if rule.injection_type != InjectionType.REMOVE:
try:
content = await provider(params)
except Exception as e:
logger.error(f"执行规则 '{rule}' (来源: {source}) 的内容提供者时失败: {e}")
continue # 跳过失败的 provider
logger.error(f"执行规则 '{rule}' (来源: {source}) 的内容提供者时失败: {e}", exc_info=True)
continue # 跳过失败的 provider,不中断整个流程
# 应用注入逻辑
try:
if rule.injection_type == InjectionType.PREPEND:
if content:
@@ -155,10 +222,12 @@ class PromptComponentManager:
if content:
modified_template = f"{modified_template}\n{content}"
elif rule.injection_type == InjectionType.REPLACE:
# 只有在 content 不为 None 且 target_content 有效时才执行替换
if content is not None 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:
# 使用 `\g<0>` 在正则匹配的整个内容后添加新内容
replacement = f"\\g<0>\n{content}"
modified_template = re.sub(rule.target_content, replacement, modified_template)
elif rule.injection_type == InjectionType.REMOVE:
@@ -167,11 +236,41 @@ class PromptComponentManager:
except re.error as e:
logger.error(f"应用规则时发生正则错误: {e} (pattern: '{rule.target_content}')")
except Exception as e:
logger.error(f"应用注入规则 '{rule}' 失败: {e}")
logger.error(f"应用注入规则 '{rule}' (来源: {source}) 失败: {e}", exc_info=True)
return modified_template
# --- 状态查询API ---
async def preview_prompt_injections(
self, target_prompt_name: str, params: PromptParameters
) -> str:
"""
【预览功能】模拟应用所有注入规则,返回最终生成的模板字符串,而不实际修改任何状态。
这个方法对于调试和测试非常有用,可以查看在特定参数下,
一个核心提示词经过所有注入规则处理后会变成什么样子。
Args:
target_prompt_name (str): 希望预览的目标核心提示词名称。
params (PromptParameters): 模拟的请求参数。
Returns:
str: 模拟生成的最终提示词模板字符串。如果找不到模板,则返回错误信息。
"""
try:
# 从全局提示词管理器获取最原始的模板内容
original_prompt = global_prompt_manager._prompts.get(target_prompt_name)
if not original_prompt:
logger.warning(f"无法预览 '{target_prompt_name}',因为找不到这个核心 Prompt。")
return f"Error: Prompt '{target_prompt_name}' not found."
original_template = original_prompt.template
except KeyError:
logger.warning(f"无法预览 '{target_prompt_name}',因为找不到这个核心 Prompt。")
return f"Error: Prompt '{target_prompt_name}' not found."
# 直接调用核心注入逻辑来模拟结果
return await self.apply_injections(target_prompt_name, original_template, params)
# --- 状态观测与查询 API ---
def get_core_prompts(self) -> list[str]:
"""获取所有已注册的核心提示词模板名称列表(即所有可注入的目标)。"""
@@ -187,11 +286,22 @@ class PromptComponentManager:
return [info for info in components if isinstance(info, PromptInfo)]
async def get_full_injection_map(self) -> dict[str, list[dict]]:
"""获取当前完整的注入映射图。"""
"""
获取当前完整的注入映射图。
此方法提供了一个系统全局的注入视图展示了每个核心提示词target
被哪些注入组件source以何种优先级注入。
Returns:
dict[str, list[dict]]: 一个字典,键是目标提示词名称,
值是按优先级排序的注入信息列表。
`[{"name": str, "priority": int, "source": str}]`
"""
injection_map = {}
async with self._lock:
# 合并所有动态规则的目标和所有核心提示词,确保所有潜在目标都被包含
all_targets = set(self._dynamic_rules.keys()) | set(self.get_core_prompts())
for target in all_targets:
for target in sorted(list(all_targets)):
rules = self._dynamic_rules.get(target, {})
if not rules:
injection_map[target] = []
@@ -199,15 +309,23 @@ class PromptComponentManager:
info_list = []
for prompt_name, (rule, _, source) in rules.items():
info_list.append(
{"name": prompt_name, "priority": rule.priority, "source": source}
)
info_list.append({"name": prompt_name, "priority": rule.priority, "source": source})
# 按优先级排序后存入 map
info_list.sort(key=lambda x: x["priority"])
injection_map[target] = info_list
return injection_map
async def get_injections_for_prompt(self, target_prompt_name: str) -> list[dict]:
"""获取指定核心提示词模板的所有注入信息(增强版,包含更多细节)。"""
"""
获取指定核心提示词模板的所有注入信息(包含详细规则)。
Args:
target_prompt_name (str): 目标核心提示词的名称。
Returns:
list[dict]: 一个包含注入规则详细信息的列表,已按优先级排序。
"""
rules_for_target = self._dynamic_rules.get(target_prompt_name, {})
if not rules_for_target:
return []
@@ -227,13 +345,15 @@ class PromptComponentManager:
return info_list
def get_all_dynamic_rules(self) -> dict[str, dict[str, "InjectionRule"]]:
"""获取所有当前的动态注入规则,以 InjectionRule 对象形式返回。"""
"""
获取所有当前的动态注入规则,以 InjectionRule 对象形式返回。
此方法返回一个深拷贝的规则副本,隐藏了 `content_provider` 等内部实现细节。
适合用于展示或序列化当前的规则配置。
"""
rules_copy = {}
# 只返回规则对象,隐藏 provider 实现细节
for target, rules in self._dynamic_rules.items():
target_copy = {}
for name, (rule, _, _) in rules.items():
target_copy[name] = rule
target_copy = {name: rule for name, (rule, _, _) in rules.items()}
rules_copy[target] = target_copy
return copy.deepcopy(rules_copy)
@@ -241,23 +361,12 @@ class PromptComponentManager:
"""
获取所有注入到指定核心提示词的动态规则。
此函数允许你查询一个特定的核心Prompt例如 'core_prompt'
并获取一个包含所有以它为目标的注入规则的字典。
Args:
target_prompt (str): 目标核心提示词的名称。
Returns:
dict[str, InjectionRule]:
一个字典,其中:
- 键 (str): 是注入组件的名称 (例如 'persona_component')。
- 值 (InjectionRule): 是该组件对应的 `InjectionRule` 完整对象。
如果找不到任何注入到该目标的规则,则返回一个空字典。
示例:
>>> rules = prompt_component_manager.get_rules_for_target("core_prompt")
>>> for component_name, rule in rules.items():
... print(f"组件 '{component_name}' 以优先级 {rule.priority} 注入")
dict[str, InjectionRule]: 一个字典,键是注入组件的名称,值是 `InjectionRule` 对象。
如果找不到任何注入到该目标的规则,则返回一个字典
"""
target_rules = self._dynamic_rules.get(target_prompt, {})
return {name: copy.deepcopy(rule_info[0]) for name, rule_info in target_rules.items()}
@@ -266,23 +375,12 @@ class PromptComponentManager:
"""
获取由指定的单个注入组件定义的所有动态规则。
此函数允许你查询一个特定的注入组件(例如 'persona_component'
并找出它注入到了哪些核心Prompt中以及具体是如何注入的。
Args:
component_name (str): 注入组件的名称。
Returns:
dict[str, InjectionRule]:
一个字典,其中:
- 键 (str): 是该组件注入的目标核心提示词的名称 (例如 'core_prompt')。
- 值 (InjectionRule): 是该组件对应的 `InjectionRule` 完整对象。
如果该组件没有定义任何注入规则,则返回一个空字典。
示例:
>>> rules = prompt_component_manager.get_rules_by_component("persona_component")
>>> for target_name, rule in rules.items():
... print(f"组件 'persona_component' 注入到了 '{target_name}'")
dict[str, InjectionRule]: 一个字典,键是目标核心提示词的名称,值是 `InjectionRule` 对象。
如果该组件没有定义任何注入规则,则返回一个字典
"""
found_rules = {}
for target, rules in self._dynamic_rules.items():
@@ -291,26 +389,8 @@ class PromptComponentManager:
found_rules[target] = copy.deepcopy(rule_info[0])
return found_rules
async def preview_prompt_injections(
self, target_prompt_name: str, params: PromptParameters
) -> str:
"""
【预览功能】模拟应用所有注入规则,返回最终生成的模板字符串,而不实际修改任何状态。
"""
# 确保我们从最新的模板开始
try:
# Note: 这里我们不关心注入,只想获取最原始的模板,所以直接访问内部属性
original_prompt = global_prompt_manager._prompts.get(target_prompt_name)
if not original_prompt:
logger.warning(f"无法预览 '{target_prompt_name}',因为找不到这个核心 Prompt。")
return f"Error: Prompt '{target_prompt_name}' not found."
original_template = original_prompt.template
except KeyError:
logger.warning(f"无法预览 '{target_prompt_name}',因为找不到这个核心 Prompt。")
return f"Error: Prompt '{target_prompt_name}' not found."
# 直接调用核心注入逻辑
return await self.apply_injections(target_prompt_name, original_template, params)
# 创建全局单例
# 创建全局单例 (Singleton)
# 在整个应用程序中,应该只使用这一个 `prompt_component_manager` 实例,
# 以确保所有部分都共享和操作同一份动态规则集。
prompt_component_manager = PromptComponentManager()