AIFOD 日韩 AI 新闻聚合器:每日午夜邮件摘要
henryhyunwookim/AI-news-aggregator-KRJP
该项目是一个基于 Python 的无服务器服务,从韩国和日本抓取 AI 相关新闻,经 RapidFuzz 模糊标题聚类去重后,用 gemini-3.5-flash-lite 从约 70 条标题中筛选出 10 条韩国和 10 条日本候选。
AIFOD Daily AI News Aggregator (Korea & Japan)
A Python-based serverless service that fetches AI-related news from South Korea and Japan, filters them for relevance to AIFOD's mission, algorithmically deduplicates press releases and similar stories via fuzzy title clustering, enriches candidate articles with real publisher content, translates and deeply synthesizes the top 5 most impactful stories in English with guaranteed country balance (including AIFOD strategic insights and practitioner Q&As), and emails a daily digest to the practitioner every day at midnight (Asia/Tokyo time).
Deployed on Google Cloud Run and scheduled via Google Cloud Scheduler.
Architecture & System Flowcharts
1. High-Level Infrastructure Overview
flowchart TD
%% -------------------------------------------------------------
%% Trigger & Orchestration Layer
%% -------------------------------------------------------------
subgraph TriggerLayer ["1. Trigger & Scheduling Layer"]
CRON["⏰ GCP Cloud Scheduler<br/><code>ai-news-aggregator-daily-trigger</code><br/>(0 0 * * * Asia/Tokyo)"]
CLI["💻 Local CLI / Manual Trigger<br/><code>python -m src.main --hours 24</code>"]
SA["🔑 IAM Service Account<br/><code>ai-news-scheduler-sa</code><br/>(roles/run.invoker + OIDC)"]
end
%% -------------------------------------------------------------
%% Compute & Hosting Infrastructure
%% -------------------------------------------------------------
subgraph ComputeLayer ["2. Compute & Runtime Infrastructure (GCP Cloud Run)"]
CR["🚀 Cloud Run Service<br/><code>ai-news-aggregator-krjp</code><br/>(Region: asia-northeast1)"]
GUNICORN["🦄 Gunicorn WSGI Server<br/>(1 Worker, 8 Threads, Port: 8080)"]
FLASK["🐍 Flask App<br/><code>src/app.py</code><br/>(POST /?hours=24)"]
MAIN["⚙️ Orchestration Core<br/><code>src/main.py</code> (main())"]
end
%% -------------------------------------------------------------
%% Ingestion & Anti-Blocking Resilience Layer
%% -------------------------------------------------------------
subgraph ScrapingLayer ["3. Ingestion & Dual-Engine Resilience Layer (src/rss_parser.py)"]
QUERIES["🔍 Search Query Matrix<br/>• 18 KR Queries (AI, ODA, 격차, 정책)<br/>• 19 JP Queries (AI, ODA, 格差, 政策)"]
POOL["⚡ ThreadPoolExecutor<br/>(max_workers=6 concurrent tasks)"]
subgraph DualEngine ["Multi-Engine Fallback Chain"]
GN["1️⃣ Google News RSS<br/>(Direct XML with Desktop User-Agents)"]
BING["2️⃣ Bing News RSS Fallback<br/>(High Datacenter IP Reliability)"]
end
ALGO_DEDUP["🧹 Algorithmic Fuzzy Clustering (RapidFuzz)<br/>• URL Hash Set (seen_urls)<br/>• Title Normalization & Token-Set Clustering (sim >= 65%)<br/>• Preserves 1 Best Article per Event Cluster"]
end
%% -------------------------------------------------------------
%% AI Intelligence & Content Enrichment Layer
%% -------------------------------------------------------------
subgraph AILayer ["4. Two-Stage AI Reasoning & Enrichment Layer (src/llm_filter.py)"]
STAGE1["🎯 Stage 1: Country-Balanced Candidate Selection<br/>• Model: <code>gemini-3.5-flash-lite</code> (Compact Input Format)<br/>• Pick Top 10 KR & Top 10 JP Candidates from ~70 Headline Pool"]
ENRICHER["🌐 Real Web Content Enrichment<br/>• <code>googlenewsdecoder</code> (Unwrap CBMi Redirects)<br/>• Fetch <code>og:description</code> & Lead Paragraphs"]
STAGE2["✨ Stage 2: Deep Synthesis (Google Gemini API)<br/>• Model: <code>gemini-3.8-flash</code><br/>• Country Balance: 2-3 KR and 2-3 JP (Total 5)<br/>• Rich Factual Summaries, Strategic Insights & Q&A"]
GUARDRAIL["🛡️ Post-LLM Deduplication Guardrail<br/>Verify Cross-Article Title Similarity < 55%"]
end
%% -------------------------------------------------------------
%% Email Generation & Delivery Layer
%% -------------------------------------------------------------
subgraph DeliveryLayer ["5. Email Formatting & Delivery (src/email_sender.py)"]
HTML_BUILDER["🎨 Premium Responsive HTML Builder<br/>• Plus Jakarta Sans Typography<br/>• KR/JP Country Badges & Direct Canonical Links<br/>• Gradient Header & KPI Counters"]
GMAIL_AUTH["🔐 Gmail OAuth 2.0 Auth<br/><code>credentials.json</code> + <code>token.json</code><br/>(Auto-refresh Token Flow)"]
GMAIL_API["📬 Google Gmail API<br/>(users.messages.send)"]
INBOX["📩 Recipient Mailbox<br/><code>recipient@example.com</code>"]
end
%% -------------------------------------------------------------
%% CI/CD & Deployment Layer
%% -------------------------------------------------------------
subgraph DevOpsLayer ["6. DevOps & Build Infrastructure"]
DOCKER["🐳 Dockerfile (python:3.11-slim)"]
CB["🏗️ Google Cloud Build"]
AR["📦 Artifact Registry"]
PS["📜 PowerShell Deploy Script<br/><code>deployment/deploy_cloud.ps1</code>"]
end
%% Flow Connections
CRON -->|OIDC Auth POST| SA --> CR
CLI --> MAIN
CR --> GUNICORN --> FLASK --> MAIN
MAIN --> POOL
QUERIES --> POOL
POOL --> GN
GN -.->|On 503 Block / Empty Feed| BING
GN & BING --> ALGO_DEDUP
ALGO_DEDUP --> STAGE1
STAGE1 --> ENRICHER
ENRICHER --> STAGE2
STAGE2 --> GUARDRAIL
GUARDRAIL --> MAIN
MAIN --> HTML_BUILDER
GMAIL_AUTH --> GMAIL_API
HTML_BUILDER --> GMAIL_API
GMAIL_API --> INBOX
PS --> CB --> AR --> CR
DOCKER --> CB
Loading
2. Two-Stage AI Selection & Deep Synthesis Pipeline
flowchart TD
%% Input Articles
subgraph InputStage ["1. Deduplicated Event Pool (src/rss_parser.py)"]
RAW_FEED["📥 400+ Raw Articles Fetched"]
FUZZ_CLUSTER["🧹 RapidFuzz Title Clustering<br/>Group identical press releases into single events"]
SPLIT_POOLS["👥 Initial Pool: Up to 70 Headlines<br/>• KR Pool (Top 35 Date-Sorted)<br/>• JP Pool (Top 35 Date-Sorted)"]
end
%% Stage 1 Candidate Selection
subgraph Stage1 ["2. Stage 1: Candidate Selection (Gemini API)"]
S1_PROMPT["🧠 Candidate Selection Prompt<br/>Filter for AIFOD Mission Alignment & Thematic Diversity"]
S1_SELECT["🎯 Select Top 20 Candidates<br/>(10 from KR + 10 from JP)"]
end
%% Web Content Enrichment
subgraph EnrichmentStage ["3. Real Web Content & Canonical URL Enrichment"]
DECODER["🔓 <code>googlenewsdecoder</code><br/>Unwrap Google News CBMi redirects to canonical publisher URLs"]
FETCHER["🌐 Parallel Metadata Extraction<br/>Extract <code>og:description</code>, meta descriptions & lead paragraphs (~1000 chars)"]
ENRICHED_DATA["📑 Enriched Candidate Payloads (20 Articles)"]
end
%% Stage 2 Deep Synthesis
subgraph Stage2 ["4. Stage 2: Deep Factual Synthesis (Gemini API)"]
S2_PROMPT["🧠 Deep Synthesis Prompt<br/>Enforce country balance (2-3 KR, 2-3 JP) & rich factual density"]
S2_OUTPUT["📋 Structured JSON Generation (Final 5 Stories):<br/>• english_title<br/>• english_summary (3-4 dense factual sentences)<br/>• aifod_insight (Strategic implications for Global South)<br/>• aifod_question (Critical policy dilemma)<br/>• aifod_suggested_answer (Actionable stance)"]
end
%% Post-Guardrail
subgraph GuardrailStage ["5. Post-Generation Verification Guardrail"]
SIM_CHECK{"Check Pairwise Title<br/>Similarity < 55%?"}
POST_VERIFY["✅ Verified Balanced Top 5 Digest"]
SUB_ALT["🔁 Substitute with Unused Candidate"]
end
%% Connections
RAW_FEED --> FUZZ_CLUSTER --> SPLIT_POOLS
SPLIT_POOLS --> S1_PROMPT --> S1_SELECT
S1_SELECT --> DECODER --> FETCHER --> ENRICHED_DATA
ENRICHED_DATA --> S2_PROMPT --> S2_OUTPUT
S2_OUTPUT --> SIM_CHECK
SIM_CHECK -- "Pass" --> POST_VERIFY
SIM_CHECK -- "Duplicate Found" --> SUB_ALT --> POST_VERIFY
Loading
3. Data Ingestion & Fallback Pipeline
flowchart TD
%% Query Generation
subgraph QueryDispatch ["1. Query Formulation & Concurrent Dispatch"]
K_LIST["🇰🇷 18 Korean Keywords<br/>(AI 개발도상국, 국제협력, ODA, 정보격차...)"]
J_LIST["🇯🇵 19 Japanese Keywords<br/>(AI 途上国, 国際協力, ODA, デジタル格差...)"]
DISPATCH["⚡ ThreadPoolExecutor (max_workers=6)<br/>Execute <code>get_feed_articles(query, country)</code>"]
end
%% Dual Engine Fetching
subgraph DualEngineFetch ["2. Dual-Engine Retrieval"]
GN_TRY["🌐 1. Attempt Google News RSS<br/><code>news.google.com/rss/search?q=...</code><br/>(Timeout: 8s)"]
GN_CHECK{"Google RSS<br/>Valid Entries?"}
BING_FALLBACK["🛡️ 2. Fallback: Bing News RSS<br/><code>bing.com/news/search?q=...&format=rss</code><br/>(Timeout: 8s)"]
end
%% Pre-Processing & Deduplication
subgraph DedupPipeline ["3. Algorithmic Deduplication & Clustering"]
TIME_FILTER["⏱️ Time Window Filter (<= hours_back)"]
URL_DEDUP["🔗 Exact URL Deduplication (seen_urls set)"]
NORM_TITLE["🔤 Title Normalization<br/>Strip media tags (- 로이터, | 연합뉴스), brackets, punctuation"]
CLUSTER["📊 RapidFuzz Token-Set Clustering (sim >= 65%)<br/>Cluster identical press releases & pick longest description"]
OUTPUT_BUFFER["📁 Deduplicated Event Stories Buffer"]
end
%% Connections
K_LIST & J_LIST --> DISPATCH
DISPATCH --> GN_TRY --> GN_CHECK
GN_CHECK -- "✅ Success" --> TIME_FILTER
GN_CHECK -- "❌ Empty / 503 Blocked" --> BING_FALLBACK --> TIME_FILTER
TIME_FILTER --> URL_DEDUP --> NORM_TITLE --> CLUSTER --> OUTPUT_BUFFER
Loading
Features
- Resilient Dual-Engine Retrieval: Queries Google News RSS feeds for South Korea and Japan using AIFOD-targeted keywords. Automatically falls back to Bing News RSS if Google News blocks cloud egress IPs or returns empty feeds.
- Algorithmic Fuzzy Deduplication: Eliminates identical press releases across multiple news outlets using RapidFuzz token-set title clustering (
token_set_ratio >= 65). Merges duplicate coverage into a single best-quality article before AI evaluation. - Article Content Enrichment: Resolves Google News
CBMi...redirects to canonical publisher URLs viagooglenewsdecoderand fetches real article metadata (og:description,meta[name="description"], and lead paragraphs). - Two-Stage Country-Balanced AI Pipeline:
- Initial Headline Pool: Ingests an initial pool of up to 70 deduplicated headlines (up to 35 date-sorted headlines from South Korea and 35 from Japan) evaluated for AIFOD mission relevance.
- Stage 1 (Flash-Lite & Compact Format): Selects 10 candidate stories from South Korea and 10 from Japan (total 20 candidates, configurable via
STAGE1_CANDIDATES_PER_COUNTRY) usinggemini-3.5-flash-liteand token-optimized line formatting. - Content Enrichment: Enriches all 20 candidates concurrently with canonical publisher URLs,
og:description, and full lead paragraphs. - Stage 2 (Deep Reasoning Flash): Evaluates enriched texts and generates a strictly balanced 5-article digest (2-3 from Korea and 2-3 from Japan) using rich source text and
gemini-3.8-flash.
- High-Density AIFOD Deliverables:
- English Summary: 3-4 dense, factual sentences naming specific actors, partner countries, dates, venues, and policies.
- AIFOD Strategic Insight: Analytical "So What?" highlighting implications for the Global South without restating summary facts.
- Practitioner Discussion Q&A: A forward-looking policy dilemma paired with an actionable stance representing AIFOD's perspective.
- Premium Email Digests: Compiles a responsive, beautifully styled HTML email with country badges, direct publisher links, and KPI metrics sent directly via the Gmail API.
- GCP Serverless Native: Fully containerized for Google Cloud Run, triggered securely with OIDC authentication by Cloud Scheduler.
🏛️ Technical & Architectural Decisions
-
Tiered Model Routing (
gemini-3.5-flash-lite+gemini-3.8-flash) & Compact Formatting:- Decision: Decouple Stage 1 candidate ID selection from Stage 2 synthesis by routing Stage 1 to
gemini-3.5-flash-litewith compact line formatting ([ID] Title (Source)), reservinggemini-3.8-flashfor Stage 2 factual synthesis. - Context & Motivation: Ingesting up to 70 headlines in indented JSON format with a high-capacity reasoning model consumed unnecessary tokens and budget for what is fundamentally a fast ID classification and ranking task.
- Rationale & Alternatives Considered: Flash-Lite delivers equivalent precision for candidate ranking at roughly 25% of the token cost and sub-second latency. Compact line formatting eliminates redundant JSON syntax tokens while ensuring Stage 2 retains full reasoning capacity for strategic AIFOD insights.
- Decision: Decouple Stage 1 candidate ID selection from Stage 2 synthesis by routing Stage 1 to
-
Two-Stage Country-Balanced AI Pipeline (Candidate Filtering → Content Enrichment → Deep Synthesis):
- Decision: Decouple lightweight headline candidate triage from rich web content enrichment and final structured JSON generation.
- Context & Motivation: Ingesting 400+ raw articles across Korea and Japan into full-content extraction and single-stage LLM context exceeds API token limits, risks rate throttling, and introduces context dilution.
- Rationale & Alternatives Considered: A single-pass approach either forces summarization of bare headlines (producing shallow summaries) or scraping hundreds of web pages (causing network timeouts and bot blocks). Two-stage filtering selects the top 20 candidates (10 KR, 10 JP) first, enriches only those via parallel metadata extraction, and then synthesizes the final 5 balanced stories.
- Consequences & Impact: Guarantees high-signal summaries with exact country parity (2-3 KR, 2-3 JP) while slashing token costs and scraping overhead by >90%.
-
Dual-Engine RSS Retrieval with Bing News Fallback:
- Decision: Query Google News RSS feeds with automatic, transparent fallback to Bing News RSS upon HTTP 503 errors or empty feeds.
- Context & Motivation: Cloud Run egress IP ranges in
asia-northeast1occasionally encounter anti-scraping rate-limiting from Google News endpoints. - Rationale & Alternatives Considered: Deploying rotating residential proxy pools introduces substantial monthly recurring costs and latency. Bing News RSS provides near-100% uptime from datacenter IPs for regional Korean and Japanese keywords.
- Consequences & Impact: Ensures zero-failure daily automated runs without requiring third-party proxy subscriptions.
-
Algorithmic Fuzzy Clustering (RapidFuzz) Prior to AI Ingestion:
- Decision: Cluster raw headlines using token-set ratio similarity (
>= 65%) before passing stories to the LLM. - Context & Motivation: Major government and corporate press releases are syndicated across dozens of Korean and Japanese news outlets with near-identical headlines.
- Rationale & Alternatives Considered: Passing syndicated articles to the LLM wastes candidate quota and risks multiple entries covering the exact same event. Fuzzy token-set clustering merges duplicate coverage into a single best-quality article before AI evaluation.
- Consequences & Impact: Maximizes thematic diversity in the candidate pool without the compute overhead of dense vector embeddings.
- Decision: Cluster raw headlines using token-set ratio similarity (
-
Serverless Cloud Run Containerization with OIDC Cloud Scheduler:
- Decision: Deploy as a containerized Flask microservice on Google Cloud Run triggered via Cloud Scheduler OIDC authentication.
- Context & Motivation: The pipeline runs once per day at midnight (
Asia/Tokyo), making 24/7 provisioned servers economically wasteful. - Rationale & Alternatives Considered: Cloud Run scales to zero between runs, incurring minimal compute costs while isolating execution dependencies (Python 3.11, Docker, C libraries) reliably.
-
Secret Manager Single-Version Retention & Artifact Registry Cost Hygiene:
- Decision: Enforce automated version pruning in
src/config.pyanddeployment/sync_secrets.pyupon updating secrets, coupled with Artifact Registry cleanup policies. - Context & Motivation: Secret Manager charges for active versions exceeding the 6-version free tier. Repeated deployments or secret updates without lifecycle management cause dangling versions and unexpected billing creep.
- Rationale & Alternatives Considered: The application automatically destroys superseded enabled versions upon writing new ones, guaranteeing only the single latest version remains active within the free tier. Container repositories apply lifecycle policies keeping the 3 most recent builds and purging untagged digests older than 3 days. Non-sensitive recipient emails (
RECIPIENT_EMAIL) are injected directly via Cloud Run environment variables to preserve free secret quotas.
- Decision: Enforce automated version pruning in
-
Resilient URL Decoding & Upstream Dependency Isolation:
- Decision: Pin
selectolax<1.0.0inrequirements.txtand wrapgooglenewsdecoderimports defensively with fallback exception handling insrc/rss_parser.py. - Context & Motivation:
googlenewsdecoderrelies on the legacyselectolax.parsermodule. Whenselectolaxreleased major version 1.0.0 with the Modest backend removed, unpinned container builds encountered fatal worker boot crashes. - Rationale & Alternatives Considered: Rather than allowing an external scraper library to break WSGI boot, defense-in-depth ensures the core web server and daily news pipeline continue running with canonical URL fallback even if third-party decoding libraries fail.
- Decision: Pin
📬 Sample Daily Digest Output Preview
Below is an authentic visual preview of the daily email digest delivered directly to the practitioner's inbox via Gmail:
Note
📬 AIFOD Daily Intelligence Briefing — Korea & Japan AI News
Selected and synthesized for international development relevance and policy strategy.
| 📊 Evaluated | 🎯 Selected | ⚖️ Country Balance | 🗓️ Date |
|---|---|---|---|
| 248 articles | 5 stories | 🇰🇷 3 Korea · 🇯🇵 2 Japan | Sep 16, 2026 |
🇰🇷 South Korea's MSIT and KOICA Launch $45M AI Capacity-Building Initiative for ASEAN Partner Nations
🇰🇷 South Korea · Yonhap News • Sep 16, 02:30 UTC
Original: 과기정통부-KOICA, 아세안 개도국 대상 600억원 규모 디지털 AI 역량강화 ODA 사업 착수
South Korea's Ministry of Science and ICT (MSIT), in partnership with KOICA, officially launched a 60 billion KRW ($45M) multi-year ODA initiative on September 15, 2026, aimed at establishing sovereign AI training centers across Indonesia, Vietnam, and the Philippines. The program deploys open-source Korean large language models fine-tuned on local Southeast Asian languages alongside cloud compute subsidies and technical faculty training. Pilot programs will commence in Q1 2027 in Jakarta and Hanoi to develop public sector AI services for healthcare and agricultural monitoring.
Important
AIFOD Strategic Insight
This initiative reflects a crucial shift from generic ICT hardware donations toward high-value sovereign AI capability building in the Global South. For AIFOD, the focus on local language fine-tuning and public sector use cases provides an actionable precedent for avoiding technological dependency on single-nation proprietary LLMs.
Tip
Practitioner Q&A & Stance
Q: How can developing nation partner agencies prevent compute infrastructure gifts from becoming unsustainable once foreign donor subsidies expire?
AIFOD Stance: Multilateral ODA agreements must incorporate tiered local financing roadmaps and prioritize energy-efficient, edge-deployable open-weights models rather than relying indefinitely on recurring high-overhead cloud computing grants.
🇯🇵 Japan's METI and JICA Partner on AI Governance Framework for Global South Industrial Cooperation
🇯🇵 Japan · Nikkei Asia • Sep 16, 04:15 UTC
Original: 経済産業省とJICA、グローバルサウス向けAIガバナンス指針と産業応用支援枠組みを共同策定
Japan's Ministry of Economy, Trade and Industry (METI) and JICA unveiled a comprehensive AI governance guideline on September 15, 2026, specifically tailored for emerging economies across South and Southeast Asia. The framework provides risk-based safety standards aligned with the Hiroshima AI Process while establishing collaborative testing testbeds for SME manufacturing and disaster risk prediction. JICA has earmarked 18 billion JPY over three fiscal years to assist partner ministries in developing their own national regulatory sandboxes.
Important
AIFOD Strategic Insight
Aligning developing economy AI frameworks with the Hiroshima AI Process helps Global South economies participate in global AI supply chains without premature over-regulation. AIFOD practitioners should leverage these Japanese bilateral sandboxes to advocate for inclusive IP protections and local algorithmic fairness standards.
Tip
Practitioner Q&A & Stance
Q: How can developing nation regulators balance rigorous AI risk compliance with urgent local economic innovation demands?
AIFOD Stance: Regulators should adopt agile sandbox models that grant provisional compliance exemptions to high-impact developmental use cases (such as agricultural AI and micro-finance credit scoring) while retaining strict safeguards for citizen biometric data.
Multi-PC Cloud-Native Architecture
This repository is built on a fully portable, multi-PC cloud-native architecture. Developers and automated pipelines can run the project on ANY workstation or Cloud Run container without creating or maintaining local credential files or .env files.
Cloud Service Mapping
| Layer | Google Cloud Service | Canonical Resource | Resolution & Portability Strategy |
|---|---|---|---|
| API Keys & Secrets | Secret Manager | secrets/gemini-api-key/versions/latest |
Resolved via Python SDK on Cloud Run; auto-fallback to gcloud secrets versions access CLI on local workstations. |
| OAuth 2.0 Tokens | Secret Manager | secrets/gmail-agent-token/versions/latest |
Resolved from Secret Manager; refreshed in-memory; updated in Secret Manager automatically; cached only in OS temp dir (tempfile.gettempdir()). |
| OAuth Client Config | Secret Manager | secrets/gmail-oauth-credentials/versions/latest |
Loaded directly from Secret Manager when interactive browser authorization is needed. |
| Persistent State | Cloud Storage (GCS) | gs://<project-id>-ai-news-data/ai-news-aggregator-krjp/state.json (asia-northeast1) |
Regional bucket in Tokyo co-located with Cloud Run; single source of truth for delivered article URLs, hashes, and run counters; local cache strictly in OS temp directory. |
| Operational & Audit Logs | GCS & Cloud Logging | gs://<project-id>-ai-news-data/ai-news-aggregator-krjp/run_log.json + stdout |
Decoupled from memory/state; structured JSON logs emitted to stdout for streaming to Google Cloud Logging. |
Environment Variables & Local Overrides
In this cloud-native architecture, environment variables can be provided via Google Cloud Run environment settings or an optional local .env file (see .env.example):
| Variable | Description | Default | Source / Fallback |
|---|---|---|---|
GCP_PROJECT_ID |
Google Cloud Platform project ID | Auto-resolved | Resolves from gcloud config get-value project |
GCP_REGION |
Cloud Run and Scheduler region | asia-northeast1 |
Environment or CLI parameter |
SERVICE_NAME |
Cloud Run service name | ai-news-aggregator-krjp |
Environment or CLI parameter |
JOB_NAME |
Cloud Scheduler job name | ai-news-aggregator-daily-trigger |
Environment or CLI parameter |
GEMINI_STAGE1_MODEL |
Google Gemini model name for Stage 1 candidate selection | gemini-3.5-flash-lite |
High-efficiency candidate filter |
GEMINI_STAGE2_MODEL |
Google Gemini model name for Stage 2 synthesis | gemini-3.8-flash |
Configurable model identifier |
STAGE1_CANDIDATES_PER_COUNTRY |
Candidates selected per country in Stage 1 | 10 |
10 KR + 10 JP = 20 total candidates |
GEMINI_API_KEY |
Gemini API authentication key | Secret Manager | Secret gemini-api-key |
RECIPIENT_EMAIL |
Target email address for daily digest | Secret Manager | Secret ai-news-recipient-email |
RECIPIENT_NAME |
Recipient name for digest personalization | AIFOD Practitioner |
Local environment override |
TIMEZONE |
Timezone for report dates and scheduling | Asia/Tokyo |
Standard IANA timezone string |
Project Structure
AI-news-aggregator-KRJP/
├── src/ # Application Package
│ ├── __init__.py # Package initialization
│ ├── app.py # Flask web service entry point for Cloud Run (?hours=24&dry_run=true)
│ ├── auth.py # Dual-mode Secret Manager Gmail OAuth loader & refresh
│ ├── config.py # Secret Manager resolution, GCS configuration & search queries
│ ├── email_sender.py # HTML email generator & Gmail REST API sender
│ ├── llm_filter.py # Two-stage candidate selection, enrichment & synthesis
│ ├── main.py # Orchestration pipeline, CLI entrypoint & --dry-run support
│ ├── memory.py # GCS state persistence & decoupled operational run logging
│ └── rss_parser.py # Dual-engine RSS fetcher, RapidFuzz clustering, enrichment
├── deployment/
│ ├── deploy_cloud.ps1 # Automated deployment script (APIs, GCS bucket, IAM, Cloud Run, Scheduler)
│ └── sync_secrets.py # One-shot utility to push local credentials to Secret Manager
├── .dockerignore # Build context exclusion rules
├── .env.example # Cloud architecture environment template
├── .gcloudignore # Cloud Build ignore rules
├── .gitignore # Strict Git ignore rules ensuring zero credential/state pollution
├── Dockerfile # Production container definition (python:3.11-slim)
├── LICENSE # MIT License definition
├── requirements.txt # Python dependencies (includes google-cloud-secret-manager & storage)
└── README.md # Project documentation (this file)
Key files:
- src/config.py: Implements
resolve_cloud_secretandsave_cloud_secretwith dual-mode fallback (SDK +gcloudCLI). - src/auth.py: Resolves Gmail OAuth tokens from Secret Manager without requiring local
token.jsonorcredentials.json. - src/memory.py: Persists delivery state (
state.json) and audit logs (run_log.json) to Google Cloud Storage. - src/main.py: Primary orchestrator supporting lookback windows, state deduplication, and
--dry-run. - deployment/sync_secrets.py: One-shot utility to synchronize local tokens or keys to Secret Manager.
- deployment/deploy_cloud.ps1: Complete infrastructure deployment script.
Multi-PC Zero-Setup & Execution
1. Prerequisites (Any Machine)
Clone the repository on any computer. You only need:
- Python 3.11+
- Google Cloud SDK (
gcloud)
Authenticate your workstation once:
gcloud auth login
gcloud config set project YOUR_GCP_PROJECT_IDInstall dependencies:
pip install -r requirements.txt
2. Zero-Setup Local Dry-Run
Run the complete pipeline end-to-end with ZERO local .env, credentials.json, or token.json files:
# Execute dry-run (fetches news, clusters, runs Gemini, verifies HTML compilation without sending email)
python -m src.main --dry-runAll credentials (gemini-api-key, gmail-agent-token, gmail-oauth-credentials) are pulled on-the-fly from Secret Manager!
3. Running a Live Harvest Locally
To run a live daily news harvest and send the digest email:
python -m src.main
Or specify a custom lookback window:
python -m src.main --hours 48
4. Interactive Re-Authentication (If Refresh Token Revoked)
If OAuth tokens need to be generated or renewed:
python -m src.main --auth
This automatically retrieves OAuth client secrets from Secret Manager, opens the browser consent screen, and pushes the newly minted token directly into Secret Manager.
5. Syncing Local Credentials to Secret Manager
If you ever have local credentials or keys you wish to upload in one shot:
python deployment/sync_secrets.py
Cloud Deployment (Google Cloud Run & Cloud Scheduler)
Automated deployment to Google Cloud Platform is managed via the PowerShell deployment script:
.\deployment\deploy_cloud.ps1 -ProjectId "YOUR_GCP_PROJECT_ID" -RecipientEmail "recipient@example.com"
What the Deployment Script Does:
- API Enablement: Enables
run.googleapis.com,cloudbuild.googleapis.com,artifactregistry.googleapis.com,cloudscheduler.googleapis.com,secretmanager.googleapis.com, andstorage.googleapis.com. - GCS Bucket Setup: Automatically provisions
gs://<project-id>-ai-news-datafor state and log persistence. - Secret & IAM Permissions: Ensures
ai-news-recipient-emailexists in Secret Manager if provided, and grantsroles/secretmanager.secretAccessorandroles/storage.objectUserto the Cloud Run runtime service account. - Container Build & Deploy: Builds and deploys the container from source to Cloud Run as a private service with configured environment variables.
- Scheduler Job: Configures Cloud Scheduler recurring trigger (
0 0 * * *inAsia/Tokyo, 300s attempt deadline) with OIDC authentication to invoke Cloud Run daily.
Verification & Manual Trigger
You can manually trigger the deployed Cloud Run service through Cloud Scheduler at any time using gcloud:
gcloud scheduler jobs run ai-news-aggregator-daily-trigger --location=asia-northeast1To view live Cloud Run execution logs:
gcloud beta run services logs tail ai-news-aggregator-krjp --region=asia-northeast1
License
This project is licensed under the MIT License.
来源:GitHub · github.com