ChatGPT

【Slack連携】OpenAIでYahoo!ニュースの主要トピックス新着記事(RSS)を要約し、Slackに投稿する

openai-slack
o_wani

手軽に素早く最新ニュースを要約する方法を調べました

性能がどんどん上がっているOpenAIですが、もちろん記事の要約も簡単にできちゃうようです。

今回は試しに、Yahoo!ニュースの主要トピックスの記事のRSSを読み込んで、要約してSlack連携する処理を下記の記事を参考に作ってみました。

summary

まずは、連携の準備として、OpenAIのシークレットキーの発行とSlackのIncomingWebhooksのURLを取得します。

OpenAIのシークレットキーの発行

OpenAIのログイン画面からログインし、下記のようにログイン直後の選択画面に遷移するので、APIを選択します。

OpenAI -API

その後、表示される画面のサイドメニュー「API keys」を選択します。

API keys

API keysの画面に遷移しますので、[+Create new secret key]ボタンでシークレットキーを生成し、コピーしておきましょう。

Create new secret key

SlackのIncomingWebhooksのURLを取得

Slackに連携する機能として、IncomingWebhooksがある。

Incoming Webhooks は他のアプリから Slack にメッセージを投稿するためのとてもシンプルな方法です。Incoming Webhook を作成すると、メッセージとその他のオプションを含む JSON ペイロードを送るための一意な URL が払い出されます。

https://slack.dev/java-slack-sdk/guides/ja/incoming-webhooks

このIncomingWebhooksを使っていきます。

1
From scratch

Slackアプリの新規作成

 Slack アプリ管理画面 にアクセスして、新規でアプリを作ります。

a.[Create New App]をクリックします。

Create New App

b.From scratchで進めます。

Create an App from scratch

c.アプリの名前とSlackのワークスペースを設定します。

  • App Name:アプリの名前を入力します。
  • Pick a workspace to develop your app in::Slackのワークスペースを選択します。

そして、[Create App]をクリックします。

Create App
2
[Incoming Webhooks

WebhookのURLを発行

a.アプリを新規作成後に遷移した画面のサイドメニューのFeatures項目の[Incoming Webhooks]を選択します。

Incomig Webhooks

b.Activate Incomig Webhooksを[ON]にします。

Incomig Webhooks -ON

c.その後、Settings項目の[Basic Information]を選択し、[Install to Workspace]をクリックします。

Install to Workspace

d.チャンネルを選び[許可する]をクリックしてインストールします。

アクセス許可

e.サイドメニューのFeatures項目の[Incoming Webhooks]を選択します。許可した後は、Webhook URLが作成されています。

Webhook URL

ローカルでPythonのスクリプトを実行する

私は、Macを利用しておりますので、その手順となります。

a.Pythonのライブラリインストールします。
私はすでに、「openai」「requests」はインストールしてありました。適宜必要なライブラリはインストールしてください

% pip install feedparser

インストール後の確認です。

% pip list              
Package            Version
------------------ ---------
feedparser         6.0.10
openai             1.3.3
requests           2.31.0

b.Pythonのスクリプトyahoo_openai.pyを準備します。(試しに、参考サイトのスクリプトそのままコピーしました)

import openai
import feedparser
import json
import requests
from typing import List, Dict
from datetime import datetime, timedelta, timezone
import os

# タイムゾーンを日本時間に設定
JST = timezone(timedelta(hours=+9))

# APIキーの設定
openai.api_key = os.environ["OPENAI_API_KEY"]

# Slack Webhook URL
WEBHOOK_URL = "{通知先のWebhook URL}"

# OpenAI API モデル名
OPENAI_MODEL: str = "gpt-3.5-turbo"

# RSSフィードURL(DevelopersIO)
FEED_URL = "https://dev.classmethod.jp/feed/"

SYSTEM_PARAMETER = """```
与えられたフィードの情報を、以下の制約条件をもとに要約を出力してください。

制約条件:
・文章は簡潔にわかりやすく。
・箇条書きで3行で出力。
・要約した文章は日本語へ翻訳。
・最終的な結論を含めること。

期待する出力フォーマット:
1.
2.
3.
```"""

def get_feed_entries() -> List[Dict]:
    """
    RSSフィードの取得し、過去1時間以内の更新のみを取得する
    """
    updated_since = datetime.now(JST) - timedelta(hours=1)
    # RSSフィードの取得
    feed = feedparser.parse(FEED_URL)
    # 更新日時の閾値よりも新しいエントリーのみを取得
    new_entries: List[Dict] = [
        entry for entry in feed.entries
        if datetime(*entry.updated_parsed[:6], tzinfo=timezone.utc)
        .astimezone(JST) > updated_since
    ]

    return new_entries

def generate_summary(feed) -> str:
    """
    RSSフィードを元に、OpenAIで要約を実行
    """
    text = f"{feed.title}>\n{feed.summary}"
    response = openai.ChatCompletion.create(
        model=OPENAI_MODEL,
        messages=[
            {"role": "system", "content": SYSTEM_PARAMETER},
            {"role": "user", "content": text},
        ],
        temperature=0.25,
    )
    summary: str = response.choices[0]["message"]["content"].strip()
    return summary

def post_to_slack(message: str, link_url: str, title: str) -> None:
    """
    RSSフィードとOpenAIの要約SlackのWebhookURLへPOST
    """
    data = {
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"<{link_url}|{title}>",
                },
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": message,
                },
            },
            {"type": "divider"},
        ],
        "unfurl_links": False,
    }
    requests.post(WEBHOOK_URL, data=json.dumps(data))

def handler() -> None:

    # RSSフィードから記事を取得し、要約を生成してSlackに投稿する
    for entry in get_feed_entries():
        summary: str = generate_summary(entry)

        post_to_slack(summary, entry.link, entry.title)

if __name__ == "__main__":
    handler()

c.OPENAI_API_KEYのエクスポートをします。

% export OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

d.サンプルコードyahoo_openai.pySlack Webhook URLRSSフィードURLを該当のものに変更しましょう。

WEBHOOK_URL = "{通知先のWebhook URL}"
FEED_URL = "https://dev.classmethod.jp/feed/"

e.変更後、スクリプトを実行してみます。

% python3 yahoo_openai.py
..
..
..

openai.RateLimitError: Error code: 429 - {'error': {'message': 'You exceeded your current quota, please check your plan and billing details.', 'type': 'insufficient_quota', 'param': None, 'code': 'insufficient_quota'}}

無料で利用できない範囲にいるようですので、5ドルチャージしてみました。。(泣)

Billing settings

チャージ後、再度実行しましたが、エラーでした。

% python3 yahoo_openai.py
..
..
summary: str = response.choices[0]["message"]["content"].strip()
                   ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
TypeError: 'Choice' object is not subscriptable

コードを修正して試しましたが、エラーで進めない感じになったので、一度まっさらな状態から、ライブラリをインストールしてやってみます。Pythonの仮想環境を構築します。

1
仮想環境構築venv

Python仮想環境の構築

a.venvという名前で、Pythonの仮想環境を作成します。

% python3 -m venv venv

実行すると、現在のディレクトリに venv という名前のフォルダが作成され、そのフォルダはPythonの実行ファイルやpipなどのツールのコピー、そしてプロジェクトのための独立したPythonライブラリを置く場所が含まれます。

  • python3 は、Python 3.xバージョンのインタープリタを指します。
  • -m venv は、Pythonに標準で含まれている venv モジュールを使用するように指示します。venv モジュールは、仮想環境を作成するためのものです。
  • venv は、新しく作成される仮想環境のディレクトリ名です。この名前は任意で変更可能です。
引用:chatGPTの解説より

b.作成された仮想環境を使用するためには、それをアクティベートする必要があります。

 % source venv/bin/activate

c.openaiをインストールし直します。

% pip install openai

バージョンは1.3.5がインストールされました。

% pip list                              
Package           Version
----------------- ----------
annotated-types   0.6.0
anyio             3.7.1
certifi           2023.11.17
distro            1.8.0
h11               0.14.0
httpcore          1.0.2
httpx             0.25.2
idna              3.5
openai            1.3.5
pip               23.2.1
pydantic          2.5.2
pydantic_core     2.14.5
setuptools        68.1.2
sniffio           1.3.0
tqdm              4.66.1
typing_extensions 4.8.0

d.そしてRSSを読み込むために必要なライブラリを再度インストールします。

% pip install feedparser

バージョンは6.0.10がインストールされました。

% pip list
Package            Version
------------------ ----------
annotated-types    0.6.0
anyio              3.7.1
certifi            2023.11.17
charset-normalizer 3.3.2
distro             1.8.0
feedparser         6.0.10
h11                0.14.0
httpcore           1.0.2
httpx              0.25.2
idna               3.5
openai             1.3.5
pip                23.3.1
pydantic           2.5.2
pydantic_core      2.14.5
requests           2.31.0
setuptools         68.1.2
sgmllib3k          1.0.0
sniffio            1.3.0
tqdm               4.66.1
typing_extensions  4.8.0
urllib3            2.1.0

環境構築は完了です。

2
最終完成版

Pythonスクリプトの作成

改めて、Yahoo!ニュースの主要トピックスの記事のRSSを読み込んで、OpenAIで要約してSlack連携する処理を作成します。

最新のOpenAIのバージョンに合わせて作成したスクリプトは下記です。先程のスクリプトの内容からアップデートした最終版yahoo_openai.pyです。

import os
import feedparser
from openai import OpenAI
import requests

# 環境変数からAPIキーを取得
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")

# 使用するモデルを指定(GPT-3.5-turboを仮定していますが、適宜変更してください)
OPENAI_MODEL = "gpt-3.5-turbo"

# RSSフィードURL
FEED_URL = "https://news.yahoo.co.jp/rss/topics/top-picks.xml"

# OpenAIのAPIキーを設定
# openai.api_key = OPENAI_API_KEY
client = OpenAI(api_key = OPENAI_API_KEY)

def fetch_rss_feed(url):
    #RSSフィードを取得してパースする
    return feedparser.parse(url)

def generate_summary(title, url):
    #OpenAIを使用してニュース記事の要約を生成する
    prompt_text = f"Please summarize the following news article titled '{title}'. You can find the article at {url}."

    chat_completion = client.chat.completions.create(
        messages=[
            {
                "role": "system", "content": "You are an AI trained to summarize news articles.",
                "role": "user","content": prompt_text,
            }
        ],
        model=OPENAI_MODEL,
    )

    # choices 属性の内容を確認する
    choices = chat_completion.choices
    if choices:
        # choices がリストの場合、最初の要素の内容を取得する
        summary = choices[0].message.content.strip()
    else:
        # choices が空か、期待したデータ構造でない場合の処理
        summary = "No completion found or unexpected data structure."

    return summary

def post_to_slack(title, summary):
    #Slackにメッセージを投稿する
    message = f"Title: {title}\nSummary: {summary}"
    requests.post(SLACK_WEBHOOK_URL, json={"text": message})

def main():
    # RSSフィードを取得
    feed = fetch_rss_feed(FEED_URL)

    # 各エントリに対して処理
    for entry in feed.entries:
        # 要約を生成
        summary = generate_summary(entry.title, entry.link)

        # Slackに投稿
        post_to_slack(entry.title, summary)

if __name__ == "__main__":
    main()

実行してみると、Slackに通知が来ました。

Slack - english summary

たしかに、Yahoo!ニュース・トピックス – 主要 のフィードの内容になってます。

Yahoo!ニュース

あとは要約が英語なので、日本語に変換して完了です。

プロンプトが英語なので、日本語に修正すると、日本語で要約されます。

..
..
prompt_text = f"次の記事を要約してください。タイトル: '{title}'. 記事はこちらのリンクです: {url}. 日本語でお願いします."
..
..

日本語になりました。

Slack - japanese summary
STAFF
o_wani
o_wani
スタッフ
大学卒業後、15年間WEB業界で働く。現在はマネジメントに従事していますが、ChatGPTの登場に触発され、このブログを再開。AIをパートナーに、自分で手を動かして実装する楽しさと喜びを再発見中。時代が変わりつつある中でも、陳腐化しない情報発信も目指しています。
記事URLをコピーしました