RH.
RYOGO_HASUMOTO — PORTFOLIO // 2026
← BACK_TO_INDEX
POSTED: 2026.07.14

無料枠だけでを作る — LLMプロバイダ切替とサーバー管理ゼロの運用

経済ニュースを追うのは時間がかかるわりに、頭に残るのは「今日ざっくり何があったか」程度。それなら収集も要約も機械にやらせればいい——ということで、RSS で経済ニュースを自動収集し、LLM が毎朝「前日のざっくりまとめ」を生成して Discord に届けてくれる個人用ツール「NEON WIRE」を作った。

デモ: https://neon-wire.onrender.com/

技術的には「データを自動収集 → LLM で加工 → 定時に配信して蓄積する」という、業務でもよくあるパターンの一人分サイズの実装になっている。この記事では、作ってみて特に効いた 3 つの設計判断を書く。

全体構成

  • 収集: 国内外 9 の RSS フィードを定期取得し、SQLite に蓄積(重複排除)
  • 生成: 1 日分の見出し・概要を LLM に渡し、「総括/国内経済/海外・マーケット/注目トピック」構成の日本語ダイジェストを生成
  • 配信: Discord Webhook へ自動投稿
  • UI: Express + vanilla JS のニュース端末風フロントエンド

スタックは Node.js / Express / libSQL (Turso) / node-cron / GitHub Actions。フレームワークなしの素朴な構成にしている。

1. LLM プロバイダの抽象化 — 「どの LLM で動かすか」を環境変数に追い出す

このアプリはローカルの Ollama、Claude API、Gemini API のどれでも動く。生成部分を関数テーブルにして、プロバイダの解決を 1 箇所に寄せた。

// anthropic | gemini | ollama。未指定ならキーがある方、なければ ollama
export function resolveProvider() {
  const p = (process.env.DIGEST_PROVIDER || '').toLowerCase();
  if (p === 'anthropic' || p === 'gemini' || p === 'ollama') return p;
  if (process.env.ANTHROPIC_API_KEY) return 'anthropic';
  if (process.env.GEMINI_API_KEY) return 'gemini';
  return 'ollama';
}

const generators = {
  anthropic: generateWithAnthropic,
  gemini: generateWithGemini,
  ollama: generateWithOllama,
};
const content = await generators[provider](prompt);

プロバイダを増やすときは生成関数を 1 つ足すだけ。実際、最初は Ollama と Claude の 2 択だったところに Gemini を後から足したが、変更は生成関数 1 つと設定の追記だけで済んだ。

もう 1 つ効いたのが「プロバイダごとの入力予算」。ローカルの 8B モデルはコンテキストが小さいので、渡す記事数と概要の文字数をプロバイダ別に絞っている。

const LIMITS = {
  anthropic: { maxArticles: 120, descLen: 160 },
  gemini: { maxArticles: 120, descLen: 160 },
  ollama: { maxArticles: 80, descLen: 80 },
};

「モデルの制約から逆算して入力を設計する」のは、LLM をアプリに組み込むときに毎回やることになる作業だと思う。抽象化の単位に入力予算まで含めておくと、切替がただの設定変更になる。

2. ローカル LLM をデフォルトにする — データを外に出さない選択肢を残す

API キーを何も設定しなければ、このアプリは Ollama(ローカル LLM)で動く。つまり収集したデータを一切外部に送信せずに全機能が使える。キーを置けば自動的に API 系へ切り替わる。

個人ツールでここまでする必要はないのだが、これは意図的な設計で、「社内データを扱う業務ツール」への転用を想定している。LLM を業務に入れるときにまず聞かれるのは「そのデータはどこに送られるのか」で、「ローカルで完結する構成が既定で、必要なら API に切り替えられる」と答えられる作りにしておきたかった。

関連して、記事の本文は保存していない。RSS の見出しと概要だけを扱い、本文はリンク先の各メディアで読む設計にしている(著作権と保存コストの両面の判断)。

3. 無料枠だけの全自動運用 — サーバーを 1 台も管理しない

このアプリは今、毎朝 6:50 に前日分のダイジェストを生成して Discord に投稿し、日中は 6 回フィードを取得して記事を蓄積している。この運用に使っているのは以下の 3 つで、月額は 0 円。

  • GitHub Actions: 定時実行(cron)。取得・生成・投稿のジョブを回す
  • Turso: SQLite 互換のクラウド DB。記事とダイジェストの永続化
  • Render: Web UI のホスティング(無料プラン)
on:
  schedule:
    # フィード取得のみ(JST 7:17 / 10:17 / 13:17 / 16:17 / 19:17 / 22:17)
    - cron: '17 22,1,4,7,10,13 * * *'
    # 取得 + 前日分ダイジェスト生成 + Discord 投稿(JST 6:50)
    - cron: '50 21 * * *'

ポイントは DB を Turso に置いたことで、GitHub Actions・Web UI・ローカル開発が同じデータを共有できるようになった。Actions が朝に生成したダイジェストは、そのまま Web UI で閲覧できる。

DB 層は libSQL クライアントで書いてあり、環境変数がなければローカルファイル、TURSO_DATABASE_URL があれば Turso に接続する。ローカル開発とクラウド運用が同一コードで、開発時はネットワークすら不要になる。

Render の無料プランは 15 分でスリープするが、データが Turso にあるので「起きたら全履歴が見える」。スリープをデータ消失ではなく単なる起動待ちに格下げできたのが、この構成の一番気に入っているところ。

コストと運用の実際

項目

サービス

月額

定時実行

GitHub Actions

0 円

DB

Turso

0 円

Web UI

Render

0 円

LLM

Gemini API(無料枠)/ ローカル Ollama

0 円

サーバーの OS 更新も証明書も監視も存在しない。個人ツールの運用としてはこれが正解だと思っている。

おわりに

このアプリは要件定義から実装・動作確認まで AI コーディングエージェント(Claude Code)主導で開発した。人間側の仕事は要件の言語化・レビュー・実機検証で、たとえば「API キーを URL クエリで渡していたのをヘッダーに直す」といった指摘はレビューで拾っている。初版はほぼ 1 日、Discord 連携から無料枠での全自動運用までの拡張もほぼ 1 日だった。

「収集 → LLM 加工 → 定時配信」のパターンは応用が利くので、同じ構成で別ドメインのツールを作るのも簡単だと思う。デモは下記から触れます。

https://neon-wire.onrender.com/

--- EOF ---