7.3 KiB
7.3 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14-robustness-cost | 02 | execute | 1 |
|
true |
|
|
Purpose: 提高 LLM 输出格式不稳定时的容错能力,记录详细日志便于诊断。 Output: 改进的 _parse_result() 方法,带详细的错误日志和重试逻辑。
<execution_context> @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/phases/14-robustness-cost/14-CONTEXT.md @.planning/phases/14-robustness-cost/14-RESEARCH.md @.planning/phases/14-robustness-cost/14-VALIDATION.md @.planning/ROADMAP.md @.planning/REQUIREMENTS.md @.planning/STATE.md@kb_workflow/srt/semantic_engine.py @.planning/phases/14-robustness-cost/14-01-PLAN.md
From kb_workflow/srt/semantic_engine.py:
def extract_json_from_llm_response(response: str) -> dict:
"""从 LLM 响应中提取 JSON
处理以下情况:
- 纯 JSON
- 包裹在 ```json ``` 中的 JSON
- 前后包含额外说明文本的 JSON
Raises:
ValueError: 无法提取有效 JSON
"""
# 尝试直接解析
try:
return json.loads(response.strip())
except json.JSONDecodeError:
pass
# 尝试提取 ```json ``` 代码块
json_match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group(1))
except json.JSONDecodeError:
pass
# 尝试提取任何 JSON 对象
json_match = re.search(r'\{.*\}', response, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError as e:
raise ValueError(f"无法从 LLM 响应中提取有效 JSON: {e}")
raise ValueError("LLM 响应中未找到 JSON 内容")
class SegmentationResult(BaseModel):
video_type: str
segments: List[VideoSegment]
actual_segment_count: int
def _parse_result(
self,
llm_output: str,
fallback_segments: List
) -> List[VideoSegment]:
"""解析 LLM 输出并验证
Raises:
Exception: 解析或验证失败时
"""
try:
# 提取 JSON
data = extract_json_from_llm_response(llm_output)
# Pydantic 验证
result = SegmentationResult(**data)
logger.info(
f"LLM 分段成功: 类型={result.video_type}, "
f"分段数={result.actual_segment_count}"
)
return result.segments
except (json.JSONDecodeError, ValidationError) as e:
logger.error(f"LLM 分段结果验证失败: {e}")
logger.error(f"原始 LLM 输出: {llm_output[:500]}...")
# 降级到规则分段
logger.info("降级到规则分段")
raise
except Exception as e:
logger.error(f"未知错误: {e}")
raise
关键观察:
- extract_json_from_llm_response() 可能抛出 ValueError (无法提取 JSON)
- SegmentationResult(**data) 可能抛出 ValidationError (Pydantic 验证失败)
- 当前实现: 所有解析失败都抛出异常,由 segment() 方法的 try-except 捕获并降级
1. 拆分 except 块,区分不同的错误类型:
```python
except json.JSONDecodeError as e:
# JSON 解析失败(可恢复)
logger.error(
f"JSON 解析失败: {e}\n"
f"LLM 输出片段: {llm_output[:500]}..."
)
# 抛出特定异常,由 segment() 决定是否重试
raise
except ValidationError as e:
# Pydantic 验证失败(不可恢复)
logger.error(
f"Pydantic 验证失败: {e}\n"
f"LLM 输出片段: {llm_output[:500]}..."
)
# 直接降级,不重试
raise
except ValueError as e:
# 无法提取 JSON(可恢复)
logger.error(
f"JSON 提取失败: {e}\n"
f"LLM 输出片段: {llm_output[:500]}..."
)
raise
```
2. 在 segment() 方法中,修改 LLM 调用部分的重试逻辑:
- 对 JSONDecodeError 和 ValueError 进行重试
- 对 ValidationError 不重试,直接降级
- 使用 _is_recoverable_error() 的模式,创建 _is_recoverable_parse_error()
3. 统一日志格式:
- 重试: info 级别
- 降级: warning 级别,包含 "降级到规则分段"
遵守用户决策: 解析失败直接降级(不重试),但 JSONDecodeError 可以重试 1 次。
pytest tests/test_semantic_robustness.py::test_parse_failure_retry -x
- JSONDecodeError 和 ValueError 触发重试 1 次
- ValidationError 直接降级,不重试
- 所有解析失败都记录详细日志(错误类型 + LLM 输出片段)
- 测试覆盖各种解析失败场景
## 整体验证标准
- JSON 解析失败: JSONDecodeError 触发重试 1 次,仍失败则降级
- Pydantic 验证失败: ValidationError 直接降级,不重试
- JSON 提取失败: ValueError 触发重试 1 次,仍失败则降级
- 日志完整性: 所有解析失败都记录错误类型和 LLM 输出片段
运行完整测试套件:
python3 -m pytest tests/test_semantic_robustness.py::test_parse_failure_retry -v -x
<success_criteria>
- JSONDecodeError 重试 1 次后降级
- ValidationError 直接降级,不重试
- 所有解析失败都记录 warning 级别日志
- 日志包含错误类型、错误消息、LLM 输出前 500 字符
- 所有测试通过 </success_criteria>