import re
import uuid

import modal
from google.genai import types

from BowongModalFunctions.utils.HTTPUtils import GoogleAuthUtils, FlatJsonSchemaGenerator
from ..video import downloader_image, app, config

with downloader_image.imports():
    import os, json
    from typing import List
    from loguru import logger
    from modal import current_function_call_id
    from fastapi import HTTPException
    from starlette import status
    from BowongModalFunctions.utils.SentryUtils import SentryUtils
    from BowongModalFunctions.models.media_model import MediaSource
    from BowongModalFunctions.models.web_model import SentryTransactionInfo, WebhookNotify, \
    GeminiFirstStageResponseModel, GeminiSecondStageResponseModel
    from BowongModalFunctions.models.ffmpeg_worker_model import FFMpegSliceSegment
    from BowongModalFunctions.utils.TimeUtils import TimeDelta, merge_product_data
    from BowongModalFunctions.utils.VideoUtils import FFMPEGSliceOptions, VideoUtils


    @app.function(cpu=(0.5, 64), timeout=1800,
                  max_containers=config.video_downloader_concurrency,
                  secrets=[modal.Secret.from_name("gemini-prompt")],
                  volumes={
                      config.S3_mount_dir: modal.CloudBucketMount(
                          bucket_name=config.S3_bucket_name,
                          secret=modal.Secret.from_name("aws-s3-secret", environment_name=config.modal_environment),
                      ),
                  },
                  region="us",
                  cloud='gcp'
                  )
    @modal.concurrent(max_inputs=1)
    async def video_hls_slice_inference(media: MediaSource,
                                        google_api_key: str,
                                        product_grid_list: List[str],
                                        product_list: List[any],
                                        start_time: str,
                                        end_time: str,
                                        options: FFMPEGSliceOptions,
                                        sentry_trace: SentryTransactionInfo,
                                        webhook: WebhookNotify = None,
                                        retry_time: int = 3,
                                        scale:float = 0.9
                                        ):
        logger.info(
            f"region {os.environ.get('MODAL_REGION', 'unknown')}, provider {os.environ.get('MODAL_CLOUD_PROVIDER', 'unknown')}")

        # Gemini可用区
        gemini_region = [
            #"us-central1",
            "us-east1",
            "us-east5",
            "us-west1",
            "us-south1",
            "europe-central2",
            "europe-north1",
            "europe-west1",
            "europe-west4",
            "europe-west8",
        ]

        logger.info(f"\nmedia || {media.urn},"
                    f"\nstart_time || {start_time}, "
                    f"\nend_time || {end_time}, "
                    f"\nproduct_grid_list || {json.dumps(product_grid_list, ensure_ascii=False, indent=2)}, "
                    f"\nproduct_list || {json.dumps(product_list, ensure_ascii=False, indent=2)}"
                    f"\noptions || {options.model_dump_json()}, "
                    f"\nscale || {scale}")

        client = GoogleAuthUtils.GoogleGenaiClient(
            cloudflare_project_id="67720b647ff2b55cf37ba3ef9e677083",
            cloudflare_gateway_id="bowong-dev",
            google_project_id="gen-lang-client-0413414134",
            regions=gemini_region, access_token=google_api_key,
        )

        # 动态Prompt
        if "IMAGE_PRODUCT_IDENTIFICATION_PROMPT" in os.environ:
            IMAGE_PRODUCT_IDENTIFICATION_PROMPT = os.environ["IMAGE_PRODUCT_IDENTIFICATION_PROMPT"]
        else:
            IMAGE_PRODUCT_IDENTIFICATION_PROMPT = """<prompt>
            <instruction>
            你是专业的商品识别专家。我上传了商品图片网格，需要你识别图片中的商品并与商品列表进行匹配。
    
            **输入材料**：
            - 🖼️ **商品图片网格**：包含多个黑色边框区域，每个区域内有商品图片+商品名称文字
            - 📋 **商品列表**：标准商品名称参考清单
            
            **Token自动管理**
            适时记录思考token使用量，根据任务复杂程度自动调整思考token的使用量(不允许超过上限65535)，任务复杂时使用摘要思考模式，仅记录决策结果不记录决策详细过程。
    
            **核心任务**：
            1. **扫描黑色边框区域**：从左上角开始，按行扫描每个黑色边框区域
            2. **提取文字信息**：精确提取每个区域内的所有文字信息
            3. **与商品列表匹配**：将图片文字与商品列表进行高相似度匹配
            4. **提取商品图片特征**：从商品图片提取详细有辨识度可以用于商品辨认任务的特征，包括颜色、图案、纹理、材质、版型、款式等详细特征
    
            **严格约束**：
            - 🚫 只识别有黑色边框包围的商品区域
            - 🚫 每个商品必须有清晰可见的文字标注
            - 🚫 不得推测或添加图片中不存在的商品
            - ✅ 输出商品数量不得超过图片中的黑色边框区域数量
    
            **商品列表-标准商品名称**：
            <items>
            {PRODUCT_LIST}
            </items>
            </instruction>
            
            <output_format>
            请按以下JSON格式输出：
            [
              {{
                "image_order": 1,
                "image_name": "图片上显示的原始文字",
                "matched_product": "匹配到的标准商品名称(必须与商品列表-标准商品名称一致)或null",
                "match_confidence": 95,
                "visual_features": {{
                  "color": "商品详细配色等有辨识度的色彩特征",
                  "pattern": "商品详细图案纹理布料等有辨识度的材质特征，材质可根据商品名以及图片综合进行判断",
                  "style": "商品详细款式风格等有辨识度的款式特征"
                }}
              }}
            ]
            </output_format>
            </prompt>"""

        if "VIDEO_TIMELINE_ANALYSIS_PROMPT" in os.environ:
            VIDEO_TIMELINE_ANALYSIS_PROMPT = os.environ["VIDEO_TIMELINE_ANALYSIS_PROMPT"]
        else:
            VIDEO_TIMELINE_ANALYSIS_PROMPT = """<prompt>
            <instruction>
            基于已识别的商品清单，分析视频中每个商品的出现时间段。
    
            **输入材料**：
            - 📹 **视频**：直播带货片段
            - 📋 **已识别商品清单**：第一阶段确认的商品列表及商品特征
            
            **Token自动管理**
            适时记录思考token使用量，根据任务复杂程度自动调整思考token的使用量(不允许超过上限65535)，任务复杂时使用摘要思考模式，仅记录决策结果不记录决策详细过程。
            
            **商品匹配逻辑示例**
            - 🔄 示例：
              * 📋 材质一致性: 商品名称中"100纯棉" ↔ 视频质感"棉质" ✅一致
              * 🖼️ 风格一致性: 商品特征中"紧身包臀" ↔ 视频"紧身收腰，凸显曲线，包臀" ✅一致  
              * 📹 视频一致性: 视频"修身版型，天使图案" ↔ 商品特征"修身显瘦，天使印花" ✅一致
              * 🎤 语音一致性: 语音"纯棉材质，天使图案" ↔ 商品特征"纯棉" ✅一致
              * 综合判断: 四维一致性95% → 保留
            - 重点关注：
                * **如果图案的形状、设计、构图完全相同，但颜色搭配不同，这属于相同商品的不同配色**
                * 例如：同样的鱼图案，一个是"紫色鱼+黄色底"，另一个是"红色鱼+蓝色底"，这是**相同商品的不同配色**
                * 例如：同样的条纹图案，一个是"黑白条纹"，另一个是"蓝白条纹"，这是**相同商品的不同配色**
                * 例如：同样为衬衫，一个是"棉质黑色"，"一个为丝质米色"，这**不是同款，是不同的商品**
    
            **分析任务**：
            1. **时间段识别**：根据匹配逻辑对视频画面逐一匹配找出每个商品在视频中的展示时间，不要遗漏相同商品的不同配色
            2. **去重原则**：视频画面、语音等都要综合考虑得出置信度值， 如果同一时间段内展示的同一商品可能对应商品列表中的多个商品，只选取置信度值最高的一个
            2. **内容类型分类**：判断每个时间段的具体内容类型(必须为以下三种内容类型之一：穿着本品+介绍本品、穿着本品+他品、穿着本品+无关)
            3. **时间段合并**：合并连续或相近的时间段，去除合并后仍小于5秒的时间段
    
            **内容类型**：
            - (穿着本品+介绍本品) - 主播穿着该商品或页面主体为该商品且正在介绍该商品本身，主播话语里正在出现该商品相关描述语句或手指向或眼睛看向该商品
            - (穿着本品+他品) - 主播穿着该商品或页面主体为该商品但正在介绍其他商品，主播话语里正在出现其他商品相关描述语句或手指向或眼睛看向其他商品
            - (穿着本品+无关) - 主播穿着该商品或页面主体为该商品但在做无关商品的事情，主播话语里正在出现与商品无关的语句或者做换衣服等无关商品介绍的动作
    
            **时间格式标准**：
            - 必须严格使用格式：MM:SS.mmm - MM:SS.mmm
            - MM是分钟(00-59)，SS是秒(00-59)，mmm是毫秒(000-999)
            - 示例：05:23.500 表示5分钟23.5秒500毫秒
            - 常见错误：将05:23.500解释为5分钟23.5秒
    
            **已识别商品清单(product-商品名 feature-商品特征 feature.color-商品详细颜色配色等色彩特征 feature.pattern-商品详细图案纹理布料等材质特征 feature.style-商品详细款式风格等有辨识度的款式特征)**：
            <items>
            {IDENTIFIED_PRODUCTS}
            </items>
            </instruction>
            
            <output_format>
            请按以下JSON格式输出，确保输出格式复核JSON格式要求：
            [
              {{
                "product": "标准商品名称",
                "timeline": [
                  "01:15.200 - 02:30.800 (穿着本品+介绍本品)",
                  "05:10.100 - 06:25.600 (穿着本品+他品)"
                ]
              }}
            ]
            </output_format>
            </prompt>"""


        async def upload(file_path):
            with open(file_path, "rb") as video:
                video = video.read()
            content_length = len(video)
            content_type = f"video/{file_path.split('.')[-1]}"
            filename = ".".join([str(uuid.uuid4()), file_path.split(".")[-1]])
            if content_type not in ['video/mp4', 'video/mpeg', 'video/mov', 'video/avi', 'video/x-flv', 'video/mpg',
                                    'video/webm', 'video/wmv', 'video/3gpp']:
                raise HTTPException(status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
            logger.info(
                f"Uploading name = {filename}, size = {content_length}, type = {content_type} to google file")
            upload_response = await GoogleAuthUtils.google_upload_file(file_stream=video,
                                                                content_type=content_type,
                                                                google_api_key=google_api_key,
                                                                bucket_name="dy-media-storage",
                                                                filename=f"videos/{filename}",)

            return upload_response.urn

        def convert_json(json_like_str:str, json_model):
            try:
                resp, resp_code = client.generate_content(model_id="gemini-2.5-flash",
                                                          contents=[types.Content(role='user',
                                                                                  parts=[
                                                                                      types.Part.from_text(
                                                                                          text="<prompt>"
                                                                                               "<instruction>"
                                                                                               "请格式化以下一段非标准json格式字符串为json标准格式  \n{0}"
                                                                                               "</instruction>"
                                                                                               "</prompt>".format(
                                                                                                json_like_str
                                                                                              )
                                                                                      )
                                                                                  ]
                                                                        )
                                                                    ],
                                                          config=types.GenerateContentConfig(
                                                              temperature=0.01,
                                                              top_p=0.7,
                                                              safety_settings=[
                                                                  types.SafetySetting(
                                                                      category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
                                                                      threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                  ),
                                                                  types.SafetySetting(
                                                                      category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
                                                                      threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                  ),
                                                                  types.SafetySetting(
                                                                      category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
                                                                      threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                  ),
                                                                  types.SafetySetting(
                                                                      category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
                                                                      threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                  ),
                                                                  types.SafetySetting(
                                                                      category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
                                                                      threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                  )
                                                              ],
                                                              response_mime_type="application/json",
                                                              response_schema=json_model.model_json_schema(
                                                                  schema_generator=FlatJsonSchemaGenerator)
                                                          ), timeout=900)
            except Exception as e:
                logger.exception(f"😭格式化json请求失败 {e}")
                return None

            if resp_code == 200:
                reason = resp.candidates[0].finish_reason
                logger.info("😊格式化json用量信息" + resp.usage_metadata.model_dump_json(indent=2))
                if reason == "STOP":
                    result_text: str = resp.candidates[0].content.parts[0].text.replace("\n","").replace("\\n", "").replace("\\", "")
                    # 解析识别结果
                    result_json = json.loads(result_text)
                    return result_json
                else:
                    logger.error(f"😭格式化json推理失败, Reason {reason}")
                    return None
            else:
                logger.error(f"😭格式化json推理失败, 状态码{resp_code}")
                if resp_code == 429:
                    logger.warning("🥵请求负载过高, 随机更换地区重试")
                return None



        def parse_stage1_result(result_text):
            """解析第一阶段结果，提取已识别的商品列表"""
            try:
                # 清理结果文本，提取JSON部分
                clean_result = result_text.strip()
                if clean_result.startswith('```') and clean_result.endswith('```'):
                    clean_result = clean_result[3:-3].strip()
                    if clean_result.startswith('json'):
                        clean_result = clean_result[4:].strip()

                # 解析JSON
                try:
                    stage1_data = json.loads(clean_result)
                except json.decoder.JSONDecodeError:
                    stage1_data = convert_json(clean_result, GeminiFirstStageResponseModel)
                if not stage1_data:
                    raise Exception("json为空")
                identified_products = []
                if isinstance(stage1_data, list):
                    for item in stage1_data:
                        if isinstance(item, dict) and 'matched_product' in item:
                            matched_product = item['matched_product']
                            if matched_product and matched_product != "null":
                                identified_products.append({"product": matched_product, "feature": item["visual_features"]})

                logger.info(f"一阶段解析结果: {len(identified_products)} 个有效商品")
                return identified_products

            except Exception as e:
                logger.exception(f"一阶段结果解析失败 {str(e)}")
                raise e


        @SentryUtils.webhook_handler(webhook, current_function_call_id())
        @SentryUtils.sentry_tracker(sentry_trace.x_trace_id, sentry_trace.x_baggage, op="inference_gemini",
                                    name="Gemini推理", fn_id=current_function_call_id())
        async def _handler(media: MediaSource,
                           google_api_key: str,
                           product_grid_list: List[str],
                           product_list: List[any],
                           start_time: str,
                           end_time: str,
                           options: FFMPEGSliceOptions,
                           sentry_trace: SentryTransactionInfo,
                           retry_time: int = 3,
                           scale:float = 0.9):

            try:
                # 1、首先获取全量商品列表
                logger.info("1、获取直播间商品列表")
                if len(product_list) == 0:
                    logger.error("商品列表为空, 退出推理")
                    raise Exception("商品列表为空, 退出推理")
                product_title_list = []
                if isinstance(product_list[0], dict):
                    for product in product_list:
                        product_title_list.append(product["title"])
                else:
                    product_title_list = product_list

                # 2、切20分钟的条
                logger.info("2、开始截取指定视频")
                # 计算缩放尺寸
                metadata = VideoUtils.ffprobe_media_metadata(media.path)
                width = int(metadata.streams[0].width * scale)
                height = int(metadata.streams[0].height * scale)
                if width % 2 == 1:
                    width = width + 1
                if height % 2 == 1:
                    height = height + 1
                options.width = width
                options.height = height
                slice_fn = modal.Function.from_name(config.modal_app_name, "ffmpeg_slice_media",
                                                    environment_name=config.modal_environment)
                slice_result, sentry_trace = await slice_fn.remote.aio(media, [FFMpegSliceSegment(
                    start=TimeDelta.from_format_string(start_time), end=TimeDelta.from_format_string(end_time))], options,
                                                                       sentry_trace)
                video = MediaSource.from_str(slice_result[0].urn)
                logger.success("截取完成")
                video_path = os.path.join(config.S3_mount_dir, video.path)

                # 3、上传到gemini
                logger.info("3、视频文件开始上传到Gemini")
                video_gemini = await upload(video_path)

                async def inference_api_first_stage():
                    try:
                        logger.info("🎈开始一阶段推理")
                        product_list_str = ""
                        for i, product in enumerate(product_title_list):
                            product_list_str += f"<item>{i}. {product}</item>\n"
                        image_parts = []
                        for i in product_grid_list:
                            image_parts.append(
                                types.Part(
                                    file_data=types.FileData(
                                        mime_type="image/jpeg",
                                        file_uri=f"{i}"
                                    )
                                )
                            )
                        image_parts.append(
                            types.Part.from_text(
                                text=IMAGE_PRODUCT_IDENTIFICATION_PROMPT.format(PRODUCT_LIST=product_list_str)
                            )
                        )
                        resp, resp_code = client.generate_content(model_id="gemini-2.5-flash",
                                                         contents=[types.Content(role='user',
                                                                                 parts=image_parts
                                                                                 )
                                                         ],
                                                         config=types.GenerateContentConfig(
                                                             temperature=0.01,
                                                             top_p=0.7,
                                                             safety_settings=[
                                                                 types.SafetySetting(
                                                                     category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
                                                                     threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                 ),
                                                                 types.SafetySetting(
                                                                     category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
                                                                     threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                 ),
                                                                 types.SafetySetting(
                                                                     category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
                                                                     threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                 ),
                                                                 types.SafetySetting(
                                                                     category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
                                                                     threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                 ),
                                                                 types.SafetySetting(
                                                                     category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
                                                                     threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                 )
                                                             ],
                                                         ), timeout=900)
                    except Exception as e:
                        logger.exception(f"😭Gemini一阶段推理请求失败: {e}")
                        raise e
                    if resp_code == 200:
                        reason = resp.candidates[0].finish_reason
                        logger.info("😊一阶段用量信息"+resp.usage_metadata.model_dump_json(indent=2))
                        if reason == "STOP":
                            result_text: str = resp.candidates[0].content.parts[0].text
                            # 解析识别结果
                            identified_products = parse_stage1_result(result_text)
                            return identified_products
                        else:
                            logger.error(f"😭Gemini一阶段推理失败, Reason {reason}")
                            return None
                    else:
                        logger.error(f"😭Gemini一阶段推理失败, 状态码{resp_code}")
                        if resp_code == 429:
                            logger.warning("🥵请求负载过高, 随机更换地区重试")
                        return None

                async def inference_api_second_stage(identified_products):
                    try:
                        logger.info("🎈开始二阶段推理")
                        # 构建已识别商品清单字符串
                        identified_products_str = ""
                        for i, product in enumerate(identified_products):
                            product_info_json = json.dumps(product,ensure_ascii=False)
                            identified_products_str += f"<item>{i}. {product_info_json}<item>\n"
                        video_parts = []
                        video_parts.append(
                            types.Part(
                                file_data=types.FileData(
                                    mime_type="video/mp4",
                                    file_uri=f"{video_gemini}"
                                )
                            )
                        )
                        video_parts.append(
                            types.Part.from_text(
                                text=VIDEO_TIMELINE_ANALYSIS_PROMPT.format(IDENTIFIED_PRODUCTS=identified_products_str)
                            )
                        )
                        resp, resp_code = client.generate_content(model_id="gemini-2.5-flash",
                                                                  contents=[types.Content(role='user',
                                                                                          parts=video_parts
                                                                                          )
                                                                  ],
                                                                  config=types.GenerateContentConfig(
                                                                      temperature=0.01,
                                                                      top_p=0.7,
                                                                      safety_settings=[
                                                                          types.SafetySetting(
                                                                              category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
                                                                              threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                          ),
                                                                          types.SafetySetting(
                                                                              category=types.HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
                                                                              threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                          ),
                                                                          types.SafetySetting(
                                                                              category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
                                                                              threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                          ),
                                                                          types.SafetySetting(
                                                                              category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
                                                                              threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                          ),
                                                                          types.SafetySetting(
                                                                              category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
                                                                              threshold=types.HarmBlockThreshold.BLOCK_NONE,
                                                                          )
                                                                      ],
                                                                  ), timeout=900)
                    except Exception as e:
                        logger.exception(f"😭Gemini二阶段推理请求失败: {e}")
                        raise e
                    if resp_code == 200:
                        reason = resp.candidates[0].finish_reason
                        logger.info("😊二阶段用量信息"+resp.usage_metadata.model_dump_json(indent=2))
                        if reason == "STOP":
                            result_text: str = resp.candidates[0].content.parts[0].text
                            if len(result_text)>10:
                                if result_text.startswith("```") and result_text.endswith("```"):
                                    parts_text = result_text.split("```")[-2].replace("json","").replace("\n","").replace("\\", "").replace("\\n", "")
                                else:
                                    parts_text = result_text.replace("json", "").replace("\n",
                                                                                                          "").replace(
                                        "\\", "").replace("\\n", "")
                                try:
                                    parts = json.loads(parts_text)
                                except json.decoder.JSONDecodeError:
                                    parts = convert_json(parts_text, GeminiSecondStageResponseModel)
                                if not parts:
                                    raise Exception("json为空")
                                logger.info(f"👌合并前 {json.dumps(parts, indent=4, ensure_ascii=False)}")
                                # 合并产品和时间线
                                parts = merge_product_data(parts, start_time, end_time, merge_diff=5)
                                for part in parts:
                                    part["product"] = re.sub(r'^\x20*\d+\.\x20*', '', part["product"])
                                return parts
                            else:
                                return []
                        else:
                            logger.error(f"😭Gemini二阶段推理失败, Reason {reason}")
                            return None
                    else:
                        logger.error(f"😭Gemini二阶段推理失败, 状态码{resp_code}")
                        if resp_code == 429:
                            logger.warning("🥵请求负载过高, 随机更换地区重试")
                        return None

                logger.info("4、发起Gemini推理")

                # 一阶段推理
                product_info = None
                first_retry_time = retry_time
                while product_info is None and first_retry_time > 0:
                    product_info = await inference_api_first_stage()
                    first_retry_time -= 1
                if product_info is None:
                    raise Exception("😭一阶段推理失败")
                else:
                    logger.info(f"🥳一阶段推理完成JSON \n{json.dumps(product_info, indent=4, ensure_ascii=False)}")

                # 二阶段推理
                product_timeline_info = None
                second_retry_time = retry_time
                while product_timeline_info is None and second_retry_time > 0:
                    product_timeline_info = await inference_api_second_stage(product_info)
                    second_retry_time -= 1
                if product_timeline_info is None:
                    raise Exception("😭二阶段推理失败")
                else:
                    logger.info(f"🥳二阶段推理完成JSON \n{json.dumps(product_timeline_info, indent=4, ensure_ascii=False)}")

                return product_timeline_info, sentry_trace

            except Exception as e:
                logger.exception(f"🥲推理失败, {e}")
                raise Exception(f"🥲推理失败, {e}")

        return await _handler(media, google_api_key, product_grid_list, product_list, start_time, end_time, options,
                              sentry_trace, retry_time, scale)
