41
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LangChainでPDFを要約してかんたんな日本語に変換するWebアプリを作ってみた

Posted at

はじめに

LangChain入門ついでに何かシンプルなアプリケーションを作れないかと思い、PDFを要約してかんたんな日本語に変換するWebアプリを作ってみました。

スクリーンショット 2023-05-04 235359.png

上記は令和4年版情報通信白書の第4章第7節「ICT技術政策の推進」を要約したものです。
難しい言い回しもある程度やさしく変換されるので、内容が堅くとっつきにくい文章をざっくり把握したいときに使えそうです。

この記事では、主にLangChainを使用して実装した部分を解説します。

LangChainを使用した実装

前提として、モデルはgpt-3.5-turboを使用しています。
この場合、モデルのインスタンス生成にはChatOpenAIを使う必要があります。

llm = ChatOpenAI(model_name=MODEL_NAME, temperature=TEMPERATURE)

LangChainのissueでも案内があります。
https://github.com/hwchase17/langchain/issues/1643#issuecomment-1468520580

PDFの読み込み

日本語を安定して読むため、@shimajiroxyz さん作のCJKPDFReaderを使用させていただきました。
今回はPDFをページ単位で分割したかったため、gpt-indexで日本語PDFを読み込む【Python】に記載いただいている情報を参考にconcat_pages = Falseとしてインスタンスを作成しています。

def _load_documents(self, file_path: str) -> list:
        CJKPDFReader = download_loader("CJKPDFReader")
        loader = CJKPDFReader(concat_pages=False)

        documents = loader.load_data(file=file_path)
        langchain_documents = [d.to_langchain_format() for d in documents]
        return langchain_documents

要約

LangChainのload_summarize_chainを使用して要約しています。

def _summarize(self, langchain_documents: list) -> str:
        summarize_template = PromptTemplate(
            template=summarize_prompt_template, input_variables=["text"])

        chain = load_summarize_chain(
            self.llm,
            chain_type="map_reduce",
            map_prompt=summarize_template,
            combine_prompt=summarize_template
        )

        summary = chain.run(langchain_documents)
        return summary

要約用のプロンプトテンプレートは以下の通りで、とてもシンプルです。

summarize_prompt_template = """以下の文章を簡潔に要約してください。:

{text}

要約:"""

PDFを設定したテンプレートを用いてページ毎に要約し、そのページ毎の要約を結合して再度要約することにより、PDF全体の要約が出力されます。

LangChainを使えばたったこれだけのコードで実装できてしまうのでとても楽ですね。

かんたんな日本語への変換

LangChainのChat Modelsのはじめにを参考に、SystemMessageHumanMessageをメッセージとして送信します。
それぞれ、OpenAI APIのChat Completionにおけるsystemロールとuserロールに相当するものでしょう。
SystemMessageに設定するプロンプトは以下です。

simpify_system_message = "あなたは文章を子ども向けのかんたんな日本語に変換するのに役立つアシスタントです。"

HumanMessageには、FewShotPromptTemplateを設定しています。
かんたんな日本語に変換する例をいくつか提示することにより、変換精度を上げることを意図しています。

プロンプトの元となるものは以下です。

simplify_prompt_prefix = "元の文章の難しい表現を、元の意味を損なわないように注意して子ども向けのかんたんな表現に変換してください。語尾は「です」「ます」で統一してください。"

simplify_examples = [
    {
        "元の文章": "綿密な計画のもと、彼は革命を起こし、王朝の支配に終止符を打った。",
                "かんたんな文章": "よく考えられた計画で、彼は国の政治や社会の仕組みを大きく変えました。そして、王様の家族がずっと支配していた時代が終わりました。"
    },
    {
        "元の文章": "彼は無類の読書家であり、その博識ぶりは同僚からも一目置かれる存在だった。",
                "かんたんな文章": "彼はたくさんの本を読むのが大好きで、たくさんのことを知っています。友達も彼の知識を尊敬しています。"
    },
    {
        "元の文章": "彼女は劇団に所属し、舞台で熱演を繰り広げ、観客を魅了していた。",
                "かんたんな文章": "彼女は劇のグループに入っていて、舞台でとても上手に演じて、見ている人たちを楽しませています。"
    },
    {

        "元の文章": "宇宙の膨張は、エドウィン・ハッブルによって観測された銀河の運動から発見されました。",
                "かんたんな文章": "宇宙がどんどん広がっていることは、エドウィン・ハッブルさんがたくさんの星が集まった大きなものが動いていることを見つけることでわかりました。"
    }
]

simplify_example_formatter_template = """
元の文章: {元の文章}
かんたんな文章: {かんたんな文章}\n
"""

simplify_prompt_suffix = "元の文章: {input}\nかんたんな文章:"

これらをPromptTemplateおよびFewShotPromptTemplateに設定することで、プロンプトとして組み合わされます。

プロンプトの頭(=simplify_prompt_prefix)に「難しい漢字はひらがなにしてください」「難しい言葉には文末に解説を追加してください」「漢字にふりがなをふってください」といった指示を加えることも試してみたのですが、出力が安定せず、
「元の文章の難しい表現を、元の意味を損なわないように注意して子ども向けのかんたんな表現に変換してください。語尾は「です」「ます」で統一してください。」
に落ち着いています。
gpt-4を使えば追加できる制約の数や複雑さも変わってくると思うので、モデルによってプロンプトを使い分けるというのもよさそうです。

また、few shotで与える例文はGPT-4に考えてもらっています。
ChatGPTはサンプル生成にも有用ですよね。

これらを組み合わせ、変換結果を取得するための一連のコードは以下の通り。

def _simplify(self, summary: str) -> str:
        simplify_example_prompt = PromptTemplate(
            input_variables=["元の文章", "かんたんな文章"],
            template=simplify_example_formatter_template,
        )

        simplify_few_shot_prompt_template = FewShotPromptTemplate(
            examples=simplify_examples,
            example_prompt=simplify_example_prompt,
            prefix=simplify_prompt_prefix,
            suffix=simplify_prompt_suffix,
            input_variables=["input"],
            example_separator="\n\n",
        )

        simplify_few_shot_prompt = simplify_few_shot_prompt_template.format(
            input=summary)

        messages = [
            SystemMessage(content=simpify_system_message),
            HumanMessage(content=simplify_few_shot_prompt),
        ]
        result = self.llm(messages)

        return result.content

FewShotTemplateを使用した例はLangChainのドキュメントHow to create a prompt template that uses few shot examplesにもありますので、他の例はこちらを参照してください。

実行

後はこれらを順番に実行するのみです。
今回はPdfSimplySummarizerというクラスのrunメソッドにまとめる形にしました。

class PdfSimplySummarizer():
    def __init__(self, llm: ChatOpenAI):
        self.llm = llm
    
    def _load_documents(self, file_path: str) -> list:
    # 省略

    def _summarize(self, langchain_documents: list) -> str:
    # 省略

    def _simplify(self, summary: str) -> str:
    # 省略

    def run(self, file_path: str):
        langchain_documents = self._load_documents(file_path)
        summary = self._summarize(langchain_documents)
        result = self._simplify(summary)
        return result
    

さいごに

アプリケーションの全体はGitHubで公開しているので、もしよければ参考にしてみてください。
https://github.com/keitomatsuri/pdf-simply-summarizer-jp

本当はこれを改良してWebサービスとして世に出したかったのですが、ChatPDFという優れたサービスが既に存在し、かつ私が認識していないだけで類似サービスがあるであろうことと、バックエンドの処理が長くFaaSでのホストが難しい(=インスタンス費用がそこそこかかる)ことから断念しました。

サービスとしての価値は提供できませんでしたが、この記事がどなたかの参考になれば幸いです。

41
44
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
41
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?