fix(llm): require aggregate-only complaints in §8.5 without fake quotes

§8.5 brief lacks per-SKU review text; tighten REPORT_SYSTEM so lexicon
hits are described as cross-SKU substring stats and forbid quoted
fabricated examples. Align sentiment helper: no quoted specifics absent
from samples.

Made-with: Cursor
This commit is contained in:
hub-gif 2026-04-14 10:55:12 +08:00
parent 708f28f40e
commit efd8aa7cfc

View File

@ -54,6 +54,10 @@ REPORT_SYSTEM = """你是业务与产品读者顾问。输入 JSON 含 `keyword`
- **Markdown** **8001500 **
- 建议用 ``####`` 组织:**执行摘要级要点**、**竞争与价盘**、**用户声量与负向事由**(须归纳用户在抱怨什么类型的问题,而非只堆关键词)、**与第九章衔接的策略边界**
- 若有 `comment_sentiment_lexicon`概括正/负向粗判局限**负向**写清事由类型口感价格物流等
- **归因与引语硬性**`consumer_feedback_by_matrix_group` `comment_sentiment_lexicon` 等均为** SKU/跨店铺的关键词子串或条数统计****不能**据此推断某一店铺某一单品的结论
- **禁止**部分用户反馈口感偏硬发粘**带引号的具体体验原话**除非输入 JSON **同一段可引用的原文**已出现该措辞本段输入通常不含评价全文故默认**不要**使用引号式举例
- 若写口感包装物流价格等维度须用**维度级聚合级**表述并点明口径在已合并的评价文本中物流价格类关键词命中较多为全样本子串计数不区分具体 SKU可结合 `matrix_overview_for_llm` 的细类名谈**结构分布**勿把词频偷换成用户点名某款商品的叙事
- 若正文前节§8.2已有带归属的抽样解读本段**只可概括其结论层级****不要**重复杜撰新的短引文
- 语气专业中文缺失项写本段未提供该项而非猜测"""
REPORT_USER_PREFIX = """请根据以下 JSON 撰写上文所述 §8.5 嵌入段落Markdown 正文,勿加 ### 8.5 标题)。\n\n"""
@ -75,16 +79,18 @@ SENTIMENT_LLM_SYSTEM = """你是电商/食品类用户研究助手。输入 JSON
- ``comment_sentiment_lexicon``关键词规则下的条数与短语命中粗判非深度学习
- ``positive_lexeme_hits_top`` / ``negative_lexeme_hits_top``短语级命中摘要与条形图同源
- ``sample_reviews_*``按同一规则从评价中抽样的短文已截断**仅可依据这些原文与 lexicon 数字归纳**
每条样本通常以 ``细类SKU品名`` 开头表示该句评价对应的 **§5 矩阵细类****具体 SKU/商品标题**写归纳与引用短引文时**须保留或复述该归属**例如先点明饼干类某 SKU再引口感原话**禁止**把多条样本混成用户普遍却不交代是哪类产品
**硬性要求**
- **仅输出 Markdown 正文**不要用 ``` 围栏包裹全文
- **不要编造**样本中未出现的具体事实品牌价格医学功效
- 条数占比等**定量表述须与** ``comment_sentiment_lexicon`` **一致**勿与样本矛盾
- 若某具体措辞口感偏硬****出现在任一 ``sample_reviews_*`` 字符串中**禁止**用引号写出该句或暗示为直接引语仅可写口感相关抱怨在样本/词表中较集中等聚合表述
- **不要**只复述某词出现 N 词频条形图已在报告正文你的价值是**语义层归纳**用户在说什么不满/满意的具体事由是什么
**建议结构**使用四级标题 ``####``
1. ``#### 正向体验主题``36 条;每条用一句话概括一类满意点(如口感、甜度、饱腹、性价比、物流),**尽量**在句末用简短「」引用样本中的原话片段佐证(无合适原话则省略引号,勿杜撰)。
2. ``#### 负向评价主题归因``**核心段落**。在「偏负向」与「混合」样本中归纳 **48 个具体问题维度**(示例维度,按需选用:口味/难吃/怪味、过甜或寡淡、质地口感、价格与促销、包装破损、物流时效、真伪与效期、与宣传不符、健康/功效疑虑等)。每个维度下用 12 条列表项写清「用户具体在抱怨什么」,并**尽量**附上来自 ``sample_reviews_negative_biased`` 或 ``sample_reviews_mixed_tone`` 的「」短引文;若某维度在样本中几乎无依据则不要硬写。
2. ``#### 负向评价主题归因``**核心段落**。在「偏负向」与「混合」样本中归纳 **48 个具体问题维度**(示例维度,按需选用:口味/难吃/怪味、过甜或寡淡、质地口感、价格与促销、包装破损、物流时效、真伪与效期、与宣传不符、健康/功效疑虑等)。每个维度下用 12 条列表项写清「用户具体在抱怨什么」,并**尽量**附上来自 ``sample_reviews_negative_biased`` 或 ``sample_reviews_mixed_tone`` 的「」短引文(引文内**尽量含** ``【细类…】`` 前缀或在同句中点明细类/SKU/品名);若某维度在样本中几乎无依据则不要硬写。
3. ``#### 混合评价中的典型张力``(可选):若 ``sample_reviews_mixed_tone`` 非空,用 24 条说明同一条评价里正负并存时在讨论什么(如「认可低糖但嫌口感」);否则写一句「本批混合样本较少,从略」。
4. ``#### 使用注意``13 句说明:关键词分桶的局限、抽样与截断、与医学/功效结论无关等。
@ -264,3 +270,136 @@ def generate_strategy_draft_markdown_llm(
raw = json.dumps(payload, ensure_ascii=False)
user = STRATEGY_USER_PREFIX + raw
return _call_llm(STRATEGY_SYSTEM, user)
MATRIX_GROUPS_SYSTEM = """你是竞品分析顾问。输入为 JSON``keyword`` 与 ``groups`` 数组。
每个 group ``group``细分类目名``sku_count````price_stats``该细类深入合并行可解析展示价的 min/max/median/mean/n **§6按细类价盘** 分位数表同源无则 n=0 或缺字段
``lines``该细类下若干 SKU 的标题/卖点/配料**摘录**均来自页面抓取拼接可能截断
**为每个细类**输出一小段 Markdown全部 groups 都要写顺序与输入一致
- ``#### `` + 与该 group 字段**完全一致**的细类名作为小节标题(不要使用 ``##`` 一级标题);
- 每段约 **100200 **中文**主体**归纳该细类下**卖点表述共性****配料类型/宣称共性**摘录中无配料则写配料摘录较少**品牌格局**可一句概括仅依据摘录中可见品牌/系列勿编造销量排名
- **价带/价位** ``price_stats.n`` 为大于 0 的整数**仅允许**用该对象里的数值写价带 minmax中位数且须与 ``price_stats`` **完全一致****禁止**价格带未明确未体现具体价位多为中端**与上述数值相矛盾**的表述 n=0 或无可信数值**不要猜测价位**可写一句深入样本可解析数值价不足价盘以 **§6** 表格为准
- **禁止**输出 Markdown 表格禁止逐条复述 SKU 明细表勿编造功效认证
- ``lines`` 很少明确写样本较少归纳供启发
总输出约 **8003500 **细类多则偏长仅输出正文 Markdown不要用代码围栏包裹全文"""
MATRIX_GROUPS_USER_PREFIX = (
"请根据以下 JSON 撰写竞品报告第五章末「细类要点归纳」正文Markdown\n\n"
)
def generate_matrix_group_summaries_llm(
groups: list[dict[str, Any]], *, keyword: str
) -> str:
trimmed: list[dict[str, Any]] = []
for g in groups:
if not isinstance(g, dict):
continue
g2 = dict(g)
ln = g2.get("lines")
if isinstance(ln, list) and len(ln) > 22:
g2["lines"] = ln[:22]
trimmed.append(g2)
payload = {"keyword": keyword, "groups": trimmed}
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 95_000:
for g2 in trimmed:
ln = g2.get("lines")
if isinstance(ln, list) and len(ln) > 12:
g2["lines"] = ln[:12]
raw = json.dumps({"keyword": keyword, "groups": trimmed}, ensure_ascii=False)
user = MATRIX_GROUPS_USER_PREFIX + raw
return _call_llm(MATRIX_GROUPS_SYSTEM, user)
COMMENT_GROUPS_SYSTEM = """你是用户研究与品类顾问。输入为 JSON``keyword`` 与 ``groups``。
每个 group ``group`` §5 矩阵一致的细分类目名``comment_flat_rows````effective_text_lines``
``focus_hit_lines``关注词子串命中摘要 §8.3 同源``sample_text_snippets``评价短摘录已截断
摘录行通常以 ``细类SKU品名`` 开头细类可与本 group 名对照**品名/SKU 表示该句具体出自哪条链接**归纳时若引用原话**须交代是哪条 SKU / 哪款品名上的反馈**勿只写有用户说口感差而不指代产品
**为每个细类**输出一小段 Markdown全部 groups 都要写顺序与输入一致
- ``#### `` + 与该 group 字段**完全一致**的细类名作为小节标题(不要使用 ``##`` 一级标题);
- 每段约 **100220 **中文归纳该细类下**消费者在讨论什么**口感价格物流功效疑虑等**关注词命中反映的诉求**勿编造摘录中未出现的品牌医学结论
- **禁止**输出 Markdown 表格禁止逐条复述全部评价
- ``effective_text_lines`` 很少明确写样本较少归纳供启发
总输出约 **6003200 **仅输出正文 Markdown不要用代码围栏包裹全文"""
COMMENT_GROUPS_USER_PREFIX = (
"请根据以下 JSON 撰写竞品报告第八章末「细类评论与关注词要点归纳」正文Markdown\n\n"
)
def generate_comment_group_summaries_llm(
groups: list[dict[str, Any]], *, keyword: str
) -> str:
trimmed: list[dict[str, Any]] = []
for g in groups:
if not isinstance(g, dict):
continue
g2 = dict(g)
sn = g2.get("sample_text_snippets")
if isinstance(sn, list) and len(sn) > 16:
g2["sample_text_snippets"] = sn[:16]
fh = g2.get("focus_hit_lines")
if isinstance(fh, list) and len(fh) > 14:
g2["focus_hit_lines"] = fh[:14]
trimmed.append(g2)
payload = {"keyword": keyword, "groups": trimmed}
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 95_000:
for g2 in trimmed:
sn = g2.get("sample_text_snippets")
if isinstance(sn, list) and len(sn) > 10:
g2["sample_text_snippets"] = sn[:10]
raw = json.dumps({"keyword": keyword, "groups": trimmed}, ensure_ascii=False)
user = COMMENT_GROUPS_USER_PREFIX + raw
return _call_llm(COMMENT_GROUPS_SYSTEM, user)
PRICE_GROUPS_SYSTEM = """你是定价与渠道顾问。输入为 JSON``keyword`` 与 ``groups``。
每个 group ``group``细分类目名 §5 矩阵§6按细类价盘小节一致``sku_count````price_stats``该细类可解析展示价的 min/max/median/mean/n §6 各细类 Markdown 分位数表同源
``listing_snippets``若干标题标价券后详情价摘录来自合并表字段已截断
**为每个细类**输出一小段 Markdown全部 groups 都要写顺序与输入一致
- ``#### `` + 与该 group 字段**完全一致**的细类名作为小节标题;
- 每段约 **80200 **中文**只写价盘与价差** ``price_stats`` 概括价带/离散度 minmax中位数相对集中或拉得开 ``listing_snippets`` 归纳**标价 vs 券后 vs 详情价**是否常一致是否常见券后低于标价价差幅度的大致印象**可一句**联系标题里**显式出现的规格数字**如克重件数解释**价高/价差大是否可能来自大规格或组合装**仅当摘录里确有数字时写勿展开成宣称解读
- **硬性禁止**本章不是卖点章不要列举或归纳0 蔗糖 / GI / 全麦 / 代餐 / 孕妇 / 控糖**营销宣称或场景关键词**不要写配料功效品牌叙事用户画像这些若出现应留给报告 **§5 细类要点归纳** **§7**
- **禁止** Markdown 表格禁止罗列全部 SKU勿编造未出现的到手价销量排名
- ``price_stats`` n=0 或缺失该细类无可解析数值价从略
总输出约 **5002800 **仅输出正文 Markdown不要用代码围栏包裹全文"""
PRICE_GROUPS_USER_PREFIX = (
"请根据以下 JSON 撰写竞品报告第六章末「细类价盘要点归纳」正文Markdown"
"本章只写**数值价带与标价/券后/详情价关系**,勿写卖点宣称关键词归纳。\n\n"
)
def generate_price_group_summaries_llm(
groups: list[dict[str, Any]], *, keyword: str
) -> str:
trimmed: list[dict[str, Any]] = []
for g in groups:
if not isinstance(g, dict):
continue
g2 = dict(g)
sn = g2.get("listing_snippets")
if isinstance(sn, list) and len(sn) > 14:
g2["listing_snippets"] = sn[:14]
trimmed.append(g2)
payload = {"keyword": keyword, "groups": trimmed}
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 95_000:
for g2 in trimmed:
sn = g2.get("listing_snippets")
if isinstance(sn, list) and len(sn) > 8:
g2["listing_snippets"] = sn[:8]
raw = json.dumps({"keyword": keyword, "groups": trimmed}, ensure_ascii=False)
user = PRICE_GROUPS_USER_PREFIX + raw
return _call_llm(PRICE_GROUPS_SYSTEM, user)