From 1ec090bcacf349036b6c7e7bb97e6b2eb4cbc471 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Thu, 23 Jul 2026 18:54:26 +0800 Subject: [PATCH 1/2] =?UTF-8?q?style:=20black=E6=A0=BC=E5=BC=8F=E5=8C=963?= =?UTF-8?q?=E4=B8=AAAI=E7=9B=B8=E5=85=B3=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ai_service.py - ai_tasks.py - test_ai_tasks.py --- apps/api/app/services/ai_service.py | 31 ++-- apps/worker/worker_app/tasks/ai_tasks.py | 24 +-- tests/unit/test_ai_tasks.py | 200 ++++++++++++++++------- 3 files changed, 168 insertions(+), 87 deletions(-) diff --git a/apps/api/app/services/ai_service.py b/apps/api/app/services/ai_service.py index 24dd1b645..bd7e3ffb4 100755 --- a/apps/api/app/services/ai_service.py +++ b/apps/api/app/services/ai_service.py @@ -298,11 +298,13 @@ def _semantic_match_fallback( score = min(1.0, base_score * 0.8 + name_bonus) score = round(score, 3) - results.append({ - **asset, - "match_score": score, - "match_reason": "fallback_keyword", - }) + results.append( + { + **asset, + "match_score": score, + "match_reason": "fallback_keyword", + } + ) # 按匹配度降序 results.sort(key=lambda x: x["match_score"], reverse=True) @@ -414,9 +416,7 @@ def semantic_match_assets( tags = asset.get("tags", []) tags_str = ",".join(str(t) for t in tags[:5]) desc = str(asset.get("description", ""))[:80] - asset_summaries.append( - f"ID:{aid} | 名称:{name} | 标签:[{tags_str}] | 描述:{desc}" - ) + asset_summaries.append(f"ID:{aid} | 名称:{name} | 标签:[{tags_str}] | 描述:{desc}") asset_ids = [str(a.get("id", "")) for a in assets[:50]] @@ -434,8 +434,7 @@ def semantic_match_assets( user_prompt = ( f"目标视频描述:{description}\n\n" - f"素材列表:\n" + "\n".join(asset_summaries) + - f"\n\n请返回每个素材的匹配分数JSON:" + f"素材列表:\n" + "\n".join(asset_summaries) + f"\n\n请返回每个素材的匹配分数JSON:" ) messages = [ @@ -457,11 +456,13 @@ def semantic_match_assets( for asset in assets: aid = str(asset.get("id", "")) score = scores.get(aid, 0.3) # 没评分的给默认偏低分 - matched.append({ - **asset, - "match_score": round(score, 3), - "match_reason": "doubao_semantic", - }) + matched.append( + { + **asset, + "match_score": round(score, 3), + "match_reason": "doubao_semantic", + } + ) matched.sort(key=lambda x: x["match_score"], reverse=True) logger.info( diff --git a/apps/worker/worker_app/tasks/ai_tasks.py b/apps/worker/worker_app/tasks/ai_tasks.py index 560acdf4c..cbc5a63e9 100755 --- a/apps/worker/worker_app/tasks/ai_tasks.py +++ b/apps/worker/worker_app/tasks/ai_tasks.py @@ -149,16 +149,18 @@ def _parse_recommend_response( # 校验 asset_id 是否在输入列表中 if asset_id and asset_id not in asset_ids: asset_id = "" - clips.append({ - "clip_type": clip.get("clip_type", "showcase"), - "order": clip.get("order", len(clips)), - "text_content": str(clip.get("text_content", "")), - "duration": max(1.0, min(30.0, float(clip.get("duration", 3.0)))), - "transition_effect": clip.get("transition_effect", "cut"), - "asset_id": asset_id, - "start_time": max(0.0, float(clip.get("start_time", 0.0))), - "config": clip.get("config", {}) or {}, - }) + clips.append( + { + "clip_type": clip.get("clip_type", "showcase"), + "order": clip.get("order", len(clips)), + "text_content": str(clip.get("text_content", "")), + "duration": max(1.0, min(30.0, float(clip.get("duration", 3.0)))), + "transition_effect": clip.get("transition_effect", "cut"), + "asset_id": asset_id, + "start_time": max(0.0, float(clip.get("start_time", 0.0))), + "config": clip.get("config", {}) or {}, + } + ) if not clips: return None @@ -219,7 +221,7 @@ def _call_ai_recommend_service( "3. 总时长接近 target_duration,每个素材至少用一次\n" "4. 转场效果合理分配,不要全用cut\n" "5. 返回纯JSON,不要其他文字\n" - "返回格式:{\"clips\": [...], \"title\": \"视频标题\", \"confidence\": 0.85}" + '返回格式:{"clips": [...], "title": "视频标题", "confidence": 0.85}' ) assets_desc = "\n".join([f" - 素材ID: {aid}" for i, aid in enumerate(asset_ids[:30])]) diff --git a/tests/unit/test_ai_tasks.py b/tests/unit/test_ai_tasks.py index 1d447767f..369f68d69 100755 --- a/tests/unit/test_ai_tasks.py +++ b/tests/unit/test_ai_tasks.py @@ -121,22 +121,41 @@ class TestRecommendResponseParsing(unittest.TestCase): """解析正常响应.""" data = { "clips": [ - {"clip_type": "intro", "order": 0, "text_content": "开场", - "duration": 3.0, "transition_effect": "fade", - "asset_id": "a1", "start_time": 0.0, "config": {}}, - {"clip_type": "showcase", "order": 1, "text_content": "展示", - "duration": 5.0, "transition_effect": "cut", - "asset_id": "a2", "start_time": 1.0, "config": {}}, - {"clip_type": "outro", "order": 2, "text_content": "结尾", - "duration": 2.0, "transition_effect": "fade", - "asset_id": "", "start_time": 0.0, "config": {}}, + { + "clip_type": "intro", + "order": 0, + "text_content": "开场", + "duration": 3.0, + "transition_effect": "fade", + "asset_id": "a1", + "start_time": 0.0, + "config": {}, + }, + { + "clip_type": "showcase", + "order": 1, + "text_content": "展示", + "duration": 5.0, + "transition_effect": "cut", + "asset_id": "a2", + "start_time": 1.0, + "config": {}, + }, + { + "clip_type": "outro", + "order": 2, + "text_content": "结尾", + "duration": 2.0, + "transition_effect": "fade", + "asset_id": "", + "start_time": 0.0, + "config": {}, + }, ], "title": "精彩视频", "confidence": 0.85, } - result = _parse_recommend_response( - json.dumps(data), self._asset_ids(), 30.0 - ) + result = _parse_recommend_response(json.dumps(data), self._asset_ids(), 30.0) self.assertIsNotNone(result) self.assertEqual(len(result["clips"]), 3) self.assertEqual(result["clips"][0]["clip_type"], "intro") @@ -145,11 +164,21 @@ class TestRecommendResponseParsing(unittest.TestCase): def test_parse_markdown_code_block(self): """解析markdown代码块.""" - data = {"clips": [ - {"clip_type": "showcase", "order": 0, "text_content": "t", - "duration": 3, "transition_effect": "cut", - "asset_id": "a1", "start_time": 0, "config": {}} - ], "confidence": 0.7} + data = { + "clips": [ + { + "clip_type": "showcase", + "order": 0, + "text_content": "t", + "duration": 3, + "transition_effect": "cut", + "asset_id": "a1", + "start_time": 0, + "config": {}, + } + ], + "confidence": 0.7, + } content = "```json\n" + json.dumps(data) + "\n```" result = _parse_recommend_response(content, self._asset_ids(), 30.0) self.assertIsNotNone(result) @@ -167,51 +196,78 @@ class TestRecommendResponseParsing(unittest.TestCase): def test_parse_no_clips(self): """无clips字段返回None.""" - result = _parse_recommend_response( - json.dumps({"title": "abc"}), self._asset_ids(), 30.0 - ) + result = _parse_recommend_response(json.dumps({"title": "abc"}), self._asset_ids(), 30.0) self.assertIsNone(result) def test_parse_filters_invalid_asset_ids(self): """过滤不在输入列表中的asset_id.""" - data = {"clips": [ - {"clip_type": "showcase", "order": 0, "text_content": "t", - "duration": 3, "transition_effect": "cut", - "asset_id": "fake-id", "start_time": 0, "config": {}} - ], "confidence": 0.7} - result = _parse_recommend_response( - json.dumps(data), self._asset_ids(), 30.0 - ) + data = { + "clips": [ + { + "clip_type": "showcase", + "order": 0, + "text_content": "t", + "duration": 3, + "transition_effect": "cut", + "asset_id": "fake-id", + "start_time": 0, + "config": {}, + } + ], + "confidence": 0.7, + } + result = _parse_recommend_response(json.dumps(data), self._asset_ids(), 30.0) self.assertIsNotNone(result) # 非法asset_id被清空 self.assertEqual(result["clips"][0]["asset_id"], "") def test_parse_clamps_duration(self): """时长被限制在合理范围.""" - data = {"clips": [ - {"clip_type": "showcase", "order": 0, "text_content": "t", - "duration": 100, "transition_effect": "cut", - "asset_id": "a1", "start_time": 0, "config": {}} - ]} - result = _parse_recommend_response( - json.dumps(data), self._asset_ids(), 30.0 - ) + data = { + "clips": [ + { + "clip_type": "showcase", + "order": 0, + "text_content": "t", + "duration": 100, + "transition_effect": "cut", + "asset_id": "a1", + "start_time": 0, + "config": {}, + } + ] + } + result = _parse_recommend_response(json.dumps(data), self._asset_ids(), 30.0) self.assertIsNotNone(result) self.assertLessEqual(result["clips"][0]["duration"], 30.0) def test_parse_reorders_clips(self): """clips按order排序并重新编号.""" - data = {"clips": [ - {"clip_type": "showcase", "order": 5, "text_content": "b", - "duration": 3, "transition_effect": "cut", - "asset_id": "a2", "start_time": 0, "config": {}}, - {"clip_type": "intro", "order": 0, "text_content": "a", - "duration": 3, "transition_effect": "fade", - "asset_id": "a1", "start_time": 0, "config": {}}, - ]} - result = _parse_recommend_response( - json.dumps(data), self._asset_ids(), 30.0 - ) + data = { + "clips": [ + { + "clip_type": "showcase", + "order": 5, + "text_content": "b", + "duration": 3, + "transition_effect": "cut", + "asset_id": "a2", + "start_time": 0, + "config": {}, + }, + { + "clip_type": "intro", + "order": 0, + "text_content": "a", + "duration": 3, + "transition_effect": "fade", + "asset_id": "a1", + "start_time": 0, + "config": {}, + }, + ] + } + result = _parse_recommend_response(json.dumps(data), self._asset_ids(), 30.0) self.assertIsNotNone(result) # 第一个应该是order=0的intro self.assertEqual(result["clips"][0]["clip_type"], "intro") @@ -221,14 +277,22 @@ class TestRecommendResponseParsing(unittest.TestCase): def test_parse_confidence_clamped(self): """confidence被限制在0-1.""" - data = {"clips": [ - {"clip_type": "showcase", "order": 0, "text_content": "t", - "duration": 3, "transition_effect": "cut", - "asset_id": "a1", "start_time": 0, "config": {}} - ], "confidence": 2.5} - result = _parse_recommend_response( - json.dumps(data), self._asset_ids(), 30.0 - ) + data = { + "clips": [ + { + "clip_type": "showcase", + "order": 0, + "text_content": "t", + "duration": 3, + "transition_effect": "cut", + "asset_id": "a1", + "start_time": 0, + "config": {}, + } + ], + "confidence": 2.5, + } + result = _parse_recommend_response(json.dumps(data), self._asset_ids(), 30.0) self.assertIsNotNone(result) self.assertLessEqual(result["confidence"], 1.0) @@ -260,12 +324,26 @@ class TestRunAIRecommend(unittest.TestCase): mock_client.is_available = True mock_response = { "clips": [ - {"clip_type": "intro", "order": 0, "text_content": "开场", - "duration": 3.0, "transition_effect": "fade", - "asset_id": "a1", "start_time": 0.0, "config": {}}, - {"clip_type": "outro", "order": 1, "text_content": "结尾", - "duration": 2.0, "transition_effect": "fade", - "asset_id": "a2", "start_time": 0.0, "config": {}}, + { + "clip_type": "intro", + "order": 0, + "text_content": "开场", + "duration": 3.0, + "transition_effect": "fade", + "asset_id": "a1", + "start_time": 0.0, + "config": {}, + }, + { + "clip_type": "outro", + "order": 1, + "text_content": "结尾", + "duration": 2.0, + "transition_effect": "fade", + "asset_id": "a2", + "start_time": 0.0, + "config": {}, + }, ], "title": "AI生成标题", "confidence": 0.9, -- 2.54.0 From 146de5f0167ce7be49e00e5102c1adbfbfe60520 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 23 Jul 2026 19:40:50 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8D2=E4=B8=AAruff?= =?UTF-8?q?=E9=94=99=E8=AF=AF=20-=20F541=E5=A4=9A=E4=BD=99f=E5=89=8D?= =?UTF-8?q?=E7=BC=80=20+=20B007=E6=9C=AA=E7=94=A8=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/services/ai_service.py | 2 +- apps/worker/worker_app/tasks/ai_tasks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/app/services/ai_service.py b/apps/api/app/services/ai_service.py index bd7e3ffb4..0d1bca0a1 100755 --- a/apps/api/app/services/ai_service.py +++ b/apps/api/app/services/ai_service.py @@ -434,7 +434,7 @@ def semantic_match_assets( user_prompt = ( f"目标视频描述:{description}\n\n" - f"素材列表:\n" + "\n".join(asset_summaries) + f"\n\n请返回每个素材的匹配分数JSON:" + f"素材列表:\n" + "\n".join(asset_summaries) + "\n\n请返回每个素材的匹配分数JSON:" ) messages = [ diff --git a/apps/worker/worker_app/tasks/ai_tasks.py b/apps/worker/worker_app/tasks/ai_tasks.py index cbc5a63e9..6ef25d7b0 100755 --- a/apps/worker/worker_app/tasks/ai_tasks.py +++ b/apps/worker/worker_app/tasks/ai_tasks.py @@ -142,7 +142,7 @@ def _parse_recommend_response( return None clips: List[Dict[str, Any]] = [] - for i, clip in enumerate(clips_data): + for _, clip in enumerate(clips_data): if not isinstance(clip, dict): continue asset_id = str(clip.get("asset_id", "")) -- 2.54.0