Zenn
🧑‍💻

AIニュースレターを支える技術

に公開
18

こんにちは!ML_Bearです。

先日、「ML_Bear Times ニュースレター創刊のお知らせ」という記事で、AI・機械学習の最新情報をお届けするニュースレター ML_Bear Times を創刊したことをご報告しました。

この記事では、ML_Bear Timesが、AI技術を活用して自動生成・配信されている技術的な裏側をご紹介します。

tl;dr

  • LangGraphで実装したAI Agent(ic workflow)によりGemini 2.5 ProClaude 3.7 Sonnetを組み合わせてAIニュースの収集・選定・執筆・配信を自動化
  • FAISSのセマンティック検索とGemini 2.5 Proで過去ニュースとの重複を排除
  • OGP画像は、Gemini 2.5 ProがHTMLテンプレートに定型文や日付を書き込み、Playwrightでレンダリング&スクショして作成
  • PromptingはLangSmithのPlaygroundで地道に実施
  • GhostブログエンジンのAPIにより入稿とメール送信を自動化

ニュースレター作成フロー

ML_Bear Timesの作成・配信は、以下の3つのプログラムで自動化しています。

1. コンテンツ収集プログラム

  • 様々なニュースソースからAI関連情報を収集します。一般的なクローラーやAPIを利用したもので面白いポイントもないので今回は割愛します。

2. ニュースレター本文作成プログラム

  • 収集した情報から、配信するニュース候補を選定し、説明文を作成します。
  • 過去のニュースレターの内容と照らし合わせ、重複するニュースを除外または更新します。
  • 確定したニュース一覧から、ニュースレターのタイトルと導入文(リード文)を生成します。

3. 送信プログラム

  • ニュースレターの内容に基づき、OGP画像(アイキャッチ画像)を動的に生成します。
  • 生成された本文とタイトルをMarkdown形式にまとめ、入稿用HTML形式へ変換します。
  • GhostブログエンジンのAPIを利用して記事を入稿・配信します。

本記事では、AI技術が深く関わる「ニュースレター本文作成プログラム」と「送信プログラム」について詳しく見ていきます。

ニュースレター本文作成プログラム

ニュースレター本文作成プログラムはLangGraphベースで実装しています。冒頭ではAI Agentと書きましたが、Reflectionなどは行なっていない単なるワークフローです。

ニュース候補作成

まず、収集したコンテンツを Gemini 2.5 Pro に渡し、ニュースレターで取り上げるべきニュース候補とその説明文を作成させます。以下のようなプロンプトを用いて、前回配信したニュースレターと被らず、多くの人が言及しているニュースを抽出します。

(以下は簡略化したプロンプトです)

あなたは優秀なメールマガジン配信者のアシスタントです。
過去数時間で起こったAI関連の出来事についてニュースレターを書きます。

以下のルールやノウハウを参照し、ニュースレターに取り上げるべきトピックを選んでください

## 基本ルール
- 10個程度のトピックを作成する
- 複数人が言及している内容を優先する
- トピックは言及されているツイートが多い順に並べる
- OpenAI、Anthropic、Googleの3大LLM関連企業の重要な発表は必ず取り上げる
- Kaggle関連の話題は積極的に取り上げる
- トピックタイトルは30文字程度で簡潔かつ明瞭に

## トピックタイトルの書き方のコツ
- トピックはなるべく細かく分ける
  - トピックの粒度が大きいとトピックのタイトルも長くなり、読者にとって読みづらくなります。
- トピックはそのヘッドラインを読んだだけで何が起こったかわかるようにする
  - 「最近のLLMの動向」のような大雑把なトピックは避けてください
  - 新聞社の見出しのように短く情報が詰まっているようにするとベストです

## なんか色々他にもルールや決まり事たくさん
- hoge
- fuga

## 過去数時間のニュース
{news_content}

## 前回配信したニュースレター
{previous_newsletter}

実際のプロンプトはもっと長く、過去数時間のニュースや前回配信したニュースレターを丸ごと入力しているため、入力は3-4万トークンになっています。コンテキストが長く、ニュースの選定もそこそこ難しいタスクだと思うのですがGemini 2.5 ProClaude 3.7 Sonnetは難なくこなしていました。最近のReasoningモデルは本当に賢いですね。今回はコストと性能のバランスからGemini 2.5 Proを選定しました。

ニュース重複チェック

ニュースレターでは情報の鮮度が重要です。同じ話題を繰り返し取り上げると読者の満足度が下がるため、ニュース候補リストアップ後は過去配信ニュースとの重複チェックを厳密に行っています。

  1. ベクトル化: Geminiが生成したニュース候補(タイトル+説明文)をOpenAI Embedding API(text-embedding-3-small)でベクトルに変換
  2. 類似過去ニュース検索: FAISSを使って過去ニュースから意味的に類似するものをセマンティック検索
  3. LLMによる判断: 類似の過去ニュースが見つかった場合、Gemini 2.5 Proに渡し、「完全に重複 (SKIP: 掲載却下)」「一部更新が必要 (UPDATE: 説明修正)」「重複なし (KEEP: そのまま掲載)」を判断させます
  4. データベース更新: 採用(KEEPまたはUPDATE)されたニュースはベクトル化してFAISSに追加

3.で利用しているプロンプトは以下のようなものです (↑と同様に簡略化したもの)

与えられた"今回のニュース"と"過去類似ニュース"を比較し、以下の結果を返すこと。
- 過去のトピックと完全に重複なら"SKIP"
- 一部更新が望ましいなら"UPDATE"
  - 例
    - 過去の類似トピックと内容が一部重複
    - トピックのタイトルが長すぎる/間違っている
  - この場合、修正したニュースタイトルと説明文も返すこと
- 問題なければ"KEEP"

# 今回のニュース
{news}

# 過去類似ニュース
1. {past_news_titles[0]}: {past_news_contents[0]}
2. {past_news_titles[1]}: {past_news_contents[1]}
3. {past_news_titles[2]}: {past_news_contents[2]}
4. {past_news_titles[3]}: {past_news_contents[3]}

FAISSでセマンティック検索を行うことで、言い回しが異なっていても同じ内容のトピックを発見できます。例えば「OpenAIがGPT-5を発表」と「GPT-5がついに公開、OpenAIが発表」は異なる表現ですが、セマンティック検索では高い類似度として検出されます。ローカルで簡単に扱えるためFAISSを選びましたが、他の手段でも問題ありません。

この重複チェック機構により、新鮮な情報を提供しつつ、過去の話題に対するアップデート(続報)も適切に扱えるようにしています。

ニュースレタータイトル & リード文作成

重複チェックを経て掲載が決まったニュース一覧から、ニュースレター全体のタイトルと導入文(リード文)を作成します。

この工程では Claude 3.7 Sonnet を使用しています。Claudeは自然で読みやすい文章生成に長けている印象があり、ニュースレターの導入部分に適していると考えて採用しました。(これはあくまで僕の文章の好みによる影響が大きいかも)

ここまでで、ニュースレターのコンテンツ生成は完了です。

ニュースレター送信プログラム

コンテンツ生成完了後、配信の準備に入ります。

OGP画像作成

ニュースレターがSNSでシェアされた際に魅力的に見えるよう、以下のようなOGP画像(アイキャッチ画像)を自動生成しています。

これは以下のように作成しています。

1. [事前準備] HTMLテンプレート作成: まず、OGP画像の元となるHTML/CSSテンプレートを作成しました。個人的にGemini 2.5 ProのHTML/CSSデザインが好きなのでGeminiを選びました。日付、エディション(朝刊/夕刊)、主要トピック3つ、総トピック数を埋め込めるようになっています。
2. コンテンツ埋め込み指示: 配信するニュースレターの文章をGemini 2.5 Proに渡し、HTMLテンプレート内の対応する箇所に埋め込む変数を回答させます。「ニュースタイトルが長すぎる場合は、意味を損なわない範囲で短縮して」といった指示も加え、タイトルが画像になるべく収まるようにしています。
3. レンダリングとスクリーンショット: Geminiが生成した変数をHTMLテンプレートに埋め込み、Playwrightでヘッドレスブラウザレンダリングし、結果をスクリーンショットとして画像ファイル(PNG形式)で保存します。(もっと簡単な方法もあるかも😅)

<!-- OGP画像のHTMLテンプレート(一部抜粋) -->
<!-- HTML前半には大量のCSSマークアップがある -->
<div class="content-area">
  <div class="main-content">
    <h1>{yyyy}年{mm}月{dd}日<br>{edition_jp}のAIニュースまとめ</h1>
    <ul>
      <li>{topic_1}</li>
      <li>{topic_2}</li>
      <li>{topic_3}</li>
      <li>…など全{total_topic_num}トピック</li>
    </ul>
  </div>
</div>

2. MarkdownからHTMLへ変換、GhostへAPI入稿

OGP画像が用意できたら、LLMが生成したニュースレター本文(Markdown形式)をHTML形式に変換したものと合わせてGhost CMSのAPIを通じて自動入稿・配信します。

ニュースレター配信基盤としてGhostを選定した理由は複数あります。第一に、HTML形式のコンテンツをAPI経由で簡単に入稿できる点が決め手でした。当初はSubstackも検討しましたが、API入稿ができず、ブラウザ操作による自動入稿もログインのボット対策が厳しそうだったため断念しました。

また、完全に趣味の領域ですが、独自ドメイン設定が容易な点も魅力でした。デザイン面では、Ghostのデフォルトテンプレートを少しカスタマイズするだけで十分な品質のニュースレターデザインを実現できました。

システム全体で共通する工夫

これらのプログラムを安定して動かすために、いくつかの工夫をしています。

LLMのフォールバック機構

2025年4月現在、Gemini 2.5 Proはまだ不安定なため、入念にフォールバックを行なっています。具体的には3回retryしてもダメならClaudeに切り替えています。(なぜかGeminiからnullだけ返ってくることがたまにある)

Structured Output

LLMからの出力をプログラムで確実に処理するため、出力形式をJSONスキーマなどで厳密に定義する Structured Outputを積極的に活用しています。

最近のLLMは賢いので出力形式をプロンプトで指定するだけでもほぼ守ってくれますが、毎日安定してニュースレターの処理が行えるよう、念には念を入れています。LangChainだとPydantic構造体を用いて以下のように簡単に書けるので便利です (もちろん他のフレームワークでも簡単に書けます)。

from typing import List
from pydantic import BaseModel, Field
from langchain_google_genai import ChatGoogleGenerativeAI

class NewsletterTopic(BaseModel):
    title: str = Field(description="トピックのタイトル。この一文を読んだだけで何がどうなったかを理解できるように書くこと。")
    descriptions: list[str] = Field(description="トピックの解説文の箇条書き。書き初めの「・」や「-」などは不要。(あとでこちらでつけます)")

class NewsletterTopics(BaseModel):
    topics: list[NewsletterTopic] = Field(description="List of topics to be included in the newsletter")

llm = ChatGoogleGenerativeAI(
    model="gemini-2.5-pro-exp-03-25",
    max_tokens=32768,
    temperature=0.1
)
structured_llm = llm.with_structured_output(NewsletterTopics)
result_object = structured_llm.invoke(prompt)

Prompting (Prompt Engineering)

ここまで紹介してきたように、各工程でLLMに対して詳細な指示を与えています。期待通りの出力を得るには、まだ緻密なPromptingが必要だと感じています。Reasoning系モデルはPromptの追従力が高いので昔より確実に楽にはなってます。

PromptingはLangSmithのPlaygroundを活用して地道に行いました。エージェントワークフローの途中のPromptを変更してテストする際には、LangSmithなどのTracerが使うのが便利です。(Langfuseなど他のTracerでも勿論良い)。

おわり

AIニュースレター「ML_Bear Times」の技術的裏側をご紹介しました。LLMと各種ツールを組み合わせた自動化の試みを通じて、多くの学びと発見がありました。

完全自動化されているニュースレターならではの課題もありますが、今後も不具合に気づくたびに少しずつ改善していければと思っております。このニュースレターがAI・機械学習のみなさまの情報収集に役立てば幸いです。

ML_Bear Times のご購読は公式サイトからどうぞ😇
(※メールアドレス登録なしでもサイトから最新版を閲覧可能です)

最後までお読みいただき、ありがとうございました。

18

Discussion

ログインするとコメントできます