見出し画像

LLMアプリケーションの新定番、Microsoft guidanceライブラリのgenメソッドを詳細に追ってみる

MicrosoftのguidanceライブラリはLLMアプリケーションを作成する際の新たな定番となりそうな気がしています。そういう訳で、今回はguidanceのgenメソッドについて詳しく追ってみたいと思います。

基本的な使い方

import guidance

gpt3 = guidance.llms.OpenAI("text-davinci-003")
gpt3_5 = guidance.llms.OpenAI("gpt-3.5-turbo")
gpt4 = guidance.llms.OpenAI("gpt-4", api_key=API_KEY)
guidance.llm = gpt3

まずはguidanceライブラリを読み込み、使用するLLMを宣言します。OpenAI APIを使用する場合、初期化パラメータとして以下のパラメータを使用できます。

model
使用するモデルの名前を指定します。指定しない場合は環境変数OPENAI_MODELから取得しようとします。それもなければ、指定したファイルから読み込むことを試みます。

caching
APIの呼び出し結果をキャッシュするかどうかを制御します。デフォルトはTrueで、APIのレスポンスがキャッシュに保存されます。

max_retries
APIの呼び出しが失敗した場合の最大再試行回数を指定します。

max_calls_per_min
1分あたりの最大API呼び出し回数を指定します。

api_key
OpenAIのAPIキーを指定します。指定しない場合は、環境変数OPENAI_API_KEYから取得しようとします。それもなければ、指定したファイルから読み込むことを試みます。

api_type
APIのタイプを指定します。デフォルトは"open_ai"です。

api_base
APIの基本URLを指定します。指定しない場合は、環境変数OPENAI_API_BASEかOPENAI_ENDPOINTから取得します。

api_version
使用するAPIのバージョンを指定します。

temperature
モデルの出力のランダム性を制御するパラメータです。値が大きいほど出力はランダムになり、値が小さいほど出力は確定的になります。

chat_mode
チャットモードの使用を制御します。"auto"に設定されている場合、使用するモデルによってチャットモードが自動的に選択されます。

organization
OpenAIの組織IDを指定します。指定しない場合は、環境変数OPENAI_ORGANIZATIONから取得します。

rest_call
RESTful API呼び出しを使用するかどうかを制御します。Falseに設定されている場合は、ライブラリのAPI呼び出しを使用します。

allowed_special_tokens
特殊なトークンの許可を制御します。許可されていない特殊なトークンは、モデルの入力や出力から削除されます。

token (*deprecated*)
APIキーの旧名であり、現在は非推奨です。指定された場合は、api_keyパラメータの値として使用されます。

endpoint (*deprecated*)
APIの基本URLの旧名であり、現在は非推奨です。指定された場合は、api_baseパラメータの値として使用されます。

テキスト補完モデルの例として、GPT-3では以下のようにプロンプトを作成することができます。

program = guidance("""This is a sentence about {{gen "completion" stop="."}}""")
executed_program = program()

genの部分がLLMの生成する箇所になります(上図で緑でハイライトされている箇所)。genの第一引数は「位置引数(positional argument)」と呼ばれており、返ってきたインスタンスに位置引数で指定した値をキーとして読み出すと、その位置引数に対応した生成文字列が返ります。

executed_program["completion"]

'\n\ntraveling'

name引数

また、以下のようにプロンプト内で複数のgen関数を呼び出すことも可能です。

program = guidance("""This is a sentence about {{gen "about" stop="."}}. Because {{gen "because" stop="."}}""")
executed_program = program()

この場合も「about」「because」をそれぞれ読み出すことはできますが、出力されたキーと文字列をひとまとめにする方法もあります。以下の例では「obj」という名前のキーでひとまとめにしています。

out = guidance("""This is a sentence about {{gen "obj.about" stop="."}}.
Because {{gen "obj.because" stop="."}}""")(obj={})
out["obj"]

{'about': '\n\ntraveling',
'because': ' of the pandemic, many people have had to put their travel plans on hold'}

stop引数

stop引数は生成を止めるタイミングをあらわす引数です。ここまでの例では「.(ピリオド)」を指定してきましたが、以下のように複数の任意の文字列を指定することもできます。

out = guidance("""We are gonna {{gen 'text' stop=[" the", " of", " a"]}}""")()
out["text"]

所感)サンプルみたいに綺麗な生成結果になることは少ない。

stop_regex引数とsave_stop_text引数

stopキーワードに正規表現を指定することもできます。以下の例ではプロンプト内の例に従って、問題を解く際の方程式を表すコードを出力させます。

import guidance

guidance.llm = guidance.llms.OpenAI("text-davinci-003", caching=False)

prompt = """次の問題を解き、回答に方程式の計算が必要な場合は以下の文字列によって計算機を呼び出して下さい。

CALC(EQUATION) = ANSWER

例えばCALC((4+3) * 2) = 14のようになります。

問題: {{problem}}

ステップバイステップで問題を解いてください: {{gen 'text' stop_regex="CALC\\(.*?\\) =" max_tokens=400 save_stop_text=True}}"""

program = guidance(prompt)
out = program(problem="ジョーはリンゴを10個持っていて、それぞれのリンゴについて5つのテストを行う必要があります。1つのテストに7分かかるとしたら、全てのテストを終えるまでにどのぐらいの時間がかかるでしょうか?")

save_stop_text引数をTrueに設定しておくと、以下のようにストップ時に出力されたテキストを取得することができます。

out["text_stop_text"]

'CALC(10 * 7) ='

max_tokens引数、n引数、temperature引数

n引数を利用することで、gen関数による生成結果を複数得ることができます。max_token引数によって最大トークン数を制限し、temperature引数で一時的にランダム性を上げて生成してみます。

out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=10 temperature=1.0}}""")()
out["text"]

['\ntraveling.\n\nTraveling can be',
' video games\n\nVideo games have become an incredibly',
' cats\n\nCats are beloved pets for many',
'\ntraveling.\n\nTraveling can be',
'\n\nMotorcycles.\n\nMotorcycles are']

複数の生成結果を得られました。ここでtemperatureを0にするとランダム性がなくなるため、同じ生成結果しか得られなくなります。gen関数ではtemperatureはデフォルトで0に設定されています。

out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=10 temperature=0}}""")()
out["text"]

['\n\neducation.\n\nEducation is an important',
'\n\neducation.\n\nEducation is an important',
'\n\neducation.\n\nEducation is an important',
'\n\neducation.\n\nEducation is an important',
'\n\neducation.\n\nEducation is an important']

top_p引数

top_pもtemperatureと同様、生成される文字列のランダム性を表す値です。temperatureが全体的な確率分布を調整するのに対し、top_pは最も可能性の高い単語群から選択する範囲を調整します。

例えばtop_pを0.95(95%)に設定した場合、モデルは予測した各単語の確率を降順に並べ、それらの累積確率が初めて95%を越えるところまでの単語集合から、次の単語をランダムに選択する動きを取ります。そのため、1に近づくほど選択される単語は多様になり、0に近づくほど確定的になります。

gen関数ではデフォルトで1に設定されています。

out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=5 top_p=0.4 temperature=1.0}}""")()
out["text"]

logprobs引数

logprobsに0以上の数値が設定されると、LLMはテキスト内の各トークンについて、その数だけ上位のトークンの対数確率を返します。試しに3を指定してみたのが以下のコードです。

対数確率が0に近づくほど選択される可能性が高くなると言うわけで、上記のケースでは最終的に「\n\ntraveling.」が生成されていることが分かります。

pattern引数

ReLLMのように正規表現に準じた生成結果を返すこともできます。ただしpattern引数はOpenAI APIで呼び出せるLLMには対応しておらず、transformersでロードするLLMでのみ使用することができます。

以下のコードではLLMに数字のみを返させています。

import guidance

gpt2 = guidance.llms.Transformers("gpt2", device=1)
guidance.llm = gpt2

out = guidance("""This is a sentence about {{gen 'text' stop=" " pattern="[0-9 ]+"}}""")()
out["text"]

hidden引数

LLMに文字列を生成させるがユーザーには生成結果を表示したくない、というケースに使えるのがhidden引数です。

以下のコード例ではtext1の文字列を表示しません。生成結果はtext1キーを指定することで取得することができます。

out = guidance("""This is a sentence about {{gen 'text1' stop=" " hidden=True}}{{gen 'text2' stop="."}}""")()
out["text1"], out["text2"]

parse引数(parse関数に変更されている?)

ドキュメントでは引数として紹介されていますが、最新版で実行しようとすると「そんな引数はない」とエラーになります。

一方で同じような機能を持つparse関数があるため、そちらを紹介します。

例えば {{charactor_name}} のような特殊文字にキャラクターの名前を割り当てたいとします。これをgen関数で書くと以下のようになりますが、キャラクターの名前を割り当てることができません。LLMは {{charactor_name}} を使えと指示されているためです。

# 埋め込みが変換されないように \{{charactor_name}} という形でエスケープしています
guidance("""Write a story about a person and use \{{character_name}} whenever you need to write their name:
STORY
{{gen max_tokens=100}}""", character_name="Jill")()

この場合、{{charactor_name}}をエスケープせずにそのまま埋め込み変数として使うようにすれば「Jill」という名前を割り当てることができますが、プロンプトとしては異なる内容になってしまいます。

guidance("""Write a story about a person and use {{character_name}} whenever you need to write their name:
STORY
{{gen max_tokens=100}}""", character_name="Jill")()

このようなケースを踏まえ、生成された文字列に対して再度値割り当てをするような動作(再帰的な動作)をサポートしているのがparse関数です。

guidance("""Write a story about a person and use \{{character_name}} whenever you need to write their name:
STORY
{{parse (gen max_tokens=100)}}""", character_name="Jill")()

LLMによって生成されたであろう {{charactor_name}} の部分がJillに置き換わっていることが分かります。

list_append引数

最後のlist_append引数は、同じ位置引数が設定されているgen関数の生成結果をリストにするかどうかを設定する引数です。

例えば以下のコード例では同じ「story」位置引数が設定されているgen関数が並んでいますが、それぞれlist_append引数がTrueに設定されているため、生成結果が全てリストで保存されます。

out = guidance("""Write three story title options about the arctic circle:
OUTLINE
1. "{{gen 'story' max_tokens=100 list_append=True}}"
2. "{{gen 'story' max_tokens=100 list_append=True}}"
3. "{{gen 'story' max_tokens=100 list_append=True}}"
""")()
out["story"]

Trueにしないと一つにまとめられてしまいます。

out = guidance("""Write three story title options about the arctic circle:
OUTLINE
1. "{{gen 'story' max_tokens=100}}"
2. "{{gen 'story' max_tokens=100}}"
3. "{{gen 'story' max_tokens=100}}"
""")()
out["story"]

One more thing: guidance関数にllmを指定することもできる

基本的にはguidance.llmグローバル変数に利用するLLMを設定してguidanceを使うわけですが、GPT-4とGPT-3.5を使い分けたいケースも当然ありますよね?

その場合はguidance関数の引数として利用するLLMを指定することが可能です。以下にコード例を示します。

gpt3 = guidance.llms.OpenAI("text-davinci-003")

out = guidance("""This is a sentence about {{gen 'text' n=5 max_tokens=5 top_p=0.4 temperature=1.0}}""",
llm=gpt3)()
out["text"]

今回はgen関数の使い方にフォーカスしましたが、本命はチャットモデルの使い方かなと思います。引き続き、guidanceライブラリの利用方法について試しながらまとめていきたいと思います。

現場からは以上です。

この記事が気に入ったらサポートをしてみませんか?