いまからわかる!ChatGPT活用プログラミング

ChatGPT APIを取り巻くライブラリ LangChainとguidanceの紹介

こんにちは! 逆瀬川@gyakuseです!

前回はOpenAIが公開しているChat APIとWhisper APIを用いて議事録文字起こしアプリケーションを作ってみました。今回は、Chat APIを便利に使うためのライブラリであるLangChainguidanceを紹介していきます。

なぜ便利に使うためのライブラリが必要なのか?

単純にChat APIにリクエストを送るだけであれば、各言語に用意されたライブラリを使うだけで良いでしょう。たとえば、Pythonにおいてはopenai-pythonが用意されています。前回紹介したとおり、Chat APIを使うだけなら以下のようなリクエストを作るだけで済みます。

import openai
openai.api_key = "sk-..."  # APIキー

completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo", 
    messages=[{"role": "user", "content": "こんにちは!"}]
)
print(completion.choices[0].message.content)

しかし、⁠複雑なプロンプトのテンプレートを管理する」⁠他の機能と連動させる」⁠Chat APIへのリクエストをチェーン状に構築する」などを行いたい場合、自前実装だけではカオスになってしまう可能性があります。

こうした課題を解決するために作られたものが、LangChainおよびguidanceとなります。

LangChainとは

LangChainとは、大規模言語モデルを使った様々なアプリケーションの開発を支援するためにつくられたライブラリです。非常に汎用的な目的のもと作られていますが、大別して『Chatbot』『Agent』という2つの機能を保持しています。

Chatbot

Chatbotは、ChatGPTのようなアプリケーションを作成するための機能です。OpenAIのライブラリに不足していた、会話の履歴を保持する能力を持ちます。LangChainでは、この履歴のことをMemoryと呼称しています。

Agent

Agentは、大規模言語モデルの判断能力を活かして、ユーザーの代わりに様々なツールを組み合わせてタスクを実行するための機能です。これは、汎用的な人工知能を目指したものであるとも言えます。

汎用人工知能には任意のタスクを任意のツールから自動で実行することが求められます。Agentでは、ツールの選択や入力の決定を言語モデルの能力を用いて行います。たとえば、⁠今日の運勢を占う」⁠今日の天気を調べる」⁠音楽を再生する」といったツールを持つAgentを作った場合、⁠今日の天気は?」という質問を投げかけると、どのツールを使うべきか自動で判断し、⁠今日は晴れのち曇り、降水確率は40%」といった回答を行ってくれるようになります。

LangChainの使い方

それではLangChainの使い方を見ておきましょう。

以下を実行し、langchainおよびopenai-pythonをインストールします。

pip install langchain
pip install openai

Chatbotを用いた履歴を保持した会話

LangChainのChatbotにおいて主に使うのは、OpenAILLMChainPromptTemplateConversationBufferWindowMemoryの各モジュールです。OpenAIはOpenAIのAPIを呼び出すために使われ、LLMChainはプロンプトやメモリ等を統括するために用いられます。PromptTemplateはプロンプトの管理役で、ConversationBufferWindowMemoryがチャット履歴の保持部分にあたります。

ここでは通常の会話をしてみましょう。

from langchain import OpenAI, LLMChain, PromptTemplate
from langchain.memory import ConversationBufferWindowMemory


template = """あなたは優秀なアシスタントです。
{history}
Human: {human_input}
Assistant:"""
prompt = PromptTemplate(
    input_variables=["history", "human_input"], 
    template=template
)


chatgpt_chain = LLMChain(
    llm=OpenAI(temperature=0), 
    prompt=prompt, 
    verbose=True, 
    memory=ConversationBufferWindowMemory(k=2),
)

output = chatgpt_chain.predict(human_input="こんにちは")
print(output)

PromptTemplatehistoryは会話履歴を参照するための変数であり、ConversationBufferWindowMemoryの引数のk=2は、過去2件の会話履歴を保持することを意味します。

このプログラムを実行すると、以下のような結果になります。

> Entering new LLMChain chain...
Prompt after formatting:
あなたは優秀なアシスタントです。

Human: こんにちは
Assistant:

> Finished chain.
 こんにちは!どうしましたか?

また続けて以下を実行すると、会話履歴を明示的に与えずに会話が引き継がれることが分かります。

output = chatgpt_chain.predict(human_input="今日はいい天気ですね")
print(output)
> Entering new LLMChain chain...
Prompt after formatting:
あなたは優秀なアシスタントです。
Human: こんにちは
AI:  こんにちは!どうしましたか?
Human: 今日はいい天気ですね
Assistant:

> Finished chain.
 はい、今日はとてもいい天気ですね!

Agentを用いたWikipediaの参照

Agentではload_toolsでツールをロードし、initialize_agentでツールやAgentを設定します。ここではWikipediaを参照して、⁠ぼっち・ざ・ろっく』というアニメーションの登場人物を聞いてみましょう。Wikipediaの参照にはwikipediaライブラリの事前インストールが必要になります。

pip install wikipedia

このライブラリではwikipediaのページをリクエストしますが、残念ながら英語版しか対応していないため、英語でプロンプトを構築することにします。

from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)
tools = load_tools(["wikipedia"], llm=llm)
agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)
agent.run("Please tell me about the characters in Bocchi the Rock?")

このプログラムを実行すると、以下のような出力が得られます。

> Entering new AgentExecutor chain...
 I should look up information about the characters in Bocchi the Rock.
Action: Wikipedia
Action Input: Bocchi the Rock characters
Observation: Page: Bocchi the Rock!
Summary: Bocchi the Rock! (ぼっち・ざ・ろっく!, Botchi Za Rokku!) is a Japanese four-panel manga series written and illustrated by Aki Hamaji. It has been serialized in Houbunsha's seinen manga magazine Manga Time Kirara Max since December 2017. Its chapters have been collected in five tankōbon volumes as of November 2022.
An anime television series adaptation produced by CloverWorks aired from October to December 2022. The series has been praised for its writing, comedy, characters, and depiction of social anxiety, with the anime's visual creativity receiving acclaim.

Page: Hitori Bocchi no Marumaru Seikatsu
Summary: Hitori Bocchi no Marumaru Seikatsu (Japanese: ひとりぼっちの○○生活, lit. "Bocchi Hitori's ____ Life" or "The ____ Life of Being Alone") is a Japanese yonkoma manga series written and illustrated by Katsuwo. It was serialized in ASCII Media Works' Comic Dengeki Daioh "g" magazine from September 2013 to April 2021. Eight tankōbon volumes have been released. An anime television series adaptation by C2C aired from April to June 2019.

Page: Yoshino Aoyama
Summary: Yoshino Aoyama (Japanese: 青山吉能, Hepburn: Aoyama Yoshino, born May 15, 1996) is a Japanese voice actress and singer affiliated with 81 Produce. Some of her noteworthy roles include Yoshino Nanase in Wake Up, Girls!, Guri in Love Tyrant, Makoto in Shachibato! President, It's Time for Battle!, and Hitori Gotō in Bocchi the Rock!.


Thought: I now know the final answer.
Final Answer: Yoshino Aoyama is a Japanese voice actress and singer affiliated with 81 Produce who is known for her role as Hitori Gotō in Bocchi the Rock!.

> Finished chain.
Yoshino Aoyama is a Japanese voice actress and singer affiliated with 81 Produce who is known for her role as Hitori Gotō in Bocchi the Rock!.

ぼっち・ざ・ろっくを検索する、という判断を行うところまでは正しく行えています。一方で、キャラクターの回答とはならず、青山吉能さんが後藤ひとりを演じたことを回答してしまいました。このように、言語モデルに柔軟に何でも実行させようとするのはまだまだ難しい面もありますが、非常に挑戦的な分野であるとも言えるでしょう。

guidanceとは

guidanceとは、Microsoftが開発した、大規模言語モデルのプロンプトエンジニアリングを効率的に行うことを補助するためのライブラリです。先ほどのLangChainのAgentは複雑な機能の開発を視野に入れたライブラリでしたが、guidanceはより言語モデル自体を使いやすくすることに焦点を当てています。

guidanceの使い方

それではguidanceを使ってみましょう。以下を実行し、guidanceをインストールします。

pip install guidance

基本的な使い方

もっともシンプルなサンプルは以下のようになります。

import guidance
guidance.llm = guidance.llms.OpenAI("gpt-4")

create_plan = guidance('''{{#system~}}
あなたは素晴らしいアシスタントです。
{{~/system}}
{{#user~}}
{{user_input}}
{{~/user}}
{{#assistant~}}
{{gen 'answer' temperature=1.0 max_tokens=600}}
{{~/assistant}}''')

out = create_plan(user_input='こんにちは', parse_best=parse_best)
out

llmの部分で言語モデルを設定しています。次のguidanceに渡すプロンプトの部分で、システム設定、ユーザー入力、アシスタント(言語モデル)からの返答の生成を定義しています。OpenAIのライブラリとは異なり、システム設定などをプログラミングライクに行えるのが特徴です。

guidance内のプロンプトでは以下の事柄を記述しています。

  • {{#system~}}{{~/system}}で囲まれた部分:システム設定
  • {{#user~}}{{~/user}}で囲まれた部分:ユーザー入力
  • {{#assistant~}}{{~/assistant}}で囲まれた部分:言語モデルからの返答
    • 生成部分は{{gen 'answer' temperature=1.0 max_tokens=600}}のように表現します。

応用的な使い方

より複雑なプロンプトの組み合わせを考えてみましょう。今回は、冷蔵庫にある食材から晩ごはんのメニューを検討し、どのように調理するかスケジュール立てをするAIを作ってみます。フローとしては、以下のようになります。

  1. 食材からメニュー候補を考える
  2. メニュー候補と現在の体調からもっとも良いと思われるものを選択する
  3. 調理方法を提案する

これを実装すると以下のようになります。

import guidance
import re
guidance.llm = guidance.llms.OpenAI("gpt-4")

def parse_best(prosandcons, options):
    best = re.search(r'Best=(\d+)', prosandcons)
    if not best:
        best =  re.search(r'Best.*?(\d+)', 'Best= option is 3')
    if best:
        best = int(best.group(1))
    else:
        best = 0
    return options[best]

create_plan = guidance('''{{#system~}}
あなたは素晴らしい料理研究家です。
{{~/system}}
{{#user~}}
私は以下の食材から料理を行いたいと思っています。
{{shokuzai}}
{{~! 食材からメニュー候補を考える ~}}
この食材群から作れる料理について 1 つの教えてください。
料理名は非常に短く、最大でも 1 行にしてください。
{{~/user}}
{{#assistant~}}
{{gen 'foods' n=5 temperature=1.0 max_tokens=600}}
{{~/assistant}}
{{~! 体調を考慮し、メニュー候補からもっとも良いと思われるものを選択する ~}}
{{#user~}}
私は今晩の晩ごはんを決めたいです。
なお、今のわたしの体調は{{condition}}です。

体調を考慮しつつ、次の各メニューから長所と短所についてコメントして、最適なメニューを選択していただけますか?
---{{#each foods}}
Menu {{@index}}: {{this}}{{/each}}
---
各メニューについて非常に簡潔に説明し (長所を1行、短所を1行)、最後に Best=X (X が最良のオプション) と言って終了してください。
{{~/user}}
{{#assistant~}}
{{gen 'prosandcons' temperature=0.0 max_tokens=600}}
{{~/assistant}}
{{#user~}}
{{~! 調理方法の提案 }}
私は今晩の晩ごはんを作りたいです。
私が作る予定の料理は以下です:
{{parse_best prosandcons foods}}
また材料は以下です:
{{shokuzai}}
この料理の作り方を詳しく説明してください。
{{~/user}}
{{#assistant~}}
{{gen 'plan' max_tokens=1024}}
{{~/assistant}}''')

out = create_plan(shokuzai='納豆、ピーマン、レタス、レッドブル、生姜、味噌、きゅうり、メロン、ハム、卵', condition='胃が重い', parse_best=parse_best)
out

最終的に以下のような胃に優しいどんぶりが提案されました。

納豆きゅうり卵の丼の作り方:

材料:
- 納豆 1パック
- きゅうり 1本
- 卵 2個
- ごはん 1膳分
- 醤油 小さじ1
- みりん 小さじ1
- 生姜 1かけ(すりおろす)

手順:
1. きゅうりを洗って、薄い輪切りにする。
2. 生姜をすりおろし、醤油とみりんと混ぜてタレを作る。
3. 納豆に付属のたれとからしを加えて、よくかき混ぜる。
4. 卵を割り入れ、納豆と一緒にさらによくかき混ぜる。
5. ごはんを丼に盛り、きゅうりを敷き詰める。
6. 納豆卵をごはんの上にかけ、作ったタレをかける。
7. お好みで、刻んだ海苔や白ごまをトッピングして完成。

注意: 今回の材料リストに含まれるピーマン、レタス、レッドブル、味噌、メロン、ハムは、納豆きゅうり卵の丼には使用していません。別の料理やサイドメニューとして活用してください。

プロンプト部分を詳しく見ていきましょう。

{{#system~}}
あなたは素晴らしい料理研究家です。
{{~/system}}
{{#user~}}
私は以下の食材から料理を行いたいと思っています。
{{shokuzai}}
{{~! 食材からメニュー候補を考える ~}}
この食材群から作れる料理について 1 つの教えてください。
料理名は非常に短く、最大でも 1 行にしてください。
{{~/user}}
{{#assistant~}}
{{gen 'foods' n=5 temperature=1.0 max_tokens=600}}
{{~/assistant}}

まず、この部分でメニュー候補を5つ生成しています。生成したメニューはfoodsという変数でアクセス可能になっています。

{{~! 体調を考慮し、メニュー候補からもっとも良いと思われるものを選択する ~}}
{{#user~}}
私は今晩の晩ごはんを決めたいです。
なお、今のわたしの体調は{{condition}}です。

体調を考慮しつつ、次の各メニューから長所と短所についてコメントして、最適なメニューを選択していただけますか?
---{{#each foods}}
Menu {{@index}}: {{this}}{{/each}}
---
各メニューについて非常に簡潔に説明し (長所を1行、短所を1行)、最後に Best=X (X が最良のオプション) と言って終了してください。
{{~/user}}
{{#assistant~}}
{{gen 'prosandcons' temperature=0.0 max_tokens=600}}
{{~/assistant}}

次にこちらで体調とメニュー候補群をもとにメニューを決めています。新しく登場した {{#each foods}}{{/each}} ですが、これはプログラミングにおけるForeach文であり、foods変数にアクセスして、メニュー候補を取得しています。これらのメニュー候補を展開後、{{gen 'prosandcons' temperature=0.0 max_tokens=600}}で長所と短所をコメントさせ、もっとも良いメニューを選択させています。

{{~! 調理方法の提案 }}
私は今晩の晩ごはんを作りたいです。
私が作る予定の料理は以下です:
{{parse_best prosandcons foods}}
また材料は以下です:
{{shokuzai}}
この料理の作り方を詳しく説明してください。
{{~/user}}
{{#assistant~}}
{{gen 'plan' max_tokens=1024}}
{{~/assistant}}

最後に選択されたメニューをもとに作り方を提案しています。prosandconsの生成時には最終行にBest=Xと出力させるようにしていましたが、parse_best関数ではこの情報をもとに何番目のメニューがもっとも良かったかという情報を抽出し、そのメニュー名を求める処理をしています。このように、外部で定義された関数も使用できます。

まとめ

今回はLangChainおよびguidanceというライブラリを紹介し、Chat APIをより簡単で便利に活用するための方法についてまとめました。これらを用いることで、より発展的なアプリケーションが作れるでしょう。

参考文献

おすすめ記事

記事・ニュース一覧