market-assistant/backend/pipeline/llm/generate_marketing_detail.py
2026-04-22 10:25:08 +08:00

129 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""策略稿 → 核心信息卡 → 商详文案包(两步 LLMJSON 输出)。"""
from __future__ import annotations
import json
from typing import Any
from .llm_client import call_llm
_MAX_STRATEGY_CHARS = 28_000
CORE_CARD_SYSTEM = """你是电商商详与营销文案顾问。根据用户提供的「策略稿全文」与结构化决策、业务备注,输出**仅一段 UTF-8 JSON 对象**(不要 Markdown 代码围栏,不要前后说明文字)。
**硬性**
- 事实、数字、功效、检测结论、销量、评价原文:**仅可**来自输入;**禁止**编造未出现的品牌名、数据、「用户说」引语。
- 食品/健康相关:**禁止**治疗承诺与夸大疗效;无依据写「输入未体现」或「待法务确认」。
- 句子短、可落地;兼顾**购买者决策**与商详写手可用性。
**JSON 键(须全部出现,值为字符串;无内容用空串)**
- one_liner_value一句话价值主张买家能得到什么
- buyer_job_to_be_done购买者的任务或情境一句
- key_pain_or_desire核心痛点或欲望与策略一致
- why_this_product为何要选这一款相对同类一句
- proof_or_trust_angle信任或证明角度无依据写「输入未体现」
- differentiation_vs_alternatives与替代方案相比的差异一句
- price_value_framing价位与价值感如何表述与策略价位可对读无则「待业务确认」
- compliance_taboos表述禁区摘要来自业务备注或策略风险
- open_points_for_business待业务补充无则空串
"""
DETAIL_PACK_SYSTEM = """你是京东商详向文案手。输入为已定稿的「核心信息卡」JSON 与关键词。请输出**仅一段 UTF-8 JSON 对象**(不要 Markdown 代码围栏)。
**硬性**
- **仅可**依据核心信息卡展开;**禁止**新增数字、功效、认证、评价引语、竞品具体名(除非信息卡里已有)。
- 购买者视角,短句;禁止输出 JSON 键名英文给最终读者(值全部为中文商详可用文案)。
- 不要泄露「核心信息卡」「策略稿」等内部词。
**JSON 键(须全部出现)**
- listing_titles字符串数组46 条商品短标题备选(每条约 30 字内)
- listing_subtitle一条列表副文案约 60 字内)
- detail_headline商详首屏下 lead12 句
- selling_bullets字符串数组58 条卖点(每条约 40 字内)
- spec_sidebar_lines字符串数组03 条参数区旁短句(可空数组)
- faq对象数组每项含 question、answer 字符串35 组;答句不得超出信息卡承诺
"""
def _truncate_strategy(md: str) -> tuple[str, bool]:
t = (md or "").strip()
if len(t) <= _MAX_STRATEGY_CHARS:
return t, False
return t[: _MAX_STRATEGY_CHARS].rstrip() + "\n\n…(策略正文已截断,以下同)\n", True
def _parse_llm_json(raw: str) -> dict[str, Any]:
s = (raw or "").strip()
if not s:
raise ValueError("大模型返回为空")
try:
out = json.loads(s)
except json.JSONDecodeError:
i = s.find("{")
j = s.rfind("}")
if i >= 0 and j > i:
out = json.loads(s[i : j + 1])
else:
raise ValueError("大模型返回不是合法 JSON") from None
if not isinstance(out, dict):
raise ValueError("大模型 JSON 须为对象")
return out
def generate_core_info_card(
*,
keyword: str,
strategy_markdown: str,
strategy_decisions: dict[str, Any] | None,
business_notes: str,
) -> dict[str, Any]:
md, truncated = _truncate_strategy(strategy_markdown)
payload = {
"keyword": keyword,
"strategy_markdown": md,
"strategy_markdown_truncated": truncated,
"strategy_decisions": strategy_decisions or {},
"business_notes": (business_notes or "").strip(),
}
user = (
"请根据以下 JSON 输出核心信息卡(仅 JSON 对象):\n"
+ json.dumps(payload, ensure_ascii=False)
)
raw = call_llm(CORE_CARD_SYSTEM, user)
return _parse_llm_json(raw)
def generate_detail_page_pack(
*,
keyword: str,
core_info_card: dict[str, Any],
) -> dict[str, Any]:
payload = {
"keyword": keyword,
"core_info_card": core_info_card,
}
user = "请根据以下 JSON 输出商详包(仅 JSON 对象):\n" + json.dumps(
payload, ensure_ascii=False
)
raw = call_llm(DETAIL_PACK_SYSTEM, user)
return _parse_llm_json(raw)
def generate_marketing_detail_pack(
*,
keyword: str,
strategy_markdown: str,
strategy_decisions: dict[str, Any] | None = None,
business_notes: str = "",
) -> dict[str, Any]:
core = generate_core_info_card(
keyword=keyword,
strategy_markdown=strategy_markdown,
strategy_decisions=strategy_decisions,
business_notes=business_notes,
)
pack = generate_detail_page_pack(keyword=keyword, core_info_card=core)
return {
"core_info_card": core,
"detail_page_pack": pack,
}