GameWith Developer Blog

GameWith のエンジニア、デザイナーが技術について日々発信していきます。

Guidance(Microsoft)のすすめ #llm #ChatGPT #LangChain #TechWith

はじめに

どうも@shgxです。 最近Chatbot系など触ったりしているのですが、Guidanceというフレームワークの触り心地が良かったので一つご紹介させて頂きたいと思います。

github.com

Guidanceとは?

GuidanceはMicrosoftが提供しているChatBot向けのフレームワークです。

ChatGPTやその他の大規模言語モデル(LLM)をより効果的に制御するために様々な機能が設計されています。

具体的には、プロンプト・構文・関数などを用いて、モデルの行動を調整し、適切なチャット応答を生成することができます。

先発のChatBot向けフレームワークとしてはLangChainがありますが、こちらの対抗馬として最近話題になっています。

Guidanceの特徴

  1. シンプルで直感的な構文: 「Handlebarsテンプレート」に基づいたテキストベースの構文を採用することで、誰でも容易に理解し、使用することができます。

  2. 豊富な出力構造: 生成、選択、条件分岐、ツールの使用など、複数の要素を組み合わせて構造を作ることができます。 これにより、高度な制御と効率性が実現されます。

  3. ストリーミング機能: JupyterやVSCodeノートブックでのプレイグラウンドのような機能を提供しています。 これにより、直感的で実験的な開発が可能になります。

  4. キャッシュベースの高速なテキスト生成: 再利用可能な情報を保存して再利用することで、高速で効率的なテキスト生成を可能にします。

  5. 役割(role)ベースのチャットモデルのサポート: ChatGPTのような役割ベースのチャットモデルをサポートしています(回答だけ、計画や専門家のロールなど) これにより、対話形式のアプリケーションでの使用が容易になります。

  6. Hugging Faceモデルとの統合: Hugging Faceの言語モデルと簡単に統合することができます。 この統合により、速度向上、プロンプトの最適化、形式の制約への対応などが可能になります。

以上のような特徴があります。 一応、公式の説明も載せておきます。

ガイダンスプログラムは、従来のプロンプティングやチェイン法よりも効果的かつ効率的に現代の言語モデルを制御することを可能にします。ガイダンスは、言語モデルがテキストを処理する方法と実際に一致するように、生成、プロンプティング、論理的な制御を連続した流れで組み合わせることができます。Chain of Thoughtやその他のバリエーション(ART、Auto-CoTなど)などのシンプルな出力構造は、言語モデルの性能を向上させることが示されています。よりパワフルなGPT-4の登場により、さらに豊かな構造が可能になり、ガイダンスによってその構造をより簡単かつ効率的に活用することができます。

出典:https://github.com/microsoft/guidance/

基本的な使い方

初期化は以下のように行います。

import guidance

# 推論する時に使用する言語モデルを指定します
guidance.llm = guidance.llms.OpenAI("gpt-3.5-turbo")

次にテンプレートを渡して、生成実行。

completion = guidance(template)
completion(query="ほげほげ")

上記の変数templateが「Handlebarsテンプレート」に当たります。

テンプレートで書いた内容(str)に従ってguidanceは実行されます。

内容については以下に具体例を載せます。

具体例1.

{{#system~}}
You are a helpful assistant.
{{~/system}}
{{#user~}}
{{query}}
{{~/user}}
{{#assistant~}}
{{gen 'answer' temperature=0.0 max_tokens=600}}
{{~/assistant}}

上記の記法が「Handlebarsテンプレート」になります。

上記内の{{query}}にはqueryとして渡した"ほげほげ"が設定されます。

その他については次の項目で説明しますが、この記法を採用することで、実際に行っているプロンプトの処理に対して見通しを良くすることが出来るようになっています。

テキストで書く形式なので変更が容易でカスタマイズも高くなっています。

上記の例の場合は1回しかPOSTをしていません({gen ~}がそれに当たる)が、記法内で複数回POSTすることも可能です。

また、結果を元にさらに質問答えたり、結果を元に関数を利用したり、ifやelseの条件分岐を書くことさえも出来るようになっています。

POST内容との結びつけ

どう見通しが良いのか分かりづらいと思うので、「Handlebarsテンプレート」記法で書いた時にguidanceがどういう挙動するかPOSTがどうなるかを説明します。

具体例1について、system, user, assistantの3つの区切りがありますが、このひとまとりをcurlでのOpenAIのCompletionAPIへのPOSTに書き直してみます。

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
     "model": "gpt-3.5-turbo",
     "messages": [
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": "{query}"},
      ],
     "temperature": 0.0
         "max_tokens": 600
   }'

上記のようなmessages(過去のメッセージ)を持つPOSTに書き換えられます。

つまり、

{"role": "system", "content": "You are a helpful assistant."}

が#systemで囲まれた部分

{"role": "user", "content": "{query}"}が#userで囲まれた部分

で表現されていると分かります。

そして#assistant内で

{{gen 'answer' temperature=0.0 max_tokens=600}}

と書いた時、#assistantまでの内容の質問をChatGPTのAPIに飛ばすようになっています。

genの後ろ部分 ‘answer’は素の状態で答えるroleです。他にも様々なroleがあります。

例えば、計画してオプションを答えるplanや、専門家として答えるexpertsなど。

roleによって、上記のPOST内容は変わるものと思われます。

関数内で使う場合

def completion(self, query: str) -> str:
  bot: Program = guidance(template)
  completion = bot(query=query)
  return completion['answer']

template内でanswerで出力する場合の実行例です。

上記のようにcompletion[{gen option}]でそのgenコマンドの出力内容をstrで取得できます。

streamで逐次取得する方法もおそらくあると思います。

応用的な使い方

planのroleで実行する際の公式のchatの実装例を見てみます。

github.com

内容はゴールだけ与えて、プランのオプションを生成して、その中からベストなプランを選択するものです。

テンプレートの形式に着目して見てみます。インデント入れて少しわかりやすくしています。

{{#system~}}
You are a helpful assistant.
{{~/system}}
{{#block hidden=True}}
 {{#user~}}
    I want to {{goal}}.
    {{~! generate potential options ~}}
    Can you please generate one option for how to accomplish this?
    Please make the option very short, at most one line.
    {{~/user}}
 {{#assistant~}}
 {{gen 'options' n=5 temperature=1.0 max_tokens=600}}
 {{~/assistant}}
{{/block}}
{{~! generate pros and cons and select the best option ~}}
{{#block hidden=True}}
 {{#user~}}
    I want to {{goal}}.
    
    Can you please comment on the pros and cons of each of the following options, and then pick the best option?
    ---{{#each options}}
    Option {{@index}}: {{this}}{{/each}}
    ---
    Please discuss each option very briefly (one line for pros, one for cons), and end by saying Best=X, where X is the best option.
    {{~/user}}
 {{#assistant~}}
 {{gen 'prosandcons' temperature=0.0 max_tokens=600}}
 {{~/assistant}}
{{/block}}
{{#user~}}
I want to {{goal}}.
{{~! Create a plan }}
Here is my plan:
{{parse_best prosandcons options}}
Please elaborate on this plan, and tell me how to best accomplish it.
Please output in Japanese.
{{~/user}}
{{#assistant~}}
{{gen 'plan' max_tokens=500}}
{{~/assistant}}

上記のように一部のやり取りをblockで囲むことができます。

blockでhiddenに設定したものは内部で自動的にプロンプトを生成して実行して、ユーザからは見えないように出来ます。

{{parse_best prosandcons options}}

ここで予め定義した関数を渡しています。

parse_bestという関数にprosandcons, optionsを渡してあげています。

関数をテンプレート内で利用するためには、以下のように渡す必要あります。

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(template)
out = create_plan(goal='read more books', parse_best=parse_best)

LangChainからの移植

ここまでの説明でGuidanceが結構使えるものというのは伝わったと思いますが、LangChainの内蔵プロンプトの効果も高いのも確かです。

そこで、LangChainのverboseモードでquestion_answeringのload_qa_chainのstuff roleのプロンプトを表示して、それをベースにGuidanceのテンプレートに移植してみます。

移植したものが以下になります。これでLangChainと同じ出力ができるようになります。

{{#system~}}
Use the following pieces of context to answer the users question. 
If you don't know the answer, just say that you don't know, don't try to make up an answer.
If following pieces of context does not relate to the question, just say that you don't know, don't try to make up an answer.
----------------
:{{doc}}
{{~/system}}
{{#user~}}
{{query}}
{{~/user}}
{{#assistant~}}
{{gen 'answer' max_tokens=1000}}
{{~/assistant}}

最後に

Guidanceは全体的に構造がわかりやすく、実装部分の見通しが良いと思っています。

LangChainでは、処理をカスタマイズするために自分でChainのクラスを作成する必要があり、その結果メンテナンスが複雑になりやすいです。
しかし、Guidanceでは、そのような面倒さがなく、プロンプトベースのシンプルな操作でチャットボットの制御ができます。

これは、POSTとGETの内容に応じて処理を行うといったシンプルな作業をする際に、構造の明快さと見通しの良さが求められることに見合っています。

一方で、GuidanceはStoreやベクトル探索などの関連技術への対応がまだ不十分な部分も多いです。これらの技術を使用するには、自前で実装するかLangChainと併用するしかなさそうですね。

LangChainは内部プロンプトを含め関連技術も良い感じによしなにやってくれて良いですが、より分かりやすい構造を求めている方や、Storeやベクトル探索などの関連技術を自前で用意できる人にはかなり使い心地良いのでGuidanceぜひオススメです!!

exampleのnotebookにここでは紹介しきれなかった様々な機能の実例が載ってますので、こちらの記事読んだ後に色々見て試してみると良いかもしれません。

 


GameWithでは現在エンジニアを絶賛募集中です!

サーバーエンジニアやフロントエンジニアの方、AIに興味がある方や、Unityでの開発に興味がある方もお気軽にカジュアル面談をお申し込みください!

github.com