正则相关

This commit is contained in:
2026-05-06 00:40:18 +08:00
parent f843a74715
commit 9faccc2c03
32 changed files with 4814 additions and 346 deletions

View File

@@ -172,6 +172,41 @@ class ChatService:
logger.error(f"读取聊天失败 {role_name}/{chat_name}: {str(e)}")
raise
def get_message(self, role_name: str, chat_name: str, floor: int) -> Dict:
"""
获取指定楼层的消息
Args:
role_name: 角色名称
chat_name: 聊天名称
floor: 楼层号
Returns:
Dict: 消息数据,如果不存在则返回 None
"""
chat_file = self.chat_dir / role_name / f"{chat_name}.jsonl"
if not chat_file.exists():
return None
try:
with open(chat_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 找到对应的消息行floor + 1因为第0行是header
message_line_index = floor + 1
if message_line_index >= len(lines):
return None
# 解析并返回消息
msg_data = json.loads(lines[message_line_index])
return msg_data
except Exception as e:
logger.error(f"获取消息失败 {role_name}/{chat_name}/{floor}: {str(e)}")
return None
def create_chat(self, role_name: str, chat_name: str, metadata: Dict = None) -> Dict:
"""
创建新聊天

View File

@@ -156,6 +156,29 @@ class ChatWorkflowService:
print(f"[ChatWorkflow] 开始处理请求: role={current_role}, chat={current_chat}")
# ✅ 获取预设名称(用于加载预设绑定的正则规则)
preset_config = request_data.get("presetConfig", {})
preset_name = preset_config.get("selectedPreset")
# ✅ 第1.5步:应用用户输入的正则规则
from services.regex_service import regex_service
from models.regex_rules import RegexPlacement
processed_user_message = regex_service.apply_rules_by_placement(
text=user_message,
placement=RegexPlacement.USER_INPUT.value,
character_name=current_role,
preset_name=preset_name,
message_depth=0,
is_for_llm=True, # ✅ 用户输入会发送给LLM
is_markdown_rendered=False
)
if processed_user_message != user_message:
print(f"[Regex] ✅ 已应用用户输入正则规则")
user_message = processed_user_message
# === 第2步加载角色卡 ===
character_data = request_data.get("characterData")
if not character_data:
@@ -217,6 +240,21 @@ class ChatWorkflowService:
print(f"[ChatWorkflow] 生成完成,内容长度: {len(generated_content)}")
print(f"[ChatWorkflow] Token 使用: {token_usage}")
# ✅ 第6.5步:应用 AI 输出的正则规则
processed_ai_output = regex_service.apply_rules_by_placement(
text=generated_content,
placement=RegexPlacement.AI_OUTPUT.value,
character_name=current_role,
preset_name=preset_name,
message_depth=0,
is_for_llm=False, # ✅ AI输出是显示给用户的
is_markdown_rendered=False
)
if processed_ai_output != generated_content:
print(f"[Regex] ✅ 已应用 AI 输出正则规则")
generated_content = processed_ai_output
# === 第7步记录 Token 使用 ===
chat_id = f"{current_role}/{request_data.get('currentChat', '')}"
floor = request_data.get("floor", 0)
@@ -710,30 +748,243 @@ class ChatWorkflowService:
"""
组装提示词
使用PromptAssembler将角色卡、世界书、聊天历史等组装LLM消息列表
根据预设组件promptComponents动态组装LLM消息列表
"""
# 从预设配置中获取prompt components
preset_config = request_data.get("presetConfig", {})
prompt_components = preset_config.get("promptComponents", [])
# 创建配置
config = PromptConfig(
an_position="after_history", # 默认在历史之后
an_depth=4,
post_history_instructions=None
)
# ✅ 检查是否启用调试模式
debug_prompt = request_data.get("debugPrompt", False)
# 组装提示词
messages = self.prompt_assembler.assemble(
character=character,
chat_history=chat_history,
user_input=user_message,
active_entries=active_entries,
config=config
)
if debug_prompt:
print(f"\n{'='*80}")
print(f"[Prompt Debug] 🧩 预设组件配置")
print(f"{'='*80}")
print(f"[Prompt Debug] 组件数量: {len(prompt_components)}")
for i, comp in enumerate(prompt_components, 1):
print(f" {i}. {comp.get('name', 'Unknown')} (enabled={comp.get('enabled', True)}, type={comp.get('type', 'N/A')})")
print(f"{'='*80}\n")
# ✅ 如果有预设组件,使用预设组件组装
if prompt_components and len(prompt_components) > 0:
return self._assemble_prompt_from_components(
character,
chat_history,
user_message,
active_entries,
prompt_components,
debug_prompt
)
else:
# ✅ 否则使用默认的 SillyTavern 规范组装
print(f"[PromptAssembler] ⚠️ 未检测到预设组件使用默认SillyTavern规范")
# 创建配置
config = PromptConfig(
an_position="after_history", # 默认在历史之后
an_depth=4,
post_history_instructions=None
)
# 组装提示词
messages = self.prompt_assembler.assemble(
character=character,
chat_history=chat_history,
user_input=user_message,
active_entries=active_entries,
config=config
)
return messages
def _assemble_prompt_from_components(
self,
character,
chat_history: List,
user_message: str,
active_entries: List,
prompt_components: List[Dict],
debug_prompt: bool = False
) -> List:
"""
根据预设组件组装提示词
Args:
character: 角色卡数据
chat_history: 聊天历史
user_message: 用户输入
active_entries: 激活的世界书条目
prompt_components: 预设组件列表
debug_prompt: 是否输出调试信息
Returns:
List[BaseMessage]: 组装好的消息列表
"""
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
messages = []
# ✅ 按顺序处理每个启用的组件
enabled_components = [comp for comp in prompt_components if comp.get('enabled', True)]
if debug_prompt:
print(f"\n[Prompt Debug] 🔄 开始组装 {len(enabled_components)} 个启用的组件")
for i, component in enumerate(enabled_components, 1):
comp_name = component.get('name', 'Unknown')
comp_type = component.get('type', 'text') # text, system, user, assistant
comp_content = component.get('content', '')
if debug_prompt:
print(f"\n[Prompt Debug] --- 组件 {i}: {comp_name} ---")
print(f" 类型: {comp_type}")
print(f" 内容长度: {len(comp_content)} 字符")
# ✅ 替换模板变量
processed_content = self._process_component_content(
comp_content,
character,
chat_history,
user_message,
active_entries
)
if debug_prompt:
print(f" 处理后长度: {len(processed_content)} 字符")
if len(processed_content) < 500:
print(f" 完整内容:\n{processed_content}")
else:
print(f" 预览:\n{processed_content[:200]}...")
# ✅ 跳过空内容
if not processed_content or processed_content.strip() == "":
if debug_prompt:
print(f" ⚠️ 内容为空,跳过")
continue
# ✅ 根据组件类型创建对应的消息
if comp_type == 'system':
messages.append(SystemMessage(content=processed_content))
elif comp_type == 'user':
messages.append(HumanMessage(content=processed_content))
elif comp_type == 'assistant':
messages.append(AIMessage(content=processed_content))
else:
# 默认为 system
messages.append(SystemMessage(content=processed_content))
# ✅ 最后添加用户输入(如果还没有添加)
# 检查最后一个消息是否是用户输入
if messages and isinstance(messages[-1], HumanMessage):
# 已经包含用户输入,不需要再添加
pass
else:
# 添加用户输入
messages.append(HumanMessage(content=user_message))
if debug_prompt:
print(f"\n[Prompt Debug] ✅ 组装完成,总消息数: {len(messages)}")
print(f"{'='*80}\n")
return messages
def _process_component_content(
self,
content: str,
character,
chat_history: List,
user_message: str,
active_entries: List
) -> str:
"""
处理组件内容,替换模板变量
支持的变量:
- {{char}}: 角色名称
- {{user}}: 用户名称(暂时用 User
- {{description}}: 角色描述
- {{personality}}: 角色性格
- {{scenario}}: 场景
- {{mes_example}}: 对话示例
- {{first_mes}}: 第一条消息
- {{history}}: 聊天历史
- {{world_info}}: 世界书信息
Args:
content: 原始内容
character: 角色卡数据
chat_history: 聊天历史
user_message: 用户输入
active_entries: 激活的世界书条目
Returns:
str: 处理后的内容
"""
if not content:
return ""
# 替换角色相关变量
content = content.replace('{{char}}', getattr(character, 'name', 'Character'))
content = content.replace('{{user}}', 'User') # TODO: 从配置获取用户名
content = content.replace('{{description}}', getattr(character, 'description', ''))
content = content.replace('{{personality}}', getattr(character, 'personality', ''))
content = content.replace('{{scenario}}', getattr(character, 'scenario', ''))
content = content.replace('{{mes_example}}', getattr(character, 'mes_example', ''))
content = content.replace('{{first_mes}}', getattr(character, 'first_mes', ''))
# 替换聊天历史
if '{{history}}' in content:
history_text = self._format_chat_history(chat_history)
content = content.replace('{{history}}', history_text)
# 替换世界书信息
if '{{world_info}}' in content and active_entries:
world_info_text = self._format_world_info(active_entries)
content = content.replace('{{world_info}}', world_info_text)
return content
def _format_chat_history(self, chat_history: List) -> str:
"""
格式化聊天历史为文本
Args:
chat_history: 聊天历史列表
Returns:
str: 格式化后的历史文本
"""
lines = []
for msg in chat_history:
# ✅ 过滤已被总结的空消息
if hasattr(msg, 'is_summarized') and msg.is_summarized and (msg.mes == "" or msg.mes.strip() == ""):
continue
name = getattr(msg, 'name', 'User' if msg.is_user else 'Assistant')
mes = getattr(msg, 'mes', '')
lines.append(f"{name}: {mes}")
return "\n".join(lines)
def _format_world_info(self, active_entries: List) -> str:
"""
格式化世界书信息为文本
Args:
active_entries: 激活的世界书条目列表
Returns:
str: 格式化后的世界书文本
"""
lines = []
for entry in active_entries:
name = getattr(entry, 'name', 'Unknown')
content = getattr(entry, 'content', '')
lines.append(f"[{name}]\n{content}")
return "\n\n".join(lines)
async def _generate_response(
self,
prompt_messages: List,
@@ -978,6 +1229,29 @@ class ChatWorkflowService:
print(f" - Message Length: {len(user_message)}")
print(f"{'#'*80}\n")
# ✅ 获取预设名称(用于加载预设绑定的正则规则)
preset_config = request_data.get("presetConfig", {})
preset_name = preset_config.get("selectedPreset")
# ✅ 第1.5步:应用用户输入的正则规则
from services.regex_service import regex_service
from models.regex_rules import RegexPlacement
processed_user_message = regex_service.apply_rules_by_placement(
text=user_message,
placement=RegexPlacement.USER_INPUT.value,
character_name=current_role,
preset_name=preset_name,
message_depth=0,
is_for_llm=True, # ✅ 用户输入会发送给LLM
is_markdown_rendered=False
)
if processed_user_message != user_message:
print(f"[Regex] ✅ 已应用用户输入正则规则")
user_message = processed_user_message
# === 第2步加载角色卡 ===
character_data = request_data.get("characterData")
if not character_data:
@@ -1109,6 +1383,21 @@ class ChatWorkflowService:
print(f" - 平均速度: {len(generated_content)/elapsed if elapsed > 0 else 0:.0f} chars/s")
print(f"{'#'*80}\n")
# ✅ 第6.5步:应用 AI 输出的正则规则
processed_ai_output = regex_service.apply_rules_by_placement(
text=generated_content,
placement=RegexPlacement.AI_OUTPUT.value,
character_name=current_role,
preset_name=preset_name,
message_depth=0,
is_for_llm=False, # ✅ AI输出是显示给用户的
is_markdown_rendered=False
)
if processed_ai_output != generated_content:
print(f"[Regex] ✅ 已应用 AI 输出正则规则")
generated_content = processed_ai_output
# === 第7步记录 Token 使用(估算) ===
chat_id = f"{current_role}/{request_data.get('currentChat', '')}"
floor = request_data.get("floor", 0)

View File

@@ -201,7 +201,9 @@ class RegexService:
placement: int,
character_name: Optional[str] = None,
preset_name: Optional[str] = None,
message_depth: int = 0
message_depth: int = 0,
is_for_llm: bool = False, # ✅ 新增是否发送给LLM
is_markdown_rendered: bool = False # ✅ 新增是否已Markdown渲染
) -> str:
"""
根据 placement 应用正则规则
@@ -212,6 +214,8 @@ class RegexService:
character_name: 当前角色卡名称
preset_name: 当前预设名称
message_depth: 消息深度
is_for_llm: 是否用于发送给 LLM影响 promptOnly 逻辑)
is_markdown_rendered: 是否是 Markdown 渲染后的内容(影响 markdownOnly 逻辑)
Returns:
处理后的文本
@@ -220,6 +224,14 @@ class RegexService:
result = text
for rule in rules:
# ✅ 检查 promptOnly如果规则只应用于LLM但当前不是LLM场景则跳过
if rule.promptOnly and not is_for_llm:
continue
# ✅ 检查 markdownOnly如果规则只应用于Markdown渲染但当前不是渲染后则跳过
if rule.markdownOnly and not is_markdown_rendered:
continue
# 检查此规则是否适用于当前 placement
if placement not in [p.value for p in rule.placement]:
continue