word_budget_node.py
2.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"""
章节篇幅规划节点。
"""
from __future__ import annotations
import json
from typing import Any, Dict, List
from loguru import logger
from ..core import TemplateSection
from ..prompts import (
SYSTEM_PROMPT_WORD_BUDGET,
build_word_budget_prompt,
)
from .base_node import BaseNode
class WordBudgetNode(BaseNode):
"""
规划各章节字数与重点。
输出总字数、全局写作准则以及每章/小节的 target/min/max 字数约束。
"""
def __init__(self, llm_client):
"""仅记录LLM客户端引用,方便run阶段发起请求"""
super().__init__(llm_client, "WordBudgetNode")
def run(
self,
sections: List[TemplateSection],
design: Dict[str, Any],
reports: Dict[str, str],
forum_logs: str,
query: str,
template_overview: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
"""根据设计稿和所有素材规划章节字数,让LLM写作时有明确篇幅目标"""
# 输入中除了章节骨架外,还包含布局节点输出,方便约束篇幅时参考视觉主次
payload = {
"query": query,
"design": design,
"sections": [section.to_dict() for section in sections],
"templateOverview": template_overview
or {
"title": sections[0].title if sections else "",
"chapters": [section.to_dict() for section in sections],
},
"reports": reports,
"forumLogs": forum_logs,
}
user = build_word_budget_prompt(payload)
response = self.llm_client.stream_invoke_to_string(
SYSTEM_PROMPT_WORD_BUDGET,
user,
temperature=0.25,
top_p=0.85,
)
plan = self._parse_response(response)
logger.info("章节字数规划已生成")
return plan
def _parse_response(self, raw: str) -> Dict[str, Any]:
"""将LLM输出的JSON文本转为字典,失败时提示规划异常"""
cleaned = raw.strip()
if cleaned.startswith("```json"):
cleaned = cleaned[7:]
if cleaned.startswith("```"):
cleaned = cleaned[3:]
if cleaned.endswith("```"):
cleaned = cleaned[:-3]
cleaned = cleaned.strip()
if not cleaned:
raise ValueError("篇幅规划LLM返回空内容")
try:
return json.loads(cleaned)
except json.JSONDecodeError as exc:
raise ValueError(f"篇幅规划JSON解析失败: {exc}") from exc
__all__ = ["WordBudgetNode"]