はじめに
すでにarXivで同じことをしている人はたくさんいるけれども,私の分野だとPubMedも必要だったのでガッチャンコ(死語)した。両方から最新の論文データ(タイトルやアブストラクト)を引っ張ってきて,ChatGPTを使って解説させる。
基本的には色々な情報を繋ぎ合わせただけ。元ネタは下記。
ChatGPT APIの準備
皆大好きOpenAIでAPI利用登録をする。
「新規利用登録で~~ドル分使える!」みたいな記事も多かったけど,私が登録したときにはサイフはすっからかんだった。ChatGPT無料で使いまくってるからね,しょうがないね。
登録後,構築したシステムにいくら掛かるかわからないけど恐る恐る5ドル課金
Slack Appの準備
ネットの記事の通りにアプリを登録する。
ここで実行テストをするんだけど,マルチバイト文字はパーセントエンコーディングをしないと動かなかった。
つまり,投稿したいチャンネルの名前を「#論文」としていたんだけど,以下だと動かない。
$ curl -X POST 'https://slack.com/api/chat.postMessage' \
-d 'token=xoxb-668814143316-2441900189495-E0TZsRkP18KjsKVNByhZwIIG' \
-d 'channel=#論文' \
-d 'text=hinna hinna'
以下だと動く。エンコーディングしてやる必要がある。
$ curl -X POST 'https://slack.com/api/chat.postMessage' \
-d 'token=xoxb-668814143316-2441900189495-E0TZsRkP18KjsKVNByhZwIIG' \
-d 'channel=#%E8%AB%96%E6%96%87' \
-d 'text=hinna hinna'
Pythonスクリプト
パッケージ関係
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
import arxiv
import openai
import random
import openai
import requests
from lxml import etree
import datetime
arXivからデータ取得
こんな感じ。
- タイトル,雑誌,アブストラクト,キーワード,等の全てのフィールドから関心のあるキーワードに合致し,新しいもの(1年以内)を検索して
- 新しい順に50件取得して
- そのうち2件をランダムに選択する
PubMedとデータの形式を合わせたいので自作の辞書型に必要な情報を格納して返すようにする。
クエリについては公式を参照
AX_QUERY = """
all:("acoustic" AND ("enrironmental noise" OR "road traffic noise" OR "railway noise" OR "aircraft noise" OR "wind turbine noise" OR "community noise" OR "traffic noise"))
&lastUpdatedDate:[{mindate} TO {maxdate}]
""".format(
mindate = (datetime.datetime.today() + datetime.timedelta(days=-365)).strftime("%Y/%m/%d"),
maxdate = datetime.datetime.today().strftime("%Y/%m/%d")
).replace('\n', '').replace('"', '%22')
NUM_PAPERS = {
"arXiv": 2,
"PubMed": 2
}
def get_arxiv_papers():
ax_client = arxiv.Client(page_size = 100)
search = arxiv.Search(
query=AX_QUERY,
max_results=50,
sort_by=arxiv.SortCriterion.SubmittedDate,
sort_order=arxiv.SortOrder.Descending,
)
result_list = []
for result in ax_client.results(search):
result_list.append({
"title": result.title,
"abstract": result.summary,
"date": result.published.strftime("%Y-%m-%d %H:%M:%S"),
"url": result.entry_id
})
results = random.sample(result_list, k=NUM_PAPERS["arXiv"])
return results
PubMedからデータ取得
arXivとやっていることは同じ。
- タイトル,雑誌,アブストラクト,キーワード,等の全てのフィールドから関心のあるキーワードに合致し,新しいもの(1年以内)を検索して
- 新しい順に50件取得して
- そのうち2件をランダムに選択する
もっと簡単な書き方があるんだろうと思うけど,動けばよいので気にしていない。
こちらのクエリも公式を参照。
PM_QUERY = """
db=pubmed
&retmode=json
&retstart=0
&retmax=50
&sort=pub_date
&mindate={mindate}
&maxdate={maxdate}
&term="envrironmental noise" OR "road traffic noise" OR "railway noise" OR "aircraft noise" OR "wind turbine noise" OR "community noise" OR "traffic noise"
""".format(
mindate = (datetime.datetime.today() + datetime.timedelta(days=-365)).strftime("%Y/%m/%d"),
maxdate = datetime.datetime.today().strftime("%Y/%m/%d")
).replace('\n', '').replace(" ", "+")
def get_pubmed_papers():
# get PMID
pmid_response = requests.get("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?" + PM_QUERY)
pmid_response_json = pmid_response.json()
pmids = pmid_response_json["esearchresult"]["idlist"]
pmids_ext = random.sample(pmids, k = NUM_PAPERS["PubMed"])
# get abstract
results = []
for pmid in pmids_ext:
try:
response = requests.get(
"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=xml&id={}".format(pmid)
)
root = etree.fromstring(response.content)
try:
date_str = root.find('.//DateRevised//Year').text + "-" + root.find('.//DateRevised//Month').text + "-" + root.find('.//DateRevised//Day').text
except:
date_str = "Date not found"
try:
url = "https://doi.org/"+root.find('.//ArticleId[@IdType="doi"]').text
except:
url = "URL not found"
results.append({
"title": root.find('.//ArticleTitle').text,
"abstract": ''.join(root.xpath('//Abstract//*/text()')),
"date": date_str,
"url": url
})
except:
continue
return results
ChatGPTに投げる部分
コスト重視でChatGPT3.5を使用。指示に従わないわからずやだけど,詳しく調べたいときには自分でソースに当たるから問題ない。
プロンプトは,もっと良い書き方があると思うんだよなあ。でもとりあえず動いたので良しとする。
使うときにはAPI keyの指定が必要。ただし,.env
ファイルにOPENAI_API_KEY=**
というかたちで指定しておけばそれでも動く。以下のスクリプトはそうしている。
SYSTEM = """
### 指示 ###
論文の内容を理解した上で,概要,結果,結論,解説を平易な日本語で述べよ。
### 概要の制約 ###
- 論文の背景・目的・手法などについて100文字以内で述べる
### 結果の制約 ###
- 論文で示された最も重要な結果・考察について100文字以内で述べる
### 結論の制約 ###
- 論文より得られる洞察,限界,今後の展望について100文字以内で述べる
### 解説の制約 ###
- 専門用語の使用を避ける
- 300文字以上500文字以内
- 高い視座,広い視野,様々な視点から論文の重要性を述べる
### 対象とする論文の内容 ###
{text}
### 出力形式 ###
タイトル:タイトル(和名)
概要:
概要
結果:
結果
結論:
結論
解説:
解説
"""
def get_summary(result):
client = openai.OpenAI()
text = f"title: {result['title']}\nbody: {result['abstract']}"
response = client.chat.completions.create(
model="gpt-3.5-turbo-0125",
messages=[
{'role': 'system', 'content': SYSTEM},
{'role': 'user', 'content': text}
],
temperature=0.25,
)
summary = response.choices[0].message.content
title_en = result["title"]
title, *body = summary.split('\n')
body = '\n'.join(body)
message = f"発行日: {result['date']}\n{result['url']}\n{title_en}\n{title}\n{body}\n"
return message
main部分(slackに投げる部分)
SLACK_API_TOKEN = 'xoxb-hinnahinna'
SLACK_CHANNEL = "#論文"
def main(event, context):
slk_client = WebClient(token=SLACK_API_TOKEN)
ax_results = get_arxiv_papers()
pm_results = get_pubmed_papers()
all_results = ax_results + pm_results
for i, result in enumerate(all_results):
try:
message = "今日の論文です! " + str(i+1) + "本目\n" + get_summary(result)
response = slk_client.chat_postMessage(
channel=SLACK_CHANNEL,
text=message
)
print(f"Message posted: {response['ts']}")
except SlackApiError as e:
print(f"Error posting message: {e}")
if __name__ == '__main__':
main(None, None)
その他
上のスクリプトを全部繋げてmain.py
として,同じフォルダに.env
ファイルを作ってOPENAI_API_KEY=**
というかたちでAPI Keyを入力して,main.py
を実行すれば動く。
スケジューリング
省略。というかまだやっていない。
Googleよりかは自分のところのサーバーで動かしたいな。
使ってみた
こんな感じで投稿される。
もっと検索キーワードは考えた方が良いけど,ま,やりたいことはできた。
気になるOpenAI APIの使用料については,1投稿で0.01ドルもいかないくらい。5ドルあればかなり楽しめそうです。
おわりに
とくになし。Enjoy!