fix(ai-title): 过滤模型「前导思考」,只取标题 (§19.31)
推理型文本模型即便提示词要求「只返回逗号连接的标题」,仍常在标题前先输出 一段思考散文;该散文自身带逗号,直接逗号切分会切出假标题混进结果。切分前 先剥离前导思考(_strip_title_preamble),两层: - ① 哨兵优先:文本含 ===TITLES===(正则 =+\s*TITLES\s*=+,忽略大小写、 吞相邻 * / # 装饰)时只取最后一个哨兵之后,思考连同哨兵丢弃 - ② 句号兜底(无需改提示词):无哨兵时切最后一个 。!? 及之前;真标题不含 句末标点,故最后一个句号即前导散文↔标题列表的分界;仅当其后仍含逗号/换行 分隔符时才剥离,避免末条标题的收尾句号被误当分界清空 - ai_text_service.py:加 _TITLE_SENTINEL / _SENTENCE_END / _strip_title_preamble, _clean_titles 切分前调用 - config_service.py:DEFAULT_TITLE_PROMPT 补哨兵约定(思考写最前、单独一行 ===TITLES===、其后只放逗号标题) - docs/11 §17.2/§17.3 更新 + 新增 §17.9 决策;tasks.md §19.31 - 不碰 packaging/default_config/title_prompt.txt(用户精细模板,并行未提交) 验证:test_ai_text_service(22)+ 全套 py37(236 测试)通过,唯一失败为 无关的 test_config_service packaging 模板;离屏冒烟用真实返回样本 → 前导思考 被剥离、取全部干净标题,默认提示词含哨兵。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,52 @@ class TestExtractText(unittest.TestCase):
|
||||
|
||||
self.assertEqual(extract_titles_from_response({"choices": []}), [])
|
||||
|
||||
def test_strips_reasoning_preamble_by_period(self):
|
||||
"""§19.31: 真实返回——前导思考散文(带逗号、以 。结尾)被剥离,取全部标题。"""
|
||||
from services.ai_text_service import extract_titles_from_response
|
||||
|
||||
content = (
|
||||
"我會直接產出符合格式的標題,並先用字元計數檢查每條都落在 54–58 字元內。"
|
||||
"接下來我用快速本地檢查避免字數超標,然後一次輸出 42 個標題。"
|
||||
"【台灣出貨】 寬鬆T恤 滿額免運 印花T恤 韓系簡約 透氣舒適 不挑身形 夏季抗熱必備,"
|
||||
"【台灣出貨】 韓版T恤 蝦皮特殺 圖案T恤 日系休閒 親膚舒適 日常女孩 日常百搭穿搭,"
|
||||
"【台灣出貨】 韓系上衣 2026熱銷爆款 印花短袖 美式復古 夏季必備 通勤族 休閒出遊,"
|
||||
"【台灣出貨】 oversize短袖 獨家自訂款 圖案短袖 街頭潮流 百搭單品 學生族 懶人寬鬆穿搭,"
|
||||
"【台灣出貨】 寬鬆版上衣 免運優惠 印花上衣 輕熟女百搭 日常百搭 上班族 辦公日常"
|
||||
)
|
||||
titles = extract_titles_from_response({"choices": [{"message": {"content": content}}]})
|
||||
self.assertEqual(len(titles), 5) # 5 条真标题,无思考碎片
|
||||
self.assertTrue(all(t.startswith("【台灣出貨】") for t in titles))
|
||||
self.assertTrue(all("。" not in t for t in titles)) # 前导散文被切干净
|
||||
self.assertEqual(titles[0],
|
||||
"【台灣出貨】 寬鬆T恤 滿額免運 印花T恤 韓系簡約 透氣舒適 不挑身形 夏季抗熱必備")
|
||||
self.assertNotIn("我會直接產出符合格式的標題", titles)
|
||||
|
||||
def test_sentinel_takes_priority_over_thinking_commas(self):
|
||||
"""§19.31: 有 ===TITLES=== 时只取哨兵之后;哨兵前的思考(含逗号/句号)全丢。"""
|
||||
from services.ai_text_service import extract_titles_from_response
|
||||
|
||||
content = "先想一下,分两步。还有备注,继续。 ===TITLES=== 甲款,乙款,丙款"
|
||||
titles = extract_titles_from_response({"choices": [{"message": {"content": content}}]})
|
||||
self.assertEqual(titles, ["甲款", "乙款", "丙款"])
|
||||
|
||||
def test_sentinel_tolerates_decoration_and_takes_last(self):
|
||||
"""§19.31: 哨兵可带 ** 修饰/等号数不定/大小写;取最后一个哨兵之后。"""
|
||||
from services.ai_text_service import extract_titles_from_response
|
||||
|
||||
content = "思路: 提到 ===titles=== 这个词\n**==TITLES==**\n甲,乙"
|
||||
titles = extract_titles_from_response({"choices": [{"message": {"content": content}}]})
|
||||
self.assertEqual(titles, ["甲", "乙"])
|
||||
|
||||
def test_trailing_period_without_preamble_not_wiped(self):
|
||||
"""§19.31: 无前导、末条带收尾 。—— 不误当分界清空,标题仍全部保留。"""
|
||||
from services.ai_text_service import extract_titles_from_response
|
||||
|
||||
content = "甲款,乙款,丙款。"
|
||||
titles = extract_titles_from_response({"choices": [{"message": {"content": content}}]})
|
||||
self.assertEqual(len(titles), 3)
|
||||
self.assertEqual(titles[:2], ["甲款", "乙款"])
|
||||
|
||||
|
||||
class TestBuildTextPayload(unittest.TestCase):
|
||||
def test_chat_with_image_includes_image_url(self):
|
||||
|
||||
Reference in New Issue
Block a user