From 9ea70d01a771a508cc8b88a8c15c1d80e4188c57 Mon Sep 17 00:00:00 2001 From: "hehihoho3@gmail.com" Date: Tue, 4 Aug 2026 18:42:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=83=AD=20=EC=BB=B7?= =?UTF-8?q?=EB=B3=84=20=EC=B6=94=EC=B2=9C=20=EB=AA=A8=EB=8D=B8=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4(gemini-3.5-flash)=20+=20429/503=20=EC=9E=AC=EC=8B=9C?= =?UTF-8?q?=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gemini-2.5-flash 가 HTTP 503("high demand")을 자주 반환해(실측 3연속 503) ai_pick_cuts 가 재시도 없이 즉시 포기, 하이라이트의 컷 전체가 🤖 0개가 되는 증상의 근본 원인이었다. plan.py 가 이미 쓰는 gemini-3.5-flash 로 모델을 통일하고, 429/503 한정으로 3초·8초 간격 재시도(최대 3회 시도)를 추가했다. 그 외 오류와 재시도 소진 시에는 기존과 동일하게 None 을 반환한다(예외 미노출). Co-Authored-By: Claude Opus 5 (1M context) --- capcut_agent/recommend.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/capcut_agent/recommend.py b/capcut_agent/recommend.py index fa5e49c..16db89d 100644 --- a/capcut_agent/recommend.py +++ b/capcut_agent/recommend.py @@ -9,6 +9,8 @@ Gemini 없이 단위 검증이 된다. from __future__ import annotations import json +import time +import urllib.error import urllib.request from typing import Dict, List, Optional @@ -69,7 +71,10 @@ def build_cut_picks(cuts, comments, ai_picks: Optional[Dict[int, List[int]]], # Gemini에 넘길 댓글 후보 수. 더 늘려도 채택률이 안 오르고 토큰만 먹는다. AI_CANDIDATES = 150 TEXT_CAP = 200 # 댓글 본문 절단 길이 -AI_MODEL = "gemini-2.5-flash" # correct.py 와 같은 무료 티어 모델 +# gemini-2.5-flash 는 실측상 HTTP 503("high demand")이 잦아(3연속 503도 관측됨) 컷별 +# 추천이 통째로 비는 원인이었다. plan.py 가 이미 쓰는 gemini-3.5-flash 로 통일 — +# 같은 프롬프트·같은 컷으로 실측해도 결과가 동등해 품질 손해는 없다. +AI_MODEL = "gemini-3.5-flash" _ENDPOINT = ("https://generativelanguage.googleapis.com/v1beta/models/" "{model}:generateContent?key={key}") @@ -168,13 +173,27 @@ def ai_pick_cuts(cuts, comments, quotas, *, model: str = AI_MODEL, _ENDPOINT.format(model=model, key=key), data=json.dumps(body).encode("utf-8"), headers={"Content-Type": "application/json"}) - try: - with urllib.request.urlopen(req, timeout=timeout) as resp: - data = json.loads(resp.read().decode("utf-8")) - raw = data["candidates"][0]["content"]["parts"][0]["text"] - except (KeyError, IndexError, json.JSONDecodeError, ValueError, - OSError): # URLError, TimeoutError are OSError subclasses; ValueError covers UnicodeDecodeError - return None # 429 포함 — 폴백은 호출부 몫 + # 429("quota")·503("high demand")만 짧게 쉬고 재시도한다(최대 2회 더 = 총 3회 시도). + # 둘 다 "지금 당장은 안 됨"이지 "영영 안 됨"이 아니라서다. 그 외 오류는 재시도해 봐야 + # 같은 결과라 즉시 포기한다. + RETRY_WAITS = (3.0, 8.0) + raw = None + for attempt in range(1 + len(RETRY_WAITS)): + try: + with urllib.request.urlopen(req, timeout=timeout) as resp: + data = json.loads(resp.read().decode("utf-8")) + raw = data["candidates"][0]["content"]["parts"][0]["text"] + break + except urllib.error.HTTPError as e: + if e.code in (429, 503) and attempt < len(RETRY_WAITS): + time.sleep(RETRY_WAITS[attempt]) + continue + return None # 재시도 소진, 또는 재시도 대상이 아닌 HTTP 오류 + except (KeyError, IndexError, json.JSONDecodeError, ValueError, + OSError): # URLError, TimeoutError are OSError subclasses; ValueError covers UnicodeDecodeError + return None # 폴백은 호출부 몫 + if raw is None: # 방어적 — 위 루프는 항상 break/return 으로 빠진다 + return None try: rows = json.loads(raw) except (json.JSONDecodeError, TypeError):