refactor(json_parser): 统一 LLM 响应的 JSON 解析逻辑,简化代码并提高解析成功率
This commit is contained in:
216
docs/development/json_parser_unification.md
Normal file
216
docs/development/json_parser_unification.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# JSON 解析统一化改进文档
|
||||
|
||||
## 改进目标
|
||||
统一项目中所有 LLM 响应的 JSON 解析逻辑,使用 `json_repair` 库和统一的解析工具,简化代码并提高解析成功率。
|
||||
|
||||
## 创建的新工具模块
|
||||
|
||||
### `src/utils/json_parser.py`
|
||||
提供统一的 JSON 解析功能:
|
||||
|
||||
#### 主要函数:
|
||||
1. **`extract_and_parse_json(response, strict=False)`**
|
||||
- 从 LLM 响应中提取并解析 JSON
|
||||
- 自动处理 Markdown 代码块标记
|
||||
- 使用 json_repair 修复格式问题
|
||||
- 支持严格模式和容错模式
|
||||
|
||||
2. **`safe_parse_json(json_str, default=None)`**
|
||||
- 安全解析 JSON,失败时返回默认值
|
||||
|
||||
3. **`extract_json_field(response, field_name, default=None)`**
|
||||
- 从 LLM 响应中提取特定字段的值
|
||||
|
||||
#### 处理策略:
|
||||
1. 清理 Markdown 代码块标记(```json 和 ```)
|
||||
2. 提取 JSON 对象或数组(使用栈匹配算法)
|
||||
3. 尝试直接解析
|
||||
4. 如果失败,使用 json_repair 修复后解析
|
||||
5. 容错模式下返回空字典或空列表
|
||||
|
||||
## 已修改的文件
|
||||
|
||||
### 1. `src/chat/memory_system/memory_query_planner.py` ✅
|
||||
- 移除了自定义的 `_extract_json_payload` 方法
|
||||
- 使用 `extract_and_parse_json` 替代原有的解析逻辑
|
||||
- 简化了代码,提高了可维护性
|
||||
|
||||
**修改前:**
|
||||
```python
|
||||
payload = self._extract_json_payload(response)
|
||||
if not payload:
|
||||
return self._default_plan(query_text)
|
||||
try:
|
||||
data = orjson.loads(payload)
|
||||
except orjson.JSONDecodeError as exc:
|
||||
...
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```python
|
||||
data = extract_and_parse_json(response, strict=False)
|
||||
if not data or not isinstance(data, dict):
|
||||
return self._default_plan(query_text)
|
||||
```
|
||||
|
||||
### 2. `src/chat/memory_system/memory_system.py` ✅
|
||||
- 移除了自定义的 `_extract_json_payload` 方法
|
||||
- 在 `_evaluate_information_value` 方法中使用统一解析工具
|
||||
- 简化了错误处理逻辑
|
||||
|
||||
### 3. `src/chat/interest_system/bot_interest_manager.py` ✅
|
||||
- 移除了自定义的 `_clean_llm_response` 方法
|
||||
- 使用 `extract_and_parse_json` 解析兴趣标签数据
|
||||
- 改进了错误处理和日志输出
|
||||
|
||||
### 4. `src/plugins/built_in/affinity_flow_chatter/chat_stream_impression_tool.py` ✅
|
||||
- 将 `_clean_llm_json_response` 标记为已废弃
|
||||
- 使用 `extract_and_parse_json` 解析聊天流印象数据
|
||||
- 添加了类型检查和错误处理
|
||||
|
||||
## 待修改的文件
|
||||
|
||||
### 需要类似修改的其他文件:
|
||||
1. `src/plugins/built_in/affinity_flow_chatter/proactive_thinking_executor.py`
|
||||
- 包含自定义的 JSON 清理逻辑
|
||||
|
||||
2. `src/plugins/built_in/affinity_flow_chatter/user_profile_tool.py`
|
||||
- 包含自定义的 JSON 清理逻辑
|
||||
|
||||
3. 其他包含自定义 JSON 解析逻辑的文件
|
||||
|
||||
## 改进效果
|
||||
|
||||
### 1. 代码简化
|
||||
- 消除了重复的 JSON 提取和清理代码
|
||||
- 减少了代码行数和维护成本
|
||||
- 统一了错误处理模式
|
||||
|
||||
### 2. 解析成功率提升
|
||||
- 使用 json_repair 自动修复常见的 JSON 格式问题
|
||||
- 支持多种 JSON 包装格式(代码块、纯文本等)
|
||||
- 更好的容错处理
|
||||
|
||||
### 3. 可维护性提升
|
||||
- 集中管理 JSON 解析逻辑
|
||||
- 易于添加新的解析策略
|
||||
- 便于调试和日志记录
|
||||
|
||||
### 4. 一致性提升
|
||||
- 所有 LLM 响应使用相同的解析流程
|
||||
- 统一的日志输出格式
|
||||
- 一致的错误处理
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法:
|
||||
```python
|
||||
from src.utils.json_parser import extract_and_parse_json
|
||||
|
||||
# LLM 响应可能包含 Markdown 代码块或其他文本
|
||||
llm_response = '```json\\n{"key": "value"}\\n```'
|
||||
|
||||
# 自动提取和解析
|
||||
data = extract_and_parse_json(llm_response, strict=False)
|
||||
# 返回: {'key': 'value'}
|
||||
|
||||
# 如果解析失败,返回空字典(非严格模式)
|
||||
# 严格模式下返回 None
|
||||
```
|
||||
|
||||
### 提取特定字段:
|
||||
```python
|
||||
from src.utils.json_parser import extract_json_field
|
||||
|
||||
llm_response = '{"score": 0.85, "reason": "Good quality"}'
|
||||
score = extract_json_field(llm_response, "score", default=0.0)
|
||||
# 返回: 0.85
|
||||
```
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **单元测试**:
|
||||
- 测试各种 JSON 格式(带/不带代码块标记)
|
||||
- 测试格式错误的 JSON(验证 json_repair 的修复能力)
|
||||
- 测试嵌套 JSON 结构
|
||||
- 测试空响应和无效响应
|
||||
|
||||
2. **集成测试**:
|
||||
- 在实际 LLM 调用场景中测试
|
||||
- 验证不同模型的响应格式兼容性
|
||||
- 测试错误处理和日志输出
|
||||
|
||||
3. **性能测试**:
|
||||
- 测试大型 JSON 的解析性能
|
||||
- 验证缓存和优化策略
|
||||
|
||||
## 迁移指南
|
||||
|
||||
### 旧代码模式:
|
||||
```python
|
||||
# 旧的自定义解析逻辑
|
||||
def _extract_json(response: str) -> str | None:
|
||||
stripped = response.strip()
|
||||
code_block_match = re.search(r"```(?:json)?\\s*(.*?)```", stripped, re.DOTALL)
|
||||
if code_block_match:
|
||||
return code_block_match.group(1)
|
||||
# ... 更多自定义逻辑
|
||||
|
||||
# 使用
|
||||
payload = self._extract_json(response)
|
||||
if payload:
|
||||
data = orjson.loads(payload)
|
||||
```
|
||||
|
||||
### 新代码模式:
|
||||
```python
|
||||
# 使用统一工具
|
||||
from src.utils.json_parser import extract_and_parse_json
|
||||
|
||||
# 直接解析
|
||||
data = extract_and_parse_json(response, strict=False)
|
||||
if data and isinstance(data, dict):
|
||||
# 使用数据
|
||||
pass
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **导入语句**:确保添加正确的导入
|
||||
```python
|
||||
from src.utils.json_parser import extract_and_parse_json
|
||||
```
|
||||
|
||||
2. **错误处理**:统一工具已包含错误处理,无需额外 try-except
|
||||
```python
|
||||
# 不需要
|
||||
try:
|
||||
data = extract_and_parse_json(response)
|
||||
except Exception:
|
||||
...
|
||||
|
||||
# 应该
|
||||
data = extract_and_parse_json(response, strict=False)
|
||||
if not data:
|
||||
# 处理失败情况
|
||||
pass
|
||||
```
|
||||
|
||||
3. **类型检查**:始终验证返回值类型
|
||||
```python
|
||||
data = extract_and_parse_json(response)
|
||||
if isinstance(data, dict):
|
||||
# 处理字典
|
||||
elif isinstance(data, list):
|
||||
# 处理列表
|
||||
```
|
||||
|
||||
## 后续工作
|
||||
|
||||
1. 完成剩余文件的迁移
|
||||
2. 添加完整的单元测试
|
||||
3. 更新相关文档
|
||||
4. 考虑添加性能监控和统计
|
||||
|
||||
## 日期
|
||||
2025年11月2日
|
||||
Reference in New Issue
Block a user