98
77

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

notionに論文を登録すると数式もふくめよしなに翻訳してくれるフローを構築した

Last updated at Posted at 2024-10-09

はじめに

論文は読んでいますでしょうか。
私は英語がからっきし駄目なので、億劫で読みたい論文が溜まってしまうことがしばしばです
そんな自分でも、少しでも気楽に論文を読めるように、NotionにPDFリンクを貼ると、自動的にChatGPTが翻訳してくれる仕組みを作りました。

{86449148-2F85-462E-9CC6-718545B14965}.png
↑ こんな感じのページが自動で作られます

PDFを翻訳してくれるサービスはそこそこあるのですが、数式が入ってくるとポンコツになるものが多く、またフォーマットもPDFではブラウザで読みにくいです
そこで、Notion上で全ての内容をオブジェクトとして再構成し、数式や画像、表も含めて読みやすい形式にしました

まず初めに「全体の流れ」でこのフロー全体を説明し、次に「PDFをどうやって和訳したのか」でNotionに書き込む処理(フローチャートで言うCloudRunJobs)を説明します

全体の流れ

各矢印についてそれぞれ説明していきます

NotionAutomationについて

Notionは多機能なオンラインメモツールで、メモやToDo管理だけでなく、データベースとしても使える便利なサービスです
私は論文の管理をNotionで行い、PDFや自分用のメモを一緒に保存しています

NotionにはAPIがあり、ページの読み書きはできるのですが、イベントトリガー機能は現状ないようです
それと似た機能に、「Notion Automation」があります
これは「条件が満たされたときXXする」という機能です
XXに該当するものは無料ユーザーは「Slack通知を送信」のみなので、これを用いました

image.png

Slackは社内チャットツールですが、今回は自分専用のBotがいるワークスペースを作り、そこに通知を送るようにしています。

Slack Events APIについて

SlackではSlack Events APIが用意されており、Slack内のイベントが起こった際にリアルタイムでHTTPリクエストを送ることができます
GoogleCloudRunFunctionsは、HTTPリクエストを元に簡単なコードが実行できるサービスです。

今回は

  • チャンネル内で投稿(=notionの論文の追加)があった際に、GoogleCloudRunFunctionsを叩きに行くSlackBot
  • 叩かれたとき、GoogleCloudRunJobsを起動するGoogleCloudRunFunctions

の2つをつくりました。

# GoogleCloudFunctionsのコード

import os
import functions_framework
import google.auth
from google.auth.transport.requests import Request
import requests
from flask import jsonify

@functions_framework.http
def process(request):
    ret = request.get_json(silent=True)
    
    # slackのtokenが適切かチェック
    if ret is None:
        ret = {"error": "Missing Content"}
        return jsonify(ret), 403
    elif ret.get("token") != os.environ.get('TOKEN_SLACK'):
        ret = {"error": "Invalid token"}
        return jsonify(ret), 403

    # Cloud Run ジョブのAPIエンドポイント
    job_name = os.environ.get('JOB_NAME')
    region = os.environ.get('REGION')
    project_id = os.environ.get('PROJECT_ID')
    url = f"https://{region}-run.googleapis.com/v2/projects/{project_id}/locations/{region}/jobs/{job_name}:run"
    
    # ヘッダーに認証トークンを付与
    credentials, project = google.auth.default()
    credentials.refresh(Request())
    token = credentials.token
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }

    # ジョブを実行するためのリクエストを送信
    response = requests.post(url, headers=headers)
    if response.status_code in [200, 202]:
        ret["result"] = "Cloud Run job triggered successfully"
    else:
        ret["result"] = "Failed to trigger Cloud Run job"
        ret["response"] = response.text
    ret["status_code"] = str(response.status_code)
    return jsonify(ret)

一応念のために、slackのtokenが適切かチェックする機構を入れています。
いたずらでURL叩かれまくってしまったら怖いので......

NotionAPIについて

NotionAPIではデータの読み込みと書き込みができます。
CloudRunJobsでは未翻訳のNotionページのPDFリンクを読み込み、翻訳内容を書き込む処理を記述します

PDFをどうやって和訳したのか

PDFではデータの取り回しが面倒なため、一度論文をマークダウンに変換しました

pdf → markdown

変換するライブラリとしてはpix2textをつかいました
pix2textはMathpix(数式周りのWebサービス・API)をopen-sourceで再現しようというプロジェクトで、PDF→HTMLのライブラリを色々試しましたが、こちらは論文に特化しているためか一番精度が良かったです
特に数式に関しては、ほぼミスなくTeX形式に変換してくれます
ちょっと変換に時間がかかるところ以外は最高です

from pix2text import Pix2Text

p2t = Pix2Text.from_config(enable_table=False)
doc = p2t.recognize_pdf("./ronbun.pdf")
doc.to_markdown("./output")

で論文をMarkdown化してくれます

Pix2Text.from_config実行時に必要なファイル(モデルとか)が配置されていないと、自動でダウンロードする処理が入ります。
そのため、環境がリセットされるCloudRun用のDockerImageを作る際は、ダウンロードによる実行時間をなくすため、事前にPix2Text.from_configを実行したイメージを使用すると良いでしょう。

数式に関して

数式は数式の始まり・終わりで記号の置換を行っています

  • $\alpha$[$\alpha$]
  • $$\alpha$$[$$\alpha$$]

これにより、数式モードと通常のテキストの区別をしやすくしています。

本文に関して

本文の翻訳にはChatGPTを使っています
専門用語が不自然に翻訳されるのを避けるため、科学的な専門用語はそのまま英語表記にするようプロンプトを工夫しています

from openai import OpenAI
def text2jp(text):
    # return text  # testmode
    if len(text) <= 5:
        return text
    if text.startswith("[$") and text.endswith("$]"):
        return text

    client = OpenAI(api_key=OPENAI_KEY)
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system",
                "content": "あなたは翻訳ツールです。翻訳内容以外の文章は決して出力しないでください。インライン数式は[$, $]で、ブロック数式は[$$, $$]で囲われています。科学的専門用語は無理に訳さず英語表記でおねがいします。この表記を変更しないでください。入力された文章を日本語で意訳しなさい。"},
            {"role": "user", "content": text}
        ],
        max_tokens=4096,
        temperature=0.0,
    )
    text_ja = response.choices[0].message.content
    return text_ja

画像について

NotionAPIでは画像のアップロード機能はなく、リンク先の画像を表示させる機能しかありません。
そのため、論文中の画像を一度GyazoのAPIでアップロードし、そのリンクをNotionに記載します

file_path = "paper_no_img.png"
with open(file_path, 'rb') as file:
    response = requests.post(
        GYAZO_UPLOAD_URL,
        files={'imagedata': file},
        data={'access_token': GYAZO_ACCESS_TOKEN}
    )
url = response.json()['url']

notionについて

ライブラリはnotion_clientを用いたものの、レイアウトは気合で作りました
あまりにコードが汚いので公開しないです。
なかなかパワーなコードです

APIには一回で長過ぎる文書を送れない制約があるので、適度に文書を分割する必要があります。
なので適宜文字を分割しつつNotionに送るのがいいでしょう
自分は

def split_text(self, text, chunk_size=2000):
    for text_split in text.split("\n"):
        for i in range(0, len(text_split), chunk_size):
            yield text[i:i + chunk_size]

な関数を多用しています

まとめ

Notionのページ更新をイベントトリガーに、数式混じりPDFを自動翻訳するツールをつくりました
ある程度気軽に論文が読めるようになったので、気になったものをサクッと読んでいきたいです

98
77
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
77

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?