mirror of
https://github.com/primedigitaltech/market-assistant.git
synced 2026-07-21 23:41:39 +08:00
feat(llm): 策略与机会输入各章大模型节选并与前文对齐
Made-with: Cursor
This commit is contained in:
parent
b8516662a8
commit
9f0f2ed181
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 缩写(用「第一大……份额」「前三家合计」);不要输出与输入矛盾的价带描述。
|
||||
|
||||
篇幅约 **900~3200 字**(数据丰富可偏长)。"""
|
||||
|
||||
|
||||
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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user