Files
beige/backend/app/workers/pipeline_steps.py
yangqianqian 6a2632da70 baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

94 lines
3.6 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.
"""
app/workers/pipeline_steps.py — 生产链 Step1-4
Step1: 查 DBtask/product
Step2: 查 key → Fernet 解密局部变量不传出基石B
Step3: 构建 AI clients
Step4: 推 task_started SSE + 飞轮上下文
"""
import json
import logging
logger = logging.getLogger(__name__)
def load_task_and_product(db, task_id: int):
"""Step1: 查任务 + 产品,失败返 None 或抛异常。"""
from app.models.task import GenerationTask
from app.models.product import Product
task = db.query(GenerationTask).filter(GenerationTask.id == task_id).first()
if not task:
logger.error("task_id=%s not found", task_id)
return None, None
product = db.query(Product).filter(Product.id == task.product_id).first()
if not product:
raise ValueError(f"product_id={task.product_id} not found")
return task, product
def decrypt_user_key(db, operator_id: int, workspace_id: int) -> str:
"""
Step2: 查 key → Fernet 解密,返回 plain_key只活在调用方局部变量
绝不打印、不持久化 plain_key基石B
"""
from app.models.workspace import UserApiKey
from app.utils.fernet_utils import decrypt_key
api_key_row = db.query(UserApiKey).filter(
UserApiKey.user_id == operator_id,
UserApiKey.workspace_id == workspace_id,
UserApiKey.provider.in_(["openai", "apiports"]), # G6坑修复接受主备通道名
).first()
if not api_key_row:
raise ValueError("用户未配置 API Key请先录入")
return decrypt_key(api_key_row.encrypted_key)
def build_clients_and_clear_key(plain_key: str):
"""
Step3: 构建 AIClientsplain_key 传入后立即由调用方置 None。
返回 clients 对象。
"""
from app.services.ai_engine.gemini_factory import build_ai_clients
return build_ai_clients(plain_key)
def build_product_dict(product) -> dict:
"""把 ORM product 转成 AI 引擎所需的 dict不含任何 key"""
return {
"name": product.name,
"category": product.category or "通用好物",
"selling_points": json.loads(product.selling_points or "[]"),
"style_tone": product.style_tone or "素人分享风",
"text_angles": json.loads(product.text_angles or "[]"),
"custom_prompt": product.custom_prompt or "",
"brand_keyword": product.brand_keyword or "", # S3: 品牌词透传进生成prompt(每条植入)
"target_audience": product.target_audience or "", # 012: 人群透传进storyboard/文案prompt
"image_path": product.image_path or "", # 产品参考图路径(前端上传后填入)
}
def load_flywheel_context(db, workspace_id: int, product_id: int, product_dict: dict) -> tuple[str, dict]:
"""
查最近50条飞轮事件聚合偏好上下文。
返回 (prompt_fragment, full_ctx)。
"""
from app.models.flywheel import PreferenceEvent
from app.services.ai_engine.preference_aggregator import aggregate_preference_context
recent = db.query(PreferenceEvent).filter(
PreferenceEvent.workspace_id == workspace_id,
PreferenceEvent.product_id == product_id,
).order_by(PreferenceEvent.id.desc()).limit(50).all()
events_dicts = [
{"signal_type": e.signal_type, "workspace_id": e.workspace_id,
"product_id": e.product_id, "angle_label": e.angle_label or "",
"signal_weight": e.signal_weight, "reason": e.reason or ""}
for e in recent
]
ctx = aggregate_preference_context(events_dicts, product_dict, workspace_id, product_id)
return ctx.get("prompt_fragment", ""), ctx