mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 13:38:43 +00:00
refactor(json_utils): 重构JSON解析逻辑,提取通用方法并清理代码
This commit is contained in:
@@ -5,6 +5,7 @@ JSON处理工具模块
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from ....utils.logger import logger
|
from ....utils.logger import logger
|
||||||
|
|
||||||
@@ -81,131 +82,93 @@ def fix_json(text: str) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def parse_json_response(
|
def _parse_json_with_pattern(
|
||||||
result_text: str, data_type: str
|
result_text: str, pattern: str, data_type: str, expected_type_name: str = "数据"
|
||||||
) -> tuple[bool, list[dict] | None, str | None]:
|
) -> tuple[bool, Any, str | None]:
|
||||||
"""
|
"""
|
||||||
统一的JSON解析方法(用于JSON数组响应)
|
通用内部 JSON 解析逻辑,包含提取、直接解析、修复后重试。
|
||||||
|
|
||||||
Args:
|
|
||||||
result_text: LLM返回的原始文本
|
|
||||||
data_type: 数据类型 ('topics' | 'user_titles' | 'golden_quotes')
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(成功标志, 解析后的数据列表, 错误消息)
|
|
||||||
"""
|
"""
|
||||||
fixed_json_text = None
|
fixed_json_text = None
|
||||||
try:
|
try:
|
||||||
# 1. 提取JSON部分
|
# 1. 基础清理:去除 markdown 代码块标记
|
||||||
json_match = re.search(r"\[.*\]", result_text, re.DOTALL)
|
clean_text = result_text.strip()
|
||||||
|
clean_text = re.sub(r"```(?:json)?\s*", "", clean_text)
|
||||||
|
clean_text = re.sub(r"```\s*$", "", clean_text)
|
||||||
|
|
||||||
|
# 2. 提取 JSON 部分
|
||||||
|
json_match = re.search(pattern, clean_text, re.DOTALL)
|
||||||
if not json_match:
|
if not json_match:
|
||||||
error_msg = f"{data_type}响应中未找到JSON格式"
|
error_msg = f"{data_type}响应中未找到JSON{expected_type_name}"
|
||||||
logger.warning(error_msg)
|
logger.warning(error_msg)
|
||||||
return False, None, error_msg
|
return False, None, error_msg
|
||||||
|
|
||||||
json_text = json_match.group()
|
json_text = json_match.group()
|
||||||
logger.debug(f"{data_type}分析JSON原文: {json_text[:500]}...")
|
logger.debug(f"{data_type}分析JSON原文: {json_text[:500]}...")
|
||||||
|
|
||||||
# 2. 尝试直接解析
|
# 3. 尝试直接解析
|
||||||
try:
|
try:
|
||||||
data = json.loads(json_text)
|
data = json.loads(json_text)
|
||||||
logger.info(f"{data_type}直接解析成功,解析到 {len(data)} 条数据")
|
count_info = f",包含 {len(data)} 条数据" if isinstance(data, list) else ""
|
||||||
|
logger.info(f"{data_type}直接解析成功{count_info}")
|
||||||
return True, data, None
|
return True, data, None
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logger.debug(f"{data_type}直接解析失败,尝试修复JSON...")
|
logger.debug(f"{data_type}直接解析失败,尝试修复JSON...")
|
||||||
|
|
||||||
# 3. 修复JSON
|
# 4. 修复后重试
|
||||||
fixed_json_text = fix_json(json_text)
|
fixed_json_text = fix_json(json_text)
|
||||||
logger.debug(f"{data_type}修复后的JSON: {fixed_json_text[:300]}...")
|
# 修复后需要重新提取,因为 fix_json 可能会改变文本结构(例如补齐括号)
|
||||||
|
fixed_match = re.search(pattern, fixed_json_text, re.DOTALL)
|
||||||
# 4. 解析修复后的JSON
|
|
||||||
data = json.loads(fixed_json_text)
|
|
||||||
logger.info(f"{data_type}修复后解析成功,解析到 {len(data)} 条数据")
|
|
||||||
return True, data, None
|
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
error_msg = f"{data_type}JSON解析失败: {e}"
|
|
||||||
logger.warning(error_msg)
|
|
||||||
logger.debug(f"修复后的JSON: {fixed_json_text or 'N/A'}")
|
|
||||||
return False, None, error_msg
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"{data_type}解析异常: {e}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
return False, None, error_msg
|
|
||||||
|
|
||||||
|
|
||||||
def parse_json_object_response(
|
|
||||||
result_text: str, data_type: str
|
|
||||||
) -> tuple[bool, dict | None, str | None]:
|
|
||||||
"""
|
|
||||||
统一的JSON解析方法(用于JSON对象响应,如聊天质量分析)
|
|
||||||
|
|
||||||
与 parse_json_response 不同,此函数用于解析返回单个 JSON 对象 {...}
|
|
||||||
而非数组 [{...}, {...}] 的场景。
|
|
||||||
|
|
||||||
解析策略:
|
|
||||||
1. 先去除 markdown 代码块标记
|
|
||||||
2. 直接解析原始 JSON(避免 fix_json 破坏中文引号等合法内容)
|
|
||||||
3. 若直接解析失败,再使用 fix_json 修复后重试
|
|
||||||
|
|
||||||
Args:
|
|
||||||
result_text: LLM返回的原始文本
|
|
||||||
data_type: 数据类型标识(用于日志)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(成功标志, 解析后的字典, 错误消息)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 1. 去除 markdown 代码块标记
|
|
||||||
raw_text = result_text.strip()
|
|
||||||
raw_text = re.sub(r"```(?:json)?\s*", "", raw_text)
|
|
||||||
raw_text = re.sub(r"```\s*$", "", raw_text)
|
|
||||||
raw_text = raw_text.strip()
|
|
||||||
|
|
||||||
# 2. 提取 JSON 对象
|
|
||||||
json_match = re.search(r"\{.*\}", raw_text, re.DOTALL)
|
|
||||||
if not json_match:
|
|
||||||
error_msg = f"{data_type}响应中未找到JSON对象"
|
|
||||||
logger.warning(error_msg)
|
|
||||||
return False, None, error_msg
|
|
||||||
|
|
||||||
json_text = json_match.group()
|
|
||||||
logger.debug(f"{data_type}分析JSON原文: {json_text[:500]}...")
|
|
||||||
|
|
||||||
# 3. 尝试直接解析(保留原始文本,避免中文引号被破坏)
|
|
||||||
try:
|
|
||||||
data = json.loads(json_text)
|
|
||||||
logger.info(f"{data_type}直接解析成功")
|
|
||||||
return True, data, None
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
logger.debug(f"{data_type}直接解析失败,尝试修复JSON...")
|
|
||||||
|
|
||||||
# 4. 使用 fix_json 修复后重试
|
|
||||||
fixed_json = fix_json(json_text)
|
|
||||||
fixed_match = re.search(r"\{.*\}", fixed_json, re.DOTALL)
|
|
||||||
if fixed_match:
|
if fixed_match:
|
||||||
try:
|
try:
|
||||||
data = json.loads(fixed_match.group())
|
data = json.loads(fixed_match.group())
|
||||||
logger.info(f"{data_type}修复后解析成功")
|
count_info = (
|
||||||
|
f",包含 {len(data)} 条数据" if isinstance(data, list) else ""
|
||||||
|
)
|
||||||
|
logger.info(f"{data_type}修复后解析成功{count_info}")
|
||||||
return True, data, None
|
return True, data, None
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
error_msg = f"{data_type}JSON修复后解析仍失败: {e}"
|
error_msg = f"{data_type}JSON修复后解析仍失败: {e}"
|
||||||
logger.warning(error_msg)
|
logger.warning(error_msg)
|
||||||
return False, None, error_msg
|
return False, None, error_msg
|
||||||
|
|
||||||
error_msg = f"{data_type}修复后未找到JSON对象"
|
error_msg = f"{data_type}修复后未找到JSON{expected_type_name}"
|
||||||
return False, None, error_msg
|
return False, None, error_msg
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
error_msg = f"{data_type}JSON解析失败: {e}"
|
|
||||||
logger.warning(error_msg)
|
|
||||||
return False, None, error_msg
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"{data_type}解析异常: {e}"
|
error_msg = f"{data_type}解析异常: {e}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
return False, None, error_msg
|
return False, None, error_msg
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_response(
|
||||||
|
result_text: str, data_type: str
|
||||||
|
) -> tuple[bool, list[dict] | None, str | None]:
|
||||||
|
"""
|
||||||
|
统一的JSON解析方法(用于JSON数组响应)
|
||||||
|
"""
|
||||||
|
return _parse_json_with_pattern(
|
||||||
|
result_text, r"\[.*\]", data_type, expected_type_name="数组"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_object_response(
|
||||||
|
result_text: str, data_type: str
|
||||||
|
) -> tuple[bool, dict | None, str | None]:
|
||||||
|
"""
|
||||||
|
统一的JSON解析方法(用于JSON对象响应)
|
||||||
|
"""
|
||||||
|
return _parse_json_with_pattern(
|
||||||
|
result_text, r"\{.*\}", data_type, expected_type_name="对象"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_json_string(text: str) -> str:
|
||||||
|
"""
|
||||||
|
清理 JSON 字符串中的转义字符,用于正则提取后的数据清洗。
|
||||||
|
"""
|
||||||
|
return text.replace('\\"', '"').replace("\\n", " ").replace("\\t", " ")
|
||||||
|
|
||||||
|
|
||||||
def extract_topics_with_regex(result_text: str, max_topics: int) -> list[dict]:
|
def extract_topics_with_regex(result_text: str, max_topics: int) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
使用正则表达式提取话题信息
|
使用正则表达式提取话题信息
|
||||||
@@ -232,10 +195,7 @@ def extract_topics_with_regex(result_text: str, max_topics: int) -> list[dict]:
|
|||||||
for match in matches[:max_topics]:
|
for match in matches[:max_topics]:
|
||||||
topic_name = match[0].strip()
|
topic_name = match[0].strip()
|
||||||
contributors_str = match[1].strip()
|
contributors_str = match[1].strip()
|
||||||
detail = match[2].strip()
|
detail = _clean_json_string(match[2].strip())
|
||||||
|
|
||||||
# 清理detail中的转义字符
|
|
||||||
detail = detail.replace('\\"', '"').replace("\\n", " ").replace("\\t", " ")
|
|
||||||
|
|
||||||
# 解析参与者列表
|
# 解析参与者列表
|
||||||
contributors = [
|
contributors = [
|
||||||
@@ -287,10 +247,7 @@ def extract_user_titles_with_regex(result_text: str, max_count: int) -> list[dic
|
|||||||
user_id = match[1].strip()
|
user_id = match[1].strip()
|
||||||
title = match[2].strip()
|
title = match[2].strip()
|
||||||
mbti = match[3].strip()
|
mbti = match[3].strip()
|
||||||
reason = match[4].strip()
|
reason = _clean_json_string(match[4].strip())
|
||||||
|
|
||||||
# 清理转义字符
|
|
||||||
reason = reason.replace('\\"', '"').replace("\\n", " ").replace("\\t", " ")
|
|
||||||
|
|
||||||
titles.append(
|
titles.append(
|
||||||
{
|
{
|
||||||
@@ -334,15 +291,9 @@ def extract_golden_quotes_with_regex(result_text: str, max_count: int) -> list[d
|
|||||||
matches = re.findall(pattern, result_text, re.DOTALL)
|
matches = re.findall(pattern, result_text, re.DOTALL)
|
||||||
|
|
||||||
for match in matches[:max_count]:
|
for match in matches[:max_count]:
|
||||||
content = match[0].strip()
|
content = _clean_json_string(match[0].strip())
|
||||||
sender = match[1].strip()
|
sender = match[1].strip()
|
||||||
reason = match[2].strip()
|
reason = _clean_json_string(match[2].strip())
|
||||||
|
|
||||||
# 清理转义字符
|
|
||||||
content = (
|
|
||||||
content.replace('\\"', '"').replace("\\n", " ").replace("\\t", " ")
|
|
||||||
)
|
|
||||||
reason = reason.replace('\\"', '"').replace("\\n", " ").replace("\\t", " ")
|
|
||||||
|
|
||||||
quotes.append({"content": content, "sender": sender, "reason": reason})
|
quotes.append({"content": content, "sender": sender, "reason": reason})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user