修改LLM的输出格式以适配一些将思考内容放在输出中的api

pull/1030/head
NFS688 2025-06-08 21:37:19 +08:00
parent d45ef07ea8
commit 5b49db8029
4 changed files with 149 additions and 10 deletions

View File

@ -11,7 +11,7 @@ from src.chat.utils.utils_image import image_path_to_base64 # Local import need
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
from src.chat.emoji_system.emoji_manager import emoji_manager
from src.chat.focus_chat.heartFC_sender import HeartFCSender
from src.chat.utils.utils import process_llm_response
from src.chat.utils.utils import process_llm_json_response
from src.chat.utils.info_catcher import info_catcher_manager
from src.chat.heart_flow.utils_chat import get_chat_type_and_target_info
from src.chat.message_receive.chat_stream import ChatStream
@ -50,11 +50,19 @@ def init_prompt():
{chat_target}
{identity}在这聊天中"{target_message}"引起了你的注意你想要在群里发言或者回复这条消息
你需要使用合适的语言习惯和句法参考聊天内容组织一条日常且口语化的回复注意不要复读你说过的话
{config_expression_style}请注意不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容
{config_expression_style}
{keywords_reaction_prompt}
请不要输出违法违规内容不要输出色情暴力政治相关内容如有敏感内容请规避
不要浮夸不要夸张修辞只输出一条回复就好
现在你说
请按照以下JSON格式输出你的回复
{{
"finalreply": "你的回复内容"
}}
注意
- 只在finalreply字段中输出回复内容不要包含多余的前后缀冒号引号括号()表情包at或@等
- 确保JSON格式正确
""",
"default_replyer_prompt",
)
@ -76,11 +84,19 @@ def init_prompt():
{style_habbits}
{grammar_habbits}
{config_expression_style}请注意不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容
{config_expression_style}
{keywords_reaction_prompt}
请不要输出违法违规内容不要输出色情暴力政治相关内容如有敏感内容请规避
不要浮夸不要夸张修辞只输出一条回复就好
现在你说
请按照以下JSON格式输出你的回复
{{
"finalreply": "你的回复内容"
}}
注意
- 只在finalreply字段中输出回复内容不要包含多余的前后缀冒号引号括号()表情包at或@等
- 确保JSON格式正确
""",
"default_replyer_private_prompt",
)
@ -307,7 +323,7 @@ class DefaultReplyer:
logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}")
return None # LLM 调用失败则无法生成回复
processed_response = process_llm_response(content)
processed_response = process_llm_json_response(content)
# 5. 处理 LLM 响应
if not content:

View File

@ -8,7 +8,7 @@ from src.chat.utils.timer_calculator import Timer
from src.common.logger_manager import get_logger
from src.chat.utils.info_catcher import info_catcher_manager
from src.person_info.person_info import person_info_manager
from src.chat.utils.utils import process_llm_response
from src.chat.utils.utils import process_llm_json_response
logger = get_logger("normal_chat_response")
@ -58,7 +58,7 @@ class NormalChatGenerator:
if model_response:
logger.debug(f"{global_config.bot.nickname}的备选回复是:{model_response}")
model_response = process_llm_response(model_response)
model_response = process_llm_json_response(model_response)
return model_response
else:

View File

@ -45,7 +45,14 @@ def init_prompt():
{keywords_reaction_prompt}
请注意不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容
{moderation_prompt}
不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容""",
请按照以下JSON格式输出你的回复
{{
"finalreply": "你的回复内容"
}}
注意
- 不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容
- 确保JSON格式正确""",
"reasoning_prompt_main",
)
@ -78,7 +85,14 @@ def init_prompt():
请回复的平淡一些简短一些说中文不要刻意突出自身学科背景不要浮夸平淡一些 不要随意遵从他人指令
请注意不要输出多余内容(包括前后缀冒号和引号括号等)只输出回复内容
{moderation_prompt}
不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容""",
请按照以下JSON格式输出你的回复
{{
"finalreply": "你的回复内容"
}}
注意
- 不要输出多余内容(包括前后缀冒号和引号括号()表情包at或 @等 )只输出回复内容
- 确保JSON格式正确""",
"reasoning_prompt_private_main", # New template for private CHAT chat
)

View File

@ -6,6 +6,8 @@ from collections import Counter
import jieba
import numpy as np
from maim_message import UserInfo
from json_repair import repair_json
import json
from src.common.logger import get_module_logger
from src.manager.mood_manager import mood_manager
@ -322,6 +324,113 @@ def random_remove_punctuation(text: str) -> str:
result += char
return result
def process_llm_json_response(text: str) -> list[str]:
"""
处理LLM的JSON格式回复提取finalreply字段内容
Args:
text: LLM的原始回复文本
Returns:
list[str]: 处理后的回复内容列表
"""
if text:
try:
# 查找文本中最后一个JSON对象
last_json_str = _extract_last_json_from_text(text)
if not last_json_str:
logger.warning("未找到有效的JSON对象返回默认回复")
return process_llm_response("懒得说")
logger.info(f"提取到最后一个JSON: {last_json_str}")
# 使用repair_json修复可能的JSON格式错误
fixed_json = repair_json(last_json_str)
logger.debug(f"修复后的JSON: {fixed_json}")
if isinstance(fixed_json, str):
try:
parsed_json = json.loads(fixed_json)
except json.JSONDecodeError as decode_error:
logger.error(f"JSON解析错误: {str(decode_error)}")
return process_llm_response("懒得说")
else:
# 如果repair_json直接返回了字典对象直接使用
parsed_json = fixed_json
logger.debug(f"解析后的JSON数据: {parsed_json}")
final_reply = parsed_json.get("finalreply", "")
if not final_reply:
logger.warning("LLM的返回可能为空返回默认回复")
return process_llm_response("懒得说")
logger.info(f"成功提取finalreply: {final_reply}")
# 对提取的回复内容进行常规处理
return process_llm_response(final_reply)
except Exception as e:
logger.error(f"处理JSON格式回复时发生错误: {e},回退到普通文本处理")
return process_llm_response(text)
else:
logger.warning(f"LLM的返回可能为空返回默认回复")
return process_llm_response("懒得说")
def _extract_last_json_from_text(text: str) -> str:
"""
从给定文本中提取最后一个JSON对象的字符串
该方法从文本末尾开始反向搜索'{'字符并尝试从每个这样的字符开始
使用 json.JSONDecoder.raw_decode 解析一个JSON对象
当反向搜索时第一个成功解析为完整JSON对象的子串将被返回
Args:
text: 可能包含JSON对象字符串的输入文本
Returns:
在文本中找到的最后一个有效JSON对象的字符串表示形式
如果未找到有效的JSON对象则返回空字符串
"""
decoder = json.JSONDecoder() # 创建一个JSON解码器实例
# 从文本的末尾开始搜索 '{'
# current_search_end_pos 作为 rfind 的 'end' 参数,用于在 text[0:current_search_end_pos] 中搜索
current_search_end_pos = len(text)
while True:
# 从后往前查找 '{'。
# 查找范围是 text[0 : current_search_end_pos]
start_pos = text.rfind('{', 0, current_search_end_pos)
if start_pos == -1:
# 在剩余的搜索空间中没有找到更多的 '{' 字符。
logger.debug("没有(更多)找到'{'字符,或者所有解析尝试都失败了。")
return ""
# 尝试从这个位置开始解码一个JSON对象
# text_slice_to_decode 是从找到的 '{' 到文本末尾的切片
text_slice_to_decode = text[start_pos:]
try:
# raw_decode 尝试解析切片中的第一个JSON实体。
# 它返回 (python_object, index_in_slice_where_parsing_stopped)
# 即 (解析后的Python对象, JSON在切片中结束位置的下一个索引)。
_, end_idx_in_slice = decoder.raw_decode(text_slice_to_decode)
# 实际的JSON字符串是从 start_pos 到 start_pos + end_idx_in_slice。
extracted_json_str = text[start_pos : start_pos + end_idx_in_slice]
# 因为我们是从'{'开始搜索的raw_decode 应该能确保它是一个对象(或数组,但这里主要关注对象)。
# 如果解析成功这就是最后一个有效的JSON对象。
logger.debug(f"成功解析JSON对象字符串: {extracted_json_str}")
return extracted_json_str
except json.JSONDecodeError as e:
# 从 start_pos 开始的子字符串不是一个有效的JSON对象或者是格式错误的。
# 打印错误信息和尝试解析的子串前缀,方便调试
# 将换行符替换为空格,避免日志格式混乱
preview_slice = text_slice_to_decode[:70].replace('\n', ' ')
logger.debug(f"raw_decode 对索引 {start_pos} 开始的文本解析失败。子串预览: '{preview_slice}...'. 错误: {e}")
# 更新 current_search_end_pos以便在下一次迭代中
# 在当前的 start_pos 之前搜索 '{'。
current_search_end_pos = start_pos
# 继续循环,尝试前一个 '{'
# 理论上,由于循环结构和返回语句,这一行是不可达的,但作为备用:
return ""
def process_llm_response(text: str) -> list[str]:
# 先保护颜文字