fix(llm): 评论细类归纳载荷移除 focus_hit_lines 并对齐系统提示

build_comment_groups_llm_payload 不再传入关注词或生成子串命中摘要;COMMENT_GROUPS_SYSTEM 仅依据 effective_text_lines 与 sample_text_snippets。探针合并与 PROBE 提示词去掉 focus_hit_lines 表述;runner/演示脚本去掉无效的 comment_focus_words 装配。

Made-with: Cursor
This commit is contained in:
hub-gif 2026-04-21 10:35:52 +08:00
parent 30ea9783b3
commit 396ccd941a
6 changed files with 30 additions and 77 deletions

View File

@ -220,12 +220,11 @@ def build_promo_groups_llm_payload(
def build_comment_groups_llm_payload(
*,
feedback_groups: list[tuple[str, list[dict[str, str]], list[str]]],
focus_words: tuple[str, ...],
merged_rows: list[dict[str, str]],
sku_header: str,
title_h: str,
) -> list[dict[str, Any]]:
"""供 ``generate_comment_group_summaries_llm``"""
"""供 ``generate_comment_group_summaries_llm``:仅含评价文本单元与短摘录,不含关注词子串计数摘要"""
if not feedback_groups:
return []
sku_meta: dict[str, tuple[str, str, str]] = {}
@ -245,10 +244,6 @@ def build_comment_groups_llm_payload(
for gname, cr, tu in feedback_groups:
if not tu and not cr:
continue
gh = _group_keyword_hits(cr, tu, focus_words=focus_words)
focus_hit_lines = [
f"{w}{n}" for w, n in gh.most_common(14) if n > 0
]
snippets: list[str] = []
for row in cr[:48]:
txt = _cell(row, _COMMENT_CSV_BODY, "tagCommentContent")
@ -277,7 +272,6 @@ def build_comment_groups_llm_payload(
"group": gname,
"comment_flat_rows": f"评价行 {len(cr)};有效文本单元 {len(tu)}",
"effective_text_lines": eff,
"focus_hit_lines": focus_hit_lines,
"sample_text_snippets": snippets,
}
)

View File

@ -100,14 +100,14 @@ PROBE_TEXT_MINING_SYSTEM = """你是用户研究与文本挖掘方向的助手
- ``probe_status````ok`` 表示该细类已完成分词与统计``skipped`` 表示样本过少等未下钻
- ``word_freq_top`` / ``tfidf_top`` / ``cooccurrence_top``统计型特征开放词表
- ``lda``无监督自动归纳的主题词**仅为探索**同一词可出现在多主题**禁止**当作严格品类或固定标签
- ``focus_hit_lines`` / ``sample_text_snippets``与正式管线摘取方式**类似**仅用于**对照语境**若与关键词突出度焦点冲突**整句原文**为准并在段末或使用注意中可点明统计与语义可能不一致
- ``sample_text_snippets``与正式报告管线同源的评价短摘录若有仅用于**对照语境**若与关键词突出度焦点冲突**整句原文**为准并在段末或使用注意中可点明统计与语义可能不一致
**任务** ``groups`` **每一项**输出对应 Markdown**顺序与输入一致**
- ``probe_status == "ok"`` ``#### `` + 与该条 ``group`` 字段**完全一致**的细类名作为小节标题(勿用 ``##`` 一级标题);每段约 **100260 字**。
- 内容须包含 **12 **概括该细类评论**主要讨论焦点**综合词频与关键词突出度**不要罗列具体数字** **12 **说明共现词对**暗示**哪些维度常一起出现**非因果** ``lda.topics`` 非空**12 **说明主题粗分侧重点**明确**算法无监督****与矩阵细类一一对应 **12 **体现 ``sample_text_snippets`` / ``focus_hit_lines`` 中的**用户语气与关切****转述**为主若必须引用原文**全小节合计** **一处**极短引号内容**40 ****不要**输出 ``细类SKU店铺`` 等长前缀若无可用摘录则写明 **使用场景仅从评论推断** **02 **概括**何时何地何人如何搭配**如早餐加餐控糖人群配牛奶等**只能**依据本细类 ``word_freq``/``tfidf``/``cooccurrence``/``lda`` 与摘录中**已出现或可合理概括**的信息**禁止**套用正式报告场景分组或其它外部场景分类若统计与摘录中**均无**场景线索**一句**写明评论中未体现清晰使用场景即可
- 内容须包含 **12 **概括该细类评论**主要讨论焦点**综合词频与关键词突出度**不要罗列具体数字** **12 **说明共现词对**暗示**哪些维度常一起出现**非因果** ``lda.topics`` 非空**12 **说明主题粗分侧重点**明确**算法无监督****与矩阵细类一一对应 **12 **体现 ``sample_text_snippets`` 中的**用户语气与关切****转述**为主若必须引用原文**全小节合计** **一处**极短引号内容**40 ****不要**输出 ``细类SKU店铺`` 等长前缀若无可用摘录则写明 **使用场景仅从评论推断** **02 **概括**何时何地何人如何搭配**如早餐加餐控糖人群配牛奶等**只能**依据本细类 ``word_freq``/``tfidf``/``cooccurrence``/``lda`` 与摘录中**已出现或可合理概括**的信息**禁止**套用正式报告场景分组或其它外部场景分类若统计与摘录中**均无**场景线索**一句**写明评论中未体现清晰使用场景即可
- ``probe_status == "skipped"``该小节仅 **一句**说明原因
**禁止**编造数据中未出现的品牌价格医学功效或疗效承诺不要把 ``keyword`` 监测词写进用户原话不要输出 Markdown 表格不要声称本段与正式报告第八章末完全同源本任务为**补充分析解读****禁止**把输入里的 ``sample_text_snippets`` / ``focus_hit_lines`` **逐条罗列****多条整段复制**到输出那不是归纳是重复贴评论
**禁止**编造数据中未出现的品牌价格医学功效或疗效承诺不要把 ``keyword`` 监测词写进用户原话不要输出 Markdown 表格不要声称本段与正式报告第八章末完全同源本任务为**补充分析解读****禁止**把输入里的 ``sample_text_snippets`` **逐条罗列****多条整段复制**到输出那不是归纳是重复贴评论
全文末可另起一段 **使用注意**简短点明开放词表统计与人工阅读差异主题归纳局限小样本细类不可靠
@ -381,23 +381,6 @@ def _narrative_stub(
return "\n".join(lines)
def _effective_focus_words(run_dir: Path) -> tuple[str, ...]:
"""与 ``runner.write_competitor_analysis_for_run_dir`` 一致:优先 ``effective_report_config.json``。"""
p = run_dir / "effective_report_config.json"
if p.is_file():
try:
eff = json.loads(p.read_text(encoding="utf-8"))
except json.JSONDecodeError:
eff = None
if isinstance(eff, dict):
fw_src = eff.get("comment_focus_words") or list(jcr.COMMENT_FOCUS_WORDS)
fw_tuple = tuple(
str(x).strip() for x in fw_src if str(x).strip()
) or jcr.COMMENT_FOCUS_WORDS
return fw_tuple
return jcr.COMMENT_FOCUS_WORDS
def _truncate_probe_payload(payload: dict[str, Any]) -> dict[str, Any]:
"""压缩摘录长度,避免单次 JSON 顶满上下文。"""
out: dict[str, Any] = dict(payload)
@ -407,6 +390,7 @@ def _truncate_probe_payload(payload: dict[str, Any]) -> dict[str, Any]:
groups.append(g)
continue
g2 = dict(g)
g2.pop("focus_hit_lines", None)
sn = g2.get("sample_text_snippets")
if isinstance(sn, list):
# 条数略减,降低模型「照抄罗列」倾向;仍以转述为主见系统提示
@ -421,7 +405,6 @@ def _merge_snippets_from_comment_groups(
*,
merged_rows: list[dict[str, str]],
comment_rows: list[dict[str, str]],
run_dir: Path,
) -> None:
"""把正式 ``build_comment_groups_llm_payload`` 中的摘录并入补充分析行(原地修改)。"""
sku_h = MERGED_FIELD_TO_CSV_HEADER["sku_id"]
@ -431,10 +414,8 @@ def _merge_snippets_from_comment_groups(
comment_rows=comment_rows,
sku_header=sku_h,
)
fw = _effective_focus_words(run_dir)
pl = jcr.build_comment_groups_llm_payload(
feedback_groups=fb,
focus_words=fw,
merged_rows=merged_rows,
sku_header=sku_h,
title_h=title_h,
@ -448,9 +429,6 @@ def _merge_snippets_from_comment_groups(
if not src:
continue
row["comment_flat_rows"] = src.get("comment_flat_rows")
fh = src.get("focus_hit_lines")
if isinstance(fh, list):
row["focus_hit_lines"] = [str(x) for x in fh[:8]]
sn = src.get("sample_text_snippets")
if isinstance(sn, list):
row["sample_text_snippets"] = [str(x)[:220] for x in sn[:8]]
@ -689,15 +667,14 @@ def build_markdown(
probe_rows,
merged_rows=merged,
comment_rows=comments,
run_dir=run_dir,
)
llm_payload: dict[str, Any] = {
"schema_version": 1,
"keyword": kw,
"probe_note": (
"中文分词 + 停用词;关键词突出度/共现/主题归纳为统计库;"
"与正式报告的关注词规则统计、已废弃的预设口语短语情感口径均不同;"
"评价摘录字段合并自 build_comment_groups_llm_payload供语境对照"
"与正式报告第八章第二节图表中的关注词计数等规则统计、已废弃的预设口语短语情感口径均不同;"
"评价摘录合并自 build_comment_groups_llm_payload(与正式管线同源,不含关注词子串计数摘要),供语境对照;"
"「使用场景」若出现,须仅能从本 JSON 内统计与摘录推断,不接入正式场景分组。"
),
"groups": probe_rows,

View File

@ -354,13 +354,8 @@ def main() -> None:
comment_rows=comment_rows,
sku_header=sku_h,
)
fw_src = eff_rc.get("comment_focus_words") or list(jcr.COMMENT_FOCUS_WORDS)
fw_tuple = tuple(
str(x).strip() for x in fw_src if str(x).strip()
) or jcr.COMMENT_FOCUS_WORDS
pl_cg = jcr.build_comment_groups_llm_payload(
feedback_groups=fb,
focus_words=fw_tuple,
merged_rows=merged,
sku_header=sku_h,
title_h=title_h,
@ -374,7 +369,7 @@ def main() -> None:
return generate_comment_group_summaries_llm(pl_cg, keyword=keyword)
_run_one(
"§8 细类评价与关注词comment_groups",
"§8 细类评价要点comment_groups",
_cg,
live=args.live,
preview_chars=args.preview_chars,

View File

@ -679,13 +679,8 @@ def write_competitor_analysis_for_run_dir(
comment_rows=comment_rows,
sku_header=sku_h,
)
fw_src = eff_rc.get("comment_focus_words") or list(jcr.COMMENT_FOCUS_WORDS)
fw_tuple = tuple(
str(x).strip() for x in fw_src if str(x).strip()
) or jcr.COMMENT_FOCUS_WORDS
pl_cg = jcr.build_comment_groups_llm_payload(
feedback_groups=fb_cg,
focus_words=fw_tuple,
merged_rows=merged_rows,
sku_header=sku_h,
title_h=title_h,

View File

@ -50,15 +50,14 @@ def generate_matrix_group_summaries_llm(
COMMENT_GROUPS_SYSTEM = """你是用户研究与品类顾问。输入为 JSON``keyword`` 与 ``groups``。
每个 group ``group`` 第五章矩阵一致的细分类目名``comment_flat_rows````effective_text_lines``
``focus_hit_lines``关注词子串命中摘要 **第八章第二节**关注词与场景路径左栏同源``sample_text_snippets``评价短摘录已截断
摘录行通常以 ``细类SKU品名店铺`` 开头细类可与本 group 名对照**品名/SKU/店铺**表示该句具体出自哪条链接归纳时若引用原话**须交代是哪家店哪条 SKU哪款品名上的反馈**勿只写有用户说口感差而不指代产品
关注词命中为子串统计可能与句意不一致**请以整句语义**判断褒贬软硬适中没那么甜常为满意表述不得据此写成质地问题
每个 group ``group`` 第五章矩阵一致的细分类目名``comment_flat_rows````effective_text_lines``该细类下从评价中抽取的短文本单元``sample_text_snippets`` SKU/品名/店铺前缀的评价短摘录已截断
摘录行通常以 ``细类SKU品名店铺`` 开头**品名/SKU/店铺**表示该句具体出自哪条链接归纳时若引用原话**须交代是哪家店哪条 SKU哪款品名上的反馈**勿只写有用户说口感差而不指代产品
**以整句语义**判断褒贬软硬适中没那么甜常为满意表述不得仅凭片段词就写成负面结论
**为每个细类**输出一小段 Markdown全部 groups 都要写顺序与输入一致
- ``#### `` + 与该 group 字段**完全一致**的细类名作为小节标题(不要使用 ``##`` 一级标题);
- 每段约 **100220 **中文归纳该细类下**消费者在讨论什么**口感价格物流功效疑虑等**关注词命中反映的诉求**勿编造摘录中未出现的品牌医学结论
- **去重与可证本章仅评论侧**本段****依据评价/关注词摘录**禁止** ``keyword``品类常识或商品标题卖点套话写成用户评价**禁止**各细类段首复用同一句总括整体上满足了消费者对低 GI高蛋白便携性的需求每段开头句式须**有变化**并至少一句体现**该细类与相邻细类在讨论焦点上的差异****利益/诉求词** GI高蛋白便携代餐控糖等**仅当** ``sample_text_snippets````effective_text_lines`` ``focus_hit_lines`` **原文**中可子串命中或可明确同义便携随身小包装单片独立装等时才写上述字段中****出现蛋白便携随身小包装单片**不得**高蛋白便携性**禁止**为凑齐常见卖点组合而脑补未在输入中出现的词
- 每段约 **100220 **中文归纳该细类下**消费者在讨论什么**口感价格物流功效疑虑等勿编造摘录中未出现的品牌医学结论
- **去重与可证本章仅评论侧**本段****依据 ``sample_text_snippets`` ``effective_text_lines`` 中的**原文****禁止** ``keyword``品类常识或商品标题卖点套话写成用户评价**禁止**各细类段首复用同一句总括整体上满足了消费者对低 GI高蛋白便携性的需求每段开头句式须**有变化**并至少一句体现**该细类与相邻细类在讨论焦点上的差异****利益/诉求词** GI高蛋白便携代餐控糖等**仅当**上述字段**原文**中可子串命中或可明确同义便携随身小包装单片独立装等时才写****出现蛋白便携随身小包装单片**不得**高蛋白便携性**禁止**为凑齐常见卖点组合而脑补未在输入中出现的词
- **禁止**输出 Markdown 表格禁止逐条复述全部评价
- ``effective_text_lines`` 很少明确写样本较少归纳供启发
@ -66,7 +65,7 @@ COMMENT_GROUPS_SYSTEM = """你是用户研究与品类顾问。输入为 JSON
COMMENT_GROUPS_USER_PREFIX = (
"请根据以下 JSON 撰写竞品报告第八章末「细类评论与关注词要点归纳」正文Markdown\n\n"
"请根据以下 JSON 撰写竞品报告第八章末「细类评论要点归纳」正文Markdown\n\n"
)
@ -85,7 +84,6 @@ def generate_comment_group_summaries_llm(
eff_max: int,
sn_n: int,
sn_max: int,
fh_n: int,
) -> dict[str, Any]:
g2: dict[str, Any] = {
"group": g.get("group"),
@ -101,11 +99,6 @@ def generate_comment_group_summaries_llm(
g2["sample_text_snippets"] = [str(x)[:sn_max] for x in sn[:sn_n]]
else:
g2["sample_text_snippets"] = []
fh = g.get("focus_hit_lines")
if isinstance(fh, list):
g2["focus_hit_lines"] = [str(x) for x in fh[:fh_n]]
else:
g2["focus_hit_lines"] = []
return g2
ctx = llm_context_window_size()
@ -115,24 +108,24 @@ def generate_comment_group_summaries_llm(
est = estimate_chat_input_tokens(system, user_p)
return est < 15_500
levels: list[tuple[int, int, int, int, int]] = [
(14, 260, 10, 200, 10),
(12, 220, 8, 180, 8),
(10, 180, 8, 160, 6),
(8, 150, 6, 140, 6),
(6, 120, 5, 120, 5),
(5, 100, 4, 100, 4),
(4, 80, 3, 80, 3),
(3, 70, 3, 70, 3),
(3, 50, 2, 60, 2),
levels: list[tuple[int, int, int, int]] = [
(14, 260, 10, 200),
(12, 220, 8, 180),
(10, 180, 8, 160),
(8, 150, 6, 140),
(6, 120, 5, 120),
(5, 100, 4, 100),
(4, 80, 3, 80),
(3, 70, 3, 70),
(3, 50, 2, 60),
]
user = ""
chosen = levels[-1]
for level in levels:
chosen = level
eff_n, eff_max, sn_n, sn_max, fh_n = level
eff_n, eff_max, sn_n, sn_max = level
trimmed = [
_compact_one(g, eff_n=eff_n, eff_max=eff_max, sn_n=sn_n, sn_max=sn_max, fh_n=fh_n)
_compact_one(g, eff_n=eff_n, eff_max=eff_max, sn_n=sn_n, sn_max=sn_max)
for g in groups
if isinstance(g, dict)
]
@ -144,9 +137,9 @@ def generate_comment_group_summaries_llm(
break
else:
tail = "\n\nJSON 已截断以适配上下文;仅依据可见字段撰写。)\n"
eff_n, eff_max, sn_n, sn_max, fh_n = chosen
eff_n, eff_max, sn_n, sn_max = chosen
trimmed = [
_compact_one(g, eff_n=eff_n, eff_max=eff_max, sn_n=sn_n, sn_max=sn_max, fh_n=fh_n)
_compact_one(g, eff_n=eff_n, eff_max=eff_max, sn_n=sn_n, sn_max=sn_max)
for g in groups
if isinstance(g, dict)
]

View File

@ -1,4 +1,4 @@
"""§8 末「细类评论与关注词要点归纳」大模型单细类烟测mock 网关,不调真实 API"""
"""§8 末「细类评论要点归纳」大模型单细类烟测mock 网关,不调真实 API"""
from __future__ import annotations
import json
@ -44,7 +44,6 @@ def _payload_single_category_biscuit() -> tuple[list[dict], str, str]:
)
pl = jcr.build_comment_groups_llm_payload(
feedback_groups=fb,
focus_words=jcr.COMMENT_FOCUS_WORDS,
merged_rows=merged,
sku_header=sku_h,
title_h=title_h,
@ -68,7 +67,7 @@ class CommentGroupSummariesLlmTests(SimpleTestCase):
mock_llm.assert_called_once()
_sys, user = mock_llm.call_args[0]
self.assertIn("细类评论与关注词要点归纳", user)
self.assertIn("细类评论要点归纳", user)
self.assertIn("低GI测试", user)
raw = user.split("正文Markdown\n\n", 1)[-1]
data = json.loads(raw.strip())