mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-22 08:01:34 +08:00
feat(报告): 评价大模型解读强化负向主题归因与抽样
Made-with: Cursor
This commit is contained in:
parent
c76efd80e5
commit
bac71c00be
@ -508,11 +508,15 @@ def _comment_sentiment_lexicon(texts: list[str]) -> dict[str, Any]:
|
||||
def build_comment_sentiment_llm_payload(
|
||||
texts: list[str],
|
||||
*,
|
||||
max_samples_per_tone: int = 14,
|
||||
max_chars_per_review: int = 240,
|
||||
max_samples_positive: int = 16,
|
||||
max_samples_negative: int = 30,
|
||||
max_samples_mixed: int = 10,
|
||||
max_chars_per_review: int = 300,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
供大模型做正/负向语义归纳:附规则统计与**去重后的评价原文抽样**(与 §8.2 词表分桶一致)。
|
||||
|
||||
负向样本默认多于正向,便于大模型做「具体问题是什么」的主题归因,而非只复述词频。
|
||||
"""
|
||||
pos_only_texts: list[str] = []
|
||||
neg_only_texts: list[str] = []
|
||||
@ -530,7 +534,7 @@ def build_comment_sentiment_llm_payload(
|
||||
elif hn:
|
||||
neg_only_texts.append(s)
|
||||
|
||||
def _sample(seq: list[str]) -> list[str]:
|
||||
def _sample(seq: list[str], cap: int) -> list[str]:
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for raw in seq:
|
||||
@ -541,16 +545,22 @@ def build_comment_sentiment_llm_payload(
|
||||
out.append(raw[:max_chars_per_review] + "…")
|
||||
else:
|
||||
out.append(raw)
|
||||
if len(out) >= max_samples_per_tone:
|
||||
if len(out) >= cap:
|
||||
break
|
||||
return out
|
||||
|
||||
lex = _comment_sentiment_lexicon(texts)
|
||||
pos_h = lex.get("positive_tone_lexeme_hits") or []
|
||||
neg_h = lex.get("negative_tone_lexeme_hits") or []
|
||||
pos_h_top = [x for x in pos_h[:12] if isinstance(x, dict)]
|
||||
neg_h_top = [x for x in neg_h[:12] if isinstance(x, dict)]
|
||||
return {
|
||||
"comment_sentiment_lexicon": lex,
|
||||
"sample_reviews_positive_biased": _sample(pos_only_texts),
|
||||
"sample_reviews_negative_biased": _sample(neg_only_texts),
|
||||
"sample_reviews_mixed_tone": _sample(mixed_texts)[:8],
|
||||
"positive_lexeme_hits_top": pos_h_top,
|
||||
"negative_lexeme_hits_top": neg_h_top,
|
||||
"sample_reviews_positive_biased": _sample(pos_only_texts, max_samples_positive),
|
||||
"sample_reviews_negative_biased": _sample(neg_only_texts, max_samples_negative),
|
||||
"sample_reviews_mixed_tone": _sample(mixed_texts, max_samples_mixed),
|
||||
}
|
||||
|
||||
|
||||
@ -1753,7 +1763,7 @@ def build_competitor_markdown(
|
||||
"",
|
||||
"- **细类划分**:与 **§5 竞品矩阵** 相同,依据商详类目路径解析为「饼干 / 西式糕点 / …」等(规则见 §5 章首说明)。",
|
||||
"- **归因**:每条评价按其 SKU 对应到深入样本,再映射到该 SKU 所属细类;SKU 不在合并表中的评价单独归入说明性分组。",
|
||||
"- **正负面粗判(§8.2)**:先以关键词规则与图表做粗分;若任务开启 **llm_comment_sentiment**,可附**大模型对抽样原文的语义归纳**(与规则统计互补)。",
|
||||
"- **正负面粗判(§8.2)**:先以关键词规则与图表做粗分;若任务开启 **llm_comment_sentiment**,可附**大模型对抽样原文的主题归因**(尤其负向「用户在抱怨什么」),与词频条形图互补。",
|
||||
"- **关注词按细类(§8.3)**:对组内评价正文做子串计数并出条形图;若无逐条正文则用该细类下评价摘要列拼接兜底;与配置关注词及联想扩展同源。",
|
||||
"- **用途/场景按细类(§8.4)**:对组内每条有效文本独立扫描**本次任务生效的场景词组**(来自报告调参或系统默认),一条可属多场景;条形图横轴为**占该细类有效文本比例 %**(多标签下各比例可相加大于 100%)。",
|
||||
"",
|
||||
@ -1813,9 +1823,9 @@ def build_competitor_markdown(
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"#### 大模型解读(正/负向评价要点)",
|
||||
"#### 大模型深入解读(主题归因,与词频统计互补)",
|
||||
"",
|
||||
"> **说明**:基于与上节**同一分桶规则**抽样的评价原文,由大模型做语义归纳,与关键词条数、条形图**互补**;具体措辞以原评论为准。",
|
||||
"> **说明**:基于与上节**同一分桶规则**抽样的评价原文,由大模型归纳**用户在说什么**(尤其是负向的具体事由),与上列条数、条形图**互补**;引文以原评论为准。",
|
||||
"",
|
||||
_llm_s,
|
||||
]
|
||||
|
||||
@ -268,7 +268,13 @@ def write_competitor_analysis_for_run_dir(
|
||||
try:
|
||||
from .llm_generate import generate_comment_sentiment_analysis_llm
|
||||
|
||||
pl = jcr.build_comment_sentiment_llm_payload(comment_units)
|
||||
pl = jcr.build_comment_sentiment_llm_payload(
|
||||
comment_units,
|
||||
max_samples_positive=16,
|
||||
max_samples_negative=30,
|
||||
max_samples_mixed=10,
|
||||
max_chars_per_review=300,
|
||||
)
|
||||
pl["keyword"] = kw
|
||||
llm_sentiment_md = generate_comment_sentiment_analysis_llm(pl)
|
||||
sentiment_llm_record["ok"] = True
|
||||
|
||||
@ -49,7 +49,7 @@ REPORT_SYSTEM = """你撰写一段**短小的「速读与策略补充」**,插
|
||||
**请输出**(仅输出正文,不要前言后语):
|
||||
- 使用 **Markdown**,控制在约 **800~1500 字**;
|
||||
- 建议小节标题(二级):**执行摘要要点**、**竞争与价盘速读**、**用户声量与关注点**、**策略提示与数据边界**;
|
||||
- 若有 `comment_sentiment_lexicon`,概括正/负向粗判与局限(非深度学习);
|
||||
- 若有 `comment_sentiment_lexicon`,概括正/负向粗判与局限(非深度学习);**负向**须点出用户在抱怨的**具体事由类型**(如口感、价格、物流),避免只复述词频;
|
||||
- 语气专业、中文;缺失项写「本摘要未提供该项」而非猜测。"""
|
||||
|
||||
REPORT_USER_PREFIX = """请根据以下 JSON 撰写完整竞品分析报告(Markdown 正文)。\n\n"""
|
||||
@ -68,19 +68,22 @@ def generate_competitor_report_markdown_llm(brief: dict[str, Any], keyword: str)
|
||||
|
||||
SENTIMENT_LLM_SYSTEM = """你是电商/食品类用户研究助手。输入 JSON 含:
|
||||
- ``comment_sentiment_lexicon``:关键词规则下的条数与短语命中(粗判,非深度学习);
|
||||
- ``positive_lexeme_hits_top`` / ``negative_lexeme_hits_top``:短语级命中摘要(与条形图同源);
|
||||
- ``sample_reviews_*``:按同一规则从评价中抽样的短文(已截断),**仅可依据这些原文与 lexicon 数字归纳**。
|
||||
|
||||
**硬性要求**:
|
||||
- **仅输出 Markdown 正文**(不要用 ``` 围栏包裹全文);
|
||||
- **不要编造**样本中未出现的具体事实、品牌、价格、医学功效;
|
||||
- 条数、占比等**定量表述须与** ``comment_sentiment_lexicon`` **一致**,勿与样本矛盾。
|
||||
- 条数、占比等**定量表述须与** ``comment_sentiment_lexicon`` **一致**,勿与样本矛盾;
|
||||
- **不要**只复述「某词出现 N 次」——词频条形图已在报告正文;你的价值是**语义层归纳**:用户在说什么、不满/满意的具体事由是什么。
|
||||
|
||||
**建议结构**(使用四级标题 ``####``):
|
||||
1. ``#### 正向要点归纳``:3~6 条要点,概括满意点(口感、甜度、包装、物流、性价比等);
|
||||
2. ``#### 负向与风险点归纳``:3~6 条要点;
|
||||
3. ``#### 使用注意``:1~2 句说明样本量、抽样局限、与关键词规则可能不一致之处。
|
||||
1. ``#### 正向体验主题``:3~6 条;每条用一句话概括一类满意点(如口感、甜度、饱腹、性价比、物流),**尽量**在句末用简短「」引用样本中的原话片段佐证(无合适原话则省略引号,勿杜撰)。
|
||||
2. ``#### 负向评价主题归因``:**核心段落**。在「偏负向」与「混合」样本中归纳 **4~8 个具体问题维度**(示例维度,按需选用:口味/难吃/怪味、过甜或寡淡、质地口感、价格与促销、包装破损、物流时效、真伪与效期、与宣传不符、健康/功效疑虑等)。每个维度下用 1~2 条列表项写清「用户具体在抱怨什么」,并**尽量**附上来自 ``sample_reviews_negative_biased`` 或 ``sample_reviews_mixed_tone`` 的「」短引文;若某维度在样本中几乎无依据则不要硬写。
|
||||
3. ``#### 混合评价中的典型张力``(可选):若 ``sample_reviews_mixed_tone`` 非空,用 2~4 条说明同一条评价里正负并存时在讨论什么(如「认可低糖但嫌口感」);否则写一句「本批混合样本较少,从略」。
|
||||
4. ``#### 使用注意``:1~3 句说明:关键词分桶的局限、抽样与截断、与医学/功效结论无关等。
|
||||
|
||||
总字数约 **400~900 字**,简体中文,语气客观。"""
|
||||
总字数约 **700~1600 字**,简体中文,语气客观。"""
|
||||
|
||||
|
||||
def generate_comment_sentiment_analysis_llm(payload: dict[str, Any]) -> str:
|
||||
@ -88,14 +91,15 @@ def generate_comment_sentiment_analysis_llm(payload: dict[str, Any]) -> str:
|
||||
p = dict(payload)
|
||||
raw = json.dumps(p, ensure_ascii=False)
|
||||
if len(raw) > 88_000:
|
||||
for k in (
|
||||
"sample_reviews_positive_biased",
|
||||
"sample_reviews_negative_biased",
|
||||
"sample_reviews_mixed_tone",
|
||||
# 超长时优先压缩正向与混合,保留更多负向样本以利主题归因
|
||||
for k, cap, maxlen in (
|
||||
("sample_reviews_positive_biased", 8, 140),
|
||||
("sample_reviews_mixed_tone", 6, 140),
|
||||
("sample_reviews_negative_biased", 18, 160),
|
||||
):
|
||||
lst = p.get(k)
|
||||
if isinstance(lst, list):
|
||||
p[k] = [str(x)[:140] for x in lst[:8]]
|
||||
p[k] = [str(x)[:maxlen] for x in lst[:cap]]
|
||||
raw = json.dumps(p, ensure_ascii=False)
|
||||
if len(raw) > 88_000:
|
||||
raw = raw[:82_000] + "\n\n…(输入过长已截断,请勿编造截断外内容)\n"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user