优化ComfyUI Worker等
* Merge remote-tracking branch 'origin/feature/temp-comfy-cluster' into feature/temp-comfy-cluster * FIX 修复缓存校验路径问题 * Merge branch refs/heads/feature/modal-cluster into refs/heads/feature/temp-comfy-cluster * Merge remote-tracking branch 'origin/feature/temp-comfy-cluster' into feature/temp-comfy-cluster * FIX webhook直接返回ComfyUI结果 * Merge remote-tracking branch 'origin/feature/temp-comfy-cluster' into feature/temp-comfy-cluster * 校验媒体文件是否存在缓存中 * Merge remote-tracking branch 'origin/feature/temp-comfy-cluster' into feature/temp-comfy-cluster * FIX 修改不规范的返回值 * 校验媒体文件是否存在缓存中 * PERF 去除多余的运行完成后重启ComfyUI操作 * 改进 Sentry 追踪系统:为每个关键步骤的 span 添加状态标记 * PERF 分离comfyui router * Merge branch 'feature/modal-cluster' into feature/temp-comfy-cluster * Merge branch 'feature/modal-cluster' into feature/temp-comfy-cluster * Merge remote-tracking branch 'origin/feature/temp-comfy-cluster' into feature/temp-comfy-cluster * 修改sentry_sdk的弃用方法 * Merge remote-tracking branch 'origin/feature/temp-comfy-cluster' into feature/temp-comfy-cluster * PERF 去除上传配置文件 * PERF 去除贝蒂上传配置文件 --------- Merge request URL: https://g-ldyi2063.coding.net/p/dev/d/modalDeploy/git/merge/4739 Co-authored-by: 康宇佳,gexianmeng
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
.pypirc
|
.pypirc
|
||||||
.env
|
.env
|
||||||
.idea
|
.runtime.env
|
||||||
|
.idea
|
||||||
|
.venv
|
||||||
|
test*
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
MODAL_ENVIRONMENT=dev
|
MODAL_ENVIRONMENT=dev
|
||||||
modal_app_name=bowong-ai-video
|
modal_app_name=cluster-test
|
||||||
S3_mount_dir=/mntS3
|
S3_mount_dir=/mntS3
|
||||||
S3_bucket_name=modal-media-cache
|
S3_bucket_name=modal-media-cache
|
||||||
S3_region=ap-northeast-2
|
S3_region=ap-northeast-2
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
import modal
|
from fastapi import FastAPI, Request
|
||||||
from typing import Annotated
|
|
||||||
from fastapi import FastAPI, Response, Depends, Header, Request
|
|
||||||
from starlette import status
|
|
||||||
from scalar_fastapi import get_scalar_api_reference
|
from scalar_fastapi import get_scalar_api_reference
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from sentry_sdk.integrations.loguru import LoguruIntegration, LoggingLevels
|
from sentry_sdk.integrations.loguru import LoguruIntegration, LoggingLevels
|
||||||
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from .middleware.authorization import verify_token
|
|
||||||
from .utils.KVCache import KVCache
|
from .utils.KVCache import KVCache
|
||||||
from .utils.ModalUtils import ModalUtils
|
from .router import ffmpeg, cache, comfyui
|
||||||
from .models.media_model import MediaSource
|
|
||||||
from .models.web_model import (SentryTransactionInfo,
|
|
||||||
SentryTransactionHeader,
|
|
||||||
ModalTaskResponse,
|
|
||||||
ComfyTaskRequest,
|
|
||||||
ComfyTaskStatusResponse
|
|
||||||
)
|
|
||||||
from .router import ffmpeg, cache
|
|
||||||
from .config import WorkerConfig
|
from .config import WorkerConfig
|
||||||
|
|
||||||
config = WorkerConfig()
|
config = WorkerConfig()
|
||||||
@@ -93,100 +81,4 @@ async def scalar():
|
|||||||
|
|
||||||
web_app.include_router(ffmpeg.router)
|
web_app.include_router(ffmpeg.router)
|
||||||
web_app.include_router(cache.router)
|
web_app.include_router(cache.router)
|
||||||
|
web_app.include_router(comfyui.router)
|
||||||
|
|
||||||
@web_app.post("/comfyui/v1", tags=["ComfyUI"], summary="发起ComfyUIBase任务", description="发起ComfyUIBase任务",
|
|
||||||
responses={
|
|
||||||
status.HTTP_200_OK: {
|
|
||||||
"description": "",
|
|
||||||
"headers": sentry_header_schema
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies=[Depends(verify_token)]
|
|
||||||
)
|
|
||||||
async def comfyui_v1(item: ComfyTaskRequest, headers: Annotated[SentryTransactionHeader, Header()]):
|
|
||||||
sentry_trace = None
|
|
||||||
if headers.x_trace_id and headers.x_baggage:
|
|
||||||
sentry_trace = SentryTransactionInfo(x_trace_id=headers.x_trace_id, x_baggage=headers.x_baggage)
|
|
||||||
cls = modal.Cls.from_name(config.modal_app_name, "ComfyUI", environment_name=config.modal_environment)
|
|
||||||
fn_call = cls().api.spawn(item.video_path.path, item.start_time, item.filename_prefix, item.tts_text1,
|
|
||||||
item.tts_text2, item.tts_text3, item.tts_text4, item.anchor_id, item.speed, sentry_trace,
|
|
||||||
item.webhook)
|
|
||||||
return ModalTaskResponse(success=True, taskId=fn_call.object_id)
|
|
||||||
|
|
||||||
|
|
||||||
@web_app.get("/comfyui/v1/{task_id}", tags=["ComfyUI"], summary="查询ComfyUIBase任务",
|
|
||||||
description="查询ComfyUIBase任务",
|
|
||||||
responses={
|
|
||||||
status.HTTP_200_OK: {
|
|
||||||
"description": "",
|
|
||||||
"headers": sentry_header_schema
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies=[Depends(verify_token)]
|
|
||||||
)
|
|
||||||
async def comfyui_v1_status(task_id: str, response: Response):
|
|
||||||
task_status, code, reason, result, transaction = await ModalUtils.get_modal_task_status(task_id)
|
|
||||||
media = None
|
|
||||||
if transaction:
|
|
||||||
response.headers["x-trace-id"] = transaction.x_trace_id
|
|
||||||
response.headers["x-baggage"] = transaction.x_baggage
|
|
||||||
if task_status == "success":
|
|
||||||
task_status = result["status"]
|
|
||||||
if task_status != "success":
|
|
||||||
reason = result["msg"]
|
|
||||||
else:
|
|
||||||
media = MediaSource.from_str("s3://" + "/".join(
|
|
||||||
[config.S3_region, config.S3_bucket_name, config.comfyui_s3_output, result["file_name"]]))
|
|
||||||
return ComfyTaskStatusResponse(taskId=task_id, status=task_status, code=code,
|
|
||||||
error=reason, result=media.urn if media else None)
|
|
||||||
|
|
||||||
|
|
||||||
@web_app.post("/comfyui/v2", tags=["ComfyUI"], summary="发起ComfyUILatentSync1.5任务",
|
|
||||||
description="发起ComfyUILatentSync1.5任务",
|
|
||||||
responses={
|
|
||||||
status.HTTP_200_OK: {
|
|
||||||
"description": "",
|
|
||||||
"headers": sentry_header_schema
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies=[Depends(verify_token)]
|
|
||||||
)
|
|
||||||
async def comfyui_v2(item: ComfyTaskRequest, headers: Annotated[SentryTransactionHeader, Header()]):
|
|
||||||
sentry_trace = None
|
|
||||||
if headers.x_trace_id and headers.x_baggage:
|
|
||||||
sentry_trace = SentryTransactionInfo(x_trace_id=headers.x_trace_id, x_baggage=headers.x_baggage)
|
|
||||||
cls = modal.Cls.from_name(config.modal_app_name, "ComfyUILatentSync15", environment_name=config.modal_environment)
|
|
||||||
fn_call = cls().api.spawn(item.video_path.path, item.start_time, item.filename_prefix, item.tts_text1,
|
|
||||||
item.tts_text2, item.tts_text3, item.tts_text4, item.anchor_id, item.speed, sentry_trace,
|
|
||||||
item.webhook)
|
|
||||||
return ModalTaskResponse(success=True, taskId=fn_call.object_id)
|
|
||||||
|
|
||||||
|
|
||||||
@web_app.get("/comfyui/v2/{task_id}", tags=["ComfyUI"], summary="查询ComfyUILatentSync1.5任务",
|
|
||||||
description="查询ComfyUILatentSync1.5任务",
|
|
||||||
responses={
|
|
||||||
status.HTTP_200_OK: {
|
|
||||||
"description": "",
|
|
||||||
"headers": sentry_header_schema
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dependencies=[Depends(verify_token)]
|
|
||||||
)
|
|
||||||
async def comfyui_v2_status(task_id: str, response: Response):
|
|
||||||
task_status, code, reason, result, transaction = await ModalUtils.get_modal_task_status(task_id)
|
|
||||||
media = None
|
|
||||||
|
|
||||||
if transaction:
|
|
||||||
response.headers["x-trace-id"] = transaction.x_trace_id
|
|
||||||
response.headers["x-baggage"] = transaction.x_baggage
|
|
||||||
if task_status == "success":
|
|
||||||
task_status = result["status"]
|
|
||||||
if task_status != "success":
|
|
||||||
reason = result["msg"]
|
|
||||||
else:
|
|
||||||
media = MediaSource.from_str("s3://" + "/".join(
|
|
||||||
[config.S3_region, config.S3_bucket_name, config.comfyui_s3_output, result["file_name"]]))
|
|
||||||
return ComfyTaskStatusResponse(taskId=task_id, status=task_status, code=code,
|
|
||||||
error=reason,
|
|
||||||
result=media.urn if media else "")
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
@@ -54,15 +55,33 @@ class MediaSource(BaseModel):
|
|||||||
endpoint=parsed_url.netloc, # domain of http url
|
endpoint=parsed_url.netloc, # domain of http url
|
||||||
bucket=None,
|
bucket=None,
|
||||||
urn=media_url)
|
urn=media_url)
|
||||||
elif media_url.startswith('s3://'): # s3://{endpoint}/{bucket}/{url}
|
elif media_url.startswith('s3://'):
|
||||||
|
|
||||||
|
pattern = r"^s3://[a-z]{2}-[a-z]+-\d/.*$"
|
||||||
|
if not re.match(pattern, media_url):
|
||||||
|
media_url = media_url.replace("s3://", f"s3://{s3_region}/")
|
||||||
|
# s3://{endpoint}/{bucket}/{url}
|
||||||
paths = media_url[5:].split('/')
|
paths = media_url[5:].split('/')
|
||||||
|
|
||||||
|
|
||||||
if len(paths) < 3:
|
if len(paths) < 3:
|
||||||
raise ValidationError("URN-s3 格式错误")
|
raise ValidationError("URN-s3 格式错误")
|
||||||
return MediaSource(path='/'.join(paths[2:]),
|
|
||||||
|
media_source = MediaSource(path='/'.join(paths[2:]),
|
||||||
protocol=MediaProtocol.s3,
|
protocol=MediaProtocol.s3,
|
||||||
endpoint=paths[0],
|
endpoint=paths[0],
|
||||||
bucket=paths[1],
|
bucket=paths[1],
|
||||||
urn=media_url)
|
urn=media_url)
|
||||||
|
|
||||||
|
s3_mount_path = config.S3_mount_dir
|
||||||
|
cache_path = os.path.join(s3_mount_path, media_source.cache_filepath)
|
||||||
|
|
||||||
|
# 校验媒体文件是否存在缓存中
|
||||||
|
if not os.path.exists(cache_path):
|
||||||
|
raise ValueError(f"媒体文件 {media_source.cache_filepath} 不存在于缓存中")
|
||||||
|
|
||||||
|
return media_source
|
||||||
|
|
||||||
elif media_url.startswith('vod://'): # vod://{endpoint}/{subAppId}/{fileId}
|
elif media_url.startswith('vod://'): # vod://{endpoint}/{subAppId}/{fileId}
|
||||||
paths = media_url[6:].split('/')
|
paths = media_url[6:].split('/')
|
||||||
if len(paths) < 3:
|
if len(paths) < 3:
|
||||||
@@ -124,6 +143,7 @@ class MediaSource(BaseModel):
|
|||||||
case _:
|
case _:
|
||||||
return f"{self.protocol.value}/{self.endpoint}/{self.bucket}/{self.path}"
|
return f"{self.protocol.value}/{self.endpoint}/{self.bucket}/{self.path}"
|
||||||
|
|
||||||
|
|
||||||
@computed_field(description="文件后缀名")
|
@computed_field(description="文件后缀名")
|
||||||
@cached_property
|
@cached_property
|
||||||
def file_extension(self) -> Optional[str]:
|
def file_extension(self) -> Optional[str]:
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ class ComfyTaskStatusResponse(BaseModel):
|
|||||||
|
|
||||||
class ComfyTaskRequest(BaseFFMPEGTaskRequest):
|
class ComfyTaskRequest(BaseFFMPEGTaskRequest):
|
||||||
video_path: MediaSource = Field(
|
video_path: MediaSource = Field(
|
||||||
default=MediaSource.from_str("s3://modal-media-cache/comfyui-input/markers/1016.mp4"), description="视频源")
|
default=None, description="视频源")
|
||||||
start_time: str = Field(default="00:00:01.600", description="开始时间")
|
start_time: str = Field(default="00:00:01.600", description="开始时间")
|
||||||
filename_prefix: str = Field(default=str(uuid.uuid4()), description="生成文件名前缀")
|
filename_prefix: str = Field(default=str(uuid.uuid4()), description="生成文件名前缀")
|
||||||
tts_text1: str = Field(default="好好看,这是我们家专门为女生定制的背心,", description="tts文本1")
|
tts_text1: str = Field(default="好好看,这是我们家专门为女生定制的背心,", description="tts文本1")
|
||||||
|
|||||||
127
src/BowongModalFunctions/router/comfyui.py
Normal file
127
src/BowongModalFunctions/router/comfyui.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
import modal
|
||||||
|
from fastapi import APIRouter, Depends, Header
|
||||||
|
from fastapi.responses import Response
|
||||||
|
from starlette import status
|
||||||
|
|
||||||
|
from ..config import WorkerConfig
|
||||||
|
from ..middleware.authorization import verify_token
|
||||||
|
from ..models.media_model import MediaSource
|
||||||
|
from ..utils.ModalUtils import ModalUtils
|
||||||
|
from ..models.web_model import (SentryTransactionHeader,
|
||||||
|
ModalTaskResponse, SentryTransactionInfo,
|
||||||
|
ComfyTaskRequest, ComfyTaskStatusResponse)
|
||||||
|
|
||||||
|
config = WorkerConfig()
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/comfyui")
|
||||||
|
|
||||||
|
sentry_header_schema = {
|
||||||
|
"x-trace-id": {
|
||||||
|
"description": "Sentry Transaction ID",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-baggage": {
|
||||||
|
"description": "Sentry Transaction baggage",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@router.post("/v1", tags=["ComfyUI"], summary="发起ComfyUIBase任务", description="发起ComfyUIBase任务",
|
||||||
|
responses={
|
||||||
|
status.HTTP_200_OK: {
|
||||||
|
"description": "",
|
||||||
|
"headers": sentry_header_schema
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies=[Depends(verify_token)]
|
||||||
|
)
|
||||||
|
async def comfyui_v1(item: ComfyTaskRequest, headers: Annotated[SentryTransactionHeader, Header()]):
|
||||||
|
sentry_trace = None
|
||||||
|
if headers.x_trace_id and headers.x_baggage:
|
||||||
|
sentry_trace = SentryTransactionInfo(x_trace_id=headers.x_trace_id, x_baggage=headers.x_baggage)
|
||||||
|
cls = modal.Cls.from_name(config.modal_app_name, "ComfyUI", environment_name=config.modal_environment)
|
||||||
|
fn_call = cls().api.spawn(item.video_path.path.replace(config.comfyui_s3_input+"/",""), item.start_time, item.filename_prefix, item.tts_text1,
|
||||||
|
item.tts_text2, item.tts_text3, item.tts_text4, item.anchor_id, item.speed, sentry_trace,
|
||||||
|
item.webhook)
|
||||||
|
return ModalTaskResponse(success=True, taskId=fn_call.object_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/v1/{task_id}", tags=["ComfyUI"], summary="查询ComfyUIBase任务",
|
||||||
|
description="查询ComfyUIBase任务",
|
||||||
|
responses={
|
||||||
|
status.HTTP_200_OK: {
|
||||||
|
"description": "",
|
||||||
|
"headers": sentry_header_schema
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies=[Depends(verify_token)]
|
||||||
|
)
|
||||||
|
async def comfyui_v1_status(task_id: str, response: Response):
|
||||||
|
task_status, code, reason, result, transaction = await ModalUtils.get_modal_task_status(task_id)
|
||||||
|
media = None
|
||||||
|
if transaction:
|
||||||
|
response.headers["x-trace-id"] = transaction.x_trace_id
|
||||||
|
response.headers["x-baggage"] = transaction.x_baggage
|
||||||
|
if task_status == "success":
|
||||||
|
task_status = result["status"]
|
||||||
|
if task_status != "success":
|
||||||
|
reason = result["msg"]
|
||||||
|
else:
|
||||||
|
media = MediaSource.from_str(result["file_name"])
|
||||||
|
return ComfyTaskStatusResponse(taskId=task_id, status=task_status, code=code,
|
||||||
|
error=reason, result=media.urn if media else None)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/v2", tags=["ComfyUI"], summary="发起ComfyUILatentSync1.5任务",
|
||||||
|
description="发起ComfyUILatentSync1.5任务",
|
||||||
|
responses={
|
||||||
|
status.HTTP_200_OK: {
|
||||||
|
"description": "",
|
||||||
|
"headers": sentry_header_schema
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies=[Depends(verify_token)]
|
||||||
|
)
|
||||||
|
async def comfyui_v2(item: ComfyTaskRequest, headers: Annotated[SentryTransactionHeader, Header()]):
|
||||||
|
sentry_trace = None
|
||||||
|
if headers.x_trace_id and headers.x_baggage:
|
||||||
|
sentry_trace = SentryTransactionInfo(x_trace_id=headers.x_trace_id, x_baggage=headers.x_baggage)
|
||||||
|
cls = modal.Cls.from_name(config.modal_app_name, "ComfyUILatentSync15", environment_name=config.modal_environment)
|
||||||
|
fn_call = cls().api.spawn(item.video_path.path.replace(config.comfyui_s3_input+"/",""), item.start_time, item.filename_prefix, item.tts_text1,
|
||||||
|
item.tts_text2, item.tts_text3, item.tts_text4, item.anchor_id, item.speed, sentry_trace,
|
||||||
|
item.webhook)
|
||||||
|
return ModalTaskResponse(success=True, taskId=fn_call.object_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/v2/{task_id}", tags=["ComfyUI"], summary="查询ComfyUILatentSync1.5任务",
|
||||||
|
description="查询ComfyUILatentSync1.5任务",
|
||||||
|
responses={
|
||||||
|
status.HTTP_200_OK: {
|
||||||
|
"description": "",
|
||||||
|
"headers": sentry_header_schema
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies=[Depends(verify_token)]
|
||||||
|
)
|
||||||
|
async def comfyui_v2_status(task_id: str, response: Response):
|
||||||
|
task_status, code, reason, result, transaction = await ModalUtils.get_modal_task_status(task_id)
|
||||||
|
media = None
|
||||||
|
|
||||||
|
if transaction:
|
||||||
|
response.headers["x-trace-id"] = transaction.x_trace_id
|
||||||
|
response.headers["x-baggage"] = transaction.x_baggage
|
||||||
|
if task_status == "success":
|
||||||
|
task_status = result["status"]
|
||||||
|
if task_status != "success":
|
||||||
|
reason = result["msg"]
|
||||||
|
else:
|
||||||
|
media = MediaSource.from_str(result["file_name"])
|
||||||
|
return ComfyTaskStatusResponse(taskId=task_id, status=task_status, code=code,
|
||||||
|
error=reason,
|
||||||
|
result=media.urn if media else "")
|
||||||
@@ -55,6 +55,10 @@ class SentryUtils:
|
|||||||
status = TaskStatus.success
|
status = TaskStatus.success
|
||||||
error = None
|
error = None
|
||||||
code = ErrorCode.SUCCESS.value
|
code = ErrorCode.SUCCESS.value
|
||||||
|
if not result:
|
||||||
|
status = TaskStatus.failed
|
||||||
|
error = "Internal Error"
|
||||||
|
code = ErrorCode.BUSINESS_ERROR.value
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
result = None
|
result = None
|
||||||
@@ -72,9 +76,13 @@ class SentryUtils:
|
|||||||
code=code,
|
code=code,
|
||||||
results=[result] if isinstance(result, str) else result,
|
results=[result] if isinstance(result, str) else result,
|
||||||
).model_dump())
|
).model_dump())
|
||||||
logger.info(f"webhook response = {response}")
|
|
||||||
case WebhookMethodEnum.GET:
|
case WebhookMethodEnum.GET:
|
||||||
response = client.post(url=webhook.endpoint.__str__())
|
response = client.get(url=webhook.endpoint.__str__(), params=BaseFFMPEGTaskStatusResponse(
|
||||||
|
taskId=func_id, status=status, error=error,
|
||||||
|
code=code,
|
||||||
|
results=[result] if isinstance(result, str) else result,
|
||||||
|
).model_dump())
|
||||||
|
logger.info(f"webhook response = {response}")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -16,5 +16,5 @@ app = modal.App(config.modal_app_name,
|
|||||||
app.include(media_app)
|
app.include(media_app)
|
||||||
app.include(ffmpeg_app)
|
app.include(ffmpeg_app)
|
||||||
app.include(web_app)
|
app.include(web_app)
|
||||||
# app.include(comfyui_v1_app)
|
app.include(comfyui_v1_app)
|
||||||
# app.include(comfyui_v2_app)
|
app.include(comfyui_v2_app)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# ComfyUI模板--Base Auth
|
# ComfyUI模板--Base Auth
|
||||||
|
import backoff
|
||||||
import modal
|
import modal
|
||||||
from dotenv import dotenv_values
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
@@ -11,8 +12,7 @@ comfyui_image = (
|
|||||||
.run_commands("add-apt-repository -y contrib "
|
.run_commands("add-apt-repository -y contrib "
|
||||||
"&& wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb&&dpkg -i cuda-keyring_1.1-1_all.deb")
|
"&& wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb&&dpkg -i cuda-keyring_1.1-1_all.deb")
|
||||||
.apt_install("git", "gcc", "libportaudio2", "cuda-toolkit", "ffmpeg")
|
.apt_install("git", "gcc", "libportaudio2", "cuda-toolkit", "ffmpeg")
|
||||||
.add_local_file("../whl/comfy_cli-0.0.0-py3-none-any.whl", "/root/comfy_cli-0.0.0-py3-none-any.whl",copy=True)
|
.pip_install("comfy_cli==0.0.0", index_url="https://packages-1747622887395:0ee15474ccd7b27b57ca63a9306327678e6c2631@g-ldyi2063-pypi.pkg.coding.net/dev/packages/simple")
|
||||||
.pip_install("/root/comfy_cli-0.0.0-py3-none-any.whl")
|
|
||||||
.run_commands("comfy --skip-prompt install --nvidia --version 0.3.10")
|
.run_commands("comfy --skip-prompt install --nvidia --version 0.3.10")
|
||||||
.env(dotenv_values("../.runtime.env"))
|
.env(dotenv_values("../.runtime.env"))
|
||||||
.workdir("/root/comfy")
|
.workdir("/root/comfy")
|
||||||
@@ -50,8 +50,6 @@ comfyui_image = (
|
|||||||
).run_commands(
|
).run_commands(
|
||||||
"rm -rf /root/comfy/ComfyUI/models"
|
"rm -rf /root/comfy/ComfyUI/models"
|
||||||
)
|
)
|
||||||
.add_local_file("../config/config.yaml", "/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",copy=True)
|
|
||||||
.add_local_file("../config/config.py","/root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/tools/config.py",copy=True)
|
|
||||||
.add_local_file("BowongModalFunctions/template/template_v1.json", "/root/src/BowongModalFunctions/template/template_v1.json", copy=True)
|
.add_local_file("BowongModalFunctions/template/template_v1.json", "/root/src/BowongModalFunctions/template/template_v1.json", copy=True)
|
||||||
.add_local_python_source('BowongModalFunctions')
|
.add_local_python_source('BowongModalFunctions')
|
||||||
.add_local_python_source('cluster')
|
.add_local_python_source('cluster')
|
||||||
@@ -70,17 +68,12 @@ app.set_description("ComfyUI v1 Server")
|
|||||||
with comfyui_image.imports():
|
with comfyui_image.imports():
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import uuid
|
import uuid
|
||||||
import psutil
|
import psutil
|
||||||
from typing import Tuple, Any, Dict, Optional
|
from typing import Tuple, Any, Dict, Optional
|
||||||
import loguru
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
import datetime
|
|
||||||
import tempfile
|
|
||||||
import traceback
|
|
||||||
from modal import current_function_call_id
|
from modal import current_function_call_id
|
||||||
from BowongModalFunctions.config import WorkerConfig
|
from BowongModalFunctions.config import WorkerConfig
|
||||||
from BowongModalFunctions.models.web_model import SentryTransactionInfo, WebhookNotify
|
from BowongModalFunctions.models.web_model import SentryTransactionInfo, WebhookNotify
|
||||||
@@ -90,6 +83,7 @@ with comfyui_image.imports():
|
|||||||
config = WorkerConfig()
|
config = WorkerConfig()
|
||||||
vol = modal.Volume.from_name("comfyui-model", create_if_missing=True, environment_name=config.modal_environment)
|
vol = modal.Volume.from_name("comfyui-model", create_if_missing=True, environment_name=config.modal_environment)
|
||||||
secret = modal.Secret.from_name("aws-s3-secret", environment_name=config.modal_environment)
|
secret = modal.Secret.from_name("aws-s3-secret", environment_name=config.modal_environment)
|
||||||
|
custom_secret = modal.Secret.from_name("comfyui-custom-secret", environment_name=config.modal_environment)
|
||||||
output_dir = "/root/comfy/ComfyUI/output"
|
output_dir = "/root/comfy/ComfyUI/output"
|
||||||
|
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
@@ -124,7 +118,7 @@ with comfyui_image.imports():
|
|||||||
cpu=(2, 64),
|
cpu=(2, 64),
|
||||||
memory=(20480, 131072),
|
memory=(20480, 131072),
|
||||||
enable_memory_snapshot=False,
|
enable_memory_snapshot=False,
|
||||||
secrets=[secret],
|
secrets=[secret, custom_secret],
|
||||||
volumes={
|
volumes={
|
||||||
"/root/comfy/ComfyUI/models": vol,
|
"/root/comfy/ComfyUI/models": vol,
|
||||||
"/root/comfy/ComfyUI/input_s3": modal.CloudBucketMount(
|
"/root/comfy/ComfyUI/input_s3": modal.CloudBucketMount(
|
||||||
@@ -151,20 +145,25 @@ with comfyui_image.imports():
|
|||||||
|
|
||||||
@modal.method()
|
@modal.method()
|
||||||
async def api(self, video_path: str, start_time: str = "00:00:03.600", filename_prefix=str(uuid.uuid4()),
|
async def api(self, video_path: str, start_time: str = "00:00:03.600", filename_prefix=str(uuid.uuid4()),
|
||||||
tts_text1: str = "测试",
|
tts_text1: str = "测试", tts_text2: str = "", tts_text3: str = "", tts_text4: str = "",
|
||||||
tts_text2: str = "", tts_text3: str = "", tts_text4: str = "", anchor_id: str = "dawan",
|
anchor_id: str = "dawan", speed: float = 1, sentry_trace: Optional[SentryTransactionInfo] = None,
|
||||||
speed: float = 1, sentry_trace: Optional[SentryTransactionInfo] = None, webhook:WebhookNotify = None):
|
webhook: WebhookNotify = None):
|
||||||
|
|
||||||
|
# 导入必要模块
|
||||||
|
import datetime
|
||||||
|
import tempfile
|
||||||
|
import traceback
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
# 生成当前函数调用的唯一ID
|
# 生成当前函数调用的唯一ID
|
||||||
function_call_id = current_function_call_id()
|
function_call_id = current_function_call_id()
|
||||||
print(f"开始处理任务,会话ID: {self.session_id}, 调用ID: {function_call_id}")
|
logger.info(f"开始处理任务,会话ID: {self.session_id}, 调用ID: {function_call_id}")
|
||||||
|
|
||||||
@SentryUtils.webhook_handler(webhook=webhook, func_id=function_call_id)
|
@SentryUtils.webhook_handler(webhook=webhook, func_id=function_call_id)
|
||||||
async def infer(workflow: Dict):
|
async def infer(workflow: Dict) -> str | None:
|
||||||
self.poll_server_health()
|
self.poll_server_health()
|
||||||
self.prompt_uuid = str(uuid.uuid4())
|
self.prompt_uuid = str(uuid.uuid4())
|
||||||
print("Workflow JSON:")
|
logger.info(f"Workflow JSON: \n{json.dumps(workflow, indent=4, ensure_ascii=False)}")
|
||||||
print(json.dumps(workflow, indent=4, ensure_ascii=False))
|
|
||||||
for node in workflow.values():
|
for node in workflow.values():
|
||||||
if node.get("class_type") == "ComfyUIDeployExternalText":
|
if node.get("class_type") == "ComfyUIDeployExternalText":
|
||||||
if node.get("inputs").get("input_id") == "file_path":
|
if node.get("inputs").get("input_id") == "file_path":
|
||||||
@@ -175,13 +174,16 @@ with comfyui_image.imports():
|
|||||||
shutil.copy(file_to_move, file_to_move.replace("input_s3", "input"))
|
shutil.copy(file_to_move, file_to_move.replace("input_s3", "input"))
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
print("Try download file from S3 manually")
|
logger.warning("尝试手动从S3下载文件")
|
||||||
# S3 Fallback
|
# S3 Fallback
|
||||||
import boto3
|
import boto3
|
||||||
import yaml
|
import yaml
|
||||||
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
if "aws_key_id" in list(os.environ.keys()):
|
||||||
encoding="utf-8", mode="r+") as myconfig:
|
yaml_config = os.environ
|
||||||
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
else:
|
||||||
|
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
||||||
|
encoding="utf-8", mode="r+") as myconfig:
|
||||||
|
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
||||||
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
||||||
aws_secret_access_key=yaml_config["aws_access_key"])
|
aws_secret_access_key=yaml_config["aws_access_key"])
|
||||||
awss3.meta.client.download_file(config.S3_bucket_name,
|
awss3.meta.client.download_file(config.S3_bucket_name,
|
||||||
@@ -196,15 +198,15 @@ with comfyui_image.imports():
|
|||||||
fi.write(json.dumps(workflow, ensure_ascii=False))
|
fi.write(json.dumps(workflow, ensure_ascii=False))
|
||||||
cmd = f"comfy run --workflow /root/{self.prompt_uuid}.json --wait --timeout 1190 --verbose"
|
cmd = f"comfy run --workflow /root/{self.prompt_uuid}.json --wait --timeout 1190 --verbose"
|
||||||
|
|
||||||
self.file_prefix = [
|
self.file_prefix = \
|
||||||
node.get("inputs")
|
[node.get("inputs") for node in workflow.values() if node.get("class_type") == "VHS_VideoCombine"][
|
||||||
for node in workflow.values()
|
0][
|
||||||
if node.get("class_type") == "VHS_VideoCombine"
|
"filename_prefix"]
|
||||||
][0]["filename_prefix"]
|
|
||||||
|
|
||||||
subprocess.run(cmd, shell=True, check=True, timeout=1195)
|
subprocess.run(cmd, shell=True, check=True, timeout=1195)
|
||||||
# 获取按照文件时间创建排序的列表,默认是按时间升序
|
|
||||||
file_list = os.listdir(output_dir)
|
file_list = os.listdir(output_dir)
|
||||||
|
# 获取按照文件时间创建排序的列表,默认是按时间升序
|
||||||
new_file_list = sorted(file_list, key=lambda file: os.path.getctime(os.path.join(output_dir, file)),
|
new_file_list = sorted(file_list, key=lambda file: os.path.getctime(os.path.join(output_dir, file)),
|
||||||
reverse=True)
|
reverse=True)
|
||||||
for f in new_file_list:
|
for f in new_file_list:
|
||||||
@@ -216,20 +218,23 @@ with comfyui_image.imports():
|
|||||||
os.path.join(output_dir.replace("output", "output_s3"), f))
|
os.path.join(output_dir.replace("output", "output_s3"), f))
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
print("Try move file to S3 manually")
|
logger.warning("尝试手动将文件移动到S3")
|
||||||
# S3 Fallback
|
# S3 Fallback
|
||||||
import boto3
|
import boto3
|
||||||
import yaml
|
import yaml
|
||||||
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
if "aws_key_id" in list(os.environ.keys()):
|
||||||
encoding="utf-8", mode="r+") as myconfig:
|
yaml_config = os.environ
|
||||||
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
else:
|
||||||
|
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
||||||
|
encoding="utf-8", mode="r+") as myconfig:
|
||||||
|
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
||||||
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
||||||
aws_secret_access_key=yaml_config["aws_access_key"])
|
aws_secret_access_key=yaml_config["aws_access_key"])
|
||||||
awss3.meta.client.upload_file(os.path.join(output_dir, f), config.S3_bucket_name,
|
awss3.meta.client.upload_file(os.path.join(output_dir, f), config.S3_bucket_name,
|
||||||
config.comfyui_s3_output + "/" + f)
|
config.comfyui_s3_output + "/" + f)
|
||||||
except:
|
except:
|
||||||
raise Exception("Failed to move file to S3 manually")
|
raise Exception("Failed to move file to S3 manually")
|
||||||
return f
|
return "s3://" + "/".join([config.S3_region, config.S3_bucket_name, config.comfyui_s3_output, f])
|
||||||
|
|
||||||
# 使用临时文件
|
# 使用临时文件
|
||||||
temp_log_path = None
|
temp_log_path = None
|
||||||
@@ -237,31 +242,23 @@ with comfyui_image.imports():
|
|||||||
|
|
||||||
# 创建主Sentry事务
|
# 创建主Sentry事务
|
||||||
if sentry_trace and sentry_trace.x_trace_id and sentry_trace.x_baggage:
|
if sentry_trace and sentry_trace.x_trace_id and sentry_trace.x_baggage:
|
||||||
transaction = sentry_sdk.continue_trace(environ_or_headers={"sentry-trace": sentry_trace.x_trace_id,
|
transaction = sentry_sdk.continue_trace(
|
||||||
"baggage": sentry_trace.x_baggage, })
|
environ_or_headers={"sentry-trace": sentry_trace.x_trace_id, "baggage": sentry_trace.x_baggage, })
|
||||||
else:
|
else:
|
||||||
transaction = sentry_sdk.start_transaction(op="video.processing", name=f"API调用: {video_path}")
|
transaction = sentry_sdk.start_transaction(op="video.processing", name=f"API调用: {video_path}")
|
||||||
sentry_trace = SentryTransactionInfo(x_trace_id=transaction.trace_id, x_baggage=transaction.containing_transaction.get_baggage().serialize())
|
sentry_trace = SentryTransactionInfo(x_trace_id=transaction.trace_id,
|
||||||
|
x_baggage=transaction.containing_transaction.get_baggage().serialize())
|
||||||
with transaction:
|
with transaction:
|
||||||
# 设置全局Sentry上下文
|
# 设置全局Sentry上下文
|
||||||
with sentry_sdk.configure_scope() as scope:
|
sentry_sdk.set_tag("session_id", self.session_id)
|
||||||
# 添加更丰富的上下文数据
|
sentry_sdk.set_tag("function_call_id", function_call_id)
|
||||||
scope.set_tag("session_id", self.session_id)
|
sentry_sdk.set_tag("video_path", video_path)
|
||||||
scope.set_tag("function_call_id", function_call_id)
|
sentry_sdk.set_context("api_parameters",
|
||||||
scope.set_tag("video_path", video_path)
|
{"current_function_call_id": function_call_id, "session_id": self.session_id,
|
||||||
scope.set_context("api_parameters", {
|
"video_path": video_path, "start_time": start_time,
|
||||||
"current_function_call_id": function_call_id,
|
"filename_prefix": filename_prefix, "tts_text1": tts_text1,
|
||||||
"session_id": self.session_id,
|
"tts_text2": tts_text2, "tts_text3": tts_text3, "tts_text4": tts_text4,
|
||||||
"video_path": video_path,
|
"anchor_id": anchor_id, "speed": speed})
|
||||||
"start_time": start_time,
|
|
||||||
"filename_prefix": filename_prefix,
|
|
||||||
"tts_text1": tts_text1,
|
|
||||||
"tts_text2": tts_text2,
|
|
||||||
"tts_text3": tts_text3,
|
|
||||||
"tts_text4": tts_text4,
|
|
||||||
"anchor_id": anchor_id,
|
|
||||||
"speed": speed
|
|
||||||
})
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用Span跟踪日志文件创建
|
# 使用Span跟踪日志文件创建
|
||||||
@@ -269,7 +266,7 @@ with comfyui_image.imports():
|
|||||||
# 创建临时日志文件
|
# 创建临时日志文件
|
||||||
temp_log_file = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
|
temp_log_file = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
|
||||||
temp_log_path = temp_log_file.name
|
temp_log_path = temp_log_file.name
|
||||||
print(f"创建临时日志文件: {temp_log_path}")
|
logger.debug(f"创建临时日志文件: {temp_log_path}")
|
||||||
|
|
||||||
span.set_data("log_path", temp_log_path)
|
span.set_data("log_path", temp_log_path)
|
||||||
span.set_tag("file_operation", "create")
|
span.set_tag("file_operation", "create")
|
||||||
@@ -283,6 +280,9 @@ with comfyui_image.imports():
|
|||||||
temp_log_file.flush()
|
temp_log_file.flush()
|
||||||
temp_log_file.close()
|
temp_log_file.close()
|
||||||
|
|
||||||
|
# 设置状态为成功
|
||||||
|
span.set_status("ok")
|
||||||
|
|
||||||
# 使用Span跟踪模板处理
|
# 使用Span跟踪模板处理
|
||||||
with sentry_sdk.start_span(op="template.json", description="处理工作流模板") as span:
|
with sentry_sdk.start_span(op="template.json", description="处理工作流模板") as span:
|
||||||
# 正常业务逻辑
|
# 正常业务逻辑
|
||||||
@@ -307,17 +307,17 @@ with comfyui_image.imports():
|
|||||||
|
|
||||||
modify_span.set_data("modified_nodes",
|
modify_span.set_data("modified_nodes",
|
||||||
["211", "399", "331", "406", "424", "225", "330", "515"])
|
["211", "399", "331", "406", "424", "225", "330", "515"])
|
||||||
|
# 设置状态为成功
|
||||||
|
modify_span.set_status("ok")
|
||||||
|
|
||||||
span.set_data("workflow_size", len(workflow_file))
|
span.set_data("workflow_size", len(workflow_file))
|
||||||
span.set_tag("workflow_type", "video_processing")
|
span.set_tag("workflow_type", "video_processing")
|
||||||
|
# 设置状态为成功
|
||||||
|
span.set_status("ok")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 记录执行的关键步骤作为breadcrumbs
|
# 记录执行的关键步骤作为breadcrumbs
|
||||||
sentry_sdk.add_breadcrumb(
|
sentry_sdk.add_breadcrumb(category="process", message="开始调用infer方法", level="info")
|
||||||
category="process",
|
|
||||||
message="开始调用infer方法",
|
|
||||||
level="info"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用Span跟踪推理过程
|
# 使用Span跟踪推理过程
|
||||||
with sentry_sdk.start_span(op="ai.inference", description="执行模型推理") as infer_span:
|
with sentry_sdk.start_span(op="ai.inference", description="执行模型推理") as infer_span:
|
||||||
@@ -328,6 +328,8 @@ with comfyui_image.imports():
|
|||||||
with sentry_sdk.start_span(op="inference.prepare", description="准备推理环境") as prep_span:
|
with sentry_sdk.start_span(op="inference.prepare", description="准备推理环境") as prep_span:
|
||||||
prep_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
prep_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
||||||
prep_span.set_tag("model_type", "video_generation")
|
prep_span.set_tag("model_type", "video_generation")
|
||||||
|
# 设置状态为成功
|
||||||
|
prep_span.set_status("ok")
|
||||||
|
|
||||||
# 调用推理方法
|
# 调用推理方法
|
||||||
fname = await infer(workflow_file)
|
fname = await infer(workflow_file)
|
||||||
@@ -337,6 +339,8 @@ with comfyui_image.imports():
|
|||||||
description="处理推理结果") as post_span:
|
description="处理推理结果") as post_span:
|
||||||
post_span.set_data("output_file", fname)
|
post_span.set_data("output_file", fname)
|
||||||
post_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
post_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
||||||
|
# 设置状态为成功
|
||||||
|
post_span.set_status("ok")
|
||||||
|
|
||||||
infer_end_time = datetime.datetime.now()
|
infer_end_time = datetime.datetime.now()
|
||||||
duration_seconds = (infer_end_time - infer_start_time).total_seconds()
|
duration_seconds = (infer_end_time - infer_start_time).total_seconds()
|
||||||
@@ -346,21 +350,24 @@ with comfyui_image.imports():
|
|||||||
infer_span.set_data("duration_seconds", duration_seconds)
|
infer_span.set_data("duration_seconds", duration_seconds)
|
||||||
infer_span.set_data("output_file", fname)
|
infer_span.set_data("output_file", fname)
|
||||||
infer_span.set_tag("success", "true" if fname else "false")
|
infer_span.set_tag("success", "true" if fname else "false")
|
||||||
|
# 设置状态为成功
|
||||||
|
infer_span.set_status("ok")
|
||||||
|
|
||||||
sentry_sdk.add_breadcrumb(
|
sentry_sdk.add_breadcrumb(category="process", message="infer方法调用完成", level="info")
|
||||||
category="process",
|
|
||||||
message="infer方法调用完成",
|
|
||||||
level="info"
|
|
||||||
)
|
|
||||||
|
|
||||||
if fname is None:
|
if fname is None:
|
||||||
with sentry_sdk.start_span(op="error.handling", description="处理文件缺失错误"):
|
with sentry_sdk.start_span(op="error.handling",
|
||||||
|
description="处理文件缺失错误") as error_span:
|
||||||
|
error_span.set_status("internal_error") # 设置状态为错误
|
||||||
raise RuntimeError("Output File not found")
|
raise RuntimeError("Output File not found")
|
||||||
|
|
||||||
# 使用Span跟踪返回结果
|
# 使用Span跟踪返回结果
|
||||||
with sentry_sdk.start_span(op="result.preparation", description="准备返回结果"):
|
with sentry_sdk.start_span(op="result.preparation", description="准备返回结果") as result_span:
|
||||||
j = {"status": "success", "file_name": fname}
|
j = {"status": "success", "file_name": fname}
|
||||||
loguru.logger.success(j)
|
logger.success(f"处理成功: {j}")
|
||||||
|
|
||||||
|
# 设置span状态为成功
|
||||||
|
result_span.set_status("ok")
|
||||||
|
|
||||||
# 设置事务状态为成功
|
# 设置事务状态为成功
|
||||||
transaction.set_status("ok")
|
transaction.set_status("ok")
|
||||||
@@ -369,20 +376,21 @@ with comfyui_image.imports():
|
|||||||
|
|
||||||
except Exception as infer_error:
|
except Exception as infer_error:
|
||||||
# 使用原始异常对象
|
# 使用原始异常对象
|
||||||
sentry_sdk.add_breadcrumb(
|
sentry_sdk.add_breadcrumb(category="error", message=f"infer方法调用失败: {str(infer_error)}",
|
||||||
category="error",
|
level="error")
|
||||||
message=f"infer方法调用失败: {str(infer_error)}",
|
|
||||||
level="error"
|
|
||||||
)
|
|
||||||
with sentry_sdk.start_span(op="error.handling", description="处理推理错误") as error_span:
|
with sentry_sdk.start_span(op="error.handling", description="处理推理错误") as error_span:
|
||||||
error_span.set_data("error_type", type(infer_error).__name__)
|
error_span.set_data("error_type", type(infer_error).__name__)
|
||||||
error_span.set_data("error_message", str(infer_error))
|
error_span.set_data("error_message", str(infer_error))
|
||||||
error_span.set_tag("error", "true")
|
error_span.set_tag("error", "true")
|
||||||
|
# 设置状态为错误
|
||||||
|
error_span.set_status("internal_error")
|
||||||
|
logger.error(f"推理失败: {str(infer_error)}")
|
||||||
raise infer_error
|
raise infer_error
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 异常处理 - 使用捕获的异常对象e
|
# 异常处理 - 使用捕获的异常对象e
|
||||||
j = {"status": "fail", "msg": str(e)}
|
j = {"status": "failed", "msg": str(e)}
|
||||||
|
logger.error(f"处理异常: {str(e)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用Span跟踪错误记录过程
|
# 使用Span跟踪错误记录过程
|
||||||
@@ -411,58 +419,67 @@ with comfyui_image.imports():
|
|||||||
f.write("\n====== ComfyUI日志 ======\n")
|
f.write("\n====== ComfyUI日志 ======\n")
|
||||||
f.write(cf.read())
|
f.write(cf.read())
|
||||||
log_span.set_tag("success", "true")
|
log_span.set_tag("success", "true")
|
||||||
|
log_span.set_status("ok") # 设置状态为成功
|
||||||
except Exception as log_read_error:
|
except Exception as log_read_error:
|
||||||
f.write(f"\n无法读取ComfyUI日志: {str(log_read_error)}\n")
|
f.write(f"\n无法读取ComfyUI日志: {str(log_read_error)}\n")
|
||||||
log_span.set_data("error", str(log_read_error))
|
log_span.set_data("error", str(log_read_error))
|
||||||
log_span.set_tag("success", "false")
|
log_span.set_tag("success", "false")
|
||||||
|
# 设置状态为错误
|
||||||
|
log_span.set_status("internal_error")
|
||||||
|
logger.warning(f"无法读取ComfyUI日志: {str(log_read_error)}")
|
||||||
else:
|
else:
|
||||||
log_span.set_tag("file_exists", "false")
|
log_span.set_tag("file_exists", "false")
|
||||||
|
# 设置状态为通知
|
||||||
|
log_span.set_status("unavailable")
|
||||||
|
logger.warning("ComfyUI日志文件不存在")
|
||||||
|
|
||||||
error_span.set_data("log_path", temp_log_path)
|
error_span.set_data("log_path", temp_log_path)
|
||||||
error_span.set_tag("error_type", type(e).__name__)
|
error_span.set_tag("error_type", type(e).__name__)
|
||||||
|
# 设置状态为错误
|
||||||
|
error_span.set_status("internal_error")
|
||||||
|
|
||||||
# 仅当尚未报告错误时才添加附件和报告异常
|
# 仅当尚未报告错误时才添加附件和报告异常
|
||||||
if not error_reported:
|
if not error_reported:
|
||||||
with sentry_sdk.configure_scope() as scope:
|
with sentry_sdk.start_span(op="error.report",
|
||||||
with sentry_sdk.start_span(op="error.report",
|
description="向Sentry报告错误") as report_span:
|
||||||
description="向Sentry报告错误") as report_span:
|
# 使用新的API替换configure_scope
|
||||||
scope.add_attachment(
|
# 添加附件
|
||||||
path=temp_log_path,
|
sentry_sdk.add_attachment(path=temp_log_path, filename=f"full_{function_call_id}.txt",
|
||||||
filename=f"full_{function_call_id}.txt",
|
add_to_transactions=True)
|
||||||
add_to_transactions=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# 添加更多异常上下文
|
# 添加更多异常上下文
|
||||||
scope.set_context("error_details", {
|
sentry_sdk.set_context("error_details",
|
||||||
"error_type": type(e).__name__,
|
{"error_type": type(e).__name__, "error_message": str(e),
|
||||||
"error_message": str(e),
|
"time_of_error": datetime.datetime.now().isoformat()})
|
||||||
"time_of_error": datetime.datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
# 设置事务状态为错误
|
# 设置事务状态为错误
|
||||||
transaction.set_status("internal_error")
|
transaction.set_status("internal_error")
|
||||||
|
# 设置span状态为错误
|
||||||
|
report_span.set_status("internal_error")
|
||||||
|
|
||||||
# 使用异常对象e
|
# 使用异常对象e
|
||||||
sentry_sdk.capture_exception(e)
|
sentry_sdk.capture_exception(e)
|
||||||
error_reported = True
|
error_reported = True
|
||||||
print("已向Sentry报告异常")
|
logger.info("已向Sentry报告异常")
|
||||||
report_span.set_tag("reported", "true")
|
report_span.set_tag("reported", "true")
|
||||||
|
|
||||||
except Exception as log_error:
|
except Exception as log_error:
|
||||||
print(f"处理日志出错: {log_error}")
|
logger.error(f"处理日志出错: {log_error}")
|
||||||
# 设置事务状态为错误
|
# 设置事务状态为错误
|
||||||
transaction.set_status("internal_error")
|
transaction.set_status("internal_error")
|
||||||
|
|
||||||
# 仅当尚未报告错误时才报告原始异常
|
# 仅当尚未报告错误时才报告原始异常
|
||||||
if not error_reported:
|
if not error_reported:
|
||||||
sentry_sdk.capture_exception(e)
|
sentry_sdk.capture_exception(e)
|
||||||
print("已向Sentry报告原始异常(日志处理失败)")
|
logger.info("已向Sentry报告原始异常(日志处理失败)")
|
||||||
|
|
||||||
return j, sentry_trace
|
return j, sentry_trace
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# 使用Span跟踪清理过程
|
# 使用Span跟踪清理过程
|
||||||
with sentry_sdk.start_span(op="system.cleanup", description="清理资源") as cleanup_span:
|
with sentry_sdk.start_span(op="system.cleanup", description="清理资源") as cleanup_span:
|
||||||
|
cleanup_successful = True # 跟踪清理是否成功的标志
|
||||||
|
|
||||||
# 清理临时文件和重启ComfyUI
|
# 清理临时文件和重启ComfyUI
|
||||||
try:
|
try:
|
||||||
# 尝试复制日志到S3
|
# 尝试复制日志到S3
|
||||||
@@ -472,56 +489,47 @@ with comfyui_image.imports():
|
|||||||
try:
|
try:
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
shutil.copy(temp_log_path, f"{log_dir}/full.txt")
|
shutil.copy(temp_log_path, f"{log_dir}/full.txt")
|
||||||
print(f"已复制日志到: {log_dir}/full.txt")
|
logger.info(f"已复制日志到: {log_dir}/full.txt")
|
||||||
copy_span.set_data("source", temp_log_path)
|
copy_span.set_data("source", temp_log_path)
|
||||||
copy_span.set_data("destination", f"{log_dir}/full.txt")
|
copy_span.set_data("destination", f"{log_dir}/full.txt")
|
||||||
copy_span.set_tag("success", "true")
|
copy_span.set_tag("success", "true")
|
||||||
|
# 设置状态为成功
|
||||||
|
copy_span.set_status("ok")
|
||||||
except Exception as copy_error:
|
except Exception as copy_error:
|
||||||
print(f"复制日志到S3失败: {copy_error}")
|
logger.error(f"复制日志到S3失败: {copy_error}")
|
||||||
copy_span.set_data("error", str(copy_error))
|
copy_span.set_data("error", str(copy_error))
|
||||||
copy_span.set_tag("success", "false")
|
copy_span.set_tag("success", "false")
|
||||||
|
# 设置状态为错误
|
||||||
|
copy_span.set_status("internal_error")
|
||||||
|
cleanup_successful = False
|
||||||
|
|
||||||
# 清理临时文件
|
# 清理临时文件
|
||||||
with sentry_sdk.start_span(op="db.file.delete", description="删除临时文件") as del_span:
|
with sentry_sdk.start_span(op="db.file.delete", description="删除临时文件") as del_span:
|
||||||
try:
|
try:
|
||||||
os.unlink(temp_log_path)
|
os.unlink(temp_log_path)
|
||||||
print(f"已清理临时文件: {temp_log_path}")
|
logger.debug(f"已清理临时文件: {temp_log_path}")
|
||||||
del_span.set_data("file", temp_log_path)
|
del_span.set_data("file", temp_log_path)
|
||||||
del_span.set_tag("success", "true")
|
del_span.set_tag("success", "true")
|
||||||
|
# 设置状态为成功
|
||||||
|
del_span.set_status("ok")
|
||||||
except Exception as unlink_error:
|
except Exception as unlink_error:
|
||||||
print(f"清理临时文件失败: {unlink_error}")
|
logger.error(f"清理临时文件失败: {unlink_error}")
|
||||||
del_span.set_data("error", str(unlink_error))
|
del_span.set_data("error", str(unlink_error))
|
||||||
del_span.set_tag("success", "false")
|
del_span.set_tag("success", "false")
|
||||||
|
# 设置状态为错误
|
||||||
|
del_span.set_status("internal_error")
|
||||||
|
cleanup_successful = False
|
||||||
except Exception as cleanup_error:
|
except Exception as cleanup_error:
|
||||||
print(f"清理过程出错: {cleanup_error}")
|
logger.error(f"清理过程出错: {cleanup_error}")
|
||||||
cleanup_span.set_data("error", str(cleanup_error))
|
cleanup_span.set_data("error", str(cleanup_error))
|
||||||
cleanup_span.set_tag("success", "false")
|
cleanup_span.set_tag("success", "false")
|
||||||
|
cleanup_successful = False
|
||||||
|
|
||||||
print("清理资源并重启ComfyUI")
|
# 根据清理和重启结果设置总体清理状态
|
||||||
with sentry_sdk.start_span(op="system.restart", description="重启ComfyUI") as restart_span:
|
if cleanup_successful:
|
||||||
try:
|
cleanup_span.set_status("ok")
|
||||||
# 停止ComfyUI
|
else:
|
||||||
with sentry_sdk.start_span(op="system.stop", description="停止ComfyUI") as stop_span:
|
cleanup_span.set_status("internal_error")
|
||||||
cmd = "comfy stop"
|
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
|
||||||
time.sleep(1)
|
|
||||||
stop_span.set_tag("success", "true")
|
|
||||||
except Exception as stop_error:
|
|
||||||
print(f"停止ComfyUI失败: {stop_error}")
|
|
||||||
restart_span.set_data("stop_error", str(stop_error))
|
|
||||||
restart_span.set_tag("stop_success", "false")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 重启ComfyUI
|
|
||||||
with sentry_sdk.start_span(op="system.start", description="启动ComfyUI") as start_span:
|
|
||||||
cmd = "comfy launch --background"
|
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
|
||||||
start_span.set_tag("success", "true")
|
|
||||||
except Exception as start_error:
|
|
||||||
print(f"启动ComfyUI失败: {start_error}")
|
|
||||||
restart_span.set_data("start_error", str(start_error))
|
|
||||||
restart_span.set_tag("start_success", "false")
|
|
||||||
modal.experimental.stop_fetching_inputs()
|
|
||||||
|
|
||||||
|
|
||||||
def poll_server_health(self):
|
def poll_server_health(self):
|
||||||
@@ -529,7 +537,6 @@ with comfyui_image.imports():
|
|||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# dummy request to check if the server is healthy
|
|
||||||
req = urllib.request.Request("http://127.0.0.1:8188/system_stats")
|
req = urllib.request.Request("http://127.0.0.1:8188/system_stats")
|
||||||
urllib.request.urlopen(req, timeout=5)
|
urllib.request.urlopen(req, timeout=5)
|
||||||
print("ComfyUI server is healthy")
|
print("ComfyUI server is healthy")
|
||||||
|
|||||||
@@ -3,20 +3,16 @@ import modal
|
|||||||
from dotenv import dotenv_values
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
comfyui_latentsync_1_5_image = (
|
comfyui_latentsync_1_5_image = (
|
||||||
modal.Image.debian_slim(
|
modal.Image.debian_slim(python_version="3.10")
|
||||||
python_version="3.10"
|
|
||||||
)
|
|
||||||
.pip_install_from_pyproject("../pyproject_comfyui.toml")
|
.pip_install_from_pyproject("../pyproject_comfyui.toml")
|
||||||
.apt_install("wget", "software-properties-common")
|
.apt_install("wget", "software-properties-common")
|
||||||
.run_commands("add-apt-repository -y contrib "
|
.run_commands("add-apt-repository -y contrib "
|
||||||
"&& wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb&&dpkg -i cuda-keyring_1.1-1_all.deb")
|
"&& wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb&&dpkg -i cuda-keyring_1.1-1_all.deb")
|
||||||
.apt_install("git", "gcc", "libportaudio2", "cuda-toolkit", "ffmpeg")
|
.apt_install("git", "gcc", "libportaudio2", "cuda-toolkit", "ffmpeg")
|
||||||
.add_local_file("../whl/comfy_cli-0.0.0-py3-none-any.whl", "/root/comfy_cli-0.0.0-py3-none-any.whl", copy=True)
|
.pip_install("comfy_cli==0.0.0",index_url="https://packages-1747622887395:0ee15474ccd7b27b57ca63a9306327678e6c2631@g-ldyi2063-pypi.pkg.coding.net/dev/packages/simple")
|
||||||
.pip_install("/root/comfy_cli-0.0.0-py3-none-any.whl")
|
|
||||||
.run_commands("comfy --skip-prompt install --nvidia --version 0.3.10")
|
.run_commands("comfy --skip-prompt install --nvidia --version 0.3.10")
|
||||||
.env(dotenv_values("../.runtime.env"))
|
.env(dotenv_values("../.runtime.env"))
|
||||||
.workdir("/root/comfy")
|
.workdir("/root/comfy"))
|
||||||
)
|
|
||||||
|
|
||||||
comfyui_latentsync_1_5_image = (
|
comfyui_latentsync_1_5_image = (
|
||||||
comfyui_latentsync_1_5_image.run_commands("comfy node install https://github.com/M1kep/ComfyLiterals")
|
comfyui_latentsync_1_5_image.run_commands("comfy node install https://github.com/M1kep/ComfyLiterals")
|
||||||
@@ -37,44 +33,29 @@ comfyui_latentsync_1_5_image = (
|
|||||||
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git")
|
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git")
|
||||||
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/cosyvoice_comfyui.git")
|
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/cosyvoice_comfyui.git")
|
||||||
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/Comfy-LatentSync-Next-Node.git")
|
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/Comfy-LatentSync-Next-Node.git")
|
||||||
.run_commands(
|
.run_commands("mkdir -p /root/comfy/ComfyUI/models/ComfyUI-CustomNode/model && rm -rf /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/model && ln -s /root/comfy/ComfyUI/models/ComfyUI-CustomNode/model /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/model")
|
||||||
"mkdir -p /root/comfy/ComfyUI/models/ComfyUI-CustomNode/model && rm -rf /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/model && ln -s /root/comfy/ComfyUI/models/ComfyUI-CustomNode/model /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/model"
|
.run_commands("mkdir -p /root/comfy/ComfyUI/models/ComfyUI-LatentSync-Next-Node/checkpoints && rm -rf /root/comfy/ComfyUI/custom_nodes/Comfy-LatentSync-Next-Node/checkpoints && ln -s /root/comfy/ComfyUI/models/ComfyUI-LatentSync-Next-Node/checkpoints /root/comfy/ComfyUI/custom_nodes/Comfy-LatentSync-Next-Node/checkpoints")
|
||||||
).run_commands(
|
.run_commands("mkdir -p /root/comfy/ComfyUI/models/.cache/torch/hub/checkpoints && mkdir -p /root/.cache/torch/hub/checkpoints && rm -rf /root/.cache/torch/hub/checkpoints && ln -s /root/comfy/ComfyUI/models/.cache/torch/hub/checkpoints /root/.cache/torch/hub/checkpoints")
|
||||||
"mkdir -p /root/comfy/ComfyUI/models/ComfyUI-LatentSync-Next-Node/checkpoints && rm -rf /root/comfy/ComfyUI/custom_nodes/Comfy-LatentSync-Next-Node/checkpoints && ln -s /root/comfy/ComfyUI/models/ComfyUI-LatentSync-Next-Node/checkpoints /root/comfy/ComfyUI/custom_nodes/Comfy-LatentSync-Next-Node/checkpoints"
|
.run_commands("mkdir -p /root/comfy/ComfyUI/models/stabilityai && ln -s /root/comfy/ComfyUI/models/stabilityai /root/comfy/ComfyUI/stabilityai")
|
||||||
).run_commands(
|
.run_commands("mkdir -p /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M && mkdir -p /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M && rm -rf /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M && ln -s /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M")
|
||||||
"mkdir -p /root/comfy/ComfyUI/models/.cache/torch/hub/checkpoints && mkdir -p /root/.cache/torch/hub/checkpoints && rm -rf /root/.cache/torch/hub/checkpoints && ln -s /root/comfy/ComfyUI/models/.cache/torch/hub/checkpoints /root/.cache/torch/hub/checkpoints"
|
.run_commands("rm -rf /root/comfy/ComfyUI/models")
|
||||||
).run_commands(
|
.add_local_file("BowongModalFunctions/template/template_v2.json","/root/src/BowongModalFunctions/template/template_v2.json",copy=True)
|
||||||
"mkdir -p /root/comfy/ComfyUI/models/stabilityai && ln -s /root/comfy/ComfyUI/models/stabilityai /root/comfy/ComfyUI/stabilityai"
|
|
||||||
).run_commands(
|
|
||||||
"mkdir -p /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M && mkdir -p /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M && rm -rf /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M && ln -s /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M"
|
|
||||||
).run_commands(
|
|
||||||
"rm -rf /root/comfy/ComfyUI/models"
|
|
||||||
)
|
|
||||||
.add_local_file("../config/config.yaml", "/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml", copy=True)
|
|
||||||
.add_local_file("../config/config.py", "/root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/tools/config.py", copy=True)
|
|
||||||
.add_local_file("BowongModalFunctions/template/template_v2.json", "/root/src/BowongModalFunctions/template/template_v2.json", copy=True)
|
|
||||||
.add_local_python_source('cluster')
|
.add_local_python_source('cluster')
|
||||||
.add_local_python_source('BowongModalFunctions')
|
.add_local_python_source('BowongModalFunctions'))
|
||||||
)
|
|
||||||
|
|
||||||
app = modal.App(name="ComfyUI v2", image=comfyui_latentsync_1_5_image, include_source=False)
|
app = modal.App(name="ComfyUI v2", image=comfyui_latentsync_1_5_image, include_source=False)
|
||||||
app.set_description("ComfyUI v2 Server")
|
app.set_description("ComfyUI v2 Server")
|
||||||
|
|
||||||
|
|
||||||
with comfyui_latentsync_1_5_image.imports():
|
with comfyui_latentsync_1_5_image.imports():
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, Tuple, Any, Optional
|
from typing import Dict, Tuple, Any, Optional
|
||||||
import loguru
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
import psutil
|
import psutil
|
||||||
import datetime
|
from loguru import logger
|
||||||
import tempfile
|
|
||||||
import traceback
|
|
||||||
from BowongModalFunctions.config import WorkerConfig
|
from BowongModalFunctions.config import WorkerConfig
|
||||||
from modal import current_function_call_id
|
from modal import current_function_call_id
|
||||||
from sentry_sdk.integrations.loguru import LoguruIntegration
|
from sentry_sdk.integrations.loguru import LoguruIntegration
|
||||||
@@ -84,6 +65,7 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
config = WorkerConfig()
|
config = WorkerConfig()
|
||||||
vol = modal.Volume.from_name("comfyui-model", create_if_missing=True, environment_name=config.modal_environment)
|
vol = modal.Volume.from_name("comfyui-model", create_if_missing=True, environment_name=config.modal_environment)
|
||||||
secret = modal.Secret.from_name("aws-s3-secret", environment_name=config.modal_environment)
|
secret = modal.Secret.from_name("aws-s3-secret", environment_name=config.modal_environment)
|
||||||
|
custom_secret = modal.Secret.from_name("comfyui-custom-secret", environment_name=config.modal_environment)
|
||||||
output_dir = "/root/comfy/ComfyUI/output"
|
output_dir = "/root/comfy/ComfyUI/output"
|
||||||
|
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
@@ -106,6 +88,7 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
|
|
||||||
return (cpu_freq, cpu_count, mem)
|
return (cpu_freq, cpu_count, mem)
|
||||||
|
|
||||||
|
|
||||||
@app.cls(
|
@app.cls(
|
||||||
max_containers=200,
|
max_containers=200,
|
||||||
min_containers=0,
|
min_containers=0,
|
||||||
@@ -113,49 +96,46 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
scaledown_window=120,
|
scaledown_window=120,
|
||||||
timeout=1200,
|
timeout=1200,
|
||||||
gpu=["L4"],
|
gpu=["L4"],
|
||||||
cpu=(2,64),
|
cpu=(2, 64),
|
||||||
memory=(20480,131072),
|
memory=(20480, 131072),
|
||||||
enable_memory_snapshot=False,
|
enable_memory_snapshot=False,
|
||||||
secrets=[secret],
|
secrets=[secret, custom_secret],
|
||||||
volumes={
|
volumes={"/root/comfy/ComfyUI/models": vol,
|
||||||
"/root/comfy/ComfyUI/models": vol,
|
"/root/comfy/ComfyUI/input_s3": modal.CloudBucketMount(bucket_name=config.S3_bucket_name, secret=secret,
|
||||||
"/root/comfy/ComfyUI/input_s3": modal.CloudBucketMount(
|
key_prefix=config.comfyui_s3_input + "/"),
|
||||||
bucket_name=config.S3_bucket_name,
|
"/root/comfy/ComfyUI/output_s3": modal.CloudBucketMount(bucket_name=config.S3_bucket_name, secret=secret,
|
||||||
secret=secret,
|
key_prefix=config.comfyui_s3_output + "/"), }, )
|
||||||
key_prefix=config.comfyui_s3_input+"/"
|
|
||||||
),
|
|
||||||
"/root/comfy/ComfyUI/output_s3": modal.CloudBucketMount(
|
|
||||||
bucket_name=config.S3_bucket_name,
|
|
||||||
secret=secret,
|
|
||||||
key_prefix=config.comfyui_s3_output+"/"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@modal.concurrent(max_inputs=1)
|
@modal.concurrent(max_inputs=1)
|
||||||
class ComfyUILatentSync15:
|
class ComfyUILatentSync15:
|
||||||
@modal.enter()
|
@modal.enter()
|
||||||
def launch_comfy_background(self):
|
def launch_comfy_background(self):
|
||||||
self.session_id = str(uuid.uuid4())
|
self.session_id = str(uuid.uuid4())
|
||||||
self.file_prefix = None
|
self.file_prefix = None
|
||||||
cmd = "echo client_uuid: {} && comfy launch --background".format(self.session_id,self.session_id, self.session_id)
|
cmd = "echo client_uuid: {} && comfy launch --background".format(self.session_id, self.session_id,
|
||||||
|
self.session_id)
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
subprocess.run(cmd, shell=True, check=True)
|
||||||
|
|
||||||
@modal.method()
|
@modal.method()
|
||||||
async def api(self, video_path: str, start_time: str = "00:00:03.600", filename_prefix=str(uuid.uuid4()),
|
async def api(self, video_path: str, start_time: str = "00:00:03.600", filename_prefix=str(uuid.uuid4()),
|
||||||
tts_text1: str = "测试",
|
tts_text1: str = "测试", tts_text2: str = "", tts_text3: str = "", tts_text4: str = "",
|
||||||
tts_text2: str = "", tts_text3: str = "", tts_text4: str = "", anchor_id: str = "dawan",
|
anchor_id: str = "dawan", speed: float = 1, sentry_trace: Optional[SentryTransactionInfo] = None,
|
||||||
speed: float = 1, sentry_trace: Optional[SentryTransactionInfo] = None, webhook:WebhookNotify = None):
|
webhook: WebhookNotify = None):
|
||||||
|
|
||||||
|
# 导入必要模块
|
||||||
|
import datetime
|
||||||
|
import tempfile
|
||||||
|
import traceback
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
# 生成当前函数调用的唯一ID
|
# 生成当前函数调用的唯一ID
|
||||||
function_call_id = current_function_call_id()
|
function_call_id = current_function_call_id()
|
||||||
print(f"开始处理任务,会话ID: {self.session_id}, 调用ID: {function_call_id}")
|
logger.info(f"开始处理任务,会话ID: {self.session_id}, 调用ID: {function_call_id}")
|
||||||
|
|
||||||
@SentryUtils.webhook_handler(webhook=webhook, func_id=function_call_id)
|
@SentryUtils.webhook_handler(webhook=webhook, func_id=function_call_id)
|
||||||
async def infer(workflow: Dict):
|
async def infer(workflow: Dict) -> str | None:
|
||||||
self.poll_server_health()
|
self.poll_server_health()
|
||||||
self.prompt_uuid = str(uuid.uuid4())
|
self.prompt_uuid = str(uuid.uuid4())
|
||||||
print("Workflow JSON:")
|
logger.info(f"Workflow JSON: \n{json.dumps(workflow, indent=4, ensure_ascii=False)}")
|
||||||
print(json.dumps(workflow, indent=4, ensure_ascii=False))
|
|
||||||
for node in workflow.values():
|
for node in workflow.values():
|
||||||
if node.get("class_type") == "ComfyUIDeployExternalText":
|
if node.get("class_type") == "ComfyUIDeployExternalText":
|
||||||
if node.get("inputs").get("input_id") == "file_path":
|
if node.get("inputs").get("input_id") == "file_path":
|
||||||
@@ -166,13 +146,16 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
shutil.copy(file_to_move, file_to_move.replace("input_s3", "input"))
|
shutil.copy(file_to_move, file_to_move.replace("input_s3", "input"))
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
print("Try download file to S3 manually")
|
logger.warning("尝试手动从S3下载文件")
|
||||||
# S3 Fallback
|
# S3 Fallback
|
||||||
import boto3
|
import boto3
|
||||||
import yaml
|
import yaml
|
||||||
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
if "aws_key_id" in list(os.environ.keys()):
|
||||||
encoding="utf-8", mode="r+") as myconfig:
|
yaml_config = os.environ
|
||||||
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
else:
|
||||||
|
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
||||||
|
encoding="utf-8", mode="r+") as myconfig:
|
||||||
|
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
||||||
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
||||||
aws_secret_access_key=yaml_config["aws_access_key"])
|
aws_secret_access_key=yaml_config["aws_access_key"])
|
||||||
awss3.meta.client.download_file(config.S3_bucket_name,
|
awss3.meta.client.download_file(config.S3_bucket_name,
|
||||||
@@ -187,11 +170,8 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
fi.write(json.dumps(workflow, ensure_ascii=False))
|
fi.write(json.dumps(workflow, ensure_ascii=False))
|
||||||
cmd = f"comfy run --workflow /root/{self.prompt_uuid}.json --wait --timeout 1190 --verbose"
|
cmd = f"comfy run --workflow /root/{self.prompt_uuid}.json --wait --timeout 1190 --verbose"
|
||||||
|
|
||||||
self.file_prefix = [
|
self.file_prefix = \
|
||||||
node.get("inputs")
|
[node.get("inputs") for node in workflow.values() if node.get("class_type") == "VHS_VideoCombine"][0]["filename_prefix"]
|
||||||
for node in workflow.values()
|
|
||||||
if node.get("class_type") == "VHS_VideoCombine"
|
|
||||||
][0]["filename_prefix"]
|
|
||||||
|
|
||||||
subprocess.run(cmd, shell=True, check=True, timeout=1195)
|
subprocess.run(cmd, shell=True, check=True, timeout=1195)
|
||||||
|
|
||||||
@@ -208,20 +188,23 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
os.path.join(output_dir.replace("output", "output_s3"), f))
|
os.path.join(output_dir.replace("output", "output_s3"), f))
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
print("Try move file to S3 manually")
|
logger.warning("尝试手动将文件移动到S3")
|
||||||
# S3 Fallback
|
# S3 Fallback
|
||||||
import boto3
|
import boto3
|
||||||
import yaml
|
import yaml
|
||||||
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
if "aws_key_id" in list(os.environ.keys()):
|
||||||
encoding="utf-8", mode="r+") as myconfig:
|
yaml_config = os.environ
|
||||||
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
else:
|
||||||
|
with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml",
|
||||||
|
encoding="utf-8", mode="r+") as myconfig:
|
||||||
|
yaml_config = yaml.load(myconfig, Loader=yaml.FullLoader)
|
||||||
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"],
|
||||||
aws_secret_access_key=yaml_config["aws_access_key"])
|
aws_secret_access_key=yaml_config["aws_access_key"])
|
||||||
awss3.meta.client.upload_file(os.path.join(output_dir, f), config.S3_bucket_name,
|
awss3.meta.client.upload_file(os.path.join(output_dir, f), config.S3_bucket_name,
|
||||||
config.comfyui_s3_output + "/" + f)
|
config.comfyui_s3_output + "/" + f)
|
||||||
except:
|
except:
|
||||||
raise Exception("Failed to move file to S3 manually")
|
raise Exception("Failed to move file to S3 manually")
|
||||||
return f
|
return "s3://" + "/".join([config.S3_region, config.S3_bucket_name, config.comfyui_s3_output, f])
|
||||||
|
|
||||||
# 使用临时文件
|
# 使用临时文件
|
||||||
temp_log_path = None
|
temp_log_path = None
|
||||||
@@ -229,31 +212,22 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
|
|
||||||
# 创建主Sentry事务
|
# 创建主Sentry事务
|
||||||
if sentry_trace and sentry_trace.x_trace_id and sentry_trace.x_baggage:
|
if sentry_trace and sentry_trace.x_trace_id and sentry_trace.x_baggage:
|
||||||
transaction = sentry_sdk.continue_trace(environ_or_headers={"sentry-trace": sentry_trace.x_trace_id,
|
transaction = sentry_sdk.continue_trace(environ_or_headers={"sentry-trace": sentry_trace.x_trace_id, "baggage": sentry_trace.x_baggage, })
|
||||||
"baggage": sentry_trace.x_baggage, })
|
|
||||||
else:
|
else:
|
||||||
transaction = sentry_sdk.start_transaction(op="video.processing", name=f"API调用: {video_path}")
|
transaction = sentry_sdk.start_transaction(op="video.processing", name=f"API调用: {video_path}")
|
||||||
sentry_trace = SentryTransactionInfo(x_trace_id=transaction.trace_id, x_baggage=transaction.containing_transaction.get_baggage().serialize())
|
sentry_trace = SentryTransactionInfo(x_trace_id=transaction.trace_id,
|
||||||
|
x_baggage=transaction.containing_transaction.get_baggage().serialize())
|
||||||
with transaction:
|
with transaction:
|
||||||
# 设置全局Sentry上下文
|
# 设置全局Sentry上下文
|
||||||
with sentry_sdk.configure_scope() as scope:
|
sentry_sdk.set_tag("session_id", self.session_id)
|
||||||
# 添加更丰富的上下文数据
|
sentry_sdk.set_tag("function_call_id", function_call_id)
|
||||||
scope.set_tag("session_id", self.session_id)
|
sentry_sdk.set_tag("video_path", video_path)
|
||||||
scope.set_tag("function_call_id", function_call_id)
|
sentry_sdk.set_context("api_parameters",
|
||||||
scope.set_tag("video_path", video_path)
|
{"current_function_call_id": function_call_id, "session_id": self.session_id,
|
||||||
scope.set_context("api_parameters", {
|
"video_path": video_path, "start_time": start_time,
|
||||||
"current_function_call_id": function_call_id,
|
"filename_prefix": filename_prefix, "tts_text1": tts_text1,
|
||||||
"session_id": self.session_id,
|
"tts_text2": tts_text2, "tts_text3": tts_text3, "tts_text4": tts_text4,
|
||||||
"video_path": video_path,
|
"anchor_id": anchor_id, "speed": speed})
|
||||||
"start_time": start_time,
|
|
||||||
"filename_prefix": filename_prefix,
|
|
||||||
"tts_text1": tts_text1,
|
|
||||||
"tts_text2": tts_text2,
|
|
||||||
"tts_text3": tts_text3,
|
|
||||||
"tts_text4": tts_text4,
|
|
||||||
"anchor_id": anchor_id,
|
|
||||||
"speed": speed
|
|
||||||
})
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用Span跟踪日志文件创建
|
# 使用Span跟踪日志文件创建
|
||||||
@@ -261,7 +235,7 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
# 创建临时日志文件
|
# 创建临时日志文件
|
||||||
temp_log_file = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
|
temp_log_file = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
|
||||||
temp_log_path = temp_log_file.name
|
temp_log_path = temp_log_file.name
|
||||||
print(f"创建临时日志文件: {temp_log_path}")
|
logger.debug(f"创建临时日志文件: {temp_log_path}")
|
||||||
|
|
||||||
span.set_data("log_path", temp_log_path)
|
span.set_data("log_path", temp_log_path)
|
||||||
span.set_tag("file_operation", "create")
|
span.set_tag("file_operation", "create")
|
||||||
@@ -275,6 +249,9 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
temp_log_file.flush()
|
temp_log_file.flush()
|
||||||
temp_log_file.close()
|
temp_log_file.close()
|
||||||
|
|
||||||
|
# 设置状态为成功
|
||||||
|
span.set_status("ok")
|
||||||
|
|
||||||
# 使用Span跟踪模板处理
|
# 使用Span跟踪模板处理
|
||||||
with sentry_sdk.start_span(op="template.json", description="处理工作流模板") as span:
|
with sentry_sdk.start_span(op="template.json", description="处理工作流模板") as span:
|
||||||
# 正常业务逻辑
|
# 正常业务逻辑
|
||||||
@@ -290,8 +267,7 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
workflow_file["399"]["inputs"]["default_value"] = tts_text2
|
workflow_file["399"]["inputs"]["default_value"] = tts_text2
|
||||||
workflow_file["331"]["inputs"]["default_value"] = tts_text3
|
workflow_file["331"]["inputs"]["default_value"] = tts_text3
|
||||||
workflow_file["406"]["inputs"]["default_value"] = tts_text4
|
workflow_file["406"]["inputs"]["default_value"] = tts_text4
|
||||||
workflow_file["424"]["inputs"][
|
workflow_file["424"]["inputs"]["default_value"] = "/root/comfy/ComfyUI/input_s3/" + video_path
|
||||||
"default_value"] = "/root/comfy/ComfyUI/input_s3/" + video_path
|
|
||||||
workflow_file["225"]["inputs"]["filename_prefix"] = filename_prefix
|
workflow_file["225"]["inputs"]["filename_prefix"] = filename_prefix
|
||||||
workflow_file["330"]["inputs"]["default_value"] = start_time
|
workflow_file["330"]["inputs"]["default_value"] = start_time
|
||||||
workflow_file["515"]["inputs"]["anchor_id"] = anchor_id
|
workflow_file["515"]["inputs"]["anchor_id"] = anchor_id
|
||||||
@@ -299,17 +275,17 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
|
|
||||||
modify_span.set_data("modified_nodes",
|
modify_span.set_data("modified_nodes",
|
||||||
["211", "399", "331", "406", "424", "225", "330", "515"])
|
["211", "399", "331", "406", "424", "225", "330", "515"])
|
||||||
|
# 设置状态为成功
|
||||||
|
modify_span.set_status("ok")
|
||||||
|
|
||||||
span.set_data("workflow_size", len(workflow_file))
|
span.set_data("workflow_size", len(workflow_file))
|
||||||
span.set_tag("workflow_type", "video_processing")
|
span.set_tag("workflow_type", "video_processing")
|
||||||
|
# 设置状态为成功
|
||||||
|
span.set_status("ok")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 记录执行的关键步骤作为breadcrumbs
|
# 记录执行的关键步骤作为breadcrumbs
|
||||||
sentry_sdk.add_breadcrumb(
|
sentry_sdk.add_breadcrumb(category="process", message="开始调用infer方法", level="info")
|
||||||
category="process",
|
|
||||||
message="开始调用infer方法",
|
|
||||||
level="info"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用Span跟踪推理过程
|
# 使用Span跟踪推理过程
|
||||||
with sentry_sdk.start_span(op="ai.inference", description="执行模型推理") as infer_span:
|
with sentry_sdk.start_span(op="ai.inference", description="执行模型推理") as infer_span:
|
||||||
@@ -320,6 +296,8 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
with sentry_sdk.start_span(op="inference.prepare", description="准备推理环境") as prep_span:
|
with sentry_sdk.start_span(op="inference.prepare", description="准备推理环境") as prep_span:
|
||||||
prep_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
prep_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
||||||
prep_span.set_tag("model_type", "video_generation")
|
prep_span.set_tag("model_type", "video_generation")
|
||||||
|
# 设置状态为成功
|
||||||
|
prep_span.set_status("ok")
|
||||||
|
|
||||||
# 调用推理方法
|
# 调用推理方法
|
||||||
fname = await infer(workflow_file)
|
fname = await infer(workflow_file)
|
||||||
@@ -329,6 +307,8 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
description="处理推理结果") as post_span:
|
description="处理推理结果") as post_span:
|
||||||
post_span.set_data("output_file", fname)
|
post_span.set_data("output_file", fname)
|
||||||
post_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
post_span.set_data("timestamp", datetime.datetime.now().isoformat())
|
||||||
|
# 设置状态为成功
|
||||||
|
post_span.set_status("ok")
|
||||||
|
|
||||||
infer_end_time = datetime.datetime.now()
|
infer_end_time = datetime.datetime.now()
|
||||||
duration_seconds = (infer_end_time - infer_start_time).total_seconds()
|
duration_seconds = (infer_end_time - infer_start_time).total_seconds()
|
||||||
@@ -338,21 +318,24 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
infer_span.set_data("duration_seconds", duration_seconds)
|
infer_span.set_data("duration_seconds", duration_seconds)
|
||||||
infer_span.set_data("output_file", fname)
|
infer_span.set_data("output_file", fname)
|
||||||
infer_span.set_tag("success", "true" if fname else "false")
|
infer_span.set_tag("success", "true" if fname else "false")
|
||||||
|
# 设置状态为成功
|
||||||
|
infer_span.set_status("ok")
|
||||||
|
|
||||||
sentry_sdk.add_breadcrumb(
|
sentry_sdk.add_breadcrumb(category="process", message="infer方法调用完成", level="info")
|
||||||
category="process",
|
|
||||||
message="infer方法调用完成",
|
|
||||||
level="info"
|
|
||||||
)
|
|
||||||
|
|
||||||
if fname is None:
|
if fname is None:
|
||||||
with sentry_sdk.start_span(op="error.handling", description="处理文件缺失错误"):
|
with sentry_sdk.start_span(op="error.handling",
|
||||||
|
description="处理文件缺失错误") as error_span:
|
||||||
|
error_span.set_status("internal_error") # 设置状态为错误
|
||||||
raise RuntimeError("Output File not found")
|
raise RuntimeError("Output File not found")
|
||||||
|
|
||||||
# 使用Span跟踪返回结果
|
# 使用Span跟踪返回结果
|
||||||
with sentry_sdk.start_span(op="result.preparation", description="准备返回结果"):
|
with sentry_sdk.start_span(op="result.preparation", description="准备返回结果") as result_span:
|
||||||
j = {"status": "success", "file_name": fname}
|
j = {"status": "success", "file_name": fname}
|
||||||
loguru.logger.success(j)
|
logger.success(f"处理成功: {j}")
|
||||||
|
|
||||||
|
# 设置span状态为成功
|
||||||
|
result_span.set_status("ok")
|
||||||
|
|
||||||
# 设置事务状态为成功
|
# 设置事务状态为成功
|
||||||
transaction.set_status("ok")
|
transaction.set_status("ok")
|
||||||
@@ -361,20 +344,21 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
|
|
||||||
except Exception as infer_error:
|
except Exception as infer_error:
|
||||||
# 使用原始异常对象
|
# 使用原始异常对象
|
||||||
sentry_sdk.add_breadcrumb(
|
sentry_sdk.add_breadcrumb(category="error", message=f"infer方法调用失败: {str(infer_error)}",
|
||||||
category="error",
|
level="error")
|
||||||
message=f"infer方法调用失败: {str(infer_error)}",
|
|
||||||
level="error"
|
|
||||||
)
|
|
||||||
with sentry_sdk.start_span(op="error.handling", description="处理推理错误") as error_span:
|
with sentry_sdk.start_span(op="error.handling", description="处理推理错误") as error_span:
|
||||||
error_span.set_data("error_type", type(infer_error).__name__)
|
error_span.set_data("error_type", type(infer_error).__name__)
|
||||||
error_span.set_data("error_message", str(infer_error))
|
error_span.set_data("error_message", str(infer_error))
|
||||||
error_span.set_tag("error", "true")
|
error_span.set_tag("error", "true")
|
||||||
|
# 设置状态为错误
|
||||||
|
error_span.set_status("internal_error")
|
||||||
|
logger.error(f"推理失败: {str(infer_error)}")
|
||||||
raise infer_error
|
raise infer_error
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 异常处理 - 使用捕获的异常对象e
|
# 异常处理 - 使用捕获的异常对象e
|
||||||
j = {"status": "fail", "msg": str(e)}
|
j = {"status": "failed", "msg": str(e)}
|
||||||
|
logger.error(f"处理异常: {str(e)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用Span跟踪错误记录过程
|
# 使用Span跟踪错误记录过程
|
||||||
@@ -403,58 +387,67 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
f.write("\n====== ComfyUI日志 ======\n")
|
f.write("\n====== ComfyUI日志 ======\n")
|
||||||
f.write(cf.read())
|
f.write(cf.read())
|
||||||
log_span.set_tag("success", "true")
|
log_span.set_tag("success", "true")
|
||||||
|
log_span.set_status("ok") # 设置状态为成功
|
||||||
except Exception as log_read_error:
|
except Exception as log_read_error:
|
||||||
f.write(f"\n无法读取ComfyUI日志: {str(log_read_error)}\n")
|
f.write(f"\n无法读取ComfyUI日志: {str(log_read_error)}\n")
|
||||||
log_span.set_data("error", str(log_read_error))
|
log_span.set_data("error", str(log_read_error))
|
||||||
log_span.set_tag("success", "false")
|
log_span.set_tag("success", "false")
|
||||||
|
# 设置状态为错误
|
||||||
|
log_span.set_status("internal_error")
|
||||||
|
logger.warning(f"无法读取ComfyUI日志: {str(log_read_error)}")
|
||||||
else:
|
else:
|
||||||
log_span.set_tag("file_exists", "false")
|
log_span.set_tag("file_exists", "false")
|
||||||
|
# 设置状态为通知
|
||||||
|
log_span.set_status("unavailable")
|
||||||
|
logger.warning("ComfyUI日志文件不存在")
|
||||||
|
|
||||||
error_span.set_data("log_path", temp_log_path)
|
error_span.set_data("log_path", temp_log_path)
|
||||||
error_span.set_tag("error_type", type(e).__name__)
|
error_span.set_tag("error_type", type(e).__name__)
|
||||||
|
# 设置状态为错误
|
||||||
|
error_span.set_status("internal_error")
|
||||||
|
|
||||||
# 仅当尚未报告错误时才添加附件和报告异常
|
# 仅当尚未报告错误时才添加附件和报告异常
|
||||||
if not error_reported:
|
if not error_reported:
|
||||||
with sentry_sdk.configure_scope() as scope:
|
with sentry_sdk.start_span(op="error.report",
|
||||||
with sentry_sdk.start_span(op="error.report",
|
description="向Sentry报告错误") as report_span:
|
||||||
description="向Sentry报告错误") as report_span:
|
# 使用新的API替换configure_scope
|
||||||
scope.add_attachment(
|
# 添加附件
|
||||||
path=temp_log_path,
|
sentry_sdk.add_attachment(path=temp_log_path, filename=f"full_{function_call_id}.txt",
|
||||||
filename=f"full_{function_call_id}.txt",
|
add_to_transactions=True)
|
||||||
add_to_transactions=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# 添加更多异常上下文
|
# 添加更多异常上下文
|
||||||
scope.set_context("error_details", {
|
sentry_sdk.set_context("error_details",
|
||||||
"error_type": type(e).__name__,
|
{"error_type": type(e).__name__, "error_message": str(e),
|
||||||
"error_message": str(e),
|
"time_of_error": datetime.datetime.now().isoformat()})
|
||||||
"time_of_error": datetime.datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
# 设置事务状态为错误
|
# 设置事务状态为错误
|
||||||
transaction.set_status("internal_error")
|
transaction.set_status("internal_error")
|
||||||
|
# 设置span状态为错误
|
||||||
|
report_span.set_status("internal_error")
|
||||||
|
|
||||||
# 使用异常对象e
|
# 使用异常对象e
|
||||||
sentry_sdk.capture_exception(e)
|
sentry_sdk.capture_exception(e)
|
||||||
error_reported = True
|
error_reported = True
|
||||||
print("已向Sentry报告异常")
|
logger.info("已向Sentry报告异常")
|
||||||
report_span.set_tag("reported", "true")
|
report_span.set_tag("reported", "true")
|
||||||
|
|
||||||
except Exception as log_error:
|
except Exception as log_error:
|
||||||
print(f"处理日志出错: {log_error}")
|
logger.error(f"处理日志出错: {log_error}")
|
||||||
# 设置事务状态为错误
|
# 设置事务状态为错误
|
||||||
transaction.set_status("internal_error")
|
transaction.set_status("internal_error")
|
||||||
|
|
||||||
# 仅当尚未报告错误时才报告原始异常
|
# 仅当尚未报告错误时才报告原始异常
|
||||||
if not error_reported:
|
if not error_reported:
|
||||||
sentry_sdk.capture_exception(e)
|
sentry_sdk.capture_exception(e)
|
||||||
print("已向Sentry报告原始异常(日志处理失败)")
|
logger.info("已向Sentry报告原始异常(日志处理失败)")
|
||||||
|
|
||||||
return j, sentry_trace
|
return j, sentry_trace
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# 使用Span跟踪清理过程
|
# 使用Span跟踪清理过程
|
||||||
with sentry_sdk.start_span(op="system.cleanup", description="清理资源") as cleanup_span:
|
with sentry_sdk.start_span(op="system.cleanup", description="清理资源") as cleanup_span:
|
||||||
|
cleanup_successful = True # 跟踪清理是否成功的标志
|
||||||
|
|
||||||
# 清理临时文件和重启ComfyUI
|
# 清理临时文件和重启ComfyUI
|
||||||
try:
|
try:
|
||||||
# 尝试复制日志到S3
|
# 尝试复制日志到S3
|
||||||
@@ -464,56 +457,47 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
try:
|
try:
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
shutil.copy(temp_log_path, f"{log_dir}/full.txt")
|
shutil.copy(temp_log_path, f"{log_dir}/full.txt")
|
||||||
print(f"已复制日志到: {log_dir}/full.txt")
|
logger.info(f"已复制日志到: {log_dir}/full.txt")
|
||||||
copy_span.set_data("source", temp_log_path)
|
copy_span.set_data("source", temp_log_path)
|
||||||
copy_span.set_data("destination", f"{log_dir}/full.txt")
|
copy_span.set_data("destination", f"{log_dir}/full.txt")
|
||||||
copy_span.set_tag("success", "true")
|
copy_span.set_tag("success", "true")
|
||||||
|
# 设置状态为成功
|
||||||
|
copy_span.set_status("ok")
|
||||||
except Exception as copy_error:
|
except Exception as copy_error:
|
||||||
print(f"复制日志到S3失败: {copy_error}")
|
logger.error(f"复制日志到S3失败: {copy_error}")
|
||||||
copy_span.set_data("error", str(copy_error))
|
copy_span.set_data("error", str(copy_error))
|
||||||
copy_span.set_tag("success", "false")
|
copy_span.set_tag("success", "false")
|
||||||
|
# 设置状态为错误
|
||||||
|
copy_span.set_status("internal_error")
|
||||||
|
cleanup_successful = False
|
||||||
|
|
||||||
# 清理临时文件
|
# 清理临时文件
|
||||||
with sentry_sdk.start_span(op="db.file.delete", description="删除临时文件") as del_span:
|
with sentry_sdk.start_span(op="db.file.delete", description="删除临时文件") as del_span:
|
||||||
try:
|
try:
|
||||||
os.unlink(temp_log_path)
|
os.unlink(temp_log_path)
|
||||||
print(f"已清理临时文件: {temp_log_path}")
|
logger.debug(f"已清理临时文件: {temp_log_path}")
|
||||||
del_span.set_data("file", temp_log_path)
|
del_span.set_data("file", temp_log_path)
|
||||||
del_span.set_tag("success", "true")
|
del_span.set_tag("success", "true")
|
||||||
|
# 设置状态为成功
|
||||||
|
del_span.set_status("ok")
|
||||||
except Exception as unlink_error:
|
except Exception as unlink_error:
|
||||||
print(f"清理临时文件失败: {unlink_error}")
|
logger.error(f"清理临时文件失败: {unlink_error}")
|
||||||
del_span.set_data("error", str(unlink_error))
|
del_span.set_data("error", str(unlink_error))
|
||||||
del_span.set_tag("success", "false")
|
del_span.set_tag("success", "false")
|
||||||
|
# 设置状态为错误
|
||||||
|
del_span.set_status("internal_error")
|
||||||
|
cleanup_successful = False
|
||||||
except Exception as cleanup_error:
|
except Exception as cleanup_error:
|
||||||
print(f"清理过程出错: {cleanup_error}")
|
logger.error(f"清理过程出错: {cleanup_error}")
|
||||||
cleanup_span.set_data("error", str(cleanup_error))
|
cleanup_span.set_data("error", str(cleanup_error))
|
||||||
cleanup_span.set_tag("success", "false")
|
cleanup_span.set_tag("success", "false")
|
||||||
|
cleanup_successful = False
|
||||||
|
|
||||||
print("清理资源并重启ComfyUI")
|
# 根据清理和重启结果设置总体清理状态
|
||||||
with sentry_sdk.start_span(op="system.restart", description="重启ComfyUI") as restart_span:
|
if cleanup_successful:
|
||||||
try:
|
cleanup_span.set_status("ok")
|
||||||
# 停止ComfyUI
|
else:
|
||||||
with sentry_sdk.start_span(op="system.stop", description="停止ComfyUI") as stop_span:
|
cleanup_span.set_status("internal_error")
|
||||||
cmd = "comfy stop"
|
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
|
||||||
time.sleep(1)
|
|
||||||
stop_span.set_tag("success", "true")
|
|
||||||
except Exception as stop_error:
|
|
||||||
print(f"停止ComfyUI失败: {stop_error}")
|
|
||||||
restart_span.set_data("stop_error", str(stop_error))
|
|
||||||
restart_span.set_tag("stop_success", "false")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 重启ComfyUI
|
|
||||||
with sentry_sdk.start_span(op="system.start", description="启动ComfyUI") as start_span:
|
|
||||||
cmd = "comfy launch --background"
|
|
||||||
subprocess.run(cmd, shell=True, check=True)
|
|
||||||
start_span.set_tag("success", "true")
|
|
||||||
except Exception as start_error:
|
|
||||||
print(f"启动ComfyUI失败: {start_error}")
|
|
||||||
restart_span.set_data("start_error", str(start_error))
|
|
||||||
restart_span.set_tag("start_success", "false")
|
|
||||||
modal.experimental.stop_fetching_inputs()
|
|
||||||
|
|
||||||
|
|
||||||
def poll_server_health(self):
|
def poll_server_health(self):
|
||||||
@@ -524,10 +508,10 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
# dummy request to check if the server is healthy
|
# dummy request to check if the server is healthy
|
||||||
req = urllib.request.Request("http://127.0.0.1:8188/system_stats")
|
req = urllib.request.Request("http://127.0.0.1:8188/system_stats")
|
||||||
urllib.request.urlopen(req, timeout=5)
|
urllib.request.urlopen(req, timeout=5)
|
||||||
print("ComfyUI server is healthy")
|
logger.info("ComfyUI server is healthy")
|
||||||
except (socket.timeout, urllib.error.URLError) as e:
|
except (socket.timeout, urllib.error.URLError) as e:
|
||||||
# if no response in 5 seconds, stop the container; Modal will schedule queued inputs on a new container
|
# if no response in 5 seconds, stop the container; Modal will schedule queued inputs on a new container
|
||||||
print(f"Server health check failed: {str(e)} restarting ComfyUI...")
|
logger.error(f"Server health check failed: {str(e)} restarting ComfyUI...")
|
||||||
try:
|
try:
|
||||||
cmd = "comfy stop"
|
cmd = "comfy stop"
|
||||||
try:
|
try:
|
||||||
@@ -541,4 +525,4 @@ with comfyui_latentsync_1_5_image.imports():
|
|||||||
raise Exception("Failed to launch ComfyUI")
|
raise Exception("Failed to launch ComfyUI")
|
||||||
except:
|
except:
|
||||||
modal.experimental.stop_fetching_inputs()
|
modal.experimental.stop_fetching_inputs()
|
||||||
raise Exception("ComfyUI server is not healthy, restart failed, stopping container")
|
raise Exception("ComfyUI server is not healthy, restart failed, stopping container")
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import modal
|
import modal
|
||||||
from dotenv import dotenv_values
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
from BowongModalFunctions.config import WorkerConfig
|
|
||||||
|
|
||||||
config = WorkerConfig()
|
|
||||||
|
|
||||||
fastapi_image = (
|
fastapi_image = (
|
||||||
modal.Image
|
modal.Image
|
||||||
.debian_slim(python_version="3.11")
|
.debian_slim(python_version="3.11")
|
||||||
@@ -17,12 +13,13 @@ fastapi_image = (
|
|||||||
app = modal.App(
|
app = modal.App(
|
||||||
name="web_app",
|
name="web_app",
|
||||||
image=fastapi_image,
|
image=fastapi_image,
|
||||||
secrets=[modal.Secret.from_name("aws-s3-secret", environment_name=config.modal_environment)],
|
|
||||||
include_source=False)
|
include_source=False)
|
||||||
|
|
||||||
with fastapi_image.imports():
|
with fastapi_image.imports():
|
||||||
from BowongModalFunctions.api import web_app
|
from BowongModalFunctions.api import web_app
|
||||||
|
from BowongModalFunctions.config import WorkerConfig
|
||||||
|
|
||||||
|
config = WorkerConfig()
|
||||||
|
|
||||||
@app.function(scaledown_window=60,
|
@app.function(scaledown_window=60,
|
||||||
secrets=[
|
secrets=[
|
||||||
@@ -34,7 +31,8 @@ with fastapi_image.imports():
|
|||||||
secret=modal.Secret.from_name("aws-s3-secret",
|
secret=modal.Secret.from_name("aws-s3-secret",
|
||||||
environment_name=config.modal_environment),
|
environment_name=config.modal_environment),
|
||||||
),
|
),
|
||||||
}, )
|
},
|
||||||
|
)
|
||||||
@modal.concurrent(max_inputs=100)
|
@modal.concurrent(max_inputs=100)
|
||||||
@modal.asgi_app()
|
@modal.asgi_app()
|
||||||
def fastapi_webapp():
|
def fastapi_webapp():
|
||||||
|
|||||||
Reference in New Issue
Block a user