""" _text_prompt.py — 文案 prompt 组装 / JSON 解析 / 本地模板兜底 方法层(全品类共用):人设/变量池/5步框架/四段结构/反AI味规则 数据层(每产品各异):由 product dict 动态注入,代码不出现具体品牌/成分名 """ from __future__ import annotations import json import random import re # ── Q4 佛系反推销人设(全品类共用,数据层产品名从product动态注入)────────── _PERSONA = """你是一个日常生活分享博主,不是品牌推广号。 核心人设:佛系、不推销、真实记录日常。 内核:不主动劝买,靠真实体验让读者自己动心;写的是生活,产品只是生活的一部分。 比例:70% 写生活场景和使用感受,30% 提产品,绝不颠倒。 收尾铁律:每条结尾方式必须不同,禁止使用"东西放这了/买不买跟我没关系"这类被用滥的固定句式。""" # ── Q1 叙事切入配方(反同质化核心·北哥四象限人群法,全品类通用不写死)──────── # 方法层:只给"切入维度类型",具体人群/场景由模型按本产品卖点+目标人群填充 # 北哥四象限:场景/习惯/适配/时机——主人群维度按条轮转,保证N条开篇不撞 _AUDIENCE_DIMENSIONS: list[str] = [ "使用场景人群(按该产品最高频的使用场景圈定一类人)", "使用习惯人群(懒人省心/进阶讲究等按习惯分的一类人)", "适配特质人群(该产品特别适配的某类特质或需求人群)", "使用时机人群(按使用时间/阶段/契机圈定的一类人)", "性价比人群(看重划算、对价格敏感的一类人)", ] # 情绪触发类型(通用,模型按产品填具体内容) _EMOTION_TRIGGERS: list[str] = [ "被身边人提醒或夸到才注意到的瞬间", "自己用的时候突然意识到的变化", "对比之前踩过的雷或走过的弯路", ] # 真实小波折类型(增可信,通用不写死品类) _REAL_FLAWS: list[str] = [ "一开始用法没掌握或用量不对", "第一眼对包装或价格有点误解", "起效没想象中快、用了阵子才感觉到", ] def _pick_combo(index: int = 0) -> dict[str, str]: """抽一组通用叙事切入配方(维度类型非写死内容);主人群维度按index轮转,N条不撞""" return { "identity": _AUDIENCE_DIMENSIONS[index % len(_AUDIENCE_DIMENSIONS)], "emotion": random.choice(_EMOTION_TRIGGERS), "flaw": random.choice(_REAL_FLAWS), } # ── Q5 negative词:prompt级别负向约束,不让AI写进正文 ───────────────────── _NEGATIVE_WORDS = ( "神器、福音、救急单品、遮羞布、日常维稳、精简底妆、" "不仅而且、焕发、守护、尽享、日常维稳、" "按头安利、绝绝子、闭眼冲、杀疯了、YYDS" ) # ── emoji 规则(适度有表情,倩倩姐2026-06-08拍板:像真人发的小红书)────────── _EMOJI_RULES = """ 【emoji表情(适度有表情,必须遵守)】 - 卖点小标题前加 emoji:✅ 卖点1 / ✨ 卖点2 / 🌿 卖点3(每条卖点1个,符合语义) - 正文段落可点缀少量 emoji 烘托情绪(如 🥹 😭 🤍 💛),每段最多1-2个,不堆砌 - 结尾话题标签前后带表情,如 "#好物分享 🛒" - emoji 服务情绪和分点,不要每句都加;整条正文 emoji 总量控制在 6-12 个 - 常用小红书 emoji 池:✅✨🌿💧📦🔍💛🤍🥹😭🛒💯(按语义选,不乱用) """.strip() # ── Q2 5步框架 + Q3 四段结构 ───────────────────────────────────────────── _STRUCTURE_RULES = """ 【5步框架(必须严格遵循)】 ① Hook暴击低价/痛点:第一句戳中场景或价格锚点,吊足读者好奇 ② 痛点共鸣:2-3句描写使用前的真实困境(结合本条的情绪触发,并按北哥要害②召集多类人) ③ 救星登场:自然带出产品,口吻是"碰巧发现/朋友安利/囤货时顺手",不是"推荐给你" ④ 卖点罗列(每条加✅小标题):3条以内,卖点翻译成使用感受,不是功效列表 ⑤ 收尾(每条必须从下方策略池随机选一种,同批次不得重复同一种,禁止"东西放这了/买不买跟我没关系"此类固定句式): 【收尾策略池·每条选不同策略】 A·留白式感受:只说自己现在的状态,不提买不买,如"反正现在出门我都带着它,心里踏实多了" B·反问读者:把感受抛回给读者,如"你们有没有那种一用就回不去的小东西?" C·场景延续:把故事延伸到未来某个细节,如"下次再有人问起,我就知道该推荐啥了" D·克制回购暗示:轻描淡写说自己行为,如"第一份用完了,已经在备第二份" E·纯记录收笔:像日记最后一句,不引导不评价,如"大概就这样,记录一下" F·引导搜索(仅在有品牌词时使用):自然提一句,如"感兴趣可以搜搜『{品牌词}』",不催单 【正文叙事骨架(开篇方式由本条叙事主线决定,禁止三套同一开头)】 段1·开篇分叉(按本条叙事主线三选一,这是三套拉开差异的关键): - 痛点先行→开篇即戳使用前的困境/情绪(结合主切入人群维度,按要害②召集多类人) - 场景先行→开篇先白描一个真实高频使用场景,痛点在场景里自然带出,不喊口号 - 成分背书先行→开篇用成分原理/测评对比视角切入("为什么有用/亲测对比"),痛点后置 段2·实测记录:真实使用过程,带上本条的真实小波折(真实感来源),用第三方视角背书(北哥要害③) 段3·种草核心:产品带来的变化,用感受描述而非功效声称,核心成分逐条对应痛点(北哥要害④) 段4·引导收尾:从收尾策略池随机选一种,佛系口吻,不强推,末尾带精准购买意向标签(北哥要害⑤) ※ 卖点罗列的措辞/顺序/emoji 三套之间禁止雷同;身边人种草/被夸被问这类桥段三套不得共用同一套。 【字数】正文350-400字(不含标题tags),3-5处段落空行增强可读性 """.strip() # ── Q8 标题公式(5类结构,每批次覆盖不同类型,不重复)────────────────────── _TITLE_FORMULA = """ 【标题公式(5类,每条用不同类型,禁止同批重复)】 特征型:「{适配特征}+用了{产品}+{感受}」例→"久坐党用了这把椅子,腰一整天不酸了" 价格型:「{价格锚点}+{产品}+{效果感受}」例→"三位数买到大牌平替,用完第一份就回购" 场景型:「{使用场景}+{产品}+{可感知变化}」例→"通勤路上靠这杯,到工位整个人都醒了" 夸张型:「疑问/感叹+{夸张感受}+{产品}」例→"这什么神仙东西,用一次就回不去了" 标题党型:「{反常识/意外信息}+真相是{翻转}」例→"被夸最近状态好,没说的是我换了它一个月" 硬性约束:标题≤20字;禁止出现"绝绝子/YYDS/杀疯了";不直接写功效词(美白/祛斑等)。 硬性约束·标题必须有具体锚点:禁止"有点反差/真的绝/太惊艳/有点东西"这类空泛无信息量的标题, 必须落到一个具体的人群/场景/数字/反差细节上(读者扫一眼就知道讲什么、关我什么事)。 """.strip() _ANTI_AI = f""" 【反AI味必须遵守】 - 禁止固定开篇套话:不许"姐妹们/宝子们/今天给大家分享"开头 - 禁止以下AI味词出现在正文:{_NEGATIVE_WORDS} - 禁止人群重叠:{'{count}'}条文案主切入人群维度不能重复(靠Q1切入配方按条轮转保证) - 禁止场景重复:同批次文案开篇场景不能雷同(不能N条都挤在同一个使用场景) - 避免生硬推销词:按头安利/绝绝子/闭眼冲 不能出现 """.strip() # ── 北哥出单强化层·五大要害(实测出单命门,与富贵框架叠加,冲突以本层为准)── _BEIGE_FIVE = """ 【北哥出单强化层·五大要害(最高优先级,与上面框架叠加,冲突时以本层为准)】 要害①·标题广覆盖+悬念钩子:标题要罩住尽量多的人,不要一上来就锁死单一窄人群 (只喊一类窄人群会把其他潜在人群挡在外面);用悬念/反差/疑问做钩子留个扣子让人想点进来; 精准筛人交给标签层,不靠标题筛人。 要害②·开篇召集痛点:正文要把这个产品能解决的痛点尽量多列几个、召集多类人 ("不管你是…还是…"的覆盖感),让更多人对号入座,不要只写一类人的一个痛点。 注意:召集痛点不必都堆在第一句——开篇方式由本条的叙事主线决定(见下方叙事主线), 痛点先行的可开篇即戳痛点,场景先行/成分背书先行的则在叙事展开中自然召集多类人, 绝不能因为这条要害就把三套都写成同一个"赶时间/痛点"开头。 要害③·第三方闺蜜真实背书:种草借第三方视角(闺蜜/同事/家人说、被人问、别人推荐), 是"别人替你证言"的真实感,不是自己反复夸自己回购多少次;自夸味=广告味=掉信任。 要害④·成分逐条对应痛点:每个核心成分都明确对上它解决的那个痛点(成分1→痛点1,成分2→痛点2), 逐条映射讲清"为什么有用",翻译成用户能感知的结果,不堆术语、不把成分名堆在一起不解释。 要害⑤·精准购买意向标签埋词:tags 不要凑流量泛词(#好物分享#真实测评 这类谁都能用的大词价值低), 要埋"有明确购买意向的人才会搜"的精准词——目标人群+品类+核心卖点/场景组合成精准长尾标签, 至少2-3个精准埋词。 这5条是北哥实测出单的命门,务必逐条命中。 """.strip() # ── 系统 prompt(合并Q2/Q3/Q4/Q7/Q8 + 北哥五段,数据层走build_prompt动态注入)── COPY_SYSTEM = f"""{_PERSONA} 合规红线(全品类通用): - 禁用"美白、祛斑、速效、医用、药妆",不得暗示治疗或改善疾病 - 不得承诺效果,不得出现before/after变白对比暗示 - 图文合规:避免社交App界面、点赞评论等截图元素 {_ANTI_AI.format(count="N")} {_BEIGE_FIVE} {_TITLE_FORMULA} {_EMOJI_RULES} {_STRUCTURE_RULES} 返回纯JSON数组,每条字段: title(标题≤20字)/ content(正文,严格350-400字,按emoji规则适度带表情)/ tags(list,3-5个#话题) angle(本条角度标签)/ coverTitle(封面大字≤10字)/ imageBrief(配图方向) 硬性格式:只输出JSON,不要markdown代码块,字符串内用中文引号「」。""" # ── 第2环标杆爆款配方 → 文案 prompt(借方法层结构,禁抄竞品原话)────────────── _BM_DIM_LABELS: list[tuple[str, str]] = [ ("title_formula", "标题公式"), ("opening_hook", "开篇钩子"), ("content_structure", "内容结构"), ("selling_point_style", "卖点表达风格"), ("emotion_tone", "情绪基调"), ("topic_tags", "话题标签思路"), ] def _build_benchmark_block(refs: list[dict]) -> str: """把标杆 8维配方渲染成 prompt 块。只借'方法结构',硬约束禁抄竞品品牌/功效原话。""" if not refs: return "" lines: list[str] = [] for i, f in enumerate(refs[:3], 1): # 最多取3条标杆,避免prompt膨胀 dims = [f"{label}=「{str(f.get(key, '')).strip()}」" for key, label in _BM_DIM_LABELS if str(f.get(key, "")).strip()] if dims: lines.append(f" 标杆{i}:" + ";".join(dims)) if not lines: return "" body = "\n".join(lines) return ( "\n【对标爆款配方参考(只借方法层结构,绝不照抄)】\n" f"{body}\n" "硬性约束:仅参考上面的标题套路/开篇方式/结构节奏/情绪基调来组织本产品文案;" "禁止照搬竞品品牌名、产品名、功效原话或具体数字;产品信息一律以本产品为准。" ) def build_prompt(product: dict, count: int, extra_rules: str = "", strategy_narrative: str = "") -> str: """ 组装文案生成 user_prompt。 数据层:product 动态注入(name/selling_points/style_tone/text_angles/custom_prompt) 方法层:已在 COPY_SYSTEM 固定,这里只注入产品数据+随机变量 strategy_narrative:三套正交叙事主线(A痛点/B场景/C成分),由调用方按套传入 """ name = product.get("name", "产品") selling = "、".join(product.get("selling_points") or ["核心卖点待录入"]) style = product.get("style_tone", "素人日常分享风") angles = product.get("text_angles") or [] custom = (product.get("custom_prompt") or "").strip() brand_kw = (product.get("brand_keyword") or "").strip() # Q1:每条按index轮转主人群维度+随机情绪/波折,传给模型作切入约束 combos = [_pick_combo(i) for i in range(count)] combos_text = "\n".join( f" 第{i+1}条:主切入人群维度=「{c['identity']}」·情绪触发=「{c['emotion']}」·真实小波折=「{c['flaw']}」" for i, c in enumerate(combos) ) angle_hint = f"文案角度要覆盖:{'、'.join(angles)}(每条用不同角度)。" if angles else "" brand_rule = ( f"每条正文中自然植入品牌词「{brand_kw}」至少一次(融入语境,不生硬)。" f"标题以悬念/钩子优先(北哥要害①),品牌词不必硬塞进标题;" f"若要进标题须自然不抢戏,禁止把品牌词堆在标题开头当详情页商品名(如'{brand_kw}{name}'式)。" ) if brand_kw else "" benchmark_block = _build_benchmark_block(product.get("benchmark_refs") or []) # strategy_narrative 是三套拉开差异的核心,加强标记顶到最高权重 narrative_block = ( f"\n========== 本套叙事主线(最高优先级·决定开篇与全文走向,禁止偏离)==========\n" f"{strategy_narrative}\n" f"==============================================================\n" if strategy_narrative else "" ) lines = [ f"产品:{name}", f"核心卖点(必须翻译成用户能感知的生活化利益,禁止直接列功效/参数词;翻译范例:技术参数'XX配方/XX材质'→'用起来的某个具体好处','大容量'→'出门一整天都不用补'):{selling}", f"风格调性:{style}", narrative_block, angle_hint, brand_rule, custom, benchmark_block, f"\n【Q1切入配方·每条主切入人群维度不同,模型按本产品把维度填成具体人群】", combos_text, extra_rules, f"\n请严格按5步框架+叙事骨架生成 {count} 条,开篇方式必须服从上方叙事主线,每条350-400字,返回纯JSON数组。", ] return "\n".join(l for l in lines if l) def parse_json_array(raw: str) -> list[dict]: """从模型输出提取 JSON 数组(容错 markdown 包裹)""" text = re.sub(r"```(?:json)?", "", raw).strip() start, end = text.find("["), text.rfind("]") if start == -1 or end == -1: return [] try: return json.loads(text[start:end + 1]) except json.JSONDecodeError: return [] def build_local_drafts(product: dict, count: int) -> list[dict]: """本地模板兜底(保证永不空手,遵循四段结构) 角度从 text_angles 循环取(保证N条角度各不同,不被 dedupe_copies 吞掉) """ name = product.get("name", "产品") points = product.get("selling_points") or ["使用方便", "真实可感知"] # 从产品档案取角度池,不够就用通用角度兜底,保证每条都不同 _fallback_angles = ["生活场景型", "成分分析型", "使用感受型", "性价比型", "痛点切入型"] angles_pool = product.get("text_angles") or _fallback_angles for i in range(count): c = _pick_combo(i) # 循环取不同角度(角度相同的两条会被 dedupe_copies 过滤掉,所以必须不重复) angle = angles_pool[i % len(angles_pool)] yield { "title": f"发现一个{name}的{angle}用法", "content": ( f"最近开始用{name},{c['emotion']}。\n\n" f"用了一段时间,{points[i % len(points)]}这点最让我意外。\n\n" f"说个小波折:{c['flaw']},后来才摸到感觉。\n\n" f"反正现在用顺手了。✅" ), "tags": [f"#{name}", "#真实测评", "#好物分享"], "angle": angle, "coverTitle": f"{name}{angle}", "imageBrief": "封面产品近景,内页核心卖点+真实使用场景。", }