A8: 文案按A/B/C三套正交叙事生成,避免套路化重复

- constants: 新增 TEXT_NARRATIVE_BY_STRATEGY(A痛点/B场景/C成分),与图片侧同轴
- build_prompt: 加 strategy_narrative 参数并注入 prompt
- text_variants: 全链路透传(含优化轮)
- run_text_generation: 改循环三套,text_count均摊(divmod余前补),跨套去重,打_strategy标记
- TextCandidate: 加 strategy String(4) 字段 + 迁移021(已upgrade head)
- packaging: 打包按strategy精准配对文图(texts_by_strategy映射+三层兜底)
- SSE text_candidate 事件携带 strategy

独立agent交叉验证7改造点全过,边界(text_count<3/无别名/不截断)无must-fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yangqianqian
2026-06-22 10:00:47 +08:00
parent 4bed7425a8
commit ff21116ff8
8 changed files with 123 additions and 26 deletions

View File

@@ -70,12 +70,15 @@ async def _call_llm(client: Any, prompt: str, max_tokens: int = 8192) -> str:
TEXT_BATCH_SIZE = int(os.environ.get("TEXT_BATCH_SIZE", "4"))
async def _generate_one_batch(llm_client: Any, product: dict, batch_n: int, extra: str) -> list[dict]:
async def _generate_one_batch(llm_client: Any, product: dict, batch_n: int, extra: str,
strategy_narrative: str = "") -> list[dict]:
"""生成一批 batch_n 条含解析重试最多2次。失败返回空列表。
max_tokens 按条数缩放(每条约 1800 token封顶 8192),压进 apiports 60s 网关窗口。"""
batch_max_tokens = min(8192, max(1800, batch_n * 1800))
for attempt in range(2):
raw = await _call_llm(llm_client, build_prompt(product, batch_n, extra_rules=extra), batch_max_tokens)
raw = await _call_llm(llm_client, build_prompt(
product, batch_n, extra_rules=extra, strategy_narrative=strategy_narrative,
), batch_max_tokens)
parsed = parse_json_array(raw)
if parsed:
return parsed
@@ -84,7 +87,8 @@ async def _generate_one_batch(llm_client: Any, product: dict, batch_n: int, extr
return []
async def _generate_in_batches(llm_client: Any, product: dict, count: int, extra: str) -> list[dict]:
async def _generate_in_batches(llm_client: Any, product: dict, count: int, extra: str,
strategy_narrative: str = "") -> list[dict]:
"""把 count 条按 TEXT_BATCH_SIZE 分批,串行调用合并。
串行而非并发opus 单批就慢(~300s)且 apiports 限并发,多批 gather 会触发
大面积 503 雪崩(task45 实测)。故改串行,墙钟换稳定。"""
@@ -96,7 +100,7 @@ async def _generate_in_batches(llm_client: Any, product: dict, count: int, extra
remaining -= n
collected: list[dict] = []
for n in sizes:
r = await _generate_one_batch(llm_client, product, n, extra)
r = await _generate_one_batch(llm_client, product, n, extra, strategy_narrative)
collected.extend(r)
return collected
@@ -108,12 +112,16 @@ async def generate_text_variants(
previous_copies: list[dict] | None = None,
banned_word_rows: list[dict] | None = None,
flywheel_context: str = "",
strategy_narrative: str = "",
) -> list[dict]:
"""轨A一次出 count 条不同角度文案,三层兜底,自动优化循环"""
"""轨A一次出 count 条不同角度文案,三层兜底,自动优化循环
strategy_narrative本套正交叙事主线(A痛点/B场景/C成分),由调用方按套传入,
贯穿首批生成与优化轮,确保同套内文案同一叙事不串味。"""
banned_entries = build_entries_from_db(banned_word_rows or [])
extra = flywheel_context
copies: list[dict] = await _generate_in_batches(llm_client, product, count, extra)
copies: list[dict] = await _generate_in_batches(
llm_client, product, count, extra, strategy_narrative)
if not copies:
copies = list(build_local_drafts(product, count)) # generator → list
@@ -149,6 +157,7 @@ async def generate_text_variants(
raw2 = await _call_llm(llm_client, build_prompt(
product, len(batch_failed),
extra_rules=f"以下文案未达标,请重新生成并改进:\n{hint}\n不要重复已有标题和角度。",
strategy_narrative=strategy_narrative,
), min(8192, max(1800, len(batch_failed) * 1800)))
if not raw2:
# LLM 失败(如 503/超时):优化是锦上添花,原始候选已够用,不再耗时重试