diff --git a/playbooks/deploy-news-digest.yaml b/playbooks/deploy-news-digest.yaml index 6a8a2bb..7cb4b9a 100644 --- a/playbooks/deploy-news-digest.yaml +++ b/playbooks/deploy-news-digest.yaml @@ -94,10 +94,15 @@ steps: # /output, so the worker writes a copy of style.css into /output too. - name: docker compose build (~2-3 min first time) + # DOCKER_BUILDKIT=0 forces the legacy build path. ana-docker is on + # docker 20.10 (Debian package) which doesn't carry the buildx + # driver versions our newer client expects → "client version 1.52 + # is too new" without this fallback. Strip the noisy per-step + # download lines for log readability. shell: | set -o pipefail - cd {{ compose_dir }} && docker compose build --progress=plain 2>&1 \ - | grep -vE '^#[0-9]+ [0-9.]+ (Downloading|Collecting|Requirement|Using cached|Installing collected|Successfully (installed|built)|Saved /|━|Resolved|Prepared|Built)' + cd {{ compose_dir }} && DOCKER_BUILDKIT=0 docker compose build 2>&1 \ + | grep -vE '^Step [0-9]+/[0-9]+ : (RUN|COPY)|^Removing intermediate|^ ---> |^ ---> Running|Collecting|Downloading|Requirement|Using cached|Installing collected|Successfully (installed|built)|━' - name: Pre-stage style.css into /output so the first page render works # Worker writes index.html which references "style.css" (relative). diff --git a/stacks/news-digest/.env.example b/stacks/news-digest/.env.example index e486955..856f91f 100644 --- a/stacks/news-digest/.env.example +++ b/stacks/news-digest/.env.example @@ -21,7 +21,13 @@ NEWS_DIGEST_TZ=America/Los_Angeles # Model picked for one-shot summarization quality + low VRAM impact. # qwen3.5-35-a3b is loaded in the persistent group on ana-ml2. LLAMA_SWAP_URL=http://10.250.50.54:9292 -LLAMA_SWAP_MODEL=qwen3.5-35-a3b +# granite-4-small — small (~4B), fast (~1s/call), no extended-thinking +# phase that eats the token budget like qwen3.x do. Plenty of capability +# for the one-sentence-tldr + one-word-tag task. To swap to a larger +# model later, ones currently working: gemma4-26b-a4b, granite-4-small. +# Avoid: qwen3.5-35-a3b (model file broken — process exits on launch), +# qwen3.6-35-a3b (defaults to thinking mode, eats budget without output). +LLAMA_SWAP_MODEL=granite-4-small LLAMA_SWAP_TIMEOUT=180 # ── miniflux (feed source for Tech aggregators + subreddit list) ───── diff --git a/stacks/news-digest/Dockerfile b/stacks/news-digest/Dockerfile index d03f981..40a5905 100644 --- a/stacks/news-digest/Dockerfile +++ b/stacks/news-digest/Dockerfile @@ -19,8 +19,9 @@ WORKDIR /app COPY digest.py /app/digest.py COPY templates /app/templates COPY entrypoint.sh /usr/local/bin/entrypoint.sh +COPY run-digest.sh /usr/local/bin/run-digest.sh COPY crontab /etc/crontabs/root -RUN chmod +x /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/run-digest.sh # Sentinel + first-run output dir VOLUME /output diff --git a/stacks/news-digest/digest.py b/stacks/news-digest/digest.py index 0526049..4ffffcb 100644 --- a/stacks/news-digest/digest.py +++ b/stacks/news-digest/digest.py @@ -275,7 +275,11 @@ def summarize_source(src: Source) -> None: timeout=LLAMA_SWAP_TIMEOUT, ) r.raise_for_status() - content = r.json()["choices"][0]["message"]["content"].strip() + msg = r.json()["choices"][0]["message"] + # Models in extended-thinking mode (e.g. Qwen3.x defaults) put + # output in reasoning_content and leave content empty until they + # exit thinking — fall back so we get *something* to parse. + content = (msg.get("content") or msg.get("reasoning_content") or "").strip() # Some models wrap JSON in ```...``` even when told not to. content = re.sub(r"^```(?:json)?\s*|\s*```$", "", content, flags=re.M).strip() mapped = {x.get("id"): x for x in json.loads(content)} @@ -306,9 +310,13 @@ def render(reddit_sources: list[Source], tech_sources: list[Source], env.filters["domain"] = _domain template = env.get_template("digest.html.j2") edition = "morning" if generated_at.hour < 14 else "evening" + reddit_kept = [s for s in reddit_sources if s.items] + tech_kept = [s for s in tech_sources if s.items] return template.render( - reddit_sources=[s for s in reddit_sources if s.items], - tech_sources=[s for s in tech_sources if s.items], + reddit_sources=reddit_kept, + tech_sources=tech_kept, + reddit_total=sum(len(s.items) for s in reddit_kept), + tech_total=sum(len(s.items) for s in tech_kept), generated_at=generated_at, edition=edition, edition_short="AM" if edition == "morning" else "PM", diff --git a/stacks/news-digest/templates/digest.html.j2 b/stacks/news-digest/templates/digest.html.j2 index 1eed4e9..9d479d6 100644 --- a/stacks/news-digest/templates/digest.html.j2 +++ b/stacks/news-digest/templates/digest.html.j2 @@ -59,7 +59,7 @@ 01