fix:优化输出内容
This commit is contained in:
@@ -34,9 +34,9 @@ def _is_likely_timeout_error(exc: BaseException) -> bool:
|
|||||||
return "timed out" in s or "timeout" in s
|
return "timed out" in s or "timeout" in s
|
||||||
|
|
||||||
|
|
||||||
# 短文洗稿:5 个自然段、正文总字数上限(含标点)
|
# 短文洗稿:正文目标约 500 字,优先完整性(软约束,不硬截断)
|
||||||
MAX_BODY_CHARS = 500
|
|
||||||
MIN_BODY_CHARS = 80
|
MIN_BODY_CHARS = 80
|
||||||
|
TARGET_BODY_CHARS = 500
|
||||||
|
|
||||||
|
|
||||||
def _preview_for_log(text: str, limit: int = 400) -> str:
|
def _preview_for_log(text: str, limit: int = 400) -> str:
|
||||||
@@ -54,7 +54,7 @@ SYSTEM_PROMPT = """
|
|||||||
1) **忠实原意**:只概括、转述原文已有信息,不编造事实,不偷换主题;
|
1) **忠实原意**:只概括、转述原文已有信息,不编造事实,不偷换主题;
|
||||||
2) 语气通俗、干脆,避免套话堆砌;
|
2) 语气通俗、干脆,避免套话堆砌;
|
||||||
3) 只输出合法 JSON:title, summary, body_markdown;
|
3) 只输出合法 JSON:title, summary, body_markdown;
|
||||||
4) **body_markdown 约束**:恰好 **5 个自然段**;段与段之间用一个空行分隔;**不要**使用 # / ## 标题符号;全文(正文)总字数 **不超过 500 字**(含标点);
|
4) **body_markdown 约束**:按内容密度使用 **4~6 个自然段**;段与段之间用一个空行分隔;**不要**使用 # / ## 标题符号;正文以 **约 500 字**为目标,优先完整表达并避免冗长重复;
|
||||||
5) title、summary 也要短:标题约 8~18 字;摘要约 40~80 字;
|
5) title、summary 也要短:标题约 8~18 字;摘要约 40~80 字;
|
||||||
6) 正文每段需首行缩进(建议段首使用两个全角空格「 」),避免顶格;
|
6) 正文每段需首行缩进(建议段首使用两个全角空格「 」),避免顶格;
|
||||||
7) 关键观点需要加粗:请用 Markdown `**加粗**` 标出 2~4 个重点短语;
|
7) 关键观点需要加粗:请用 Markdown `**加粗**` 标出 2~4 个重点短语;
|
||||||
@@ -71,19 +71,19 @@ REWRITE_SCHEMA_HINT = """
|
|||||||
}
|
}
|
||||||
|
|
||||||
body_markdown 写法:
|
body_markdown 写法:
|
||||||
- 必须且只能有 **5 段**:每段若干完整句子,段之间 **\\n\\n**(空一行);
|
- 使用 **4~6 段**:每段若干完整句子,段之间 **\\n\\n**(空一行);
|
||||||
- **禁止** markdown 标题(不要用 #);
|
- **禁止** markdown 标题(不要用 #);
|
||||||
- 正文总长 **≤500 字**,宁可短而清楚,不要写满废话;
|
- 正文目标约 **500 字**(可上下浮动),以信息完整为先,避免冗长和重复;
|
||||||
- 每段段首请保留首行缩进(两个全角空格「 」);
|
- 每段段首请保留首行缩进(两个全角空格「 」);
|
||||||
- 请用 `**...**` 加粗 2~4 个关键观点词;
|
- 请用 `**...**` 加粗 2~4 个关键观点词;
|
||||||
- 内容顺序建议:第 1 段交代在说什么;中间 3 段展开关键信息;最后 1 段收束或提醒(均须紧扣原文,勿乱发挥)。
|
- 内容顺序建议:首段交代在说什么;中间段展开关键信息;末段收束或提醒(均须紧扣原文,勿乱发挥)。
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
# 通义等模型若首次过短/结构不对,再要一次
|
# 通义等模型若首次过短/结构不对,再要一次
|
||||||
_JSON_BODY_TOO_SHORT_RETRY = """
|
_JSON_BODY_TOO_SHORT_RETRY = """
|
||||||
|
|
||||||
【系统复检】上一次 body_markdown 不符合要求。请重输出**完整** JSON:
|
【系统复检】上一次 body_markdown 不符合要求。请重输出**完整** JSON:
|
||||||
- 正文必须 **恰好 5 个自然段**(仅 \\n\\n 分段),无 # 标题,总字数 **≤500 字**;
|
- 正文必须使用 **4~6 个自然段**(仅 \\n\\n 分段),无 # 标题;篇幅尽量收敛到约 500 字,同时保持信息完整;
|
||||||
- 忠实原稿、简短高效;
|
- 忠实原稿、简短高效;
|
||||||
- 引号只用「」『』;
|
- 引号只用「」『』;
|
||||||
- 只输出 JSON。
|
- 只输出 JSON。
|
||||||
@@ -347,7 +347,8 @@ class AIRewriter:
|
|||||||
self, req: RewriteRequest, cleaned_source: str, reason: str, trace: dict[str, Any] | None = None
|
self, req: RewriteRequest, cleaned_source: str, reason: str, trace: dict[str, Any] | None = None
|
||||||
) -> RewriteResponse:
|
) -> RewriteResponse:
|
||||||
sentences = self._extract_sentences(cleaned_source)
|
sentences = self._extract_sentences(cleaned_source)
|
||||||
points = self._pick_key_points(sentences, limit=5)
|
para_count = self._fallback_para_count(cleaned_source)
|
||||||
|
points = self._pick_key_points(sentences, limit=max(5, para_count))
|
||||||
title = req.title_hint.strip() or self._build_fallback_title(sentences)
|
title = req.title_hint.strip() or self._build_fallback_title(sentences)
|
||||||
|
|
||||||
summary = self._build_fallback_summary(points, cleaned_source)
|
summary = self._build_fallback_summary(points, cleaned_source)
|
||||||
@@ -358,17 +359,14 @@ class AIRewriter:
|
|||||||
t = re.sub(r"\s+", " ", (s or "").strip())
|
t = re.sub(r"\s+", " ", (s or "").strip())
|
||||||
return t if len(t) <= n else t[: n - 1] + "…"
|
return t if len(t) <= n else t[: n - 1] + "…"
|
||||||
|
|
||||||
paras = [
|
paras = [_one_line(self._build_intro(points, cleaned_source), 105)]
|
||||||
_one_line(self._build_intro(points, cleaned_source), 105),
|
if para_count >= 4:
|
||||||
_one_line(analysis["cause"], 105),
|
paras.append(_one_line(analysis["cause"], 105))
|
||||||
_one_line(analysis["impact"], 105),
|
paras.append(_one_line(analysis["impact"], 105))
|
||||||
_one_line(analysis["risk"], 105),
|
if para_count >= 5:
|
||||||
_one_line(conclusion, 105),
|
paras.append(_one_line(analysis["risk"], 105))
|
||||||
]
|
paras.append(_one_line(conclusion, 105))
|
||||||
body = "\n\n".join(paras)
|
body = "\n\n".join(paras)
|
||||||
if len(body) > MAX_BODY_CHARS:
|
|
||||||
body = body[: MAX_BODY_CHARS - 1] + "…"
|
|
||||||
|
|
||||||
normalized = {
|
normalized = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
@@ -429,6 +427,14 @@ class AIRewriter:
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _fallback_para_count(self, source: str) -> int:
|
||||||
|
length = len((source or "").strip())
|
||||||
|
if length < 240:
|
||||||
|
return 4
|
||||||
|
if length > 1200:
|
||||||
|
return 6
|
||||||
|
return 5
|
||||||
|
|
||||||
def _clean_source(self, text: str) -> str:
|
def _clean_source(self, text: str) -> str:
|
||||||
src = (text or "").replace("\r\n", "\n").strip()
|
src = (text or "").replace("\r\n", "\n").strip()
|
||||||
src = re.sub(r"https?://\S+", "", src)
|
src = re.sub(r"https?://\S+", "", src)
|
||||||
@@ -725,8 +731,6 @@ class AIRewriter:
|
|||||||
text = (body or "").strip()
|
text = (body or "").strip()
|
||||||
if not text:
|
if not text:
|
||||||
text = "(正文生成失败,请重试。)"
|
text = "(正文生成失败,请重试。)"
|
||||||
if len(text) > MAX_BODY_CHARS:
|
|
||||||
text = text[: MAX_BODY_CHARS - 1] + "…"
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def _quality_issues(
|
def _quality_issues(
|
||||||
@@ -749,16 +753,18 @@ class AIRewriter:
|
|||||||
|
|
||||||
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", body) if p.strip()]
|
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", body) if p.strip()]
|
||||||
pc = len(paragraphs)
|
pc = len(paragraphs)
|
||||||
need_p = 4 if lenient else 5
|
min_p, max_p = (3, 6) if lenient else (4, 6)
|
||||||
if pc < need_p:
|
if pc < min_p:
|
||||||
issues.append(f"正文需约 5 个自然段、空行分隔(当前 {pc} 段)")
|
issues.append(f"正文段落偏少(当前 {pc} 段),建议 {min_p}-{max_p} 段")
|
||||||
elif not lenient and pc > 6:
|
elif pc > max_p:
|
||||||
issues.append(f"正文段落过多(当前 {pc} 段),请合并为 5 段左右")
|
issues.append(f"正文段落偏多(当前 {pc} 段),建议控制在 {min_p}-{max_p} 段")
|
||||||
|
|
||||||
if len(body) > MAX_BODY_CHARS:
|
if len(body) < MIN_BODY_CHARS:
|
||||||
issues.append(f"正文超过 {MAX_BODY_CHARS} 字(当前 {len(body)} 字),请压缩")
|
|
||||||
elif len(body) < MIN_BODY_CHARS:
|
|
||||||
issues.append(f"正文过短(当前阈值 ≥{MIN_BODY_CHARS} 字)")
|
issues.append(f"正文过短(当前阈值 ≥{MIN_BODY_CHARS} 字)")
|
||||||
|
elif len(body) > 900:
|
||||||
|
issues.append(
|
||||||
|
f"正文偏长(当前 {len(body)} 字),建议收敛到约 {TARGET_BODY_CHARS} 字(可上下浮动)"
|
||||||
|
)
|
||||||
|
|
||||||
if re.search(r"(?m)^#+\s", body):
|
if re.search(r"(?m)^#+\s", body):
|
||||||
issues.append("正文请勿使用 # 标题符号,只用自然段")
|
issues.append("正文请勿使用 # 标题符号,只用自然段")
|
||||||
@@ -805,8 +811,6 @@ class AIRewriter:
|
|||||||
title = (normalized.get("title") or "").strip()
|
title = (normalized.get("title") or "").strip()
|
||||||
if len(title) < 4 or len(body) < 40:
|
if len(title) < 4 or len(body) < 40:
|
||||||
return False
|
return False
|
||||||
if len(body) > MAX_BODY_CHARS + 80:
|
|
||||||
return False
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _format_markdown(self, text: str) -> str:
|
def _format_markdown(self, text: str) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user