a82b0724df
2026-06-11 맥미니 모델 교체(Gemma4 26B→Qwen3.6-27B-6bit, 콜당 ~90~300s)의 타임아웃 상향 sweep 이 config.yaml/synthesis 만 갱신하고 digest/briefing 코드의 하드코딩 LLM_CALL_TIMEOUT=25(빠른 Gemma 기준)를 누락 → digest 600s 하드캡 초과로 06-10 이후 미생성, briefing 4/4 LLM 폴백(status=failed). (적대 리뷰로 블로커 정정: concurrency=1 사설 세마포로는 digest 44~68 클러스터가 하드캡에 여전히 걸림 + llm_gate 영구 룰 위반.) - 타임아웃·재시도·하드캡을 config.pipeline 단일소스로 이관(digest_llm_timeout_s=300, attempts=2, pipeline_hard_cap_s=3000). 다음 모델 교체 때 재발 차단. - digest/briefing LLM 호출을 사설 Semaphore 제거하고 전역 MLX gate(BACKGROUND) 경유로 변경 — llm_gate 영구 룰(같은 endpoint 단일 게이트, 새 Semaphore 금지) 준수 + ask/eid(FOREGROUND)와 조율. 동시성 lever = 기존 mlx_gate_concurrency 2→4 (continuous batching 실측 — 3동시콜 wall 121s ≈ 단일콜, 직렬 대비 ~3배). - digest/briefing pipeline cluster 루프를 asyncio.gather 동시 실행으로 전환 (실동시성은 게이트가 제한, rank/순서 보존). - deep_summary(70~300s)를 메인 consume_queue 에서 분리해 consume_deep_queue 신설 (markdown/fast split 선례) — 단일 deep 호출이 1분 틱 초과로 메인 큐를 영구 coalesce 시키던 문제 제거. - 죽은 PIPELINE_HARD_CAP=600(briefing/pipeline.py) 제거, summarizer docstring 갱신, deep 컨슈머 disjoint/hold 테스트 추가. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
1.7 KiB
Python
49 lines
1.7 KiB
Python
"""Morning Briefing 워커 — 야간 수집 뉴스 (KST 00:00~05:00) topic×country 비교 분석.
|
||
|
||
- APScheduler cron (매일 05:10 KST, PR-3 에서 등록) + 수동 호출 공용 진입점
|
||
- PIPELINE_HARD_CAP = 600초 hard cap 으로 cron stuck 절대 방지
|
||
- 단독 실행: `python -m workers.briefing_worker`
|
||
"""
|
||
|
||
import asyncio
|
||
from datetime import date
|
||
|
||
from core.config import settings
|
||
from core.utils import setup_logger
|
||
from services.briefing.pipeline import run_briefing_pipeline
|
||
|
||
logger = setup_logger("briefing_worker")
|
||
|
||
# 2026-06-15: config 단일소스 (digest 와 공유 키). 구 600s = 빠른 Gemma 기준.
|
||
PIPELINE_HARD_CAP = settings.digest_pipeline_hard_cap_s
|
||
|
||
|
||
async def run(target_date: date | None = None) -> dict | None:
|
||
"""APScheduler + 수동 호출 공용 진입점.
|
||
|
||
Args:
|
||
target_date: KST 기준 briefing_date (None = 오늘). API regenerate 가 명시 지정 가능.
|
||
"""
|
||
if "briefing" in settings.pipeline_held_stages:
|
||
logger.info("[briefing] 보류 (pipeline.held_stages) — 이번 실행 skip")
|
||
return None
|
||
try:
|
||
result = await asyncio.wait_for(
|
||
run_briefing_pipeline(target_date),
|
||
timeout=PIPELINE_HARD_CAP,
|
||
)
|
||
logger.info(f"[briefing] 워커 완료: {result}")
|
||
return result
|
||
except asyncio.TimeoutError:
|
||
logger.error(
|
||
f"[briefing] HARD CAP {PIPELINE_HARD_CAP}s 초과 — 워커 강제 중단. "
|
||
f"기존 briefing 은 commit 시점에만 갱신되므로 그대로 유지됨."
|
||
)
|
||
except Exception as e:
|
||
logger.exception(f"[briefing] 워커 실패: {e}")
|
||
return None
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(run())
|