サンプルコードだけではいまいち挙動が分からなかったので、手元でがちゃがちゃして理解してみたお話。
忙しい人向け
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)