fix: 统一AI生成失败提示

This commit is contained in:
hh
2026-07-15 13:12:35 +08:00
parent 38d8c8ad54
commit e81d7170ed
19 changed files with 1220 additions and 88 deletions
+26 -4
View File
@@ -14,7 +14,7 @@ SSE 事件(每帧 `data: {json}\n\n`,json 带 type):
saved {script_version_id, version} —— 已落库的 ScriptVersion(含 segments/metadata)
summary {text} —— 模型自己写的收尾交付语(当 AI 回复气泡,替代写死的「已生成」)
done {} —— 结束
error {detail} —— 失败(已回滚额度)
error {detail,error} —— 失败(已回滚额度;error 为安全业务错误对象)
"""
from __future__ import annotations
@@ -26,6 +26,7 @@ from pathlib import Path
from django.conf import settings
from django.utils import timezone
from apps.ai.generation_errors import classify_generation_error
from apps.ai.models import AITask, ModelConfig
from apps.billing.services.ledger import charge_reserved_credit, release_credit
@@ -621,7 +622,8 @@ def stream_script_agent(
},
)
except Exception as exc: # noqa: BLE001 — 多为额度不足
yield _sse({"type": "error", "detail": f"任务创建失败(可能额度不足):{exc}"})
internal_kind = "user_credit_insufficient" if str(exc).strip().lower() == "insufficient credit" else ""
yield _sse(_script_error_event(exc, internal_kind=internal_kind))
return
reservation = task.credit_reservation
# 额度是否已结算(charge 成功 / release 失败)。客户端中途断连时,生成器被 .close() 抛
@@ -676,7 +678,7 @@ def stream_script_agent(
_fail_task(task, reservation, str(exc))
settled = True
yield _sse({"type": "tool", "id": "generate", "status": "error"})
yield _sse({"type": "error", "detail": f"脚本生成失败:{exc}"})
yield _sse(_script_error_event(exc, reference_id=str(task.id)))
return
yield _sse({"type": "tool", "id": "generate", "status": "done"})
@@ -707,7 +709,7 @@ def stream_script_agent(
except Exception as exc: # noqa: BLE001 — 落库失败:atomic 已回滚 charge,补释放预留
_fail_task(task, reservation, f"保存脚本失败:{exc}")
settled = True
yield _sse({"type": "error", "detail": f"保存脚本失败:{exc}"})
yield _sse(_script_error_event(exc, reference_id=str(task.id), internal_kind="processing_failed"))
return
from apps.projects.serializers import ScriptVersionSerializer
@@ -743,6 +745,26 @@ def _fail_task(task, reservation, message: str) -> None:
pass
def _script_error_event(
exc: Exception,
*,
reference_id: str | None = None,
internal_kind: str = "",
) -> dict:
"""脚本 SSE 的安全失败帧;原始异常只落任务记录/日志,不回传普通用户。"""
public_error = classify_generation_error(
exc,
operation="script_generate",
internal_kind=internal_kind,
reference_id=reference_id,
)
return {
"type": "error",
"detail": public_error.fallback_message,
"error": public_error.as_dict(),
}
def _load_base_draft(project, base_version_id: str) -> dict | None:
from apps.projects.models import ScriptVersion