交易流水对账明细 | AI引擎计价规则
This commit is contained in:
@@ -8,6 +8,7 @@ from app.api.admin.private_portrait import router as private_portrait_router
|
||||
from app.api.admin.recharge_package import router as recharge_package_router
|
||||
from app.api.admin.menu_config import router as menu_config_router
|
||||
from app.api.admin.upload import router as admin_upload_router
|
||||
from app.api.admin.model_pricing import router as model_pricing_router
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(video_prompt_schema_config_router)
|
||||
@@ -18,3 +19,4 @@ router.include_router(private_portrait_router)
|
||||
router.include_router(recharge_package_router)
|
||||
router.include_router(menu_config_router)
|
||||
router.include_router(admin_upload_router)
|
||||
router.include_router(model_pricing_router)
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_admin_user, get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.model_pricing import (
|
||||
ModelPricingPreviewOut,
|
||||
ModelPricingPreviewRequest,
|
||||
ModelPricingRuleCreate,
|
||||
ModelPricingRuleListOut,
|
||||
ModelPricingRuleOut,
|
||||
ModelPricingRuleUpdate,
|
||||
)
|
||||
from app.services.model_pricing.calculator import PricingCalculationError, calculate_pricing
|
||||
from app.services.model_pricing.rule_service import (
|
||||
PricingRuleError,
|
||||
create_rule,
|
||||
disable_rule,
|
||||
get_rule_snapshot,
|
||||
list_rules,
|
||||
publish_rule,
|
||||
update_draft_rule,
|
||||
)
|
||||
from app.services.operation_log import log_operation
|
||||
from app.services.operation_log_service import log_model_pricing_event
|
||||
|
||||
|
||||
router = APIRouter(prefix="/admin/model-pricing", tags=["admin-model-pricing"])
|
||||
|
||||
|
||||
def _http_error(exc: Exception) -> HTTPException:
|
||||
return HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@router.get("/rules", response_model=ModelPricingRuleListOut)
|
||||
async def admin_list_model_pricing_rules(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=500),
|
||||
provider: str | None = Query(None),
|
||||
model_name: str | None = Query(None),
|
||||
model_category: str | None = Query(None),
|
||||
publish_status: str | None = Query(None),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
return await list_rules(
|
||||
db,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
provider=provider,
|
||||
model_name=model_name,
|
||||
model_category=model_category,
|
||||
publish_status=publish_status,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/rules/{rule_id}", response_model=ModelPricingRuleOut)
|
||||
async def admin_get_model_pricing_rule(
|
||||
rule_id: str,
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
snapshot = await get_rule_snapshot(db, rule_id)
|
||||
return {**snapshot, "referenced_count": 0}
|
||||
except PricingRuleError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/rules", response_model=ModelPricingRuleOut)
|
||||
async def admin_create_model_pricing_rule(
|
||||
req: ModelPricingRuleCreate,
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
admin_id = str(admin.id)
|
||||
admin_username = str(admin.username or "")
|
||||
try:
|
||||
snapshot = await create_rule(db, payload=req.model_dump(), operator_id=admin_id)
|
||||
await log_operation(
|
||||
db,
|
||||
admin_id,
|
||||
admin_username,
|
||||
f"创建模型计价草稿 {snapshot['model_name']}/{snapshot['version_code']}",
|
||||
"POST",
|
||||
"/admin/model-pricing/rules",
|
||||
detail=json.dumps(snapshot, ensure_ascii=False, default=str),
|
||||
)
|
||||
await db.commit()
|
||||
log_model_pricing_event(
|
||||
event_type="pricing_rule_validate",
|
||||
user_id=admin_id,
|
||||
pricing_rule_id=snapshot["id"],
|
||||
pricing_version=snapshot["version_code"],
|
||||
provider=snapshot["provider"],
|
||||
model_name=snapshot["model_name"],
|
||||
billing_mode=snapshot["billing_mode"],
|
||||
message="模型计价草稿创建成功",
|
||||
)
|
||||
return {**snapshot, "referenced_count": 0}
|
||||
except IntegrityError as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc
|
||||
except PricingRuleError as exc:
|
||||
await db.rollback()
|
||||
raise _http_error(exc) from exc
|
||||
|
||||
|
||||
@router.put("/rules/{rule_id}", response_model=ModelPricingRuleOut)
|
||||
async def admin_update_model_pricing_rule(
|
||||
rule_id: str,
|
||||
req: ModelPricingRuleUpdate,
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
admin_id = str(admin.id)
|
||||
admin_username = str(admin.username or "")
|
||||
try:
|
||||
snapshot = await update_draft_rule(
|
||||
db,
|
||||
rule_id=rule_id,
|
||||
payload=req.model_dump(exclude_unset=True),
|
||||
operator_id=admin_id,
|
||||
)
|
||||
await log_operation(
|
||||
db,
|
||||
admin_id,
|
||||
admin_username,
|
||||
f"更新模型计价草稿 {snapshot['model_name']}/{snapshot['version_code']}",
|
||||
"PUT",
|
||||
f"/admin/model-pricing/rules/{rule_id}",
|
||||
detail=json.dumps(snapshot, ensure_ascii=False, default=str),
|
||||
)
|
||||
await db.commit()
|
||||
return {**snapshot, "referenced_count": 0}
|
||||
except IntegrityError as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc
|
||||
except PricingRuleError as exc:
|
||||
await db.rollback()
|
||||
raise _http_error(exc) from exc
|
||||
|
||||
|
||||
@router.post("/rules/{rule_id}/publish", response_model=ModelPricingRuleOut)
|
||||
async def admin_publish_model_pricing_rule(
|
||||
rule_id: str,
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
admin_id = str(admin.id)
|
||||
admin_username = str(admin.username or "")
|
||||
try:
|
||||
snapshot = await publish_rule(db, rule_id=rule_id, operator_id=admin_id)
|
||||
await log_operation(
|
||||
db,
|
||||
admin_id,
|
||||
admin_username,
|
||||
f"发布模型计价版本 {snapshot['model_name']}/{snapshot['version_code']}",
|
||||
"POST",
|
||||
f"/admin/model-pricing/rules/{rule_id}/publish",
|
||||
detail=json.dumps(snapshot, ensure_ascii=False, default=str),
|
||||
)
|
||||
await db.commit()
|
||||
log_model_pricing_event(
|
||||
event_type="pricing_rule_publish",
|
||||
user_id=admin_id,
|
||||
pricing_rule_id=snapshot["id"],
|
||||
pricing_version=snapshot["version_code"],
|
||||
provider=snapshot["provider"],
|
||||
model_name=snapshot["model_name"],
|
||||
billing_mode=snapshot["billing_mode"],
|
||||
)
|
||||
return {**snapshot, "referenced_count": 0}
|
||||
except IntegrityError as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc
|
||||
except PricingRuleError as exc:
|
||||
await db.rollback()
|
||||
raise _http_error(exc) from exc
|
||||
|
||||
|
||||
@router.post("/rules/{rule_id}/disable", response_model=ModelPricingRuleOut)
|
||||
async def admin_disable_model_pricing_rule(
|
||||
rule_id: str,
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
admin_id = str(admin.id)
|
||||
admin_username = str(admin.username or "")
|
||||
try:
|
||||
snapshot = await disable_rule(db, rule_id=rule_id, operator_id=admin_id)
|
||||
await log_operation(
|
||||
db,
|
||||
admin_id,
|
||||
admin_username,
|
||||
f"停用模型计价版本 {snapshot['model_name']}/{snapshot['version_code']}",
|
||||
"POST",
|
||||
f"/admin/model-pricing/rules/{rule_id}/disable",
|
||||
detail=json.dumps(snapshot, ensure_ascii=False, default=str),
|
||||
)
|
||||
await db.commit()
|
||||
return {**snapshot, "referenced_count": 0}
|
||||
except IntegrityError as exc:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=409, detail="模型计价版本或生效区间发生并发冲突") from exc
|
||||
except PricingRuleError as exc:
|
||||
await db.rollback()
|
||||
raise _http_error(exc) from exc
|
||||
|
||||
|
||||
@router.post("/preview", response_model=ModelPricingPreviewOut)
|
||||
async def admin_preview_model_pricing(
|
||||
req: ModelPricingPreviewRequest,
|
||||
admin: User = Depends(get_admin_user),
|
||||
):
|
||||
try:
|
||||
result = calculate_pricing(
|
||||
billing_mode=req.billing_mode,
|
||||
calculator_version=req.calculator_version,
|
||||
rule_json=req.rule_json,
|
||||
usage=req.usage,
|
||||
currency=req.currency,
|
||||
)
|
||||
return {
|
||||
"amount": str(result.amount),
|
||||
"currency": result.currency,
|
||||
"is_estimated": result.is_estimated,
|
||||
"selected_rate": str(result.selected_rate) if result.selected_rate is not None else None,
|
||||
"usage_source": result.usage_source,
|
||||
"breakdown": result.breakdown,
|
||||
}
|
||||
except PricingCalculationError as exc:
|
||||
raise _http_error(exc) from exc
|
||||
@@ -37,6 +37,7 @@ from app.schemas.admin import (
|
||||
ResetPasswordRequest,
|
||||
UpdateFrontendUserKindRequest,
|
||||
OperationLogOut,
|
||||
AdminCreditRecordListOut,
|
||||
)
|
||||
from app.schemas.team import UpdateUserTeamRequest
|
||||
from app.schemas.industry import IndustryConfigCreate, IndustryConfigOut
|
||||
@@ -61,6 +62,7 @@ from app.services.generation_billing_service import (
|
||||
get_next_credit_attempt_no,
|
||||
)
|
||||
from app.services.generation_refund_service import mark_generation_record_failed_and_refund_once
|
||||
from app.services.generation_ai_service import _build_image_snapshot, _build_video_snapshot
|
||||
from app.utils.id_gen import generate_id
|
||||
from app.schemas.generation import GenerationType, ASPECT_RATIOS, RESOLUTIONS
|
||||
|
||||
@@ -481,7 +483,7 @@ async def admin_change_password(
|
||||
|
||||
# ── Credit Records ───────────────────────────────────────
|
||||
|
||||
@router.get("/credit-records")
|
||||
@router.get("/credit-records", response_model=AdminCreditRecordListOut)
|
||||
async def list_credit_records(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=1000),
|
||||
@@ -498,6 +500,12 @@ async def list_credit_records(
|
||||
source_module: str | None = Query(None),
|
||||
source_step_code: str | None = Query(None),
|
||||
billing_scene: str | None = Query(None),
|
||||
engine_provider: str | None = Query(None),
|
||||
engine_model_name: str | None = Query(None),
|
||||
pricing_version_code: str | None = Query(None),
|
||||
provider_cost_status: str | None = Query(None),
|
||||
provider_cost_is_estimated: bool | None = Query(None),
|
||||
has_attachment: bool | None = Query(None),
|
||||
start_date: str = Query(None),
|
||||
end_date: str = Query(None),
|
||||
admin: User = Depends(get_admin_user),
|
||||
@@ -520,6 +528,12 @@ async def list_credit_records(
|
||||
source_module=source_module,
|
||||
source_step_code=source_step_code,
|
||||
billing_scene=billing_scene,
|
||||
engine_provider=engine_provider,
|
||||
engine_model_name=engine_model_name,
|
||||
pricing_version_code=pricing_version_code,
|
||||
provider_cost_status=provider_cost_status,
|
||||
provider_cost_is_estimated=provider_cost_is_estimated,
|
||||
has_attachment=has_attachment,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
@@ -2046,6 +2060,9 @@ async def admin_generate_video(
|
||||
raise HTTPException(status_code=400, detail="不支持的分辨率")
|
||||
|
||||
duration = record.duration or 5
|
||||
from app.services.video_gen import get_active_engine, submit_video_task
|
||||
engine = await get_active_engine(db)
|
||||
engine_snapshot = _build_video_snapshot(engine, aspect_ratio, resolution, duration)
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
user_id=record.user_id,
|
||||
@@ -2053,14 +2070,21 @@ async def admin_generate_video(
|
||||
gen_type="video",
|
||||
duration=duration,
|
||||
resolution=resolution,
|
||||
aspect_ratio=aspect_ratio,
|
||||
fps=24,
|
||||
engine_id=engine.id,
|
||||
project_name=project_name,
|
||||
description_prefix="视频生成(管理后台)",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
media_references=record.media_references,
|
||||
)
|
||||
|
||||
record.aspect_ratio = aspect_ratio
|
||||
record.resolution = resolution
|
||||
record.engine_id = engine.id
|
||||
record.engine_snapshot_json = json.dumps(engine_snapshot, ensure_ascii=False, default=str)
|
||||
record.current_billing_attempt_no = attempt_no
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
@@ -2071,8 +2095,6 @@ async def admin_generate_video(
|
||||
await db.flush()
|
||||
|
||||
try:
|
||||
from app.services.video_gen import get_active_engine, submit_video_task
|
||||
engine = await get_active_engine(db)
|
||||
task_id = await submit_video_task(
|
||||
db,
|
||||
engine,
|
||||
@@ -2095,19 +2117,31 @@ async def admin_generate_video(
|
||||
|
||||
post_image_size = body.get("image_size", "")
|
||||
image_size = post_image_size or record.image_size or "2K"
|
||||
from app.services.image_gen import get_active_image_engine
|
||||
engine = await get_active_image_engine(db)
|
||||
image_proportion = record.image_proportion or "1:1"
|
||||
image_px = record.image_px or "2048x2048"
|
||||
engine_snapshot = _build_image_snapshot(engine, image_size, image_proportion, image_px)
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
user_id=record.user_id,
|
||||
record_id=record.id,
|
||||
gen_type="image",
|
||||
image_size=image_size,
|
||||
image_px=image_px,
|
||||
aspect_ratio=image_proportion,
|
||||
engine_id=engine.id,
|
||||
project_name=project_name,
|
||||
description_prefix="图片生成(管理后台)",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
media_references=record.media_references,
|
||||
)
|
||||
|
||||
record.image_size = image_size
|
||||
record.engine_id = engine.id
|
||||
record.engine_snapshot_json = json.dumps(engine_snapshot, ensure_ascii=False, default=str)
|
||||
record.current_billing_attempt_no = attempt_no
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
|
||||
@@ -49,6 +49,7 @@ from app.services.generation_billing_service import (
|
||||
)
|
||||
from app.services.generation_refund_service import mark_generation_record_failed_and_refund_once
|
||||
from app.services.media_token_usage_snapshot_service import sync_generation_record_media_token_snapshot
|
||||
from app.services.generation_ai_service import _build_image_snapshot, _build_video_snapshot
|
||||
from app.services.credit_record_meta_service import build_generation_record_prompt_meta
|
||||
from app.services.video_cover_service import async_create_video_cover_for_local_video
|
||||
from app.enums.audio_reference import (
|
||||
@@ -431,6 +432,9 @@ async def generate(
|
||||
raise HTTPException(status_code=400, detail="不支持的分辨率")
|
||||
|
||||
duration = record.duration or 5
|
||||
from app.services.video_gen import get_active_engine, submit_video_task
|
||||
engine = await get_active_engine(db)
|
||||
engine_snapshot = _build_video_snapshot(engine, req.aspect_ratio, req.resolution, duration)
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
@@ -438,14 +442,21 @@ async def generate(
|
||||
gen_type="video",
|
||||
duration=duration,
|
||||
resolution=req.resolution,
|
||||
aspect_ratio=req.aspect_ratio,
|
||||
fps=24,
|
||||
engine_id=engine.id,
|
||||
project_name=project_name,
|
||||
description_prefix=project_name+"-",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
media_references=record.media_references,
|
||||
)
|
||||
|
||||
record.aspect_ratio = req.aspect_ratio
|
||||
record.resolution = req.resolution
|
||||
record.engine_id = engine.id
|
||||
record.engine_snapshot_json = json.dumps(engine_snapshot, ensure_ascii=False, default=str)
|
||||
record.current_billing_attempt_no = attempt_no
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
@@ -456,11 +467,9 @@ async def generate(
|
||||
await db.flush()
|
||||
|
||||
try:
|
||||
from app.services.video_gen import get_active_engine, submit_video_task
|
||||
from app.services.error_codes import extract_error_message
|
||||
from app.services.video_queue import task_queue
|
||||
|
||||
engine = await get_active_engine(db)
|
||||
task_id = await submit_video_task(
|
||||
db,
|
||||
engine,
|
||||
@@ -480,19 +489,31 @@ async def generate(
|
||||
|
||||
elif record.gen_type == GenerationType.image:
|
||||
image_size = req.image_size or record.image_size or "2K"
|
||||
from app.services.image_gen import get_active_image_engine
|
||||
engine = await get_active_image_engine(db)
|
||||
image_proportion = record.image_proportion or "1:1"
|
||||
image_px = record.image_px or "2048x2048"
|
||||
engine_snapshot = _build_image_snapshot(engine, image_size, image_proportion, image_px)
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
record_id=record.id,
|
||||
gen_type="image",
|
||||
image_size=image_size,
|
||||
image_px=image_px,
|
||||
aspect_ratio=image_proportion,
|
||||
engine_id=engine.id,
|
||||
project_name=project_name,
|
||||
description_prefix=project_name+"-",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
media_references=record.media_references,
|
||||
)
|
||||
|
||||
record.image_size = image_size
|
||||
record.engine_id = engine.id
|
||||
record.engine_snapshot_json = json.dumps(engine_snapshot, ensure_ascii=False, default=str)
|
||||
record.current_billing_attempt_no = attempt_no
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
@@ -549,14 +570,46 @@ async def retry_generation(
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
owner_id=record.id,
|
||||
)
|
||||
media_billing = await charge_generation_media_for_record(
|
||||
if record.gen_type == GenerationType.video:
|
||||
from app.services.video_gen import get_active_engine
|
||||
engine = await get_active_engine(db)
|
||||
engine_snapshot = _build_video_snapshot(
|
||||
engine,
|
||||
record.aspect_ratio or "16:9",
|
||||
record.resolution or "720p",
|
||||
record.duration or 5,
|
||||
)
|
||||
else:
|
||||
from app.services.image_gen import get_active_image_engine
|
||||
engine = await get_active_image_engine(db)
|
||||
engine_snapshot = _build_image_snapshot(
|
||||
engine,
|
||||
record.image_size or "2K",
|
||||
record.image_proportion or "1:1",
|
||||
record.image_px or "2048x2048",
|
||||
)
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
record=record,
|
||||
user_id=record.user_id,
|
||||
record_id=record.id,
|
||||
gen_type=record.gen_type,
|
||||
image_size=record.image_size,
|
||||
image_px=record.image_px,
|
||||
aspect_ratio=record.aspect_ratio or record.image_proportion,
|
||||
duration=record.duration,
|
||||
resolution=record.resolution,
|
||||
fps=24 if record.gen_type == GenerationType.video else None,
|
||||
engine_id=engine.id,
|
||||
project_name=project_name,
|
||||
description_prefix="视频重试",
|
||||
description_prefix="生成重试-",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
media_references=record.media_references,
|
||||
)
|
||||
|
||||
record.engine_id = engine.id
|
||||
record.engine_snapshot_json = json.dumps(engine_snapshot, ensure_ascii=False, default=str)
|
||||
record.current_billing_attempt_no = attempt_no
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
record.video_url = None
|
||||
@@ -570,8 +623,7 @@ async def retry_generation(
|
||||
try:
|
||||
from app.services.video_queue import task_queue
|
||||
if record.gen_type == GenerationType.video:
|
||||
from app.services.video_gen import get_active_engine, submit_video_task, extract_error_message
|
||||
engine = await get_active_engine(db)
|
||||
from app.services.video_gen import submit_video_task, extract_error_message
|
||||
task_id = await submit_video_task(
|
||||
db,
|
||||
engine,
|
||||
|
||||
@@ -689,15 +689,20 @@ async def retry_task(
|
||||
record_id=task.id,
|
||||
gen_type=task.gen_type,
|
||||
image_size=task.image_size,
|
||||
image_px=task.image_px,
|
||||
aspect_ratio=task.aspect_ratio or task.image_proportion,
|
||||
duration=task.duration,
|
||||
resolution=task.resolution,
|
||||
fps=24 if task.gen_type == "video" else None,
|
||||
engine_id=task.engine_id,
|
||||
project_name="AI生成任务",
|
||||
description_prefix="Chat任务重试",
|
||||
owner_type=OWNER_CHAT_GENERATION_TASK,
|
||||
attempt_no=attempt_no,
|
||||
media_references=task.media_references,
|
||||
)
|
||||
|
||||
task.current_billing_attempt_no = attempt_no
|
||||
task.status = "generating"
|
||||
task.pipeline_stage = "queued"
|
||||
task.error_message = None
|
||||
|
||||
Reference in New Issue
Block a user