Showing
3 changed files
with
43 additions
and
9 deletions
| @@ -31,6 +31,7 @@ from .nodes import ( | @@ -31,6 +31,7 @@ from .nodes import ( | ||
| 31 | ChapterGenerationNode, | 31 | ChapterGenerationNode, |
| 32 | ChapterJsonParseError, | 32 | ChapterJsonParseError, |
| 33 | ChapterContentError, | 33 | ChapterContentError, |
| 34 | + ChapterValidationError, | ||
| 34 | DocumentLayoutNode, | 35 | DocumentLayoutNode, |
| 35 | WordBudgetNode, | 36 | WordBudgetNode, |
| 36 | ) | 37 | ) |
| @@ -601,11 +602,16 @@ class ReportAgent: | @@ -601,11 +602,16 @@ class ReportAgent: | ||
| 601 | stream_callback=chunk_callback | 602 | stream_callback=chunk_callback |
| 602 | ) | 603 | ) |
| 603 | break | 604 | break |
| 604 | - except (ChapterJsonParseError, ChapterContentError) as structured_error: | ||
| 605 | - error_kind = ( | ||
| 606 | - "content_sparse" if isinstance(structured_error, ChapterContentError) else "json_parse" | ||
| 607 | - ) | ||
| 608 | - readable_label = "内容密度异常" if error_kind == "content_sparse" else "JSON解析失败" | 605 | + except (ChapterJsonParseError, ChapterContentError, ChapterValidationError) as structured_error: |
| 606 | + if isinstance(structured_error, ChapterContentError): | ||
| 607 | + error_kind = "content_sparse" | ||
| 608 | + readable_label = "内容密度异常" | ||
| 609 | + elif isinstance(structured_error, ChapterValidationError): | ||
| 610 | + error_kind = "validation" | ||
| 611 | + readable_label = "结构校验失败" | ||
| 612 | + else: | ||
| 613 | + error_kind = "json_parse" | ||
| 614 | + readable_label = "JSON解析失败" | ||
| 609 | if isinstance(structured_error, ChapterContentError): | 615 | if isinstance(structured_error, ChapterContentError): |
| 610 | candidate = getattr(structured_error, "chapter_payload", None) | 616 | candidate = getattr(structured_error, "chapter_payload", None) |
| 611 | candidate_score = getattr(structured_error, "body_characters", 0) or 0 | 617 | candidate_score = getattr(structured_error, "body_characters", 0) or 0 |
| @@ -636,6 +642,10 @@ class ReportAgent: | @@ -636,6 +642,10 @@ class ReportAgent: | ||
| 636 | 'error': str(structured_error), | 642 | 'error': str(structured_error), |
| 637 | 'reason': error_kind, | 643 | 'reason': error_kind, |
| 638 | } | 644 | } |
| 645 | + if isinstance(structured_error, ChapterValidationError): | ||
| 646 | + validation_errors = getattr(structured_error, "errors", None) | ||
| 647 | + if validation_errors: | ||
| 648 | + status_payload['errors'] = validation_errors | ||
| 639 | if will_fallback: | 649 | if will_fallback: |
| 640 | status_payload['warning'] = 'content_sparse_fallback_pending' | 650 | status_payload['warning'] = 'content_sparse_fallback_pending' |
| 641 | emit('chapter_status', status_payload) | 651 | emit('chapter_status', status_payload) |
| @@ -6,7 +6,12 @@ Report Engine节点处理模块。 | @@ -6,7 +6,12 @@ Report Engine节点处理模块。 | ||
| 6 | 6 | ||
| 7 | from .base_node import BaseNode, StateMutationNode | 7 | from .base_node import BaseNode, StateMutationNode |
| 8 | from .template_selection_node import TemplateSelectionNode | 8 | from .template_selection_node import TemplateSelectionNode |
| 9 | -from .chapter_generation_node import ChapterGenerationNode, ChapterJsonParseError, ChapterContentError | 9 | +from .chapter_generation_node import ( |
| 10 | + ChapterGenerationNode, | ||
| 11 | + ChapterJsonParseError, | ||
| 12 | + ChapterContentError, | ||
| 13 | + ChapterValidationError, | ||
| 14 | +) | ||
| 10 | from .document_layout_node import DocumentLayoutNode | 15 | from .document_layout_node import DocumentLayoutNode |
| 11 | from .word_budget_node import WordBudgetNode | 16 | from .word_budget_node import WordBudgetNode |
| 12 | 17 | ||
| @@ -17,6 +22,7 @@ __all__ = [ | @@ -17,6 +22,7 @@ __all__ = [ | ||
| 17 | "ChapterGenerationNode", | 22 | "ChapterGenerationNode", |
| 18 | "ChapterJsonParseError", | 23 | "ChapterJsonParseError", |
| 19 | "ChapterContentError", | 24 | "ChapterContentError", |
| 25 | + "ChapterValidationError", | ||
| 20 | "DocumentLayoutNode", | 26 | "DocumentLayoutNode", |
| 21 | "WordBudgetNode", | 27 | "WordBudgetNode", |
| 22 | ] | 28 | ] |
| @@ -77,6 +77,18 @@ class ChapterContentError(ValueError): | @@ -77,6 +77,18 @@ class ChapterContentError(ValueError): | ||
| 77 | self.non_heading_blocks: int = int(non_heading_blocks or 0) | 77 | self.non_heading_blocks: int = int(non_heading_blocks or 0) |
| 78 | 78 | ||
| 79 | 79 | ||
| 80 | +class ChapterValidationError(ValueError): | ||
| 81 | + """ | ||
| 82 | + 章节结构在本地和LLM修复后仍无法通过校验时抛出。 | ||
| 83 | + | ||
| 84 | + 该异常用于在Agent层触发针对单章的重试,而无需重启整本报告。 | ||
| 85 | + """ | ||
| 86 | + | ||
| 87 | + def __init__(self, message: str, errors: Optional[List[str]] | None = None): | ||
| 88 | + super().__init__(message) | ||
| 89 | + self.errors: List[str] = list(errors or []) | ||
| 90 | + | ||
| 91 | + | ||
| 80 | class ChapterGenerationNode(BaseNode): | 92 | class ChapterGenerationNode(BaseNode): |
| 81 | """ | 93 | """ |
| 82 | 负责按章节调用LLM并校验JSON结构。 | 94 | 负责按章节调用LLM并校验JSON结构。 |
| @@ -268,8 +280,9 @@ class ChapterGenerationNode(BaseNode): | @@ -268,8 +280,9 @@ class ChapterGenerationNode(BaseNode): | ||
| 268 | ) | 280 | ) |
| 269 | 281 | ||
| 270 | if not valid: | 282 | if not valid: |
| 271 | - raise ValueError( | ||
| 272 | - f"{section.title} 章节JSON校验失败: {'; '.join(errors[:5])}" | 283 | + raise ChapterValidationError( |
| 284 | + f"{section.title} 章节JSON校验失败: {'; '.join(errors[:5])}", | ||
| 285 | + errors=errors, | ||
| 273 | ) | 286 | ) |
| 274 | if content_error: | 287 | if content_error: |
| 275 | raise content_error | 288 | raise content_error |
| @@ -1555,4 +1568,9 @@ class ChapterGenerationNode(BaseNode): | @@ -1555,4 +1568,9 @@ class ChapterGenerationNode(BaseNode): | ||
| 1555 | raise last_exc | 1568 | raise last_exc |
| 1556 | 1569 | ||
| 1557 | 1570 | ||
| 1558 | -__all__ = ["ChapterGenerationNode", "ChapterJsonParseError"] | 1571 | +__all__ = [ |
| 1572 | + "ChapterGenerationNode", | ||
| 1573 | + "ChapterJsonParseError", | ||
| 1574 | + "ChapterContentError", | ||
| 1575 | + "ChapterValidationError", | ||
| 1576 | +] |
-
Please register or login to post a comment