feat(prompt): 为提示词注入添加占位符保护机制
为防止注入规则(特别是使用宽泛正则表达式的 REMOVE 或 REPLACE 类型)意外修改或删除核心的 "{...}" 占位符,引入了一套新的占位符保护机制。
该机制通过以下步骤确保注入过程的安全性:
1. **保护**:在应用任何规则之前,模板中的所有占位符都会被替换为唯一的临时标记。
2. **预检与警告**:系统会检查所有危险规则(REMOVE/REPLACE),如果其目标内容可能匹配到被保护的占位符,则会记录一条警告日志。
3. **安全应用**:所有注入规则在已保护的模板上按优先级顺序执行。
4. **恢复**:完成所有注入后,临时标记被恢复为原始的占位符。
此项更改显著提升了提示词系统的鲁棒性,确保了核心模板的完整性不会被插件或动态规则无意中破坏。
This commit is contained in:
committed by
Windpicker-owo
parent
67b3643e29
commit
0c1edb40bf
@@ -250,12 +250,15 @@ class PromptComponentManager:
|
||||
"""
|
||||
【核心方法】根据目标名称,应用所有匹配的注入规则,返回修改后的模板。
|
||||
|
||||
这是提示词构建流程中的关键步骤。它会执行以下操作:
|
||||
1. 检查并确保静态规则已加载。
|
||||
2. 获取所有注入到 `target_prompt_name` 的规则。
|
||||
3. 按照规则的 `priority` 属性进行升序排序,优先级数字越小越先应用。
|
||||
4. 依次执行每个规则的 `content_provider` 来异步获取注入内容。
|
||||
5. 根据规则的 `injection_type` (如 PREPEND, APPEND, REPLACE 等) 将内容应用到模板上。
|
||||
此方法实现了“意图识别与安全执行”机制,以确保注入操作的鲁棒性:
|
||||
1. **占位符保护**: 首先,扫描模板中的所有 `"{...}"` 占位符,
|
||||
并用唯一的、无冲突的临时标记替换它们。这可以防止注入规则意外地修改或删除核心占位符。
|
||||
2. **规则预检与警告**: 在应用规则前,检查所有 `REMOVE` 和 `REPLACE` 类型的规则,
|
||||
看它们的 `target_content` 是否可能匹配到被保护的占位符。如果可能,
|
||||
会记录一条明确的警告日志,告知开发者该规则有风险,但不会中断流程。
|
||||
3. **安全执行**: 在“净化”过的模板上(即占位符已被替换的模板),
|
||||
按优先级顺序安全地应用所有注入规则。
|
||||
4. **占位符恢复**: 所有注入操作完成后,将临时标记恢复为原始的占位符。
|
||||
|
||||
Args:
|
||||
target_prompt_name (str): 目标核心提示词的名称。
|
||||
@@ -268,28 +271,51 @@ class PromptComponentManager:
|
||||
if not self._initialized:
|
||||
self.load_static_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: 按优先级排序,数字越小越优先
|
||||
# --- 占位符保护机制 ---
|
||||
placeholders = re.findall(r"({[^{}]+})", original_template)
|
||||
placeholder_map: dict[str, str] = {
|
||||
f"__PROMPT_PLACEHOLDER_{i}__": p for i, p in enumerate(placeholders)
|
||||
}
|
||||
|
||||
# 1. 保护: 将占位符替换为临时标记
|
||||
protected_template = original_template
|
||||
for marker, placeholder in placeholder_map.items():
|
||||
protected_template = protected_template.replace(placeholder, marker)
|
||||
|
||||
# 2. 预检与警告: 检查危险规则
|
||||
for rule, _, source in rules_for_target:
|
||||
if rule.injection_type in (InjectionType.REMOVE, InjectionType.REPLACE) and rule.target_content:
|
||||
try:
|
||||
for p in placeholders:
|
||||
if re.search(rule.target_content, p):
|
||||
logger.warning(
|
||||
f"注入规则警告 (来源: {source}): "
|
||||
f"规则 `target_content` ('{rule.target_content}') "
|
||||
f"可能会影响核心占位符 '{p}'。为保证系统稳定,该占位符已被保护,不会被此规则修改。"
|
||||
)
|
||||
# 只对每个规则警告一次
|
||||
break
|
||||
except re.error:
|
||||
# 正则表达式本身有误,后面执行时会再次捕获,这里可忽略
|
||||
pass
|
||||
|
||||
# 3. 安全执行: 按优先级排序并应用规则
|
||||
rules_for_target.sort(key=lambda x: x[0].priority)
|
||||
|
||||
# 步骤 3: 依次执行内容提供者并根据注入类型修改模板
|
||||
modified_template = original_template
|
||||
modified_template = protected_template
|
||||
for rule, provider, source in rules_for_target:
|
||||
content = ""
|
||||
# 对于非 REMOVE 类型的注入,需要先获取内容
|
||||
if rule.injection_type != InjectionType.REMOVE:
|
||||
try:
|
||||
content = await provider(params, target_prompt_name)
|
||||
except Exception as e:
|
||||
logger.error(f"执行规则 '{rule}' (来源: {source}) 的内容提供者时失败: {e}", exc_info=True)
|
||||
continue # 跳过失败的 provider,不中断整个流程
|
||||
continue
|
||||
|
||||
# 应用注入逻辑
|
||||
try:
|
||||
if rule.injection_type == InjectionType.PREPEND:
|
||||
if content:
|
||||
@@ -298,12 +324,10 @@ 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:
|
||||
@@ -314,7 +338,12 @@ class PromptComponentManager:
|
||||
except Exception as e:
|
||||
logger.error(f"应用注入规则 '{rule}' (来源: {source}) 失败: {e}", exc_info=True)
|
||||
|
||||
return modified_template
|
||||
# 4. 占位符恢复
|
||||
final_template = modified_template
|
||||
for marker, placeholder in placeholder_map.items():
|
||||
final_template = final_template.replace(marker, placeholder)
|
||||
|
||||
return final_template
|
||||
|
||||
async def preview_prompt_injections(
|
||||
self, target_prompt_name: str, params: PromptParameters
|
||||
|
||||
Reference in New Issue
Block a user