From 976f213cbbd1d29f2cafffcdbf4ad22e5828b6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BA=B7=E5=AE=87=E4=BD=B3?= Date: Tue, 24 Jun 2025 18:34:23 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PERF 修复prompt的label选择 * Merge branch 'main' into cluster-gemini * PERF Gemini推理增加SystemInstruct * PERF 增加上下文商品信息参数 --------- Merge request URL: https://g-ldyi2063.coding.net/p/dev/d/modalDeploy/git/merge/4867 Co-authored-by: 康宇佳 --- src/BowongModalFunctions/utils/HTTPUtils.py | 9 +++-- src/cluster/video_apps/hls_slice_inference.py | 35 +++++++++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/BowongModalFunctions/utils/HTTPUtils.py b/src/BowongModalFunctions/utils/HTTPUtils.py index 1d9fdfe..3c150c4 100644 --- a/src/BowongModalFunctions/utils/HTTPUtils.py +++ b/src/BowongModalFunctions/utils/HTTPUtils.py @@ -7,7 +7,7 @@ import backoff import httpx import asyncio -from google.genai.types import GenerateContentResponse +from google.genai.types import GenerateContentResponse, ContentUnion from loguru import logger import aiofiles from pathlib import Path @@ -143,6 +143,11 @@ class GoogleAuthUtils: def safety_settings(self) -> Optional[List[types.SafetySetting]]: return self.generation_config.safety_settings + @computed_field(alias="systemInstruction") + @property + def system_instruction(self) -> Optional[ContentUnion]: + return self.generation_config.system_instruction + class GoogleGenaiClient(BaseModel): cloudflare_project_id: str cloudflare_gateway_id: str @@ -160,7 +165,7 @@ class GoogleAuthUtils: timeout: int = 30) -> tuple[dict[Any, Any], int] | tuple[GenerateContentResponse, int]: parameter_model = GoogleAuthUtils.VertexAIRequestModel(contents=contents, generationConfig=config) json_body = parameter_model.model_dump_json(indent=2, exclude_none=True, by_alias=True, - exclude={"generation_config": {"safety_settings"}}) + exclude={"generation_config": ["safety_settings", "system_instruction"]}) logger.info(json_body) url = f"{self.gateway_url}/{model_id}:generateContent" logger.info(f"Authorization : Bearer {self.access_token}") diff --git a/src/cluster/video_apps/hls_slice_inference.py b/src/cluster/video_apps/hls_slice_inference.py index 0770657..18f1f38 100644 --- a/src/cluster/video_apps/hls_slice_inference.py +++ b/src/cluster/video_apps/hls_slice_inference.py @@ -94,11 +94,18 @@ with downloader_image.imports(): return { "temperature": c["temperature"], "top_p": c["top_p"], - "safety_settings": c["safety_settings"] - },c + "safety_settings": c["safety_settings"], + "system_instruction": c["system_instruction"] + },{ + "temperature": c["temperature"], + "top_p": c["top_p"], + "response_mime_type": c["response_mime_type"], + "response_schema": c["response_schema"], + "safety_settings": c["safety_settings"] + } # 动态Prompt, langfuse获取失败使用默认值 - IMAGE_PRODUCT_IDENTIFICATION_PROMPT = langfuse.get_prompt("Gemini自动切条/商品识别", type="text", label="latest") + IMAGE_PRODUCT_IDENTIFICATION_PROMPT = langfuse.get_prompt("Gemini自动切条/商品识别", type="text", label="production") if IMAGE_PRODUCT_IDENTIFICATION_PROMPT: prompt_config = IMAGE_PRODUCT_IDENTIFICATION_PROMPT.config first_stage_generate_config, first_stage_correct_config = split_prompt_config(prompt_config) @@ -179,10 +186,17 @@ with downloader_image.imports(): first_stage_generate_config = { "temperature": first_stage_correct_config["temperature"], "top_p": first_stage_correct_config["top_p"], - "safety_settings": first_stage_correct_config["safety_settings"] + "safety_settings": first_stage_correct_config["safety_settings"], + "system_instruction":{ + "parts": [ + { + "text": "你是商品识别专家,任务是从商品网格图片中精准识别商品、提取特征,输出为用户定义的json格式" + } + ] + } } - VIDEO_TIMELINE_ANALYSIS_PROMPT = langfuse.get_prompt("Gemini自动切条/视频时间点识别", type="text", label="latest") + VIDEO_TIMELINE_ANALYSIS_PROMPT = langfuse.get_prompt("Gemini自动切条/视频时间点识别", type="text", label="production") if VIDEO_TIMELINE_ANALYSIS_PROMPT: prompt_config = VIDEO_TIMELINE_ANALYSIS_PROMPT.config second_stage_generate_config, second_stage_correct_config = split_prompt_config(prompt_config) @@ -277,7 +291,14 @@ with downloader_image.imports(): second_stage_generate_config = { "temperature": second_stage_correct_config["temperature"], "top_p": second_stage_correct_config["top_p"], - "safety_settings": second_stage_correct_config["safety_settings"] + "safety_settings": second_stage_correct_config["safety_settings"], + "system_instruction":{ + "parts": [ + { + "text": "你是直播带货视频识别专家,任务是根据商品特征从视频中高效精准地识别出视频中每个商品出现的时间段以及内容类型,输出为用户定义的json格式" + } + ] + } } @@ -308,7 +329,7 @@ with downloader_image.imports(): types.Part.from_text( text="" "" - "请格式化以下一段非标准json格式字符串为json标准格式 \n{0}" + "格式化以下一段有问题的json, json可能存在格式错误字段错误以及部分缺失,修复后输出,若product字段中存在双引号等需转义字符需要转义 \n{0}" "" "".format( json_like_str From 11e2cd34544a8d63f01f16cc6c5d6dd7166a111b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=96=E5=AE=87=E8=BF=AA?= Date: Tue, 24 Jun 2025 18:54:06 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E9=87=87=E7=94=A8=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=AF=8F=E5=A4=A93=20AM=20=E9=A6=99=E6=B8=AF?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=B8=85=E7=90=86=E8=BF=87=E6=9C=9F=E7=9A=84?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 采用定时任务每天3 AM 香港时间清理过期的直播缓存 --------- Merge request URL: https://g-ldyi2063.coding.net/p/dev/d/modalDeploy/git/merge/4868?initial=true Co-authored-by: shuohigh@gmail.com --- src/cluster/ffmpeg_apps/schedule_cleanup.py | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/cluster/ffmpeg_apps/schedule_cleanup.py diff --git a/src/cluster/ffmpeg_apps/schedule_cleanup.py b/src/cluster/ffmpeg_apps/schedule_cleanup.py new file mode 100644 index 0000000..c361cbb --- /dev/null +++ b/src/cluster/ffmpeg_apps/schedule_cleanup.py @@ -0,0 +1,35 @@ +import shutil + +import modal + +from ..ffmpeg_app import ffmpeg_worker_image, app, config, s3_mount, local_copy_to_s3, output_path_prefix + +with ffmpeg_worker_image.imports(): + import os, time + from datetime import datetime + + hls_recording_volume = modal.Volume.from_name("stream_records", create_if_missing=True) + hls_recording_mount_point = "/mnt/stream_records" + + + # runs daily at 3 am (Hong Kong time) + @app.function( + volumes={hls_recording_mount_point: hls_recording_volume}, + schedule=modal.Cron("0 3 * * *", timezone="Asia/HongKong") + ) + def storage_cleanup(): + # todo: clean up storage + output_dir = f"{config.modal_environment}/records/hls/" + volume_output_dir = f"{hls_recording_mount_point}/{output_dir}" + + now = time.time() + cutoff = now - 24 * 60 * 60 # 24小时之前的时间戳 + old_folders = [] + for entry in os.scandir(volume_output_dir): + if entry.is_dir(): + mtime = entry.stat().st_mtime + if mtime < cutoff: + old_folders.append((entry.path, datetime.fromtimestamp(mtime))) + + for folder in old_folders: + shutil.rmtree(folder) From 8263b223467e6bf881a1e6aa14afa64ad04be2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=96=E5=AE=87=E8=BF=AA?= Date: Wed, 25 Jun 2025 10:15:53 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E9=87=87=E7=94=A8=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=AF=8F=E5=A4=A93=20AM=20=E9=A6=99=E6=B8=AF?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=B8=85=E7=90=86=E8=BF=87=E6=9C=9F=E7=9A=84?= =?UTF-8?q?=E7=9B=B4=E6=92=AD=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 采用定时任务每天3 AM 香港时间清理过期的直播缓存 --------- Merge request URL: https://g-ldyi2063.coding.net/p/dev/d/modalDeploy/git/merge/4870?initial=true Co-authored-by: shuohigh@gmail.com --- src/cluster/ffmpeg_apps/schedule_cleanup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cluster/ffmpeg_apps/schedule_cleanup.py b/src/cluster/ffmpeg_apps/schedule_cleanup.py index c361cbb..f3d0900 100644 --- a/src/cluster/ffmpeg_apps/schedule_cleanup.py +++ b/src/cluster/ffmpeg_apps/schedule_cleanup.py @@ -15,10 +15,9 @@ with ffmpeg_worker_image.imports(): # runs daily at 3 am (Hong Kong time) @app.function( volumes={hls_recording_mount_point: hls_recording_volume}, - schedule=modal.Cron("0 3 * * *", timezone="Asia/HongKong") + schedule=modal.Cron("0 3 * * *", timezone="Asia/Hong_Kong") ) def storage_cleanup(): - # todo: clean up storage output_dir = f"{config.modal_environment}/records/hls/" volume_output_dir = f"{hls_recording_mount_point}/{output_dir}" From 38d594b4e9d4cb9938928854be2b01d9addfd032 Mon Sep 17 00:00:00 2001 From: "shuohigh@gmail.com" Date: Wed, 25 Jun 2025 10:38:18 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E9=80=9A=E8=BF=87log=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=B8=85=E7=90=86=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cluster/ffmpeg_apps/schedule_cleanup.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/cluster/ffmpeg_apps/schedule_cleanup.py b/src/cluster/ffmpeg_apps/schedule_cleanup.py index f3d0900..0075d97 100644 --- a/src/cluster/ffmpeg_apps/schedule_cleanup.py +++ b/src/cluster/ffmpeg_apps/schedule_cleanup.py @@ -1,12 +1,13 @@ -import shutil - import modal -from ..ffmpeg_app import ffmpeg_worker_image, app, config, s3_mount, local_copy_to_s3, output_path_prefix +from ..ffmpeg_app import ffmpeg_worker_image, app, config with ffmpeg_worker_image.imports(): import os, time from datetime import datetime + from loguru import logger + import shutil + from typing import Tuple, List hls_recording_volume = modal.Volume.from_name("stream_records", create_if_missing=True) hls_recording_mount_point = "/mnt/stream_records" @@ -20,15 +21,19 @@ with ffmpeg_worker_image.imports(): def storage_cleanup(): output_dir = f"{config.modal_environment}/records/hls/" volume_output_dir = f"{hls_recording_mount_point}/{output_dir}" - now = time.time() cutoff = now - 24 * 60 * 60 # 24小时之前的时间戳 - old_folders = [] + new_folders: List[Tuple[str, datetime]] = [] + old_folders: List[Tuple[str, datetime]] = [] for entry in os.scandir(volume_output_dir): if entry.is_dir(): mtime = entry.stat().st_mtime if mtime < cutoff: old_folders.append((entry.path, datetime.fromtimestamp(mtime))) + else: + new_folders.append((entry.path, datetime.fromtimestamp(mtime))) - for folder in old_folders: + for folder, timestamp in old_folders: + logger.info(f"Removing {folder}, last modified {timestamp.isoformat()}") shutil.rmtree(folder) + logger.info(f"{len(old_folders)} tasks cleaned up, {len(new_folders)} tasks remaining")