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:
@@ -33,10 +33,18 @@ class BannedWordCreate(BaseModel):
|
||||
|
||||
|
||||
def _fmt_benchmark(b: BenchmarkNote) -> dict:
|
||||
import json
|
||||
features = None
|
||||
if b.features_json:
|
||||
try:
|
||||
features = json.loads(b.features_json)
|
||||
except Exception:
|
||||
features = None
|
||||
return {
|
||||
"id": b.id, "product_id": b.product_id,
|
||||
"screenshot_url": b.screenshot_url,
|
||||
"highlights": b.highlights, "link_url": b.link_url,
|
||||
"features": features, "analyze_status": b.analyze_status,
|
||||
"created_at": b.created_at.isoformat(),
|
||||
}
|
||||
|
||||
@@ -84,7 +92,66 @@ def create_benchmark(
|
||||
return ok(_fmt_benchmark(b))
|
||||
|
||||
|
||||
# ── 违禁词库 ────────────────────────────────────────────────
|
||||
@router.post("/benchmarks/{benchmark_id}/analyze")
|
||||
async def analyze_benchmark_note(
|
||||
benchmark_id: int,
|
||||
current_user: Annotated[CurrentUser, Depends(require_admin)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
第2环:对已存标杆笔记跑 8 维拆解。
|
||||
输入源=screenshot_url 截图(vision) + highlights 手填亮点(参考)。
|
||||
结果写入 features_json,analyze_status 流转 analyzing→done/failed。
|
||||
key 链路遵基石B:plain_key 只活局部,立即清零。
|
||||
"""
|
||||
import json
|
||||
from app.core.response import raise_business
|
||||
from app.models.workspace import UserApiKey
|
||||
from app.utils.fernet_utils import decrypt_key
|
||||
from app.services.ai_engine.gemini_factory import build_ai_clients
|
||||
from app.services.ai_engine.benchmark_analyzer import analyze_benchmark
|
||||
|
||||
b = db.query(BenchmarkNote).filter(
|
||||
BenchmarkNote.id == benchmark_id,
|
||||
BenchmarkNote.workspace_id == current_user.workspace_id,
|
||||
).first()
|
||||
if not b:
|
||||
raise_not_found("标杆笔记不存在")
|
||||
if not b.screenshot_url and not b.highlights:
|
||||
raise_business("该标杆无截图也无手填亮点,无法分析")
|
||||
|
||||
# 读截图字节(screenshot_url 存的是上传后的绝对/相对路径)
|
||||
screenshot = None
|
||||
if b.screenshot_url:
|
||||
try:
|
||||
with open(b.screenshot_url, "rb") as f:
|
||||
screenshot = f.read()
|
||||
except Exception as e:
|
||||
logger.warning("标杆截图读取失败 id=%s: %s", benchmark_id, e)
|
||||
|
||||
api_key_row = db.query(UserApiKey).filter(
|
||||
UserApiKey.user_id == current_user.user_id,
|
||||
UserApiKey.workspace_id == current_user.workspace_id,
|
||||
UserApiKey.provider.in_(["openai", "apiports"]),
|
||||
).first()
|
||||
if not api_key_row:
|
||||
raise_business("未配置 API Key,请先在设置中录入")
|
||||
plain_key = decrypt_key(api_key_row.encrypted_key)
|
||||
clients = build_ai_clients(plain_key)
|
||||
plain_key = None
|
||||
|
||||
b.analyze_status = "analyzing"; db.commit()
|
||||
try:
|
||||
result = await analyze_benchmark(clients, screenshot, b.highlights)
|
||||
finally:
|
||||
await clients.aclose()
|
||||
|
||||
b.features_json = json.dumps(result, ensure_ascii=False)
|
||||
b.analyze_status = "done" if result.get("source") != "fallback" else "failed"
|
||||
db.commit(); db.refresh(b)
|
||||
logger.info("benchmark analyzed id=%s status=%s source=%s",
|
||||
benchmark_id, b.analyze_status, result.get("source"))
|
||||
return ok({**_fmt_benchmark(b), "features": result, "analyze_status": b.analyze_status})
|
||||
@router.get("/banned-words")
|
||||
def list_banned_words(
|
||||
page: int = 1, page_size: int = 50,
|
||||
|
||||
Reference in New Issue
Block a user