feat(llm): 策略与机会输入各章大模型节选并与前文对齐

Made-with: Cursor
This commit is contained in:
hub-gif 2026-04-16 17:09:44 +08:00
parent b8516662a8
commit 9f0f2ed181
3 changed files with 90 additions and 19 deletions

View File

@ -6,7 +6,7 @@
- §8.2``generate_comment_sentiment_analysis_llm``
- §8末细类评价``generate_comment_group_summaries_llm``
- §8.3 右栏后使用场景``generate_scenario_group_summaries_llm``
- §9 策略与机会``generate_strategy_opportunities_llm``输入为 ``build_competitor_brief`` 摘要
- §9 策略与机会``generate_strategy_opportunities_llm````build_competitor_brief`` + 可选 ``chapter_llm_narratives`` 与各章归纳对齐
- §8.5 类全文补充独立长文``generate_competitor_report_markdown_llm``
cd backend

View File

@ -718,11 +718,38 @@ def write_competitor_analysis_for_run_dir(
try:
from ..llm.generate import generate_strategy_opportunities_llm
_strategy_narratives: dict[str, str] = {}
if (llm_sentiment_md or "").strip():
_strategy_narratives["sec8_2_sentiment_theme_attribution"] = (
llm_sentiment_md
)
if (llm_matrix_md or "").strip():
_strategy_narratives["sec5_matrix_group_summaries"] = llm_matrix_md
if (llm_price_md or "").strip():
_strategy_narratives["sec6_price_group_summaries"] = llm_price_md
if (llm_promo_md or "").strip():
_strategy_narratives["sec6_promo_group_summaries"] = llm_promo_md
if (llm_scenario_gr_md or "").strip():
_strategy_narratives["sec8_3_scenario_summaries"] = llm_scenario_gr_md
if use_ch8_probe and (chapter8_probe_embed_md or "").strip():
_strategy_narratives["sec8_3_text_mining_probe"] = (
chapter8_probe_embed_md
)
elif (llm_comment_gr_md or "").strip():
_strategy_narratives["sec8_3_comment_focus_summaries"] = (
llm_comment_gr_md
)
llm_strategy_opp_md = generate_strategy_opportunities_llm(
brief_final, keyword=kw
brief_final,
keyword=kw,
chapter_llm_narratives=_strategy_narratives or None,
)
strategy_opp_llm_rec["ok"] = True
strategy_opp_llm_rec["chars"] = len(llm_strategy_opp_md)
strategy_opp_llm_rec["prior_chapter_narrative_keys"] = sorted(
_strategy_narratives.keys()
)
except Exception as e:
strategy_opp_llm_rec["ok"] = False
strategy_opp_llm_rec["error"] = str(e)

View File

@ -785,46 +785,90 @@ def generate_scenario_group_summaries_llm_chunked(
return _join_chunked_group_markdown(parts)
STRATEGY_OPPORTUNITIES_SYSTEM = """你是 B 端市场与增长顾问。输入 JSON 含 ``keyword````competitor_brief``(与本任务规则报告同源的结构化摘要,可能经裁剪,并含 ``matrix_overview_for_llm``)。
STRATEGY_OPPORTUNITIES_SYSTEM = """你是 B 端市场与增长顾问。输入 JSON 含 ``keyword````competitor_brief``(与本任务规则报告同源的结构化摘要,可能经裁剪,并含 ``matrix_overview_for_llm``),以及可选 ``prior_chapter_llm_narratives``(本报告 **§5§8** 已生成的大模型归纳节选,与正文**同源**)。
请输出 **Markdown 正文**不要用 ``` 围栏包裹**直接嵌入**宿主文档中**已存在章节标题之下**的小节读者已知当前处于策略与机会相关章节
**与前文分析严格对齐硬性优先于自由发挥**
- **定性主题**各细类讨论焦点正负向体验场景与关注词归纳配料/卖点叙事促销形态描述等须与 ``prior_chapter_llm_narratives`` 中已出现的表述**方向一致****禁止**另写一套与节选**明显矛盾**的品类判断品牌举例或用户痛点主题
- **定量与可核验事实**价带分位数店铺/品牌占比条数评论统计字段等**** ``competitor_brief`` **为准**若节选与 brief 数字冲突**采纳 brief**且勿复述与数字冲突的节选句
- 若某键未出现在 ``prior_chapter_llm_narratives`` 或内容为空则该维度**不得**编造与可能存在的报告其他章冲突的细节仅依据 ``competitor_brief`` 或明确写输入中未体现
- **转化与体验**小节正负向体验线索须**优先呼应** ``sec8_2_sentiment_theme_attribution`` **§8.3 **节选``sec8_3_comment_focus_summaries`` ``sec8_3_text_mining_probe``视何者存在**禁止**将节选未提及的具体抱怨/品类问题写成**主要结论**可写假设待结合业务验证
**标题与措辞硬性**
- **禁止**在正文开头或任何位置重复宿主已有章名/小节名包括但不限于第九章第9章策略与机会提示策略与机会建议策略与机会**不要**自造 ``##`` 一级标题;
- 小节标题**仅允许**使用业务主题式 ``####``(如下所列),从第一句起就进入实质内容。
**必须遵守**
- **数字与事实**价格分位数集中度份额条数占比等**只能**来自输入 JSON 中已有字段**禁止编造**未出现的品牌销量具体 GMV未给出的到手价
- **数字与事实**价格分位数集中度份额条数占比等**只能**来自 ``competitor_brief`` 中已有字段**禁止编造**未出现的品牌销量具体 GMV未给出的到手价
- **语气**分节给出**可操作的假设性建议**定价区间思路应对齐的差异化观测点应规避的风险促销与机制设计线索转化与详情页/评价侧改进方向每条建议用假设待验证等标明不确定性
- **结构**至少使用 ``####`` 组织以下主题(可合并子条,但须覆盖):**定价与价带**、**差异化与应对齐的优势**、**风险与避免项**、**促销与活动机制**、**转化与体验**
- **促销与活动机制硬性**该节**必须优先依据** ``competitor_brief.price_promotion_signals``若存在 ``promo_keyword_row_hits_top`` **已出现**的活动话术类型如满减券后百亿补贴红包秒杀到手价等逐类给出**假设性**机制建议如何与标价/券后价差 ``share_coupon_below_list_when_both````median_discount_pct_when_coupon_below`` 等字段对照并说明**待验证**的测试方式**禁止**编造具体满减门槛红包面额补贴比例**禁止**在输入中完全未出现任何列表侧活动话术或价差信号时仍写一大段具体要做满减发红包而无输入中未捕获此类信号的说明 brief 中活动信号稀疏须明确写出并转向需补充列表/促销字段抓取类建议
- **转化与体验硬性**该节**同时**写清 **正向体验**如详情呈现规格可读性评价中反复被肯定的点有助于信任与下单的线索**仅依据** brief 中可见字段 **负向体验/摩擦**如评价侧抱怨主题体验短板可能损害转化的信号及**待验证**的改进方向不得只写一侧无足够依据时写明输入中信号不足而非编造
- **促销与活动机制硬性**该节**必须优先依据** ``competitor_brief.price_promotion_signals``若存在并与 ``prior_chapter_llm_narratives.sec6_promo_group_summaries``若有**不矛盾** ``promo_keyword_row_hits_top`` **已出现**的活动话术类型逐类给出**假设性**机制建议**禁止**编造具体满减门槛红包面额补贴比例**禁止**在输入中完全未出现任何列表侧活动话术或价差信号时仍写一大段具体要做满减发红包而无输入中未捕获此类信号的说明
- **转化与体验硬性****同时**写清正向与负向**禁止**使用占比均超过 130 **语义不通或混用次数/占比**的表述数字表述须与 ``competitor_brief`` 一致
- **禁止**不要写完整报告目录不要复述研究范围与方法不要使用 CR1/CR3 缩写第一大份额前三家合计不要输出与输入矛盾的价带描述
篇幅约 **9003200 **数据丰富可偏长"""
STRATEGY_OPPORTUNITIES_USER_PREFIX = (
"请根据以下 JSON 撰写策略归纳正文Markdown。宿主报告已含章节标题**勿在输出中写第九章或「策略与机会」类标题**。\n\n"
"请根据以下 JSON 撰写策略归纳正文Markdown"
"``competitor_brief`` 为结构化摘要;若含 ``prior_chapter_llm_narratives``,则为 §5§8 大模型归纳节选,须与策略正文对齐。"
"宿主报告已含章节标题,**勿在输出中写第九章或「策略与机会」类标题**。\n\n"
)
def _truncate_strategy_narrative(text: str, max_chars: int) -> str:
s = (text or "").strip()
if not s:
return ""
if len(s) <= max_chars:
return s
return (
s[: max_chars - 80].rstrip()
+ "\n\n…(前文各章归纳节选已截断;请勿编造截断后内容。)\n"
)
def generate_strategy_opportunities_llm(
brief: dict[str, Any], *, keyword: str
brief: dict[str, Any],
*,
keyword: str,
chapter_llm_narratives: dict[str, str] | None = None,
) -> str:
"""
基于 ``build_competitor_brief`` 全量摘要生成策略与机会小节正文不含章名由宿主 Markdown 加标题
``chapter_llm_narratives`` 为与本报告 §5§8 同源的大模型正文节选键名稳定 runner 传入用于与策略段严格对齐
"""
compact = compact_brief_for_llm(brief, max_chars=100_000)
narr_in = {
k: v
for k, v in (chapter_llm_narratives or {}).items()
if isinstance(v, str) and v.strip()
}
_TARGET = 118_000
for cap_brief, cap_narr in (
(100_000, 7_000),
(72_000, 5_000),
(52_000, 3_500),
(40_000, 2_500),
):
compact = compact_brief_for_llm(brief, max_chars=cap_brief)
narratives = {
k: _truncate_strategy_narrative(v, cap_narr) for k, v in narr_in.items()
}
payload: dict[str, Any] = {
"keyword": keyword,
"competitor_brief": compact,
}
if narratives:
payload["prior_chapter_llm_narratives"] = narratives
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) <= _TARGET:
user = STRATEGY_OPPORTUNITIES_USER_PREFIX + raw
return _call_llm(STRATEGY_OPPORTUNITIES_SYSTEM, user)
compact = compact_brief_for_llm(brief, max_chars=40_000)
payload = {"keyword": keyword, "competitor_brief": compact}
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 110_000:
compact = compact_brief_for_llm(brief, max_chars=65_000)
payload = {"keyword": keyword, "competitor_brief": compact}
raw = json.dumps(payload, ensure_ascii=False)
if len(raw) > 110_000:
compact = compact_brief_for_llm(brief, max_chars=40_000)
payload = {"keyword": keyword, "competitor_brief": compact}
raw = json.dumps(payload, ensure_ascii=False)
user = STRATEGY_OPPORTUNITIES_USER_PREFIX + raw
user = STRATEGY_OPPORTUNITIES_USER_PREFIX + json.dumps(
payload, ensure_ascii=False
)
return _call_llm(STRATEGY_OPPORTUNITIES_SYSTEM, user)