AI 自然言語

LangchainのMultiPrompt(Router)を分析する

サンプルコードだけではいまいち挙動が分からなかったので、手元でがちゃがちゃして理解してみたお話。

忙しい人向け

chain = MultiPromptChain(
    router_chain=router_chain,
    destination_chains=destination_chains,
    default_chain=default_chain,
    verbose=True,
)

チェイン実行時に入力したプロンプトは、router_chainで指定するPromptで解釈するよ!
解釈したプロンプトをもとに、destination_chainsに登録されているテンプレートが選択されるよ!
選択されたテンプレートはdefault_chainで実行されるよ!

以上、3行。

まずは動かしてみる

from langchain.chains.router import MultiPromptChain
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain,ConversationChain
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE

animal_template = 'あなたは動物の専門家です。質問に対して小学生でもわかりやすいように回答をしてくれます。それでは質問です:{input}'
fish_template = 'あなたは魚の専門家です。質問に対して小学生でもわかりやすいように回答をしてくれます。それでは質問です:{input}'
insect_template = 'あなたは昆虫の専門家です。質問に対して小学生でもわかりやすいように回答をしてくれます。それでは質問です:{input}'

prompt_infos = [
    {
        "name": "animal",
        "description": "動物について易しく教えてくれます。",
        "prompt_template": animal_template,
    },
    {
        "name": "fish",
        "description": "魚について易しく教えてくれます。",
        "prompt_template": fish_template,
    },
    {
        "name": "insect",
        "description": "昆虫について易しく教えてくれます。",
        "prompt_template": insect_template,
    },
]

簡単なテンプレートの分岐を作って挙動を確かめる。↑以降のコードはサンプルコードと一緒なので割愛。
インポートしているMULTI_PROMPT_ROUTER_TEMPLATEがいやに目立っていたので、中身を見てみると、プロンプトだった。ヘッダー的にプロンプトに付与することで出力を強制する感じだね。モデルサイズが小さいLLMだとちょっと厳しいけどOpenAIのAPIを呼び出せばいい感じに動く。

chain.run("マグロの産卵方法を教えて。")
> Entering new MultiPromptChain chain...
fish: {'input': 'マグロの産卵方法を教えて。'}
> Finished chain.
'\n\nマグロは、海洋で卵を産卵します。卵は、海洋の雨水や海水に浮かぶように小さな玉状の卵を山を作ります。産卵が終わると、卵は海洋中を漂います。卵が拾われると、ときどき養殖場に入れられ、生まれ変わったマグロになります。'

マグロについて聞くとfishを選んでくれる。けど、これだけだと挙動が分からなかった。
default_chainの役割とかdestination_chainsはどう絡んできているのか?などなど。

入力がどこで解釈されているか??

prompt_infos = [
    {
        "name": "animal",
        "description": "動物について語尾にニャーをつけて教えてくれます。",
        "prompt_template": animal_template,
    },
    {
        "name": "fish",
        "description": "魚について語尾にごわすをつけて教えてくれます。",
        "prompt_template": fish_template,
    },
    {
        "name": "insect",
        "description": "昆虫について語尾にだってばよをつけて教えてくれます。",
        "prompt_template": insect_template,
    },
]

prompt_infosを変えてみる。そして実行。

chain.run("マグロ")

> Entering new MultiPromptChain chain...
fish: {'input': 'マグロごわす'}
> Finished chain.
'\n\nマグロごわすは、太平洋やインド洋などの大洋、そしてそれらを流れる河川などの海域に生息しています。'

マグロごわすwww

ということで、入力プロンプトの内容をprompt_infosの指定方法で解釈をしてinputを作っているみたいだね。

もう少しだけ検証。

prompt_infos = [
    {
        "name": "animal",
        "description": "入力をそのまま渡す。",
        "prompt_template": animal_template,
    },
    {
        "name": "fish",
        "description": "入力をそのまま渡す。",
        "prompt_template": fish_template,
    },
    {
        "name": "insect",
        "description": "入力をそのまま渡す。",
        "prompt_template": insect_template,
    },
]

chain.run("ムカデ")
> Entering new MultiPromptChain chain...
insect: {'input': 'ムカデ'}
> Finished chain.
'\n\nはい、日本にはいろんな種類のムカデが生息しています。例えば、レッドモンキームカデ、ピンクスパイダームカデ、ツートンムカデなどが日本に生息しています。'

ムカデ=insectと解釈をした上で、「ムカデ」だけをインプットにしてくれている。

今、destinationには「insect:入力をそのまま渡す」と指定されていて、LLMにはこのプロンプトが渡っている状態なので、
全体的に解釈をしてinsectかつ入力をそのままinputとして渡すようにしてくれている形みたい。

言語処理能力高くないとこれは難しいね。簡単なものならいいけど、少し難しいと100%の精度は出せなさそう・・・。

inputは正しくdefault_chainに渡されているのか?

animal_template = 'あなたは動物の専門家です。質問に対して小学生でもわかりやすいように回答をしてくれます。それでは質問です:{input}の大きさは?'
fish_template = 'あなたは魚の専門家です。質問に対して小学生でもわかりやすいように回答をしてくれます。それでは質問です:{input}の生息地はどこですか?'
insect_template = 'あなたは昆虫の専門家です。質問に対して小学生でもわかりやすいように回答をしてくれます。それでは質問です:{input}は日本に生息していますか?'

templateをちょっと修正。

chain.run("クジラ")
> Entering new MultiPromptChain chain...
animal: {'input': 'クジラ'}
> Finished chain.
'\n\nクジラは、長さによって異なりますが、標準的な大きさでは、体長が20メートル(約66フィート)、重さが5万キログラム(約11万ポンド)を超えることがあります。'

chain.run("秋刀魚")
> Entering new MultiPromptChain chain...
fish: {'input': '秋刀魚'}
> Finished chain.
'\n\n秋刀魚は、日本にある多くの海岸沿いや河川、湖沼などの淡水域に生息しています。'

animalの場合は大きさを聞いて、fishの場合は生息地を答えてくれている。

ちゃんと機能しているね。

MULTI_PROMPT_ROUTER_TEMPLATEとは何か?

ちなみに、サンプルコードの通り実行を進めていくと、router_templateは以下の通りとなる。
これはほとんどMULTI_PROMPT_ROUTER_TEMPLATEのプロンプトになっている。

Given a raw text input to a language model select the model prompt best suited for the input.
 You will be given the names of the available prompts and a description of what the prompt is best suited for.
  You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.

<< FORMATTING >>
Return a markdown code snippet with a JSON object formatted to look like:
```json
{{{{
    "destination": string \\ name of the prompt to use or "DEFAULT"
    "next_inputs": string \\ a potentially modified version of the original input
}}}}
```

REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts.
REMEMBER: "next_inputs" can just be the original input if you don\'t think any modifications are needed.

<< CANDIDATE PROMPTS >>
animal: 動物について易しく教えてくれます。
fish: 魚について易しく教えてくれます。
insect: 昆虫について易しく教えてくれます。

<< INPUT >>
{{input}}

<< OUTPUT (must include ```json at the start of the response) >>
'

CANDIDATE PROMPTSにprompt_infosで作ったdescriptionが入力されている。これは、以下の操作で入力されてきたもの。
リプレース前は{destination}というプレースホルダになっていた。「description」とかが強制というわけではなく、destinationsに引き渡す時に、「\n」で区切られている文字列であれば何でもいいっぽい。なんでもいいと言っても、上の方でも書いた通り、入力したプロンプトはここと比較されるので、それなりのキーワードが含まれている必要はあるけど。

router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=destinations_str)

-AI, 自然言語
-,