Showing
4 changed files
with
35 additions
and
0 deletions
| @@ -433,6 +433,14 @@ class ReportAgent: | @@ -433,6 +433,14 @@ class ReportAgent: | ||
| 433 | }) | 433 | }) |
| 434 | # 章节流式回调:把LLM返回的delta透传给SSE,便于前端实时渲染 | 434 | # 章节流式回调:把LLM返回的delta透传给SSE,便于前端实时渲染 |
| 435 | def chunk_callback(delta: str, meta: Dict[str, Any], section_ref: TemplateSection = section): | 435 | def chunk_callback(delta: str, meta: Dict[str, Any], section_ref: TemplateSection = section): |
| 436 | + """ | ||
| 437 | + 章节内容流式回调。 | ||
| 438 | + | ||
| 439 | + Args: | ||
| 440 | + delta: LLM最新输出的增量文本。 | ||
| 441 | + meta: 节点回传的章节元数据,兜底时使用。 | ||
| 442 | + section_ref: 默认指向当前章节,保证在缺失元信息时也能定位。 | ||
| 443 | + """ | ||
| 436 | emit('chapter_chunk', { | 444 | emit('chapter_chunk', { |
| 437 | 'chapterId': meta.get('chapterId') or section_ref.chapter_id, | 445 | 'chapterId': meta.get('chapterId') or section_ref.chapter_id, |
| 438 | 'title': meta.get('title') or section_ref.title, | 446 | 'title': meta.get('title') or section_ref.title, |
| @@ -634,6 +634,13 @@ def stream_task(task_id: str): | @@ -634,6 +634,13 @@ def stream_task(task_id: str): | ||
| 634 | last_event_id = None | 634 | last_event_id = None |
| 635 | 635 | ||
| 636 | def event_generator(): | 636 | def event_generator(): |
| 637 | + """ | ||
| 638 | + SSE事件生成器。 | ||
| 639 | + | ||
| 640 | + - 负责注册并消费对应任务的事件队列; | ||
| 641 | + - 先回放历史事件再持续监听实时事件; | ||
| 642 | + - 周期性发送心跳并在任务结束后自动注销监听。 | ||
| 643 | + """ | ||
| 637 | queue = _register_stream(task_id) | 644 | queue = _register_stream(task_id) |
| 638 | try: | 645 | try: |
| 639 | # 断线重连场景下,先补发历史事件,保证界面状态一致 | 646 | # 断线重连场景下,先补发历史事件,保证界面状态一致 |
| @@ -34,6 +34,13 @@ class ChapterJsonParseError(ValueError): | @@ -34,6 +34,13 @@ class ChapterJsonParseError(ValueError): | ||
| 34 | """章节LLM输出无法解析为合法JSON时抛出的异常,附带原始文本方便排查。""" | 34 | """章节LLM输出无法解析为合法JSON时抛出的异常,附带原始文本方便排查。""" |
| 35 | 35 | ||
| 36 | def __init__(self, message: str, raw_text: Optional[str] = None): | 36 | def __init__(self, message: str, raw_text: Optional[str] = None): |
| 37 | + """ | ||
| 38 | + 构造异常并附加原始输出,便于日志中定位。 | ||
| 39 | + | ||
| 40 | + Args: | ||
| 41 | + message: 人类可读的错误描述。 | ||
| 42 | + raw_text: 触发异常的完整LLM输出。 | ||
| 43 | + """ | ||
| 37 | super().__init__(message) | 44 | super().__init__(message) |
| 38 | self.raw_text = raw_text | 45 | self.raw_text = raw_text |
| 39 | 46 | ||
| @@ -674,6 +681,7 @@ class ChapterGenerationNode(BaseNode): | @@ -674,6 +681,7 @@ class ChapterGenerationNode(BaseNode): | ||
| 674 | """ | 681 | """ |
| 675 | 682 | ||
| 676 | def walk(node: Any) -> int: | 683 | def walk(node: Any) -> int: |
| 684 | + """递归下钻block树并返回字符估算,跳过非正文类型""" | ||
| 677 | if node is None: | 685 | if node is None: |
| 678 | return 0 | 686 | return 0 |
| 679 | if isinstance(node, list): | 687 | if isinstance(node, list): |
| @@ -891,6 +899,7 @@ class ChapterGenerationNode(BaseNode): | @@ -891,6 +899,7 @@ class ChapterGenerationNode(BaseNode): | ||
| 891 | fragment_buffer: List[Dict[str, Any]] = [] | 899 | fragment_buffer: List[Dict[str, Any]] = [] |
| 892 | 900 | ||
| 893 | def flush_buffer(): | 901 | def flush_buffer(): |
| 902 | + """将当前片段缓冲写入merged列表,必要时合并为单段paragraph""" | ||
| 894 | nonlocal fragment_buffer | 903 | nonlocal fragment_buffer |
| 895 | if not fragment_buffer: | 904 | if not fragment_buffer: |
| 896 | return | 905 | return |
| @@ -427,6 +427,7 @@ class HTMLRenderer: | @@ -427,6 +427,7 @@ class HTMLRenderer: | ||
| 427 | extracted: List[Dict[str, Any]] = [] | 427 | extracted: List[Dict[str, Any]] = [] |
| 428 | 428 | ||
| 429 | def traverse(node: Any) -> None: | 429 | def traverse(node: Any) -> None: |
| 430 | + """递归遍历block树,识别text字段内潜在的嵌套block JSON""" | ||
| 430 | if isinstance(node, dict): | 431 | if isinstance(node, dict): |
| 431 | for key, value in list(node.items()): | 432 | for key, value in list(node.items()): |
| 432 | if key == "text" and isinstance(value, str): | 433 | if key == "text" and isinstance(value, str): |
| @@ -1087,10 +1088,20 @@ class HTMLRenderer: | @@ -1087,10 +1088,20 @@ class HTMLRenderer: | ||
| 1087 | return tuple(normalized) if normalized else None | 1088 | return tuple(normalized) if normalized else None |
| 1088 | 1089 | ||
| 1089 | def _normalize_kpi_item(self, item: Any) -> tuple[str, str, str, str, str] | None: | 1090 | def _normalize_kpi_item(self, item: Any) -> tuple[str, str, str, str, str] | None: |
| 1091 | + """ | ||
| 1092 | + 将单条KPI记录规整为可对比的签名。 | ||
| 1093 | + | ||
| 1094 | + 参数: | ||
| 1095 | + item: KPI数组中的原始字典,可能缺失字段或类型混杂。 | ||
| 1096 | + | ||
| 1097 | + 返回: | ||
| 1098 | + tuple | None: (label, value, unit, delta, tone) 的五元组;若输入非法则为None。 | ||
| 1099 | + """ | ||
| 1090 | if not isinstance(item, dict): | 1100 | if not isinstance(item, dict): |
| 1091 | return None | 1101 | return None |
| 1092 | 1102 | ||
| 1093 | def normalize(value: Any) -> str: | 1103 | def normalize(value: Any) -> str: |
| 1104 | + """统一各类值的表现形式,便于生成稳定签名""" | ||
| 1094 | if value is None: | 1105 | if value is None: |
| 1095 | return "" | 1106 | return "" |
| 1096 | if isinstance(value, (int, float)): | 1107 | if isinstance(value, (int, float)): |
-
Please register or login to post a comment