🦜

Plasmo + LangChain で作る Chrome 拡張 AI

2023/12/15に公開

シンプルフォーム CTO の小間 (@hkomachi) です。
本記事は SimpleForm Advent Calendar 2023 の10日目です(遅刻)。

実装の前にコンセプト検証

世はまさに大生成 AI 時代。最近の生成 AI の進化は目覚ましく、これまでに無かった生成 AI プロダクトの市場が急速に広がっています。シンプルフォームでも、先日 (2023年11月6日) の OpenAI Dev Day で発表された GPTs を用いて、生成 AI アプリケーション開発の PoC を高速に回す仕組み作りに挑戦しています。

GPTs は、ChatGPT のカスタマイズバージョンをノーコードで自由度高く作成できるツールです。作成したモデルをインターネットを通じて他のユーザーに利用してもらうことができます。
GPTs を作るプロセスは、一般的なチャットに加えて「追加知識の提供」「ウェブ検索」「画像生成」「プログラミングコードの生成・実行」などを組み合わせるといったもので、非常にシンプルです。GPTs の詳しい使い方については既に素晴らしい記事が多くありますので、そちらをご参照ください。ますみさんの記事は画像付きで解説されておりオススメです。

今回は、技術記事を素早く要約するためのコンセプトデモを GPTs で作成してみました。「要約して、と言われたら、次の記事を要約してください。【記事内容…】」といったシンプルなプロンプトを与えただけの非常に単純なものです。作成済みの GPTs を Chrome で開き、三点リーダー → その他のツール → ショートカットを作成 と進むと、GPTs をコンパクトな UI で表示することもできます。

上述のアプリはあくまでコンセプトデモです。「記事を要約して」と入力すると特定の記事の要約を出力するだけであり、任意の記事を要約できるわけではありません(しかしコンセプトを示すのには十分です)。

任意の記事を要約するためにはもう少し工夫が必要です。ここから先は、GPTs ではなく実際に任意の記事を要約する Chrome 拡張を作っていきましょう。

いざ本実装

Plasmo を使ったフロントエンド実装

今回、 Chrome 拡張のフロントエンドには Plasmo というフレームワークを採用してみました。Plasmo はブラウザ拡張機能を作成するための React フレームワークです。ライブリローディングやテスト、自動デプロイといった機能なども備わっており非常に多機能であることが魅力です。公式サイトでは The ultimate Browser Extension SDK とも謳っています。

Plasmo では、「ポップアップタイプ」「開発者ツールタイプ」「Webページ改変タイプ」といった様々なタイプの Chrome 拡張を作ることができ、そのタイプに応じて所定のファイル名の .ts ファイルもしくは .tsx ファイルを用意します。

現在閲覧している Web ページの HTML を改変するタイプの拡張を作りたい場合は content.tsx というファイルを編集します。

import cssText from "data-text:~style.css"
import type { PlasmoCSConfig } from "plasmo"
import React, { useEffect, useState } from 'react';

export const config: PlasmoCSConfig = {
  matches: ["https://zenn.dev/*"]
}

export const getStyle = () => {
  const style = document.createElement("style")
  style.textContent = cssText
  return style
}

const SummaryDiv = () => {
  const [article, setArticle] = useState<null | { summary: string }>(null);

  useEffect(() => {
    // 現在の URL を取得して、記事 ID を取得する
    const url = location.href;
    const articleId = url.split("/").pop();

    // 記事 ID をサーバに送信して、要約を取得する
    // 後述するバックエンドの URL を後でここ↓に書く
    fetch("https://XXXXXXXXXXXXXXXXXXXXXXXXXXXXX", {
      method: "POST",
      cache: "no-cache",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify({article_id: articleId})
    })
      .then(res => res.json())
      .then(res => setArticle(res));
  }, []);

  // 取得した要約を表示する
  return (
  <div>
    <div className="fixed bottom-4 right-4 w-96 h-96 overflow-auto bg-white rounded-lg shadow-md">
      <div className="p-4">
        <h2 className="text-lg font-semibold text-gray-700">Zenn 記事要約くん</h2>
        <p className="mt-2 text-gray-600">
          {article?.summary || "要約中..."}
        </p>
      </div>
    </div>
  </div>
  );
};

export default SummaryDiv;

Zenn の記事を開くと右下に「Zenn 記事要約くん」と書かれたカード型のコンポーネントが挿入されていることが確認できます。なお、スタイルは Tailwind で書いています。Plasmo で Tailwind を使う方法はこちらの公式ドキュメントを参照してください。

LangChain + Flask を使ったバックエンド実装

次にバックエンドを立てます。今回、サーバフレームワークは Flask、LLM フレームワークは LangChain を使用しました。

LangChain では (prompt | model | output_parser)といったように、大規模言語処理における登場人物(ここではプロンプト、モデル、パーサ)を | 記号で連結するように書いていく LCEL という記法が用意されており、これを用いると、言語処理における低レイヤーの処理をほとんど意識することなく本格的な生成 AI アプリケーションを短いコード量で実装できるようになっています。基本的には、

  • AI に指示を与えるプロンプト
  • gpt-4 などに代表される大規模言語モデル
  • 出力結果を整形するパーサ

を組み合わせていきます。

pip install openai langchain flask flask_cors
import os
import re
import requests

from flask import Flask, request
from flask_cors import CORS
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser

app = Flask(__name__)
CORS(
    app,
    supports_credentials=True
)

@app.route("/", methods=["POST"])
def home():
    # POST で渡された json をパースする
    request_data = request.json
    article_id = request_data["article_id"]

    # LangChain の基本要素であるプロンプト、モデル、パーサをそれぞれ用意
    template = """次の記事を300文字程度に要約してください。
<article>
{article}
</article>
"""
    prompt = ChatPromptTemplate.from_template(template)
    model = ChatOpenAI(model="gpt-3.5-turbo-16k")
    output_parser = StrOutputParser()

    # プロンプト → モデル → パーサ の順で繋げるのが LangChain の基本
    chain = (prompt | model | output_parser)

    # 要約対象の Zenn 記事の html を取得
    response = requests.get("https://zenn.dev/api/articles/{}".format(article_id))
    json_data = response.json()
    article = json_data["article"]["body_html"]

    # html タグを除去しテキストのみを残す
    article = re.sub('<.+?>', '', article)

    # 要約を実行
    summary = chain.invoke({"article": article})
    print(summary)

    # 要約文を返却
    return {
        "summary": summary
    }

if __name__ == "__main__":
    app.run(port=5000)

動作確認

サーバを立てた上で、先程の Chrome 拡張を再度確認してみましょう。

右下の「Zenn 記事要約くん」に記事の要約が表示されました!

おわりに

GPTs でコンセプトを示したあと、Plasmo と LangChain で実際に動く Chrome 拡張ベースの生成 AI アプリケーションをサクッと作ってみました。トイアプリのつもりで作ったのですが、案外サクサク動いて、しかもそこそこ満足する精度の要約が返ってくるので、 幾つもの記事をナナメ読みする際に良いかもです。

なお、今回の記事ではエラーハンドリングやバリデーションは一切していないので、実用化する際はご注意ください。

シンプルフォームでは新しい技術開発にスピーディに挑戦し続けています。いつでも積極採用中ですので、少しでも興味を持っていただいた方は、ぜひカジュアルにお話しさせてください!

シンプルフォーム株式会社 の全ての求人一覧

SimpleForm採用お問い合わせフォーム

SimpleFormカジュアル面談お申込みフォーム

Discussion