GPT-4とLangChainで技術記事のクイズを生成するサイトを作った
読んだ技術記事の理解を深めるべく、技術記事の内容からクイズを生成するサイトを作りました。
使い方
https://quizbite.yukyu.net にテキストボックスに技術記事のURLを入力し、「クイズを作る」をクリックします。
あとは、クイズが生成されるまで待ちます。生成が終わるまで大体1分半くらいかかります。
https://quizbite.yukyu.net/?url=<技術記事のURL>
とすると、テキストボックスにurlが入力された状態で共有できます。
デモ動画
動機
自分が技術記事を読んだ時の理解度を把握したいという理由で作りました。
構成
-
バックエンド
- クイズ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
おわりに
アプリケーションは公開しているので、気になる方がいたらぜひ使ってみてください!
また、このアプリケーションをテーマにLTしたのでよかったらスライドもご覧ください。
クイズ生成部分はAPIサーバーとして構築したので、自分のブログにクイズ設置するなどの使い方もしてみました。
Discussion
これ教育機関向けとしてもめっちゃ需要がありそうな気がしてます!