LangChainの対抗馬？Microsoft Guidanceを触ってみる (2)

# ①Guidanceってなに？

Guidanceは、今後さらに複雑になるであろうプロンプト開発を見越しているものと捉えています。(私の主観が入っていますが)
またユーザ目線で考えると、複雑なプロンプトを実行するとその分料金が発生します。複雑なプロンプトを簡略化できれば費用も抑えられますよね。

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

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


# ②チュートリアルを試してみる (続き)

以下の続きをやっていきたいと思います。

https://qiita.com/sakue_103/items/5f5f7ee1e60008038a22


## 選択肢を与え、選択させる

- ```select```

ポリシー違反などをチェックできそうな機能です。

```py:
# Yes, No, Maybeを答えさせる
options = ["Yes", "No", "Maybe"]
policy_list = [
    "メールアドレスが含まれている場合、必ずポリシー違反と判断する。"
]
sentence = "test@gmail.comにメールを送りたい。"

program = guidance('''以下の文章はポリシーに準拠していますか？{{options}}のどれかで回答して。
ポリシー:
{{#each policy_list}}- {{this}}
{{/each}}
文章: {{sentence}}
Answer: {{select "answer" options=options}}''')

executed_program = program(
    policy_list = policy_list,
    sentence = sentence,
    options = options
)

print(executed_program["answer"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/07661512-b54a-7378-c27b-86edd6f8d8ce.png)



## 指示&推論を一部非表示にする

- ```block hidden=True```

```py:
# 指示を一部非表示にする
options = ["Yes", "No", "Maybe"]
policy_list = [
    "メールアドレスが含まれている場合、必ずポリシー違反と判断する。"
]
sentence = "test@gmail.comにメールを送りたい。"

program = guidance('''以下の文章はポリシーに準拠していますか？
ポリシー:
{{#each policy_list}}- {{this}}
{{/each~}}

文章: {{sentence~}}

{{#block hidden=True}} {{options}}のどれかで回答する必要がある。
{{select "answer" options=options}}
{{/block}}

Answer: {{answer}}
''')

executed_program = program(
    policy_list = policy_list,
    sentence = sentence,
      options=options
)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/eae617a7-40a8-ffda-5b23-7ab1c0419134.png)



## 非同期処理

- ```await```

ユーザに入力させる、他の処理との連携などで役に立ちそうです。

```py:
# awaitで処理待ち
options = ["Yes", "No", "Maybe"]
policy_list = [
    "メールアドレスが含まれている場合、必ずポリシー違反と判断する。"
]
prompt = guidance('''以下の文章はポリシーに準拠していますか？
ポリシー:
{{#each policy_list}}- {{this}}
{{/each~}}

文章: {{await "sentence"}}

{{#block hidden=True}} {{options}}のどれかで回答する必要がある。
{{select "answer" options=options}} {{/block~}}

Answer: {{answer}}''', stream=True)

# sentenceを与えない場合、sentence以降の処理が進まない
executed_prompt = prompt(
    policy_list = policy_list,
    options=options
)
```

sentence以降は展開されていないのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e10b5420-9044-44d5-4a5c-aef42ebcf011.png)


sentenceを与えると以降の処理が実行されます。

```py:
prompt2 = executed_prompt(
    sentence="test@gmail.comからの返信です。"
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/eaf9f66d-26c7-5bc0-040f-e47493807343.png)



## Chat

- ```system```, ```user```, ```assistant```

この辺りはOpenAI APIと似ていますね。

```py:
import guidance

# モデルの指定
guidance.llm = guidance.llms.OpenAI("gpt-3.5-turbo")

# sysytem, user, assistantを設定
program = guidance('''
{{#system~}}
あなたは情報を探している人を助けるアシスタントです。
{{~/system}}

{{#user~}}
{{conversation_question}}
{{~/user}}

{{#assistant~}}
{{gen 'response' max_tokens=64}}
{{~/assistant}}''')

executed_program = program(conversation_question='ベンサムは誰？')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/7f4ee55d-d55c-9c50-d082-bb9621ea209a.png)


## Chat (Few-Shot)

- ```each```, ```if```

もはやプロンプトの定義部分に自然言語が現れず、、

```py:
system_role = "あなたは日本語を採点するアシスタントです。"

examples = [
    {"role":"user", "content":"ありがとう"},
    {"role":"assistant", "content":"ありがとうは正しいので100点"},
    {"role":"user", "content":"食べれる"},
    {"role":"assistant", "content":"食べられるが正しいので50点"},
]

# 辞書型リストの展開 & ifで分岐
prompt = guidance('''
{{#system~}}
{{system_role}}
{{~/system}}

{{#each examples}}
{{#if (== this.role "user")}}
{{#user}}{{this.content}}{{/user}}
{{else}}
{{#assistant}}{{this.content}}{{/assistant}}
{{/if}}
{{/each}}

{{#user~}}
{{user_input}}
{{~/user}}

{{#assistant~}}
{{gen "response"}}
{{~/assistant}}
''')


# ユーザの入力
user_input = "見れる"

executed_prompt = prompt(
    system_role=system_role,
    examples=examples,
    user_input=user_input
)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/109eb0b0-6d23-2725-497c-e2f8da41de1d.png)


## 自作関数の組み込み

- ```自作関数```, ```Few-Shot```, ```each```などの組み合わせ

API連携とかに使えそうです。LangchainのAgentsの機能を実装できそうですね。

```py:
import guidance

# モデルの指定
guidance.llm = guidance.llms.OpenAI("gpt-3.5-turbo")

# Few-Shot用のサンプルデータ
sample_data = [
    {"filename": "案件A", "filecontent":"AWSの移行を提案中"},
]

# Few-Shot用サンプルデータを展開するプロンプト
fs_prompt = guidance('''
{{~#each results}}
<result>
{{this.filename}}
{{this.filecontent}}
</result>
{{~/each}}''')

# Few-Shot用のチャット例
examples = [
    {"role":"user", "content":"案件Aの情報を教えて"},
    {"role":"assistant", "content":"<search>案件A</search>"},
    {"role":"user", "content": str(fs_prompt(results=sample_data))},
    {"role":"assistant", "content":"案件Aでは、AWSの移行を提案中です"},
]


# モデルによって実行する関数
def action(query):
    if "search" in query: # モデルがsearchを行うと判断した場合
        # <search>を取り除く
        query = query.split(">")[-1]
        
        # 本番データ
        files = [
             {"filename": "案件A", "filecontent":"AWSの移行を提案中"},
             {"filename": "案件B", "filecontent":"Azureのファイルサーバ受注"},
             {"filename": "施策1", "filecontent":"人材育成コンテンツ作成中"},
        ]

        # queryに該当するものを返す
        results = [file for file in files if file["filename"] == query]
        return results
    
    
# 指示文
instruct = """
あなたは必ず事実に基づいて回答をします。
検索が必要な場合は、機能:<search>query</search>を使うべきです。
私が検索結果をあなたに提示するので、その内容を踏まえて回答して。
"""

# ツールの選択を行い実行するプロンプト
prompt = guidance('''
{{#system~}} あなたは情報を探している人を助けるアシスタントです。 {{~/system}}

{{#user~}} {{instruct}} {{~/user}}

{{#assistant~}} はい、指示に従います。いくつかの例を試しましょう。 {{~/assistant}}

{{#each examples}}
{{#if (== this.role "user")}}
{{#user}} {{this.content}} {{/user}}
{{else}}
{{#assistant}} {{this.content}} {{/assistant}}
{{/if}}
{{/each}}

{{#user~}} {{user_query}} {{~/user}}

{{#assistant~}} 
{{gen "thought" stop="</search>"}}
{{~/assistant}}

{{#user~}}
Results:
{{~#each (action thought)}}
<result>
{{this.filename}}
{{this.filecontent}}
</result>
{{~/each}}
{{~/user}}

{{#assistant~}}
{{gen "answer"}}
{{~/assistant}}
''')


# ユーザの入力
user_query = "案件Bについて知りたい"
# user_query = "施策1はどうなっている？"

executed_prompt = prompt(
    instruct=instruct,
    examples=examples,
    user_query=user_query,
    is_search=is_search,
    action=action
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/74e5ed22-297d-5076-2458-b340cf50b4d5.png)


ちなみに、施策1について聞いてみるとうまくクエリを作成できていません。
ちゃんとFew-Shotしないとダメそうです。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/6ef2fbaf-258a-9c30-c6c7-0a3980a3358d.png)



# ③まとめ
個人的にはLangchainは抽象度が高すぎるのがなあ...と思っていたので、guidanceは触っていて楽しいですね。
Langchainは裏でどのようなプロンプトが生成されているのかソースコード読みにいくのが辛いです、、
guidanceはプログラムに馴染みのある人であれば、プロンプトの表現力拡張に使えそうだなと思いました:point_up: