241 lines
7.3 KiB
Markdown
241 lines
7.3 KiB
Markdown
---
|
|
phase: 14-robustness-cost
|
|
plan: 02
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- kb_workflow/srt/semantic_engine.py
|
|
autonomous: true
|
|
requirements:
|
|
- ROBUST-02
|
|
- ROBUST-04
|
|
must_haves:
|
|
truths:
|
|
- "LLM 响应解析失败时自动重试或降级"
|
|
- "降级过程记录详细日志,包含错误类型和原因"
|
|
- "Pydantic 验证失败时直接降级(不可恢复错误)"
|
|
artifacts:
|
|
- path: "kb_workflow/srt/semantic_engine.py"
|
|
provides: "_parse_result() 带解析失败处理"
|
|
exports: ["_parse_result"]
|
|
key_links:
|
|
- from: "_parse_result()"
|
|
to: "extract_json_from_llm_response()"
|
|
via: "JSON 提取"
|
|
pattern: "extract_json_from_llm_response"
|
|
- from: "_parse_result()"
|
|
to: "SegmentationResult"
|
|
via: "Pydantic 验证"
|
|
pattern: "SegmentationResult(**data)"
|
|
---
|
|
|
|
<objective>
|
|
增强 LLM 响应解析的错误处理,区分解析失败类型并采取不同策略。
|
|
|
|
Purpose: 提高 LLM 输出格式不稳定时的容错能力,记录详细日志便于诊断。
|
|
Output: 改进的 _parse_result() 方法,带详细的错误日志和重试逻辑。
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
</context>
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts from semantic_engine.py -->
|
|
|
|
From kb_workflow/srt/semantic_engine.py:
|
|
```python
|
|
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 捕获并降级
|
|
</interfaces>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: 改进解析失败的错误处理和日志</name>
|
|
<files>kb_workflow/srt/semantic_engine.py</files>
|
|
<behavior>
|
|
- 区分 JSONDecodeError (无法提取 JSON) 和 ValidationError (Pydantic 验证失败)
|
|
- JSONDecodeError 是可恢复错误(可能 LLM 输出格式问题),可以重试
|
|
- ValidationError 是不可恢复错误(数据结构错误),直接降级
|
|
- 所有解析失败都记录详细日志:
|
|
- 错误类型 (JSONDecodeError/ValidationError/ValueError)
|
|
- 错误消息
|
|
- LLM 输出前 500 字符(用于诊断)
|
|
</behavior>
|
|
<action>
|
|
修改 UnifiedSegmentationStrategy._parse_result() 方法:
|
|
|
|
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 次。
|
|
</action>
|
|
<verify>
|
|
<automated>pytest tests/test_semantic_robustness.py::test_parse_failure_retry -x</automated>
|
|
</verify>
|
|
<done>
|
|
- JSONDecodeError 和 ValueError 触发重试 1 次
|
|
- ValidationError 直接降级,不重试
|
|
- 所有解析失败都记录详细日志(错误类型 + LLM 输出片段)
|
|
- 测试覆盖各种解析失败场景
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
## 整体验证标准
|
|
|
|
1. **JSON 解析失败**: JSONDecodeError 触发重试 1 次,仍失败则降级
|
|
2. **Pydantic 验证失败**: ValidationError 直接降级,不重试
|
|
3. **JSON 提取失败**: ValueError 触发重试 1 次,仍失败则降级
|
|
4. **日志完整性**: 所有解析失败都记录错误类型和 LLM 输出片段
|
|
|
|
运行完整测试套件:
|
|
```bash
|
|
python3 -m pytest tests/test_semantic_robustness.py::test_parse_failure_retry -v -x
|
|
```
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
1. JSONDecodeError 重试 1 次后降级
|
|
2. ValidationError 直接降级,不重试
|
|
3. 所有解析失败都记录 warning 级别日志
|
|
4. 日志包含错误类型、错误消息、LLM 输出前 500 字符
|
|
5. 所有测试通过
|
|
</success_criteria>
|
|
|
|
<output>
|
|
执行完成后创建 `.planning/phases/14-robustness-cost/14-02-SUMMARY.md`
|
|
</output>
|