feat(M6): 第2环标杆8维分析引擎

8维草案(banana不变量+俊达配方+3链接):标题公式/开头钩子/正文结构/
卖点呈现/情绪语气emoji/目标人群/话题标签/视觉配图节奏。
- _benchmark_prompt.py: 8维vision prompt(不打分,提炼可复用配方)
- benchmark_analyzer.py: 引擎核心(vision读截图+手填亮点→8维JSON,失败兜底)
- benchmarks.py: POST /benchmarks/{id}/analyze 端点(写features_json,流转analyze_status)
端到端实测:8维齐全0缺失,真调LLM(conf0.9非兜底),落库done,质量高(识别收藏钩子/成分背书/诚实标未体现)
This commit is contained in:
yangqianqian
2026-06-16 14:28:17 +08:00
parent c5567614b4
commit c1ac2a6ab9
3 changed files with 159 additions and 1 deletions

View File

@@ -0,0 +1,66 @@
"""
app/services/ai_engine/benchmark_analyzer.py — 第2环 标杆8维分析引擎
复用 products.analyze_product_image 的 vision 范式,解耦成 service。
输入:爆款截图 bytes + 手填亮点输出8维配方 JSON写入 benchmark_notes.features_json
key 链路遵基石Bplain_key 只活在调用方局部,本模块只收已构建的 clients。
"""
import json
import logging
import re
from app.services.ai_engine._benchmark_prompt import BENCHMARK_8DIM_PROMPT
logger = logging.getLogger(__name__)
# 8维字段顺序与 prompt JSON key 一致analyze_status 流转 done 时校验齐全)
DIM_KEYS = [
"title_formula", "opening_hook", "content_structure", "selling_point_style",
"emotion_tone", "target_audience", "topic_tags", "visual_rhythm",
]
def parse_benchmark_json(raw: str) -> dict:
"""从模型返回文本提取 JSON容错 markdown ```json 包裹)。"""
cleaned = re.sub(r"```json\s*", "", raw)
cleaned = re.sub(r"```\s*", "", cleaned)
m = re.search(r"\{[\s\S]*\}", cleaned)
if not m:
raise ValueError("标杆分析返回中未找到 JSON")
return json.loads(m.group())
def fallback_benchmark(highlights: str | None = None) -> dict:
"""vision 失败兜底:保证 8 维不空手,标 source=fallback 供前端提示。"""
note = highlights or "截图未体现"
return {
"title_formula": note, "opening_hook": note, "content_structure": note,
"selling_point_style": note, "emotion_tone": note, "target_audience": note,
"topic_tags": note, "visual_rhythm": note,
"confidence": 0.4, "source": "fallback",
}
async def analyze_benchmark(clients, screenshot: bytes | None, highlights: str | None) -> dict:
"""
跑 8 维分析。截图走 vision无截图但有手填亮点则把亮点拼进 prompt 兜底。
返回结构化 8 维 dict含 confidence/source。失败不抛返回 fallback。
"""
prompt = BENCHMARK_8DIM_PROMPT
if highlights:
prompt = f"{prompt}\n\n运营补充的亮点描述(作为分析参考):{highlights}"
images = [screenshot] if screenshot else []
try:
if not images:
# 无截图纯文本兜底gpt_vision_analyze 支持空图走文本)
logger.info("analyze_benchmark: 无截图,走手填亮点文本分析")
raw = await clients.gpt_vision_analyze(prompt, images)
result = parse_benchmark_json(raw)
result["source"] = "vision" if images else "text"
return result
except Exception as e:
logger.warning("analyze_benchmark failed, fallback: %s", e)
result = fallback_benchmark(highlights)
result["warning"] = f"标杆分析失败,已兜底:{e}"
return result