🙆

GPT-4とLangChainで技術記事のクイズを生成するサイトを作った

2023/08/29に公開1

読んだ技術記事の理解を深めるべく、技術記事の内容からクイズを生成するサイトを作りました。

https://quizbite.yukyu.net/about

使い方

https://quizbite.yukyu.net にテキストボックスに技術記事のURLを入力し、「クイズを作る」をクリックします。

あとは、クイズが生成されるまで待ちます。生成が終わるまで大体1分半くらいかかります。
Quizbiteで実際にクイズが生成されている画面

https://quizbite.yukyu.net/?url=<技術記事のURL>とすると、テキストボックスにurlが入力された状態で共有できます。

デモ動画

https://youtu.be/ZoWNuBn9iqA?si=sQ7rbJCflrZc_uEr

動機

自分が技術記事を読んだ時の理解度を把握したいという理由で作りました。

構成

  • バックエンド

    • クイズAPIサーバーとして稼働
    • Python、FastAPI、LangChain
    • OpenAI APIでGPT-4を利用
    • デプロイ先はrender.com
  • フロントエンド

    • クイズAPIサーバーと通信してその結果を表示する
    • Next.jsを利用
    • デプロイ先はvercel
  • データベース

    • 生成したクイズを保存して、同じURLのクイズが存在する場合はDBからクイズを取得する
    • Supabaseを利用
  • LLMのログ管理

    • LangChainでのログを保存
    • LangSmithを利用

LangSmithをどのように利用したかなどを後日記事にしようかなと思います。

LangChain

LangChainを採用したのは記事を取得する機能を備えていること、今後の拡張性を考えて利用しました。

記事の内容取得

URLから記事に内容を取得するためにSelenium URL Loaderを利用しています。
実際のコードは以下のようになっています。

load_website関数内でSeleniumURLLoaderを呼び出し、loader.load()で実行しています。

def load_website(url: str):
    loader = SeleniumURLLoader(urls=[url])
    data = loader.load()
    if len(data) == 0:
      return HTTPException(status_code=404, detail="Webサイトがうまく読み込めませんでした")
    return data
@router.post("/quiz")
async def run_generate(message: Message) -> QuizList:
	url = message.text
	content = load_website(url)  
	chain = make_quiz()
	output = chain.run(content)

	# DBに保存する処理が入る(省略)
	
	return output

クイズの生成

クイズの生成に必要な一連のプロンプトや処理は、LangChainにおいてChainと呼びます。

set_make_quiz_chain関数を定義して、クイズ生成に必要なプロンプトをまとめたchainを返すようにしました。
create_structured_output_chainはOpenAIのFunctionCallingをchainに組み込むものです。
今回は三択クイズのリストを返して欲しいのでQuizListを返すよう指定しました。

@router.post("/quiz")
async def run_generate(message: Message) -> QuizList:
	url = message.text
	#contentに記事の内容が格納される
	content = load_website(url)  
	chain = set_make_quiz_chain()
	output = chain.run(content)

	# DBに保存する処理が入る(省略)
	
	return output
def set_make_quiz_chain():
    llm = ChatOpenAI(temperature=0.3, model='gpt-4')
    messages = [
      SystemMessage(
        content="文章の理解度を測るためクイズを作成してください。選択肢は3つでそのうち1つだけを正解にしてください、選択肢は文章で作成してください。問題数は3問です"
      ),
      HumanMessagePromptTemplate.from_template("{input}"),
      HumanMessage(content="クイズは全てに日本語で出題してください"),
    ]
    prompt = ChatPromptTemplate(messages=messages)
    return create_structured_output_chain(QuizList, llm, prompt, verbose=False)

class Choice(BaseModel):
  """a choice model"""
  id: int = Field(...,description="選択肢の番号。他の選択肢とは重複しない番号です")
  text: str = Field(...,description="選択肢の文章")
  is_correct: bool = Field(...,description="正解かどうか")
  explanation: str = Field(...,description="選択の正しいところ、誤っているところを説明してください")

class Quiz(BaseModel):
  """a quiz model"""
  question: str = Field(...,description="クイズの問題文")
  choices: list[Choice] = Field(...,description="クイズの選択肢。選択肢は3つ。正解は1つです。正解の順番はランダムにしてください")

class QuizList(BaseModel):
  """ quizs model"""
  quizs: list[Quiz] = Field(...,description="クイズのリスト")

chain.run(content)を実行することでset_make_quiz_chain{input}にcontentが埋め込まれます。

LangSmithでログを確認すると記事の内容がプロンプトとしてLLMに与えられていることがわかります

そして、その応答としてQuizListの形式でクイズが生成されているのが確認できます。

このあとはフロントエンドのレスポンスを元にクイズを表示しています。

課題

記事の長さによっては、Token数がGPT-4の上限を超えてしまい、エラーになります。
記事を分割して、要約しながら読み込む必要があります。

参考記事 : https://zenn.dev/ryo_kawamata/articles/98b7cc1c67ad0c

おわりに

アプリケーションは公開しているので、気になる方がいたらぜひ使ってみてください!
https://quizbite.yukyu.net

また、このアプリケーションをテーマにLTしたのでよかったらスライドもご覧ください。
https://speakerdeck.com/yukyu30/arayurusaitowo-kuizunisurusaitowotukututa

クイズ生成部分はAPIサーバーとして構築したので、自分のブログにクイズ設置するなどの使い方もしてみました。

Discussion