見出し画像

RSSフィード(Google News・Alert等)から最新ニュースを取得、整形、要約するツールをGPTsとDifyで作ってみた

こんにちは、Gotaです!

Google NewsやGoogle Alert等のRSSフィードURLから、一度にまとめて最新ニュースを取得、整形、要約できるツールをGPTsとDifyで作りましたので実装含めて公開します!

最新ニュースを楽にまとめるのは意外と難しかったですが、このツールを使えば情報収集と共有が簡単に!ツールから得られた回答をslackに貼り付けて書式設定するだけでそのまま送れます!

RSSフィードのURLさえあれば、記事をリストスタイルと詳細スタイルの2つのスタイルでまとめることが可能です。

作成したGPTs

Difyでも作ってみました!

Difyのワークフローで作成したプロトタイプ

GPTsは実装まで公開していますので、同じような悩みがある方の参考になれば嬉しいです!(RSSフィードはGoogle NewsとGoogle Alertのみに対応させているなど機能をかなり絞ってます)GPTsはchatGPT plusの費用のみで構築可能で安いです。

記事が参考になれば、いいねやシェアしてくれる嬉しいです!




作成の背景

LLMのような進歩の早い分野では情報収集は欠かせません。
皆さんはどのようにして関心のある分野の最新情報を収集していますか?

私自身LLMは、海外のニュースレターやX、人づて等様々な情報ソースから情報収集していますが、中でもGoogle Alertを利用して特定のキーワード(LLMやGenerative AI)のニュースを拾うことで世の中のトレンドも追えるようにしています。

進歩が速すぎるので、情報収集とキャッチアップだけでも結構労力使いますね。

個人であれば、毎日自動で取得されたニュースのslack投稿やメールで送られてくるニュースレターをインプットするだけでも良いのですが、会社やコミュニティで他の人にも共有しておきたいニュースがありますよね!

RSSフィードを使って自動でSlackに投稿する方法もありますが、最新のニュースが毎回通知されることにより情報負荷が高くなり、ニュースチャンネルがまったく見られなくなるような状況にもなります。

その問題を解決すべく、「1日1回業界ニュースや関連情報をまとめた投稿」を送るようにしていましたが、この作業には意外と時間がかかっていました。

そこで今回は「複数のRSSフィードから一度にまとめて最新ニュースを取得、整形、要約できる」ツールをGPTsとDifyで作りました。

ツールの全体像

ツールは下記の図のような流れで実行されています。

ツールの全体像
  1. ユーザーから取得したいRSSフィードのURL(複数でも可)と要約スタイルをGPTsに伝える

  2. GPTsがToolを実行するよう指示出し

  3. ToolでGoogle NewsやGoogle AlertのRSSフィードURLから最新ニュースを取得するようリクエストしレスポンスを得る

  4. 得たレスポンスをApps Scriptで整形し、GPTsにニュースデータを渡す

  5. 要約スタイルに従ってニュースをまとめてユーザーに返す


それでは実際のGPTsの作り方を説明します。

1. 最新ニュースの取得するRSSフィードの設定

大規模言語モデルは学習時点までの訓練データしか持っていませんので、最新のニュースを取得する必要があります。検索で最新ニュースを収集してベクトルデータベース化する案の一つですが、検索で最新のニュースをインデックスさせるのが難しいです。

そこで今回は、Google AlertやGoogle NewsのRSSフィードを利用して最新ニュースを取得します。RAGで難易度の高いプロセスの一つである適切な外部ソースを取得するプロセスを外部サービスで解決します!

Google Alertの設定方法

Google Alertは特定のキーワードがオンラインで使用されるたびに通知を提供する無料サービスです。「Generative AI」のキーワードに関連する記事をGoogleが自動で取得してくれるため、最新情報を取得する際に便利です!RSSフィードは大手情報メディアやブログなどでも公開されているため、他媒体にも適用可能です。

Google Alert

Google Alertはデフォルトでは配信先がメールアドレスになっていますが、配信先をRSSフィードにします。そうするとRSSフィードのURL「https://www.google.co.jp/alerts/feeds/07271284843608853569/18030287786298723607」を取得できます。(下記のようなXMLファイルです)

Google AlertのRSSフィード

Google Newsで自由自在に最新ニュースを取得!

より詳細に最新ニュースを取得できるようにするため、Google NewsのRSSからも取得できるようにしました。Google Alertでは直近のニュースのみでコントロールが効きませんが、Google NewsではRSSフィードURLのパラメータを変更すれば期間や言語を自由に変えてニュースを取得出来ます。

例えば、2024/5/1から2024/5/7までに配信され「LLM」に関する日本語のニュースを取得するなら下記のようなURLでニュースを取得することが出来ます。

https://news.google.com/rss/search?q=llm+after:2024/5/1+before:2024/5/7&hl=ja&gl=JP

2. 最新ニュースをLLMで取得できるツールの作成

Google AlertやGoogle NewsのRSSフィード内の記事一覧をLLMのコンテキストとして利用するためには、LLMが必要な情報を構造化して取得できる必要があります。

RSSフィードのアウトプットはXML形式であり、現時点でLLMに実装されているWebScraperなどでは直接的にデータを取得することが難しいです。

今回RSSフィードのURLからデータを取得し、構造化して記事一覧として返す処理をApps Scriptで行います。(制限はあるが無料なので)

GPTsやDifyでツールとして利用できるようにすることでLLMで記事一覧を参照できるようにします。

  1. Apps Scriptで入力されたRSSフィードURLから記事一覧を取得し、JSON配列として返すようツール化

  2. LLMがツールから記事一覧を取得できるようにし、一覧形式で返したり、ニュースを要約したりと整形する(フロントエンド:GPTs, Dify)

Apps Scriptのコード作成とデプロイ

Apps Scriptでは下記の処理を行っています

  • 複数のRSSフィードURLの入力に対して、記事一覧をまとめて出力できるような処理

  • Google AlertとGoogle NewsでRSSフィードのXML構造が異なるため、記事一覧の出力を揃える処理

function doGet(e) {
  // Google News、Google AlertのRssフィードURLを配列形式で受け取り、JSON配列形式でニュースを取得する
  const url_arr = e.parameters.urls;

  let data = [];
  let data_arr = [];
  
  for (let i = 0; i < url_arr.length; i++){
    let url = url_arr[i];
      if (url.includes('news.google.com')) {
      contents = parseGoogleNewsRss(url);
      data.push(...contents['items']);
    } else if (url.includes('google.co.jp/alerts')) {
      contents = parseGoogleAlertsRss(url);
      data.push(...contents['items']);
    } else {
      ;
    }
  }

  for (let i = 0; i < data.length; i++){
    data_arr.push(JSON.stringify(data[i]))
  }

  let result = ContentService.createTextOutput()
  result.setMimeType(ContentService.MimeType.JSON)
  result.setContent(data_arr)

  return result;
}


function parseGoogleAlertsRss(url) {
  const response = UrlFetchApp.fetch(url);
  const xml = response.getContentText();

  const document = XmlService.parse(xml);
  const root = document.getRootElement();
  const namespace = XmlService.getNamespace('http://www.w3.org/2005/Atom');
  const entries = root.getChildren('entry', namespace);

  const feedId = root.getChild('id', namespace).getText();
  const feedTitle = root.getChild('title', namespace).getText();
  const feedTitleWrang = feedTitle.replace('Google アラート - ', '');
  const feedLink = root.getChild('link', namespace).getAttribute('href').getValue();
  
  let results = {
    feed: {
      id: feedId,
      title: feedTitle,
      link: feedLink
    },
    items: []
  };

  for (var i = 0; i < entries.length; i++) {
    const entry = entries[i];
    const title = entry.getChild('title', namespace).getText();
    const url = entry.getChild('link', namespace).getAttribute('href').getValue();
    const content = entry.getChild('content', namespace).getText();

    const paramUrl = url.match(/url=([^&]+)/)[1];
    
    results['items'].push({
      category: feedTitleWrang,
      title: title,
      url: paramUrl,
      content: content
    });
  }

  return results
}

function parseGoogleNewsRss(url) {
  const response = UrlFetchApp.fetch(url);
  const xml = response.getContentText();

  const document = XmlService.parse(xml);
  const root = document.getRootElement();
  const namespace = XmlService.getNamespace('http://search.yahoo.com/mrss/');
  const channel = root.getChild('channel');
  const entries = channel.getChildren('item');

  const feedTitle = channel.getChild('title').getText();
  const feedTitleWrang = feedTitle.replace(' - Google ニュース', '').replace('"', '');
  const feedLink = channel.getChild('link').getText();
  
  let results = {
    feed: {
      title: feedTitle,
      link: feedLink
    },
    items: []
  };

  for (var i = 0; i < entries.length; i++) {
    const entry = entries[i];
    const title = entry.getChild('title').getText();
    const url = entry.getChild('link').getText();
    const content = entry.getChild('description').getText();

    
    results['items'].push({
      category: feedTitleWrang,
      title: title,
      url: url,
      content: content
    });
  }
  return results
}

コードを保存後、Apps Scriptでウェブアプリとしてデプロイしてください

ウェブアプリのURLをGPTs Actionsの設定として利用します。

GPTsのActionsのyamlを設定する

Actionsは下記のようなYaml形式で設定します

openapi: 3.0.0
info:
  title: RSS Feed Fetcher API
  description: This API retrieves entries from specified RSS feed URLs and returns them in JSON format.
  version: 1.0.0
servers:
  - url: https://script.google.com
    description: Google Apps Script as a web app
paths:
  {ここに/macros/配下のpathを入れる}:
    get:
      operationId: fetchRssFeeds
      summary: Fetch RSS feed entries from specified URLs.
      parameters:
        - name: urls
          in: query
          description: Comma-separated list of URLs to fetch RSS feeds from.
          required: true
          schema:
            type: array
            items:
              type: string
            style: form
            explode: true
      responses:
        "200":
          description: Successfully retrieved RSS feed entries
          content:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/FeedEntry"
        "400":
          description: Bad request, possibly due to missing or incorrect URL parameters.
        "500":
          description: Internal server error, possibly due to issues in fetching or parsing the feed.
components:
  schemas:
    FeedEntry:
      type: object
      properties:
        category:
          type: string
          description: Title of the RSS feed which the entry belongs to.
        title:
          type: string
          description: Title of the entry.
        url:
          type: string
          description: URL of the original article.
        content:
          type: string
          description: URL of the original article.

GPTsのプロンプトを作成する

GPTsのプロンプトはこちらです。正直プロンプトは最適化したいです。

# Instructions
あなたのタスクはRSSフィードURLからデータを取得し、Stepに従い情報を整理することです

# Step
1. 与えられたURLを基にfetchRssFeedsを実行する
2. fetchRssFeedsの取得結果で、content_titleの類似度が高い記事を除外してユニークな記事のみに絞り込む
3. 指定されたstyleを基にSummaryを作成する
- デフォルトは`list style`です。FormatとConstraintsを基に各カテゴリのSummaryを作成
- `detailed style`の指定があった場合、FormatとConstraintsを基に記事詳細を要約する
4. FormatとConstraintsを基に、絞り込まれた記事を整形する。text形式で出力する

# Tools
- fetchRssFeeds: RSS FeedのURLからRSS Feed内に含まれる記事一覧を返す
- Web Browsing: RSS Feedの記事の要約作成時に最新トレンドを検索する

# Format
## list style
style:```
■ ${cagetory}
1.  [${content_title}](${url})
2. [${content_title}](${url})
...
Summary: ${Summarize the category information releavent to rss feeds in japanese. Before a summary, please run web browsing and consider both the latest trends in the relevant category obtained through web searches and the content gathered from RSS feeds. }${cagetory2}
1.  [${content_title}](${url})
2. [${content_title}](${url})
...
Summary: ${Summarize the category information releavent to rss feeds in japanese. Before a summary, please run web browsing and consider both the latest trends in the relevant category obtained through web searches and the content gathered from RSS feeds. }
```

example:```
■ LLM
1. [ メタ「Llama 3」登場で本格化、オープン型とクローズド型AIの競争 | WIRED.jp](https://wired.jp/article/metas-open-source-llama-3-nipping-at-openais-heels/&ct=ga&cd=CAIyHDk4NDk1YjA3YmVjZTkxZjg6Y28uanA6amE6SlA&usg=AOvVaw3bdywwWdBjzF4gVnNg-oCM)
2. [NVIDIAのローカルAI「ChatRTX」、AIモデル追加で画像認識や音声入力が可能に - 窓の杜](https://forest.watch.impress.co.jp/docs/news/1588945.html&ct=ga&cd=CAIyHDk4NDk1YjA3YmVjZTkxZjg6Y28uanA6amE6SlA&usg=AOvVaw2rhcLb3-cjDUHq-L-QH2P6)
3. [GPT-4が道徳テストで人間の大学生より優れたスコアを示す - ライブドアニュース](https://news.livedoor.com/article/detail/26335963/&ct=ga&cd=CAIyHDk4NDk1YjA3YmVjZTkxZjg6Y28uanA6amE6SlA&usg=AOvVaw0p57SctF0ESaoAokBiPgB-)

Summary: Llama 3の登場でクローズLLMとローカルLLMの熾烈な競争に拍車がかかりそう
```

## detailed style
style:```
■ ${cagetory}
- [${content_title}](${url})
> ${Browse the url by web search and summarize the article details}

- [${content_title}](${url})
> ${Browse the fetch url by web search and summarize the article details}${cagetory2}
- [${content_title}](${url})
> ${Browse the fetch fetch url by web search and summarize the article details}

- [${content_title}](${url})
> ${Browse the fetch url by web search and summarize the article details}

...
```

# Constraints
- RSSフィードURLからデータが取得出来なかった場合、データを取得出来なかった旨を返してください
- 取得された結果以外返さないでください
- list styleで一覧化する最大記事数は20記事以上とする。もし記事数が20記事以上存在しなければ、取得した最大記事数とする
- Summaryを作成する際は、Web検索で該当カテゴリの直近のトレンドとRSS Feedから取得した内容の両方とも加味したうえで、100文字程度で要約してください。


# デフォルトRSSフィード URLリスト
- https://www.google.co.jp/alerts/feeds/07271284843608853569/5129268474220093805
- https://www.google.co.jp/alerts/feeds/07271284843608853569/18030287786298723607

GPTsを公開すれば完成です〜!

Difyのワークフローで作成してみた

Difyのワークフローを使ってRSSフィードURLから記事を取得し、LLMを使って一覧形式でまとめてくれるツールを作成してみました。

Difyの方は完成度は正直もう一歩です。

Difyのワークフローで作成したプロトタイプ

DifyでGPTsと同様のツールを作るにあたり、一つ壁にぶち当たっています。RSSフィードから記事一覧の取得をDifyのカスタムツールを用いて実行していますが、OpenAIのActionsと仕様が異なるようで、複数のRSSフィードURLのインプットをするとデータを取得することが出来ません。OpenAIのActionsでは複数のURLを配列形式を渡すのに対し、DifyのカスタムツールではURLカンマ区切りリストで渡していることでエラーが起こっているのではないかと推測してます。修正方法が知りたいところです。

記事長くなったので、需要あれば実装方法を別記事でちゃんとまとめるかも。

まとめ

情報収集は手段が変われどこれからも必要です。

情報収集と整理に生成AIに丸投げして、ラクしちゃいましょう!

もし記事が参考になれば、良いねとシェアしてくれると嬉しいです!

実装に関するフィードバックもいただけると助かります!

LLMに関する副業も始めようかなと考えており、絶賛募集中です。本業はデータアナリストをしており、データ利活用戦略やアプリのグロースハックも併せてサポートできますので、もしご興味ある方いらっしゃればお声がけください。

いいなと思ったら応援しよう!

この記事が参加している募集