자동 탭 컷별 추천 모델 교체(gemini-3.5-flash) + 429/503 재시도
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) <noreply@anthropic.com>
This commit is contained in:
parent
058f03265c
commit
9ea70d01a7
@ -9,6 +9,8 @@ Gemini 없이 단위 검증이 된다.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
@ -69,7 +71,10 @@ def build_cut_picks(cuts, comments, ai_picks: Optional[Dict[int, List[int]]],
|
|||||||
# Gemini에 넘길 댓글 후보 수. 더 늘려도 채택률이 안 오르고 토큰만 먹는다.
|
# Gemini에 넘길 댓글 후보 수. 더 늘려도 채택률이 안 오르고 토큰만 먹는다.
|
||||||
AI_CANDIDATES = 150
|
AI_CANDIDATES = 150
|
||||||
TEXT_CAP = 200 # 댓글 본문 절단 길이
|
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/"
|
_ENDPOINT = ("https://generativelanguage.googleapis.com/v1beta/models/"
|
||||||
"{model}:generateContent?key={key}")
|
"{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),
|
_ENDPOINT.format(model=model, key=key),
|
||||||
data=json.dumps(body).encode("utf-8"),
|
data=json.dumps(body).encode("utf-8"),
|
||||||
headers={"Content-Type": "application/json"})
|
headers={"Content-Type": "application/json"})
|
||||||
try:
|
# 429("quota")·503("high demand")만 짧게 쉬고 재시도한다(최대 2회 더 = 총 3회 시도).
|
||||||
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"]
|
RETRY_WAITS = (3.0, 8.0)
|
||||||
except (KeyError, IndexError, json.JSONDecodeError, ValueError,
|
raw = None
|
||||||
OSError): # URLError, TimeoutError are OSError subclasses; ValueError covers UnicodeDecodeError
|
for attempt in range(1 + len(RETRY_WAITS)):
|
||||||
return None # 429 포함 — 폴백은 호출부 몫
|
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:
|
try:
|
||||||
rows = json.loads(raw)
|
rows = json.loads(raw)
|
||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user