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>
50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
"""Phase 4: Global Digest 워커.
|
||
|
||
7일 뉴스를 country × topic 으로 묶어 cluster-level LLM 요약을 생성하고
|
||
global_digests / digest_topics 테이블에 저장한다.
|
||
|
||
- APScheduler cron (매일 04:00 KST) + 수동 호출 공용 진입점
|
||
- PIPELINE_HARD_CAP = 600초 hard cap 으로 cron stuck 절대 방지
|
||
- 단독 실행: `python -m workers.digest_worker`
|
||
"""
|
||
|
||
import asyncio
|
||
|
||
from core.config import settings
|
||
from core.utils import setup_logger
|
||
from services.digest.pipeline import run_digest_pipeline
|
||
|
||
logger = setup_logger("digest_worker")
|
||
|
||
# 2026-06-15: config 단일소스 (구 600s = 빠른 Gemma 기준, Qwen 27B 교체 후 누락 → 초과).
|
||
PIPELINE_HARD_CAP = settings.digest_pipeline_hard_cap_s
|
||
|
||
|
||
async def run() -> None:
|
||
"""APScheduler + 수동 호출 공용 진입점.
|
||
|
||
pipeline 자체는 timeout 으로 감싸지 않음 (per-call timeout 은 summarizer 가 처리).
|
||
여기서는 전체 hard cap 만 강제.
|
||
"""
|
||
if "digest" in settings.pipeline_held_stages:
|
||
logger.info("[global_digest] 보류 (pipeline.held_stages) — 이번 실행 skip")
|
||
return
|
||
try:
|
||
result = await asyncio.wait_for(
|
||
run_digest_pipeline(),
|
||
timeout=PIPELINE_HARD_CAP,
|
||
)
|
||
logger.info(f"[global_digest] 워커 완료: {result}")
|
||
except asyncio.TimeoutError:
|
||
logger.error(
|
||
f"[global_digest] HARD CAP {PIPELINE_HARD_CAP}s 초과 — 워커 강제 중단. "
|
||
f"기존 digest 는 commit 시점에만 갱신되므로 그대로 유지됨. "
|
||
f"다음 cron 실행에서 재시도."
|
||
)
|
||
except Exception as e:
|
||
logger.exception(f"[global_digest] 워커 실패: {e}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(run())
|