Rainbow Engine

IT技術を分かりやすく簡潔にまとめることによる学習の効率化、また日常の気付きを記録に残すことを目指します。

OpenAI Python

OpenAIでPDFファイルを要約する方法(サンプルプログラムをご紹介)

投稿日:2023年8月28日 更新日:

 

<目次>

OpenAIでPDFファイルを要約する方法(サンプルプログラムをご紹介)
 やりたいこと/概要
 サンプルプログラム
 動かし方

OpenAIでPDFファイルを要約する方法(サンプルプログラムをご紹介)

やりたいこと/概要

●やりたいこと
・PDFファイルを読み込んで、OpenAIを使い内容を要約する
 
●概要
①PDFのテキスト化
・Azureの「Form Recognizer」機能を使ってPDFを読込み、文字列を抽出。
(参考)
②要約処理
・指定の文字数(len(text) < order_wcount * 2)になるまで要約を続ける。
∟・要約する際、まずは一定の規模に分割(例:order_wcount * 5)する。
∟・分割したものを、それぞれ要約して短くなったもの同士を連結する
 
(図111)

サンプルプログラム

●メイン処理:openai_pdf_summarize.py

import openai
import re
import time
from azure.ai.formrecognizer import DocumentAnalysisClient
from azure.core.credentials import AzureKeyCredential
from log_utils import prepare_logger
import env

FR_ENDPOINT = env.get_env_variable("FR_ENDPOINT")
FR_KEY = env.get_env_variable("FR_KEY")
openai.api_key = env.get_env_variable("OPEN_AI_KEY")
openai.api_base = env.get_env_variable("OPEN_AI_BASE")
openai.api_type = env.get_env_variable("OPEN_AI_TYPE")
openai.api_version = env.get_env_variable("OPEN_AI_VERSION")
model_deploy_name = env.get_env_variable("OPEN_AI_DEPLOY_NAME")

def analyze_general_documents(binary_file,logger):
    # -----------------------------------------
    # binaryファイルを解析し、ファイル中の文字列を抽出する
    # -----------------------------------------  

    # DocumentAnalysisClientクラスのインスタンスを作成
    # →Form Recognizerのサービスを使える様にする
    document_analysis_client = DocumentAnalysisClient(endpoint=FR_ENDPOINT, credential=AzureKeyCredential(FR_KEY))

    # ドキュメントの解析
    # →ドキュメントの取得方法は「ファイルから」と「URLから」の2通りがある
    # (1)ファイル形式のドキュメント
    poller = document_analysis_client.begin_analyze_document("prebuilt-document", binary_file)
    # (2)URLからドキュメントを取得
    # poller = document_analysis_client.begin_analyze_document_from_url("prebuilt-document", docUrl)
    
    # 戻り値:AsyncLROPoller のインスタンス
    # →poller オブジェクトで result() を呼び出して、 を AnalyzeResult返します。
    result = poller.result()
    # 結果格納用の配列
    text_of_doc = []
    # 結果は「ページ単位」に分かれている
    for page in result.pages:
        # 更にその中で「行単位」に分かれる
        for line_idx, line in enumerate(page.lines):
            # 行単位に「単語」の情報を抽出
            words = line.get_words()
            for word in words:
                # リストに追加
                text_of_doc.append(word.content)

    # 配列の中身を、1つの変数に格納しなおす       
    final_text = ""
    for text in text_of_doc:
        final_text += text

    # logger.debug(f"Azure Form Recognizerの文字認識結果: {final_text}")
    return final_text

def get_summary(text, order_wcount, logger):

    # 始めから、要約文字数×2 以下の場合
    if len(text) < order_wcount * 2:
        return text
    # 要約文字数×2 以上の場合
    while len(text) > order_wcount * 2:
        # 分割して要約した結果を連結した結果
        final_result = __get_summary_internal(text, order_wcount, logger)
        # while文の判定対象であるtextを更新
        text = final_result      
    return final_result 

# 分割して要約
def __get_summary_internal(text, order_wcount, logger):
    # TODO:要約の単位は何文字ずつに区切るのが最適か?
    split_size = order_wcount * 5
    # 上限に抵触しない範囲のカタマリに分割した配列を取得
    splited_text = split_text(text, split_size)
    entire_size = len(splited_text)   
    final_result = ""

    # 上限に抵触しない範囲のカタマリに分割した配列をループし、その単位で要約
    logger.info(f"全体サイズ: {entire_size}")
    for index, text in enumerate(splited_text):
        
        logger.info(f"内部要約回数: {index + 1} 、内部要約サイズ: {len(text)}...")        
        prompt = __get_system_prompt(order_wcount)
        prompt += text
        # max_tokensの設定方法
        #  ひらがな/カタカナ/英数字 = 約2Token、漢字 = 約3Token
        #  とすると、最大3*X Tokenが来る想定。
        #  X=300の場合 3*300*10=6000
        response = ""
        try:
            response = openai.ChatCompletion.create(
                engine=model_deploy_name,
                messages=[{"role": "system", "content": prompt}],
                temperature=0,
                max_tokens=5000,
                top_p=0.95,
                frequency_penalty=0,
                presence_penalty=0,
                stop=None)
        except openai.error.InvalidRequestError:
            logger.error(
                f"openai.ChatCompletion.create() でエラーが発生しました。入力プロンプト : {prompt}")
            raise
        except openai.error.RateLimitError as e:
            logger.error(e)
            raise
        final_result += response['choices'][0]['message']['content']
        # time.sleep(60)
    logger.info(f"最終要約結果: {final_result}")
    return final_result

def split_text(text, split_size):
    # 文章を「。」「.」「!」「?」で分割して配列に格納
    # FIXME:「.」による分割はYYYY.MM.DD形式の日付が途中で分割されてしまう
    sentence_array = re.split("。|.|!|?|\.", text)
    result,chunk = [],""
    # OpenAIに渡せる「最大文字数」になるまで文章を連結し、その単位で配列に格納
    #   ・enumerate() 関数を使用することで、要素のインデックスと値の両方を取得できます。
    for index, sentence in enumerate(sentence_array):
        chunk += sentence
        if __check_if_next_iteration_exeeds_limit(chunk, index, sentence_array, split_size):
            result.append(chunk)
            chunk = ""
    return result

def __get_system_prompt(order_wcount):
    instructions =  "以下の【文章】を要約してください。" \
                    "要約する際のルールは以下です。" \
                    f"【ルール1】{order_wcount}文字以内に要約すること。" \
                    "【ルール2】要約時に内容と関連が無い内容が含まれている場合、結果からは除外してください。" \
                    "【ルール3】要約結果は箇条書きではなく、連続した文章として返却すること。" \
                    "【文章】\n"
    # logger.debug(f"システムプロンプト: {instructions}")
    return instructions

# 文字列がOpenAIに渡せる「最大文字数」に抵触しないか?のチェック
#   ・特殊メソッドは、Pythonの内部で特別な役割を果たすため、直接的に呼び出すことは一般的ではありません。
#   ・代わりに、特定の操作や構文が使用されるときに自動的に呼び出されます。
def __check_if_next_iteration_exeeds_limit(chunk, idx, sentence_array, split_size):
    # 次の要素がsentence_arrayの最後の要素の場合はTrue
    if idx + 1 == len(sentence_array):
        return True
    # 次の要素を連結し、split_sizeの値を超す場合はTrue、その他の場合はFalse
    return (len(chunk) + len(sentence_array[idx + 1])) >= split_size

def get_pdf_summary(logger):

    # PDFをテキスト化
    with open("./leaf18.pdf", "rb") as f:
        prompt_pdf_text = analyze_general_documents(f,logger)
    
    # 要約
    summary = get_summary(prompt_pdf_text,300,logger)

if __name__ == "__main__":
    logger = prepare_logger()
    get_pdf_summary(logger)

●ロギング:log_utils.py

import logging
def prepare_logger() -> logging.Logger:
    # ロガーの作成
    logger = logging.getLogger(__name__)
    # ログハンドラの作成
    handler = logging.StreamHandler()
    # ログフォーマットの設定
    formatter = logging.Formatter('xxxxxxxxxxxxx : %(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    # ログハンドラをロガーに追加
    logger.addHandler(handler)
    logger.setLevel(logging.DEBUG)
    return logger

●環境変数:env_sample.py

def get_env_variable(key):

    env_variable_dict = {
        # ------------------------------------
        # Azure Form Recognizer
        # ------------------------------------
        "FR_ENDPOINT" : "",
        "FR_KEY" : "",

        # ------------------------------------
        # OpenAI
        # ------------------------------------
        "OPEN_AI_KEY" : "",
        "OPEN_AI_BASE" : "",
        "OPEN_AI_TYPE" : "",
        "OPEN_AI_VERSION" : "",
        "OPEN_AI_DEPLOY_NAME" : "",

        # ------------------------------------
        # App名:Slack_Python_Flask
        # ------------------------------------
        # BotユーザーID
        "BOT_USER_ID" : "",
        # Botトークン(Flask)
        "WEBAPPS_SLACK_TOKEN" : "",
        "WEBAPPS_SIGNING_SECRET" : "",

        # Botトークン(ソケットモード)
        "SOCK_SLACK_BOT_TOKEN" : "",
        "SOCK_SLACK_APP_TOKEN" : ""
    }
    ret_val = env_variable_dict.get(key, None)
    return ret_val

動かし方

・①venvを作る
> python -m venv .venv
(図211)
・②venvのアクティベート
> .venv/Scripts/activate
(図212)
 ↓
・③パッケージのインストール(requirements.txt)
> pip install -r requirements.txt
(図213)
・④メインモジュール実行(openai_pdf_summarize.py)
(図214)

Adsense審査用広告コード


Adsense審査用広告コード


-OpenAI, Python
-

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

Pythonのdatapackage学習中に遭遇したエラー「StopIteration」と「AttributeError」の対応

(0)目次&概説 (1) 記事の目的 (2) エラー1:AttributeError: ‘generator’ object has no attribute ‘n …

SlackのAPIでユーザー一覧を取得する方法をご紹介

  <目次> (1) SlackのAPIでユーザー一覧を取得する方法をご紹介  (1-0) やりたいこと  (1-1) STEP1:Slackボットの作成  (1-2) STEP2:サンプル …

PythonでSQLAlchemyを使ってOracleDBに接続する方法

(0)目次&概説 (1) 記事の目的  (1-1) 目的  (1-2) 前提条件 (2) 事前準備  (2-1) 準備1(cx_oracleパッケージの導入)   (2-1-1) インストール資源の入 …

Python開発環境にPandasライブラリをインストールする手順

(0)目次&概説 (1) Pandasの導入  (1-1) Pandasとは? (2) オフラインインストール  (2-1) インストール資源の入手  (2-2) インストール時の諸注意  (2-3) …

no image

Pythonでランダムな座標データを生成する方法

  <目次> (1) Pythonでランダムな座標データを生成する方法  (1-1) 使う構文  (1-2) サンプルプログラム  (1-3) 補足:データの座標を全体的にシフトしたい場合 …

  • English (United States)
  • 日本語
Top