会员积分改版V1
This commit is contained in:
@@ -12,7 +12,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.config import settings
|
||||
from app.enums.common import ModuleEventTypeEnum, ModuleProjectStatusEnum, ModulePromptTypeEnum, ModuleStepStatusEnum
|
||||
from app.enums.credit_record import CreditRecordBillingScene, CreditRecordChargeKind, CreditRecordOwnerType
|
||||
from app.enums.llm_billing import LlmBillingConfigKey
|
||||
from app.enums.hot_opening_replicate import HotOpeningGenerationModeEnum, HotOpeningStepCodeEnum, ModuleCodeEnum
|
||||
from app.models.chat_generation_task import ChatGenerationTask
|
||||
from app.models.module_generation_project import ModuleGenerationProject
|
||||
@@ -55,13 +54,15 @@ from app.services.module_generation_log_service import log_module_error, log_mod
|
||||
from app.services.llm import optimize_prompt
|
||||
from app.services.llm_billing import (
|
||||
LlmBillingContext,
|
||||
ensure_hold_exists,
|
||||
ensure_pre_deducted,
|
||||
log_provider_failure,
|
||||
record_provider_exception,
|
||||
log_provider_start,
|
||||
log_provider_success,
|
||||
release_on_failure,
|
||||
settle_success,
|
||||
start_hold,
|
||||
refund_on_final_failure,
|
||||
finalize_llm_business_failure,
|
||||
mark_business_success,
|
||||
pre_deduct,
|
||||
)
|
||||
from app.services.module_generation_flow_base_service import (
|
||||
assert_project_has_no_active_chat_tasks as _base_assert_project_has_no_active_chat_tasks,
|
||||
@@ -892,7 +893,7 @@ async def submit_image_prompt_optimize(
|
||||
project.status = ModuleProjectStatusEnum.PROCESSING.value
|
||||
project.current_step_code = HotOpeningStepCodeEnum.IMAGE_PROMPT_OPTIMIZE.value
|
||||
project.error_message = None
|
||||
await start_hold(
|
||||
await pre_deduct(
|
||||
db,
|
||||
LlmBillingContext(
|
||||
user_id=str(project.user_id),
|
||||
@@ -906,9 +907,8 @@ async def submit_image_prompt_optimize(
|
||||
source_step_id=str(step.id),
|
||||
source_step_code=HotOpeningStepCodeEnum.IMAGE_PROMPT_OPTIMIZE.value,
|
||||
related_id=str(step.id),
|
||||
hold_config_key=LlmBillingConfigKey.HOLD_MODULE_IMAGE_PROMPT.value,
|
||||
description_prefix="爆款开头复刻图片AI提词优化",
|
||||
trace_id=f"llm-submit-hold:{step.id}",
|
||||
trace_id=f"llm-submit-pre-deduct:{step.id}",
|
||||
),
|
||||
)
|
||||
await log_module_event(db, project=project, step=step, event_type=ModuleEventTypeEnum.IMAGE_PROMPT_SUBMITTED.value, message="图片 AI 提词任务已提交")
|
||||
@@ -1005,14 +1005,13 @@ async def run_image_prompt_optimize(
|
||||
source_step_id=step_id_value,
|
||||
source_step_code=HotOpeningStepCodeEnum.IMAGE_PROMPT_OPTIMIZE.value,
|
||||
related_id=step_id_value,
|
||||
hold_config_key=LlmBillingConfigKey.HOLD_MODULE_IMAGE_PROMPT.value,
|
||||
description_prefix="爆款开头复刻图片AI提词优化",
|
||||
description_prefix="爆款开头复刻图片AI提词优化",
|
||||
trace_id=f"hot-opening-image-prompt:{step_id_value}",
|
||||
)
|
||||
hold_validation = await ensure_hold_exists(db, llm_billing_context)
|
||||
if not hold_validation.can_execute:
|
||||
pre_deduct_validation = await ensure_pre_deducted(db, llm_billing_context)
|
||||
if not pre_deduct_validation.can_execute:
|
||||
step.status = ModuleStepStatusEnum.FAILED.value
|
||||
step.error_message = f"LLM账务状态异常({hold_validation.state.value}),已终止任务"
|
||||
step.error_message = f"LLM账务状态异常({pre_deduct_validation.state.value}),已终止任务"
|
||||
step.completed_at = _now()
|
||||
project.status = ModuleProjectStatusEnum.FAILED.value
|
||||
project.error_message = step.error_message
|
||||
@@ -1023,7 +1022,7 @@ async def run_image_prompt_optimize(
|
||||
|
||||
provider_succeeded = False
|
||||
token_usage: dict[str, Any] = {}
|
||||
log_provider_start(llm_billing_context, detail={"prompt_type": "image"})
|
||||
await log_provider_start(db, llm_billing_context, detail={"prompt_type": "image"})
|
||||
try:
|
||||
request_log = {"original_prompt": prompt_text, "references": references, "gen_type": "image"}
|
||||
log_module_prompt_event(
|
||||
@@ -1048,9 +1047,11 @@ async def run_image_prompt_optimize(
|
||||
log_owner_type="module_generation_step",
|
||||
log_owner_id=step_id_value,
|
||||
generation_attempt_no=expected_step_version,
|
||||
fixed_model_config_id=llm_billing_context.model_config_id,
|
||||
fixed_model_snapshot=llm_billing_context.model_parameters_snapshot,
|
||||
)
|
||||
provider_succeeded = True
|
||||
log_provider_success(llm_billing_context, usage=token_usage)
|
||||
await log_provider_success(db, llm_billing_context, usage=token_usage)
|
||||
if execution_guard is not None:
|
||||
await execution_guard()
|
||||
project, step = await _reload_prompt_context_for_update(
|
||||
@@ -1064,16 +1065,10 @@ async def run_image_prompt_optimize(
|
||||
expected_version=expected_step_version,
|
||||
expected_input_json=expected_input_json,
|
||||
):
|
||||
# Provider 已成功,旧步骤即使失效也必须按真实 usage 结算,不能免费释放。
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-图片AI提词优化(失效结果结算)",
|
||||
)
|
||||
await db.commit()
|
||||
await log_provider_failure(db, llm_billing_context, error="当前步骤已失效,业务结果未采用")
|
||||
await finalize_llm_business_failure(llm_billing_context, error="当前步骤已失效,业务结果未采用")
|
||||
return None
|
||||
billing = await settle_success(
|
||||
billing = await mark_business_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
@@ -1121,21 +1116,19 @@ async def run_image_prompt_optimize(
|
||||
except DatabaseRowLockBusy:
|
||||
await db.rollback()
|
||||
if provider_succeeded:
|
||||
# Provider 已完成后不再重复调用模型;先按真实 usage 结算,本次结果因本地行锁冲突丢弃。
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-图片AI提词优化(行锁失败结算)",
|
||||
)
|
||||
await db.commit()
|
||||
await log_provider_failure(db, llm_billing_context, error="本地行锁冲突,业务结果未落库")
|
||||
await finalize_llm_business_failure(llm_billing_context, error="本地行锁冲突,业务结果未落库")
|
||||
return None
|
||||
# Provider 尚未成功才允许同一 attempt 做系统自动重试。
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
if not provider_succeeded:
|
||||
log_provider_failure(llm_billing_context, error=str(exc))
|
||||
if provider_succeeded:
|
||||
await log_provider_failure(db, llm_billing_context, error=str(exc))
|
||||
else:
|
||||
provider_succeeded, recovered_usage = await record_provider_exception(db, llm_billing_context, exc)
|
||||
if recovered_usage:
|
||||
token_usage = recovered_usage
|
||||
if execution_guard is not None:
|
||||
await execution_guard()
|
||||
project, step = await _reload_prompt_context_for_update(
|
||||
@@ -1150,16 +1143,7 @@ async def run_image_prompt_optimize(
|
||||
expected_input_json=expected_input_json,
|
||||
):
|
||||
await db.rollback()
|
||||
if provider_succeeded:
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-图片AI提词优化(异常失效结算)",
|
||||
)
|
||||
else:
|
||||
await release_on_failure(db, llm_billing_context, error="当前步骤已失效,释放LLM预扣积分")
|
||||
await db.commit()
|
||||
await finalize_llm_business_failure(llm_billing_context, error="当前步骤已失效,业务最终失败")
|
||||
return None
|
||||
step.status = ModuleStepStatusEnum.FAILED.value
|
||||
step.error_message = str(exc) if str(exc) else type(exc).__name__
|
||||
@@ -1178,16 +1162,8 @@ async def run_image_prompt_optimize(
|
||||
)
|
||||
_log_project_error(project=project, step=step, event_type="IMAGE_PROMPT_FAILED", message=project.error_message, exc=exc)
|
||||
await log_module_event(db, project=project, step=step, event_type=ModuleEventTypeEnum.IMAGE_PROMPT_FAILED.value, message=project.error_message)
|
||||
if provider_succeeded:
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-图片AI提词优化(本地失败结算)",
|
||||
)
|
||||
else:
|
||||
await release_on_failure(db, llm_billing_context, error=str(exc))
|
||||
await db.commit()
|
||||
await finalize_llm_business_failure(llm_billing_context, error=str(exc))
|
||||
return step
|
||||
|
||||
|
||||
@@ -1351,7 +1327,7 @@ async def submit_video_prompt_optimize(
|
||||
project.status = ModuleProjectStatusEnum.PROCESSING.value
|
||||
project.current_step_code = HotOpeningStepCodeEnum.VIDEO_PROMPT_OPTIMIZE.value
|
||||
project.error_message = None
|
||||
await start_hold(
|
||||
await pre_deduct(
|
||||
db,
|
||||
LlmBillingContext(
|
||||
user_id=str(project.user_id),
|
||||
@@ -1365,9 +1341,8 @@ async def submit_video_prompt_optimize(
|
||||
source_step_id=str(step.id),
|
||||
source_step_code=HotOpeningStepCodeEnum.VIDEO_PROMPT_OPTIMIZE.value,
|
||||
related_id=str(step.id),
|
||||
hold_config_key=LlmBillingConfigKey.HOLD_MODULE_VIDEO_PROMPT.value,
|
||||
description_prefix="爆款开头复刻视频AI提词优化",
|
||||
trace_id=f"llm-submit-hold:{step.id}",
|
||||
trace_id=f"llm-submit-pre-deduct:{step.id}",
|
||||
),
|
||||
)
|
||||
await log_module_event(db, project=project, step=step, event_type=ModuleEventTypeEnum.VIDEO_PROMPT_SUBMITTED.value, message="视频 AI 提词任务已提交")
|
||||
@@ -1462,14 +1437,13 @@ async def run_video_prompt_optimize(
|
||||
source_step_id=step_id_value,
|
||||
source_step_code=HotOpeningStepCodeEnum.VIDEO_PROMPT_OPTIMIZE.value,
|
||||
related_id=step_id_value,
|
||||
hold_config_key=LlmBillingConfigKey.HOLD_MODULE_VIDEO_PROMPT.value,
|
||||
description_prefix="爆款开头复刻视频AI提词优化",
|
||||
description_prefix="爆款开头复刻视频AI提词优化",
|
||||
trace_id=f"hot-opening-video-prompt:{step_id_value}",
|
||||
)
|
||||
hold_validation = await ensure_hold_exists(db, llm_billing_context)
|
||||
if not hold_validation.can_execute:
|
||||
pre_deduct_validation = await ensure_pre_deducted(db, llm_billing_context)
|
||||
if not pre_deduct_validation.can_execute:
|
||||
step.status = ModuleStepStatusEnum.FAILED.value
|
||||
step.error_message = f"LLM账务状态异常({hold_validation.state.value}),已终止任务"
|
||||
step.error_message = f"LLM账务状态异常({pre_deduct_validation.state.value}),已终止任务"
|
||||
step.completed_at = _now()
|
||||
project.status = ModuleProjectStatusEnum.FAILED.value
|
||||
project.error_message = step.error_message
|
||||
@@ -1480,7 +1454,7 @@ async def run_video_prompt_optimize(
|
||||
|
||||
provider_succeeded = False
|
||||
token_usage: dict[str, Any] = {}
|
||||
log_provider_start(llm_billing_context, detail={"prompt_type": "video"})
|
||||
await log_provider_start(db, llm_billing_context, detail={"prompt_type": "video"})
|
||||
try:
|
||||
request_log = {
|
||||
"source_project_name": material.get("source_project_name") or "无",
|
||||
@@ -1517,9 +1491,11 @@ async def run_video_prompt_optimize(
|
||||
project_id=project_id_value,
|
||||
step_id=step_id_value,
|
||||
trace_id=f"hot-video-prompt:{step_id_value}",
|
||||
fixed_model_config_id=llm_billing_context.model_config_id,
|
||||
fixed_model_snapshot=llm_billing_context.model_parameters_snapshot,
|
||||
)
|
||||
provider_succeeded = True
|
||||
log_provider_success(llm_billing_context, usage=token_usage)
|
||||
await log_provider_success(db, llm_billing_context, usage=token_usage)
|
||||
if execution_guard is not None:
|
||||
await execution_guard()
|
||||
project, step = await _reload_prompt_context_for_update(
|
||||
@@ -1533,15 +1509,10 @@ async def run_video_prompt_optimize(
|
||||
expected_version=expected_step_version,
|
||||
expected_input_json=expected_input_json,
|
||||
):
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-视频AI提词优化(失效结果结算)",
|
||||
)
|
||||
await db.commit()
|
||||
await log_provider_failure(db, llm_billing_context, error="当前步骤已失效,业务结果未采用")
|
||||
await finalize_llm_business_failure(llm_billing_context, error="当前步骤已失效,业务结果未采用")
|
||||
return None
|
||||
billing = await settle_success(
|
||||
billing = await mark_business_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
@@ -1592,21 +1563,19 @@ async def run_video_prompt_optimize(
|
||||
except DatabaseRowLockBusy:
|
||||
await db.rollback()
|
||||
if provider_succeeded:
|
||||
# Provider 已完成后不再重复调用模型;先按真实 usage 结算,本次结果因本地行锁冲突丢弃。
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-视频AI提词优化(行锁失败结算)",
|
||||
)
|
||||
await db.commit()
|
||||
await log_provider_failure(db, llm_billing_context, error="本地行锁冲突,业务结果未落库")
|
||||
await finalize_llm_business_failure(llm_billing_context, error="本地行锁冲突,业务结果未落库")
|
||||
return None
|
||||
# Provider 尚未成功才允许同一 attempt 做系统自动重试。
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
if not provider_succeeded:
|
||||
log_provider_failure(llm_billing_context, error=str(exc))
|
||||
if provider_succeeded:
|
||||
await log_provider_failure(db, llm_billing_context, error=str(exc))
|
||||
else:
|
||||
provider_succeeded, recovered_usage = await record_provider_exception(db, llm_billing_context, exc)
|
||||
if recovered_usage:
|
||||
token_usage = recovered_usage
|
||||
if execution_guard is not None:
|
||||
await execution_guard()
|
||||
project, step = await _reload_prompt_context_for_update(
|
||||
@@ -1621,16 +1590,7 @@ async def run_video_prompt_optimize(
|
||||
expected_input_json=expected_input_json,
|
||||
):
|
||||
await db.rollback()
|
||||
if provider_succeeded:
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-视频AI提词优化(异常失效结算)",
|
||||
)
|
||||
else:
|
||||
await release_on_failure(db, llm_billing_context, error="当前步骤已失效,释放LLM预扣积分")
|
||||
await db.commit()
|
||||
await finalize_llm_business_failure(llm_billing_context, error="当前步骤已失效,业务最终失败")
|
||||
return None
|
||||
step.status = ModuleStepStatusEnum.FAILED.value
|
||||
step.error_message = str(exc) if str(exc) else type(exc).__name__
|
||||
@@ -1649,16 +1609,8 @@ async def run_video_prompt_optimize(
|
||||
)
|
||||
_log_project_error(project=project, step=step, event_type="VIDEO_PROMPT_FAILED", message=project.error_message, exc=exc)
|
||||
await log_module_event(db, project=project, step=step, event_type=ModuleEventTypeEnum.VIDEO_PROMPT_FAILED.value, message=project.error_message)
|
||||
if provider_succeeded:
|
||||
await settle_success(
|
||||
db,
|
||||
llm_billing_context,
|
||||
usage=token_usage,
|
||||
description="爆款开头复刻-视频AI提词优化(本地失败结算)",
|
||||
)
|
||||
else:
|
||||
await release_on_failure(db, llm_billing_context, error=str(exc))
|
||||
await db.commit()
|
||||
await finalize_llm_business_failure(llm_billing_context, error=str(exc))
|
||||
return step
|
||||
|
||||
|
||||
@@ -1946,7 +1898,7 @@ async def mark_hot_opening_step_dispatch_failed(
|
||||
project.status = ModuleProjectStatusEnum.FAILED.value
|
||||
project.error_message = error_message
|
||||
if step.step_code in (HotOpeningStepCodeEnum.IMAGE_PROMPT_OPTIMIZE.value, HotOpeningStepCodeEnum.VIDEO_PROMPT_OPTIMIZE.value):
|
||||
await release_on_failure(
|
||||
await refund_on_final_failure(
|
||||
db,
|
||||
LlmBillingContext(
|
||||
user_id=str(project.user_id),
|
||||
@@ -1964,11 +1916,6 @@ async def mark_hot_opening_step_dispatch_failed(
|
||||
source_step_id=str(step.id),
|
||||
source_step_code=str(step.step_code),
|
||||
related_id=str(step.id),
|
||||
hold_config_key=(
|
||||
LlmBillingConfigKey.HOLD_MODULE_IMAGE_PROMPT.value
|
||||
if step.step_code == HotOpeningStepCodeEnum.IMAGE_PROMPT_OPTIMIZE.value
|
||||
else LlmBillingConfigKey.HOLD_MODULE_VIDEO_PROMPT.value
|
||||
),
|
||||
description_prefix=(
|
||||
"爆款开头复刻图片AI提词优化"
|
||||
if step.step_code == HotOpeningStepCodeEnum.IMAGE_PROMPT_OPTIMIZE.value
|
||||
|
||||
Reference in New Issue
Block a user