feat: 接入模型动态 Fallback 与调用审计
This commit is contained in:
@@ -20,6 +20,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
@@ -574,7 +575,7 @@ def stream_script_agent(
|
||||
):
|
||||
"""生成 SSE 帧字符串的同步生成器,供 StreamingHttpResponse 包裹。
|
||||
target_index 非空 = 精准只改第 N 镜(读全脚本上下文,后端强制保留其余镜原样)。"""
|
||||
from apps.ai.services import build_provider, create_ai_task
|
||||
from apps.ai.services import create_ai_task, stream_routed_text_request
|
||||
|
||||
yield _sse({"type": "tool", "id": "skill", "label": "加载电商脚本技能", "status": "running"})
|
||||
skill_loaded = bool(load_ecommerce_skill())
|
||||
@@ -619,6 +620,9 @@ def stream_script_agent(
|
||||
"mode": mode,
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"total_duration": total_duration,
|
||||
"base_version_id": str(base_version_id or ""),
|
||||
"target_index": target_index,
|
||||
"model_routing_v1": True,
|
||||
},
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — 多为额度不足
|
||||
@@ -639,13 +643,45 @@ def stream_script_agent(
|
||||
task.status = AITask.Status.SUBMITTED
|
||||
task.submitted_at = timezone.now()
|
||||
task.save(update_fields=["status", "submitted_at", "updated_at"])
|
||||
provider = build_provider(model_config)
|
||||
for ev in provider.chat_completion_stream(
|
||||
model=model_config.name,
|
||||
endpoint=model_config.endpoint,
|
||||
def validate_script_text(raw_text: str) -> dict:
|
||||
candidate = normalize_draft(
|
||||
raw_text,
|
||||
aspect_ratio=aspect_ratio,
|
||||
total_duration=effective_duration,
|
||||
)
|
||||
if target_index is not None and base_draft:
|
||||
return _merge_single_segment(
|
||||
base_draft,
|
||||
candidate,
|
||||
target_index,
|
||||
aspect_ratio,
|
||||
effective_duration,
|
||||
)
|
||||
return candidate
|
||||
|
||||
routed_stream = stream_routed_text_request(
|
||||
task=task,
|
||||
primary_model=model_config,
|
||||
messages=messages,
|
||||
streaming=True,
|
||||
structured_output=True,
|
||||
business_operation="script_generate",
|
||||
temperature=0.85,
|
||||
):
|
||||
validate_text=validate_script_text,
|
||||
request_summary={
|
||||
"mode": mode,
|
||||
"target_index": target_index,
|
||||
"base_version_id": str(base_version_id or ""),
|
||||
"aspect_ratio": aspect_ratio,
|
||||
"total_duration": effective_duration,
|
||||
},
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
ev = next(routed_stream)
|
||||
except StopIteration as completed:
|
||||
routed = completed.value
|
||||
break
|
||||
et = ev.get("type")
|
||||
if et == "reasoning":
|
||||
# 思考流:推理模型在出 JSON 前会先想很久,把思考逐字下发给前端(像对话一样可见),
|
||||
@@ -668,12 +704,8 @@ def stream_script_agent(
|
||||
if piece.strip():
|
||||
yield _sse({"type": "delta", "text": piece})
|
||||
elif et == "done":
|
||||
break
|
||||
raw = "".join(full)
|
||||
draft = normalize_draft(raw, aspect_ratio=aspect_ratio, total_duration=effective_duration)
|
||||
if target_index is not None and base_draft:
|
||||
# 精准改一镜:只采用新稿的第 target_index 镜,其余镜强制保持基准稿原样
|
||||
draft = _merge_single_segment(base_draft, draft, target_index, aspect_ratio, effective_duration)
|
||||
continue
|
||||
raw, _provider_response, draft = routed.value
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_fail_task(task, reservation, str(exc))
|
||||
settled = True
|
||||
@@ -819,7 +851,7 @@ def regenerate_segment_via_agent(*, project, user, model_config: ModelConfig, se
|
||||
与 stream_script_agent 的 target_index 分支同源,但同步返回(不走 SSE)。计费 reserve→charge/release 闭环。"""
|
||||
from django.db import transaction
|
||||
|
||||
from apps.ai.services import build_provider, create_ai_task
|
||||
from apps.ai.services import create_ai_task, execute_routed_text_request
|
||||
from apps.billing.services.ledger import charge_reserved_credit
|
||||
|
||||
base_draft = _draft_from_version(segment.script_version) # 用 DB 行重建基准,别用可能 stale 的 content
|
||||
@@ -850,18 +882,40 @@ def regenerate_segment_via_agent(*, project, user, model_config: ModelConfig, se
|
||||
"endpoint": model_config.endpoint,
|
||||
"mode": "revise",
|
||||
"target_index": target_index,
|
||||
"model_routing_v1": True,
|
||||
},
|
||||
)
|
||||
reservation = task.credit_reservation
|
||||
# 每条真实尝试的平台成本由统一执行器累计;用户积分仍只结算这一条脚本任务。
|
||||
task.base_cost = Decimal("0")
|
||||
task.save(update_fields=["base_cost", "updated_at"])
|
||||
# 实际平台成本由每条 AIModelAttempt 累加;用户积分仍只结算这一条逻辑任务。
|
||||
task.base_cost = Decimal("0")
|
||||
task.save(update_fields=["base_cost", "updated_at"])
|
||||
try:
|
||||
task.status = AITask.Status.SUBMITTED
|
||||
task.submitted_at = timezone.now()
|
||||
task.save(update_fields=["status", "submitted_at", "updated_at"])
|
||||
provider = build_provider(model_config)
|
||||
response = provider.chat_completion(model=model_config.name, endpoint=model_config.endpoint, messages=messages)
|
||||
raw = provider.extract_text(response)
|
||||
draft = normalize_draft(raw, aspect_ratio=aspect_ratio, total_duration=total_duration)
|
||||
draft = _merge_single_segment(base_draft, draft, target_index, aspect_ratio, total_duration)
|
||||
def validate_segment_text(raw_text: str) -> dict:
|
||||
candidate = normalize_draft(raw_text, aspect_ratio=aspect_ratio, total_duration=total_duration)
|
||||
return _merge_single_segment(base_draft, candidate, target_index, aspect_ratio, total_duration)
|
||||
|
||||
routed = execute_routed_text_request(
|
||||
task=task,
|
||||
primary_model=model_config,
|
||||
messages=messages,
|
||||
streaming=False,
|
||||
structured_output=True,
|
||||
business_operation="script_generate",
|
||||
temperature=0.3,
|
||||
validate_text=validate_segment_text,
|
||||
request_summary={
|
||||
"mode": "revise",
|
||||
"target_index": target_index,
|
||||
"base_version_id": str(segment.script_version_id),
|
||||
},
|
||||
)
|
||||
raw, _response, draft = routed.value
|
||||
with transaction.atomic():
|
||||
task.status = AITask.Status.SUCCEEDED
|
||||
task.response_payload = {"raw": raw[:8000]}
|
||||
@@ -871,6 +925,6 @@ def regenerate_segment_via_agent(*, project, user, model_config: ModelConfig, se
|
||||
charge_reserved_credit(reservation=reservation, actual_amount=task.actual_cost)
|
||||
script = persist_script_draft(project=project, user=user, task=task, draft=draft, source="revise")
|
||||
return script
|
||||
except Exception:
|
||||
_fail_task(task, reservation, "单镜重跑失败")
|
||||
except Exception as exc:
|
||||
_fail_task(task, reservation, str(exc) or "单镜重跑失败")
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user