正则相关
This commit is contained in:
@@ -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:
|
||||
"""
|
||||
创建新聊天
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user