大量优化 模型改成gpt6
Deploy dev / deploy (push) Successful in 41s

This commit is contained in:
Azmat@qq.com
2026-09-28 18:15:08 +08:00
parent a84431c9c5
commit 4421706290
14 changed files with 1878 additions and 540 deletions
+158 -32
View File
@@ -40,6 +40,7 @@ from .creation_agent import (
_RESTART_CONTINUATION,
apply_cast_relation_choice,
apply_click_swap_mode,
append_multi_character_relation_gate,
append_person_source_gate,
apply_pain_point_direction,
apply_confirm_params,
@@ -47,22 +48,27 @@ from .creation_agent import (
apply_session_params,
emit_prompt_gate,
emit_final_confirm_gate,
insufficient_cast_refs_message,
locked_product_references,
set_plot_twist_story_depth,
is_greeting,
is_pain_point_conversation,
is_pain_point_direction_payload,
is_restart_intent,
multi_character_relation_needs_clarification,
restore_gated_step_after_cancel,
get_video_gate_stage,
set_video_gate_stage,
sync_prompt_after_cast,
submit_confirmed_image,
submit_confirmed_video,
submit_generated_person_reference,
video_needs_person_source,
is_incomplete_product_brand_answer,
PRODUCT_BRAND_EMPTY_TEMPLATE,
)
from .tasks import run_creation_agent_turn_task
from .mentions import TYPE_LABELS, VALID_TYPES, refs_from_elicit_answers, search_mentions
from .mentions import TYPE_LABELS, VALID_TYPES, refs_from_elicit_answers, resolve_refs, search_mentions
from .models import AITask, CreationConversation, CreationMessage, ImageConversation, ModelConfig
from .serializers import (
AITaskSerializer,
@@ -73,7 +79,7 @@ from .serializers import (
ImageConversationTrashSerializer,
ModelConfigSerializer,
)
from .services import enqueue_standalone_images
from .services import enqueue_standalone_images, normalize_cover_product_info
logger = logging.getLogger(__name__)
@@ -433,6 +439,44 @@ _STEP_REVISE_INSTRUCTIONS = {
}
def _advance_after_video_prompt(conversation: CreationConversation) -> list[CreationMessage]:
"""Prompt 确认后按固定顺序补角色,再展示可调参数的最终生成确认。
视频架构与 Prompt 已经定稿,角色图只在这里锁定,保证预设的必经选择和
架构撰写不会被人物来源卡片抢断。多角色时继续在同一阶段确认出镜关系。
"""
from .creation_agent import video_needs_person_source
if video_needs_person_source(conversation):
set_video_gate_stage(conversation, "cast")
return [append_person_source_gate(conversation)]
if multi_character_relation_needs_clarification(conversation):
set_video_gate_stage(conversation, "cast")
return [append_multi_character_relation_gate(conversation)]
synced = sync_prompt_after_cast(conversation)
confirm = emit_final_confirm_gate(conversation)
return [item for item in (synced, confirm) if item is not None]
def _video_gate_response(
conversation: CreationConversation,
messages: list[CreationMessage],
) -> JsonResponse:
"""返回角色或最终确认卡;只有最终确认卡携带预计积分。"""
conversation.agent_status = CreationConversation.AgentStatus.AWAITING_USER
conversation.save(update_fields=["agent_status", "updated_at"])
body = {
"conversation_id": str(conversation.id),
"agent_status": conversation.agent_status,
"messages": [CreationMessageSerializer(message).data for message in messages],
}
if len(messages) == 1 and messages[0].kind == CreationMessage.Kind.CONFIRM:
credits = int((messages[0].payload or {}).get("estimated_credits") or 0)
if credits:
body["estimated_credits"] = credits
return JsonResponse(body, status=200)
def _handle_step_confirm_answer(
conversation: CreationConversation,
*,
@@ -497,20 +541,10 @@ def _handle_step_confirm_answer(
return JsonResponse(body, status=200), False, ""
if step == "prompt":
confirm = emit_final_confirm_gate(conversation)
if confirm is None:
messages = _advance_after_video_prompt(conversation)
if not messages:
return None, True, _STEP_CONTINUE_INSTRUCTIONS["prompt"]
conversation.agent_status = CreationConversation.AgentStatus.AWAITING_USER
conversation.save(update_fields=["agent_status", "updated_at"])
credits = int((confirm.payload or {}).get("estimated_credits") or 0)
body = {
"conversation_id": str(conversation.id),
"agent_status": conversation.agent_status,
"messages": [CreationMessageSerializer(confirm).data],
}
if credits:
body["estimated_credits"] = credits
return JsonResponse(body, status=200), False, ""
return _video_gate_response(conversation, messages), False, ""
# 未知 step:当普通继续
return None, True, "用户已确认上一步。继续推进创作,不要复述确认。"
@@ -539,8 +573,12 @@ class GenerateImageView(APIView):
model_entity_id = str(request.data.get("model_entity_id") or "").strip() or None
ratio = str(request.data.get("ratio") or "").strip() or None
image_model = str(request.data.get("image_model") or "").strip() or None
# 平台套图:前端传规范化平台 id(taobao/douyin/…),用于后端注入平台版式块(优化版)
# 平台套图:平台 id 已改为可选(页面不再选平台,默认通用电商主图规范);旧调用方传 canonical id
# (taobao/douyin/…)仍注入平台版式块,向后兼容。
platform_id = str(request.data.get("platform_id") or "").strip() or None
# 平台套图:用户填写的商品信息(selling_points/effect/audience/specs/notes),只对 cover 生效,
# 由 enqueue_standalone_images 规范化后写入 request_payload.product_info;不传 = 旧行为。
product_info = request.data.get("product_info") if mode == "cover" else None
conversation_id = str(request.data.get("conversation_id") or "").strip() or None
# 重跑/补图:前端带原批次 batch_id → enqueue 沿用(UUID 校验),记录归回原批次不裂新卡
batch_id = str(request.data.get("batch_id") or "").strip() or None
@@ -551,6 +589,25 @@ class GenerateImageView(APIView):
raw_refs = [s for s in raw_refs.split(",") if s.strip()]
reference_image_ids = [str(r).strip() for r in raw_refs if str(r).strip()]
team = get_current_team(request.user)
# 图片创作输入框的 @ 引用和全能创作使用同一份解析契约:
# 不信任前端传来的图片地址,只拿 type/id 回库取事实与真正的参考 Asset。
raw_mentions = request.data.get("mention_refs") or []
if not isinstance(raw_mentions, list):
raw_mentions = []
mention_refs = [
{"type": str(item.get("type") or ""), "id": str(item.get("id") or "")}
for item in raw_mentions
if isinstance(item, dict)
and str(item.get("type") or "") in VALID_TYPES
and str(item.get("id") or "").strip()
]
resolved_mentions = resolve_refs(team, mention_refs)
# @引用的素材与手动上传参考图都进同一条 image_edit 链路;保持解析器的
# 角色 → 场景 → 商品顺序,再追加用户临时上传图并去重。
reference_image_ids = list(dict.fromkeys(
[str(item["asset_id"]) for item in resolved_mentions.references if item.get("asset_id")]
+ reference_image_ids
))
if product_id:
try:
normalized_product_id = str(uuid.UUID(product_id))
@@ -604,7 +661,7 @@ class GenerateImageView(APIView):
title=(prompt[:24] or "默认创作"),
)
try:
tasks = enqueue_standalone_images(team=team, user=request.user, prompt=prompt, mode=mode, count=count, product_id=product_id, reference_product=reference_product, model_id=model_id, model_entity_id=model_entity_id, ratio=ratio, image_model=image_model, conversation=conversation, reference_image_ids=reference_image_ids, platform_id=platform_id, batch_id=batch_id, retry_of_task_id=retry_of_task_id)
tasks = enqueue_standalone_images(team=team, user=request.user, prompt=prompt, mode=mode, count=count, product_id=product_id, reference_product=reference_product, model_id=model_id, model_entity_id=model_entity_id, ratio=ratio, image_model=image_model, conversation=conversation, reference_image_ids=reference_image_ids, reference_context=resolved_mentions.facts_text, platform_id=platform_id, product_info=product_info, batch_id=batch_id, retry_of_task_id=retry_of_task_id)
except ValueError as exc: # 无可用模型 / 余额不足等,立即反馈
internal_kind = "user_credit_insufficient" if str(exc).strip().lower() == "insufficient credit" else ""
public_error = classify_generation_error(
@@ -901,6 +958,8 @@ class AITaskViewSet(TeamScopedViewSetMixin, ReadOnlyModelViewSet):
rp_prompt=KeyTextTransform("prompt", "request_payload"),
rp_ratio=KeyTextTransform("ratio", "request_payload"),
rp_platform_id=KeyTextTransform("platform_id", "request_payload"),
# 平台套图商品信息(JSON 对象的文本形态),前端恢复批次后重跑沿用;旧记录无此键 → NULL
rp_product_info=KeyTextTransform("product_info", "request_payload"),
rp_model_id=KeyTextTransform("model_id", "request_payload"),
rp_model_entity_id=KeyTextTransform("model_entity_id", "request_payload"),
# 只在重跑任务里落此键(值恒为 True);键不存在 → NULL → 假值,存在 → "true"/"1" → 真值
@@ -928,6 +987,7 @@ class AITaskViewSet(TeamScopedViewSetMixin, ReadOnlyModelViewSet):
"model_id": t.rp_model_id or "",
"model_entity_id": t.rp_model_entity_id or "",
"platform_id": t.rp_platform_id or "",
"product_info": normalize_cover_product_info(t.rp_product_info) or None,
"rerun": bool(t.rp_batch_append),
"retry_of_task_id": str((t.request_payload or {}).get("retry_of_task_id") or ""),
"created_at": t.created_at,
@@ -2030,6 +2090,26 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
record_user_message = False
force_creative_turn = True
continuation_instruction = _plot_twist_depth_continuation(depth)
elif payload.get("interaction") == "product_brief_review":
memory = dict(conversation.memory or {})
memory["product_brief_reviewed"] = True
if text:
memory["product_brief_note"] = text
conversation.memory = memory
conversation.save(update_fields=["memory", "updated_at"])
payload["answers"] = {
"review_action": "supplement" if text else "continue",
"product_brief_note": text,
}
payload["submitted"] = True
payload["answered_via"] = "chat"
pending.payload = payload
pending.save(update_fields=["payload", "updated_at"])
force_creative_turn = True
continuation_instruction = (
"用户已核对商品信息。把补充内容当作真实商品事实;未确认的品牌、价格、优惠或功效不得编造。"
"继续完成视频架构。"
)
elif payload.get("interaction") == "plot_twist_directions":
options = [item for item in (payload.get("directions") or []) if isinstance(item, dict)]
chosen = next(
@@ -2379,6 +2459,13 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
memory_now["person_confirm_pending"] = False
conversation.memory = memory_now
conversation.save(update_fields=["memory", "updated_at"])
# 角色图属于 Prompt 之后的固定闸门。用户确认平台定妆后,直接进入
# 多角色安排或最终参数确认,不再重新跑一轮架构 / Prompt Agent。
if get_video_gate_stage(conversation) == "cast":
append_message(conversation, role="user", text=clean_text)
messages = _advance_after_video_prompt(conversation)
if messages:
return _video_gate_response(conversation, messages)
force_creative_turn = True
continuation_instruction = (
"用户已确认使用当前生成的角色出镜。"
@@ -2455,7 +2542,7 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
incoming = request.data.get("params")
if incoming is not None and not isinstance(incoming, dict):
return JsonResponse({"detail": "params 必须是对象"}, status=400)
latest_params, duration_changed = apply_confirm_params(
latest_params, needs_rebuild = apply_confirm_params(
conversation, incoming if isinstance(incoming, dict) else None
)
card.payload = {
@@ -2464,14 +2551,34 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
"params": latest_params,
}
card.save(update_fields=["payload", "updated_at"])
# 改时长会让旧脚本对不上(5 秒方案不能直接出 10 秒)。确认卡作废,前端再发一轮让模型重写。
if duration_changed:
# 改时长或视频模型会影响能力与 Prompt。保留事实/素材,只重写受影响步骤。
if needs_rebuild:
return JsonResponse({
"regenerate": True,
"params": latest_params,
"message": None,
}, status=200)
is_image = (card.payload or {}).get("kind") == "image" or conversation.mode == CreationConversation.Mode.IMAGE
if not is_image:
prompt = str((card.payload or {}).get("video_prompt") or "").strip()
cast_gap = insufficient_cast_refs_message(conversation, prompt)
if cast_gap:
memory = conversation.memory if isinstance(conversation.memory, dict) else {}
if memory.get("person_source_pending"):
# 历史确认卡可能仍可见;角色正在生成时不落 ERROR,也不重复创建角色闸门。
card.payload = {**(card.payload or {}), "submitted": False}
card.save(update_fields=["payload", "updated_at"])
return JsonResponse({
"detail": "角色图正在生成,完成后会自动继续到参数确认。",
"message": None,
}, status=409)
# 兼容更新前已经生成的确认卡:把缺角色错误转换成明确的角色补全步骤。
if video_needs_person_source(conversation, prompt):
set_video_gate_stage(conversation, "cast")
return _video_gate_response(
conversation,
[append_person_source_gate(conversation, prompt)],
)
submitter = submit_confirmed_image if is_image else submit_confirmed_video
message, error = submitter(
conversation=conversation, user=request.user, confirm_message=card
@@ -2607,6 +2714,26 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
record_user_message = False
force_creative_turn = True
continuation_instruction = _plot_twist_depth_continuation(depth)
elif payload.get("interaction") == "product_brief_review":
action = str(answers.get("review_action") or "").strip()
if action not in {"continue", "supplement"}:
return JsonResponse({"detail": "请确认按现有信息继续,或补充真实商品信息"}, status=400)
note = str(answers.get("product_brief_note") or "").strip()
if action == "supplement" and not note:
return JsonResponse({"detail": "请填写要补充的真实商品信息"}, status=400)
memory = dict(conversation.memory or {})
memory["product_brief_reviewed"] = True
if note:
memory["product_brief_note"] = note
conversation.memory = memory
conversation.save(update_fields=["memory", "updated_at"])
text = ""
record_user_message = False
force_creative_turn = True
continuation_instruction = (
"用户已核对商品信息。把卡片中的补充内容当作真实商品事实;"
"任何未确认的品牌、价格、优惠或功效都不得编造。继续完成视频架构。"
)
elif payload.get("interaction") == "plot_twist_directions":
choice = str(answers.get("story_direction") or "").strip()
if not choice:
@@ -2670,6 +2797,10 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
)
if not relation:
return JsonResponse({"detail": "这个角色选项已经失效,请重新选择"}, status=400)
if get_video_gate_stage(conversation) == "cast":
messages = _advance_after_video_prompt(conversation)
if messages:
return _video_gate_response(conversation, messages)
text = ""
record_user_message = False
force_creative_turn = True
@@ -2717,6 +2848,11 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
conversation.memory = memory
conversation.status = CreationConversation.Status.RUNNING
conversation.save(update_fields=["memory", "status", "updated_at"])
# Prompt 已确认后才开始选角色;选择完成后直接开放可调视频参数。
if get_video_gate_stage(conversation) == "cast":
messages = _advance_after_video_prompt(conversation)
if messages:
return _video_gate_response(conversation, messages)
text = ""
record_user_message = False
force_creative_turn = True
@@ -2965,23 +3101,13 @@ class CreationConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
):
return _agent_busy_response()
model_config_id = None
requested = request.data.get("model_config_id")
if requested:
model_config = (
ModelConfig.objects.select_related("provider")
.filter(id=requested, capability=ModelConfig.Capability.TEXT, status=ModelConfig.Status.ACTIVE)
.first()
)
if model_config is not None:
model_config_id = str(model_config.id)
turn_kwargs = {
"conversation_id": str(conversation.id),
"user_id": str(request.user.id),
"text": text,
"refs": refs,
"model_config_id": model_config_id,
# 全能创作不接受前端覆盖语言模型;worker 内只解析固定的 GPT-6 Luna。
"model_config_id": None,
"record_user_message": record_user_message,
"force_creative_turn": force_creative_turn,
"continuation_instruction": continuation_instruction,