Closed11

LangChainのGetting StartedをGoogle Colaboratoryでやってみる ③Chains

kun432kun432

3. Chains

LLMとプロンプトテンプレートは組み合わせて使うことになる。Chainsはこれをつなぐもの。

LLMとプロンプトテンプレートを用意する。

from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
    input_variables=["product"],
    template=" {product} を作る会社にふさわしい名前を1つ挙げて下さい。",
)

Chainsを使わない場合はこうなる。

llm(prompt.format(product="カラフルな靴下"))

Chainsを使うとこうなる

from langchain.chains import LLMChain
chain = LLMChain(llm=llm, prompt=prompt)
chain.run("カラフルな靴下")

どちらの場合も以下のようなレスポンスが得られる。

\n\nカラフルソックス!

まだこのレベルだと便利さはわからないので、ChainsのGetting Startedを進めていく。

Chains' Getting Started

https://langchain.readthedocs.io/en/latest/modules/chains/getting_started.html

以下の3つが挙げられている。

  • LLM chain
  • Sequential Chain
  • Custom Chain

LLM Chain

LLM Chainはシンプルなチェーンで、プロンプトテンプレートを使ってユーザの入力値をフォーマットし、LLMに投げてレスポンスを受けとるもの。最初の例がそれ。

from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain

llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
    input_variables=["product"],
    template=" {product} を作る会社にふさわしい名前を1つ挙げて下さい。",
)
chain = LLMChain(llm=llm, prompt=prompt)
chain.run("カラフルな靴下")

プロンプトテンプレートとLLMを作成しておいて、LLMChainでllmとpromptを指定する。実行する際にはrun()でプロンプトテンプレートに入力値を渡すだけでよい。

Chainsのメリットはより複雑なチェーンを作れることにある。

Sequential Chain

SimpleSequentialChainを使うと言語モデルを連続して呼び出すことができる。つまり、LLMの応答を次のLLMの問い合わせに使うことができる。

以下のような例が挙げられている。

    1. LLMに製品を投げて会社名を生成する。
    1. 生成された会社名を再度LLMに投げて、今度はキャッチフレーズを生成する。

まずLLMとプロンプトテンプレートをペアになるように作成して(LLMは共通)、それぞれのChainを作る。

from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain

llm = OpenAI(temperature=0.9)

prompt_1 = PromptTemplate(
    input_variables=["product"],
    template=" {product} を作る会社にふさわしい名前を1つ挙げて下さい。",
)
chain1 = LLMChain(llm=llm, prompt=prompt_1)

prompt_2 = PromptTemplate(
    input_variables=["company_name"],
    template="次の会社のキャッチコピーを作って下さい:  {company_name}",
)
chain2 = LLMChain(llm=llm, prompt=prompt_2)

SimpleSequentialChainを使って2つのchainをつなぐ。

from langchain.chains import SimpleSequentialChain
overall_chain = SimpleSequentialChain(chains=[chain1, chain2], verbose=True)

chainを実行する

overall_chain.run("カラフルな靴下")

結果

> Entering new SimpleSequentialChain chain...


「Kaleidoscope Socks」


「色鮮やかな毎日を!Kaleidoscope Socksで豊かな時間を」

> Finished chain.
\n\n「色鮮やかな毎日を!Kaleidoscope Socksで豊かな時間を」

2つのchainの実行結果が表示されているのがわかると共に、2回めのchainでは1回目のchainの結果が反映されているのがわかる。

Custom Chain

自分でカスタムなChainそのものを自分で作ることができる。Custom Chainに必要なのは以下。

  • Chainクラスのサブクラスを作成
  • input_keysとoutput_keysの2つのプロパティを用意
  • チェーンの実行方法を示す_callメソッドを追加

2つのLLMchainの出力を結合するCustom Chainの例

from langchain.chains import LLMChain
from langchain.chains.base import Chain

from typing import Dict, List

class ConcatenateChain(Chain):
    chain_1: LLMChain
    chain_2: LLMChain

    @property
    def input_keys(self) -> List[str]:
        # Union of the input keys of the two chains.
        all_input_vars = set(self.chain_1.input_keys).union(set(self.chain_2.input_keys))
        return list(all_input_vars)

    @property
    def output_keys(self) -> List[str]:
        return ['concat_output']

    def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
        output_1 = self.chain_1.run(inputs)
        output_2 = self.chain_2.run(inputs)
        return {'concat_output': output_1 + output_2}

実際に使ってみる。

prompt_1 = PromptTemplate(
    input_variables=["product"],
    template=" {product} を作る会社にふさわしい名前を1つ挙げて下さい。",
)
chain_1 = LLMChain(llm=llm, prompt=prompt_1)

prompt_2 = PromptTemplate(
    input_variables=["product"],
    template=" {product} を作る会社によいスローガンを1つ挙げて下さい。",
)
chain_2 = LLMChain(llm=llm, prompt=prompt_2)

concat_chain = ConcatenateChain(chain_1=chain_1, chain_2=chain_2)
concat_output = concat_chain.run("カラフルな靴下")
print(f"結合された出力:\n{concat_output}")
結合された出力:


「スリースリーカラーズ」

「足元で彩りを!カラフルな靴下で輝くあなたをサポート!」

How-To

さらにHow-Toで細かく見ていく。

https://langchain.readthedocs.io/en/latest/modules/chains/how_to_guides.html

Chainは、プロンプト、LLM、Utilsなどのプリミティブなものや、他のChainを組み合わせることができる。3つの例が紹介されている

  • Generic Chains
  • Utility Chains
  • Asynchronous

またこれに加えて、LangChainHubからChainをインポートして使うこともできる。

Generic Chains

https://langchain.readthedocs.io/en/latest/modules/chains/generic_how_to.html#

3つの例が紹介されている。

  • LLMChain
    • プロンプトテンプレートとLLMで構成されるシンプルなChain。
  • Transformation Chain
    • Pythonの関数を入力・出力に適用するChain
  • Sequential Chain
    • 複数のチェーンを順番に呼びだすChain
LLMChain

ここまでも何度かみてきたシンプルなLLM Chain。

from langchain import PromptTemplate, OpenAI, LLMChain

template = """質問: {question}

回答: ステップ・バイ・ステップで考えてみましょう。"""
prompt = PromptTemplate(template=template, input_variables=["question"])
llm_chain = LLMChain(prompt=prompt, llm=OpenAI(temperature=0), verbose=True)

question = "ジャスティン・ビーバーが生まれた歳にスーパーボウルを優勝したNFLチームはどこですか?"

llm_chain.predict(question=question)

結果

> Entering new LLMChain chain...
Prompt after formatting:
質問: ジャスティン・ビーバーが生まれた歳にスーパーボウルを優勝したNFLチームはどこですか?

回答: ステップ・バイ・ステップで考えてみましょう。

> Finished chain.
ジャスティン・ビーバーは1994年3月1日に生まれました。1994年のスーパーボウルを優勝したNFLチームはサンフランシスコ・49ersです。

Chainの入力は1つだったが、これを複数にすることもできる。

from langchain import PromptTemplate, OpenAI, LLMChain

template = """{subject}についての{adjective}詩を書いて下さい。"""
prompt = PromptTemplate(template=template, input_variables=["adjective", "subject"])
llm_chain = LLMChain(prompt=prompt, llm=OpenAI(temperature=0), verbose=True)

llm_chain.predict(adjective="悲しい", subject="アヒル")

結果

> Entering new LLMChain chain...
Prompt after formatting:
アヒルについての悲しい詩を書いて下さい。

> Finished chain.
\n\n静かな湖面に浮かぶ\n若きアヒルの背中\n悲しみを抱えて\n涙を流している\n\n悲しみを抱えた彼は\n湖面を泳ぎ続ける\n涙を流しながら\n自分を慰める\n\n悲しみを抱えた彼は\n湖面を泳ぎ続ける\n涙を流しながら\n自分を慰める\n\n悲しみを抱えた彼は\n湖面を泳ぎ続ける\n涙を流しながら\n自分を慰める\n\n悲しみを抱えた彼は\n湖

LLMChain.from_string()を使えば、(prompt templateを使わずに)文字列テンプレートからダイレクトにChainを作ることもできるみたい。

from langchain import PromptTemplate, OpenAI, LLMChain

template = """{subject}についての{adjective}詩を書いて下さい。"""
LLMChain.from_string(llm=OpenAI(temperature=0),template=template)
llm_chain.predict(adjective="悲しい", subject="アヒル")
Sequential Chain

https://langchain.readthedocs.io/en/latest/modules/chains/generic/sequential_chains.html

連続でLLMに問い合わせるようなChain。出力を別の入力に使うようなケースで使う。Sequential Chainには以下の2つがある。

  • SimpleSequentialChain
  • SequentialChain

SimpleSequentialChainは上の例でもやったとおり、それぞれのchainが単一の入出力を持ち、1つ目の出力が次の入力となる。

例として、劇作家の立場で劇のあらすじを生成して、それを今度は批評家の立場でレビューするというSimpleSequentialChain。関係ないけどこういう使い方はChatGPTとかでも非常に有用に思える。

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain

llm = OpenAI(temperature=.7, max_tokens=500)

template = """あなたは劇作家です。あなたの仕事は、劇の題名からあらすじを書くことです。

劇名: {title}
劇作家: この劇のあらすじはこうです:"""
prompt_template = PromptTemplate(input_variables=["title"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)

template = """あなたはニューヨーク・タイムズ紙の劇評論家です。あなたの仕事は、劇のあらすじからその劇のレビューを書くことです。

劇のあらすじ:
{synopsis}
ニューヨーク・タイムズ紙による上記の劇のレビュー:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template)

overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)
review = overall_chain.run("夕暮れの浜辺の悲劇")

結果。



> Entering new SimpleSequentialChain chain...


主人公のジェイソンは、夕暮れの浜辺の景色を訪れるために海岸へとやってきました。しかし、彼が海岸で見たものは彼の人生を変えるものでした。彼は、海岸で彼の運命を決定する事を知らされていたのです。ジェイソンは、海岸に描かれていた人々の悲劇をしっかりと見つめなければなりませんでした。そして、その悲劇を生きるために、ジェイソンは彼の命を懸けて、恐ろしい運命を克服することを決心しました。


『海岸』は、夕暮れに海岸へとやってきた主人公ジェイソンの物語です。彼は、恐ろしい運命を克服することを決心する前に、海岸で描かれていた悲劇を見つめなければなりませんでした。そして、ジェイソンは、彼の運命を決定するために苦闘するのです。
この劇は、冒険、勇気、そして希望をテーマにしています。ジェイソンの旅は、彼が自分自身を見つめ、自分の内なる力を見いだすことを助けます。劇は、観客が夢の追求に熱中する気持ちを引き起こすでしょう。劇中に登場するキャラクターたちも、ジェイソンの旅に多くの深みをもたらしています。
『海岸』は、人生を変える冒険をテーマにした深い劇です。観客が自分の人生を見つめ、内なる力を見いだす機会をもたらします。脚本が誠実で、キャストが熱演しています。本劇は、観客を感動させるでしょう。

> Finished chain.

SimpleSequentialChainが単一の入出力なのに対して、SequentialChainは複数の入出力を使うような複雑なchainになる。入出力が単一ならばあまり気にしなくてよかった変数「名」に気をつける必要がある。

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SequentialChain

llm = OpenAI(temperature=.7, max_tokens=500)

template = """あなたは劇作家です。あなたの仕事は、劇の題名と部隊となる時代から、あらすじを書くことです。

劇名: {title}
時代: {era}
劇作家: この劇のあらすじはこうです:"""
prompt_template = PromptTemplate(input_variables=["title","era"], template=template)
synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="synopsis")

template = """あなたはニューヨーク・タイムズ紙の劇評論家です。あなたの仕事は、劇のあらすじからその劇のレビューを書くことです。

劇のあらすじ:
{synopsis}
ニューヨーク・タイムズ紙による上記の劇のレビュー:"""
prompt_template = PromptTemplate(input_variables=["synopsis"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")

overall_chain = SequentialChain(chains=[synopsis_chain, review_chain], input_variables=["era", "title"], output_variables=["synopsis", "review"], verbose=True)
overall_chain({"title":"夕暮れの浜辺の悲劇","era":"英国ビクトリア朝"})

結果。オブジェクトで返ってくる。

> Entering new SequentialChain chain...

> Finished chain.
{'title': '夕暮れの浜辺の悲劇',
 'era': '英国ビクトリア朝',
 'synopsis': '\n\n英国ビクトリア朝のイギリスの田舎町に住む美しい年若い女性、ヘレナは夢を抱いていました。彼女は大都会へ赴き、恋をして、何よりも自由になることを望んでいました。しかし、それはあまりにも夢でした。ヘレナは恋人のジェームズと結婚し、残念ながら彼の尊い妻の存在のため、家族の助けを得ることはありませんでした。\n\nそしてある日、ヘレナはジェームズが彼女を捨てることを知りました。ヘレナはその悲しみで酔いしれ、夕暮れの浜辺へ行きます。彼女はジェームズの捨てられたままで、波が足元を砕く中で涙を流しました。\n\nその時、彼女の願いがついに叶いました。ジェームズが彼女を取り戻してくれることを知ると、ヘレナは絶望から解き放たれました。しかし、その喜びは短いものでした。ジェームズが夕暮れの浜辺でヘレナを取り戻した翌日、彼はヘレナを誤解して、突然彼女を殺してしま',
 'review': '\n\n『ヘレナ』は素晴らしい劇です。この物語は、Victorian Englandの田舎町で繰り広げられ、美しい女性ヘレナが夢を追い求める物語です。彼女は恋人のジェームズを愛し、家族からの支援なしに自由を得ようとします。しかし、ジェームズは彼女を捨ててしまいます。そして、彼女の願いはついに叶いましたが、その喜びは短く、ジェームズが彼女を殺してしまいます。\n\n劇は深い感情を描写し、観客の心を打ちます。美しい演出と、せつないストーリーが絡み合うことで、観客はヘレナの痛みと夢を共有しているような気がします。また、劇の中では、ヘレナの苦悩を象徴するオーケストラの音楽を使用することで、劇のテンポをより活発にしています。『ヘレナ』は、彼女が自由を得ることを夢見たことを象徴しています。この劇は、夢を追い求めるためにどんな道を歩んでもいいというメッセージを観客に伝える素晴'}
Transformation Chain

https://langchain.readthedocs.io/en/latest/modules/chains/generic/transformation.html

Transformation ChainはPythonの関数を使って入出力をいじるようなChain。例として、長いテキスト文書から最初の3パラグラフのみを取ってきてLLMに渡してようやくさせるようなChainを作ってみる。

以下の総理大臣の所信演説表明をテキスト化してやってみる。
https://www.kantei.go.jp/jp/101_kishida/statement/2022/1003shoshinhyomei.html

こんな感じでテキストを読み取り、最初の3パラグラフを抽出する関数を用意しておく。

with open('japan_prime_minister_policy_statement_210.txt') as f:
    policy_statement = f.read()

def transform_func(inputs: dict) -> dict:
    text = inputs["text"]
    shortened_text = "\n\n".join(text.split("\n\n")[:3])
    return {"output_text": shortened_text}

transform_func({"text":policy_statement})

単体で動かすとこんな感じ。

{'output_text': '第二百十回国会の開会に臨み、日本を守り、未来を切り拓(ひら)く覚悟を新たにしています。足下の物価高への対応に全力をもって当たり、日本経済を必ず再生させます。多層的な外交の展開と防衛力の抜本的強化を通じて、アジアと世界の平和と安定を断固守り抜いてまいります。世界規模の物価高。急速に厳しさを増す、安全保障環境。二年半にもわたって世界を苦しめてきている感染症危機や、エネルギー・食料危機、さらには、温暖化による気候危機。半年以上も緊迫した情勢が続く、ロシアによるウクライナ侵略。国際秩序を揺るがす、地政学的挑戦。大きな変わり目を迎える、核不拡散体制。今、日本は、国難とも言える状況に直面しています。世界が、そして日本が直面する歴史的な難局を乗り越え、我が国の未来を切り拓くため、政策を、一つひとつ果断に、かつ丁寧に実行していきます。どんな困難も、皆が力を合わせ、一歩一歩前に進むことで、必ず乗り越えることができる。先日訪問した福島で、私はその思いを一層強くいたしました。長期にわたり、帰還が困難とされた区域への住民の帰還。五十五の国と地域のうち、四十三の国と地域での輸入規制の撤廃。産業創出の拠点となる、福島国際研究教育機構の設立。私に、復興に向けた強い思いを語ってくれた町役場の職員。福島を、「ワクワクするような地域にしていきたい」と語ってくれた移住してきた若者。多くの皆さんの力により、福島は、着実に、復興に向け、歩みを進めています。東日本大震災という未曽有の国難からも、立ち上がることができました。そうであれば、今我々が直面する困難も、必ずや、乗り越えていける。私は、そう確信しています。共にこの国の未来を見据え、歩みを進めていこうではありませんか。\n\n先週執り行った安倍元総理の国葬儀は、厳粛かつ心のこもったものとなりました。海外からお越しになった多数の参列者の方々から寄せられた弔意に対し、礼節をもって、丁寧にお応えすることができたと考えております。その際、国民の皆様から頂いた様々な御意見を重く受け止め、今後に活かしてまいります。また、旧統一教会との関係については、国民の皆様の声を正面から受け止め、説明責任を果たしながら、信頼回復のために、各般の取組を進めてまいります。政府としては、寄せられた相談内容を踏まえ、総合的な相談窓口を設け、法律の専門家による支援体制を充実・強化するなど、悪質商法や悪質な寄附による被害者の救済に万全を尽くすとともに、消費者契約に関する法令等について、見直しの検討をいたします。国民の皆様からの厳しい声にも、真摯に、謙虚に、丁寧に向き合っていくことをお誓いいたします。「厳しい意見を聞く」姿勢にこそ、政治家岸田文雄の原点があるとの初心を、改めて肝に銘じながら、内閣総理大臣の職責を果たすべく、全力で取り組んでまいります。\n\n日本経済の再生が最優先の課題です。我が国は、コロナ禍を乗り越え、社会経済活動の正常化が進みつつあります。しかし、足下では、ロシアによるウクライナ侵略と円安によるエネルギー・食料価格の高騰、世界の景気後退懸念が、日本経済の大きなリスク要因となっています。新しい資本主義の旗印の下で、「物価高・円安への対応」、「構造的な賃上げ」、「成長のための投資と改革」の三つを、重点分野として取り組んでいきます。'}

ではこれを使ってTransform Chainを作る。TransformChainで、入出力とそれを処理する関数を指定する。

from langchain.chains import TransformChain, LLMChain, SimpleSequentialChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

transform_chain = TransformChain(input_variables=["text"], output_variables=["output_text"], transform=transform_func)

プロンプトテンプレートでChainを作る。

template = """以下のテキストを要約して下さい:

{output_text}

要約:"""
prompt = PromptTemplate(input_variables=["output_text"], template=template)
llm_chain = LLMChain(llm=OpenAI(max_tokens=500), prompt=prompt)

上記のChainをつなげるSimpleSequentialChainを作って実行。

sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain])
sequential_chain.run(policy_statement)

結果。

' 元総理安倍の国葬儀を振り返り、今国が直面している多くの困難を乗り越えるため、政策を果断かつ丁寧に実行していくことを決意した。物価高・円安への対応、構造的な賃上げ、成長のための投資・改革を重点分野として取り組む。'

Utility Chains

https://langchain.readthedocs.io/en/latest/modules/chains/utility_how_to.html

Utilsを使ったChain。Utilsについては以下を参照。

https://zenn.dev/kun432/scraps/cefc75b14ff013

例として以下が挙げられている。

  • LLMMath
  • PAL
  • SQLDatabase Chain
  • API Chain
  • LLMBash Chain
  • LLMChecker Chain
  • LLMRequests Chain
  • Moderation Chain

あと以下もある

  • Self-Critique Chain with Constitutional AI
LLMMath

https://langchain.readthedocs.io/en/latest/modules/chains/examples/llm_math.html

LLMMatchはPython REPLを使って計算を行う。

from langchain import OpenAI, LLMMathChain

llm = OpenAI(temperature=0)
llm_math = LLMMathChain(llm=llm, verbose=True)

llm_math.run("What is 13 raised to the .3432 power?")

結果

> Entering new LLMMathChain chain...
What is 13 raised to the .3432 power?
```python
import math
print(math.pow(13, .3432))
```

Answer: 2.4116004626599237

> Finished chain.
Answer: 2.4116004626599237\n

ただし日本語でやるとPythonのコードが表示されない・・・

from langchain import OpenAI, LLMMathChain

llm = OpenAI(temperature=0)
llm_math = LLMMathChain(llm=llm, verbose=True)

llm_math.run("13の0.3432乗は?")

もしかしてLLMだけで完結しているのかも、と思ってLLMMathが内部で投げているプロンプトを見てみたら、難しい問題ならPythonコードを使ったフォーマット、そうでなければシンプルなフォーマット(Pythonコードなし)で回答するとある。

https://github.com/hwchase17/langchain/blob/master/langchain/chains/llm_math/prompt.py

でもってちゃんとPythonREPLは実行されている模様。

https://github.com/hwchase17/langchain/blob/master/langchain/chains/llm_math/base.py#L59

なので単純に表示させるだけだとこういう感じにすれば表示される。

rom langchain import OpenAI, LLMMathChain

llm = OpenAI(temperature=0)
llm_math = LLMMathChain(llm=llm, verbose=True)

llm_math.run("13の0.3432乗は?計算式も書いて。")

結果

 > Entering new LLMMathChain chain...
 13の0.3432乗は?計算式も書いて。
 ```python
 print(13**0.3432)
 ```
 
 Answer: 2.4116004626599237
 
 > Finished chain.
 Answer: 2.4116004626599237\n

で、内部的にプロンプトテンプレートは予め用意されているわけだが、これを意図的に上書きすることもできる。

from langchain.prompts.prompt import PromptTemplate

_PROMPT_TEMPLATE = """あなたはGPT-3ですが、数学ができません。

基本的な計算はできるし、暗算能力も素晴らしいのですが、人間が頭で考えてもできないような複雑な計算はできません。また、高度に特殊な、しかし間違った回答をするという厄介な傾向もあります。

そこで、私達はあなたがPython 3カーネルに接続してコードを実行できるようにしました。もしコードを実行したら、print関数を使って最終的な答えをプリントアウトしなければなりません。あなたの質問に答えるためには、Pythonのパッケージであるnumpyを必ず使用しなければなりません。numpyをインポートする場合はnpとしてインポートしなければなりません。

問題: ${{Question with hard calculation.}}
```python
${{Code that prints what you need to know}}
print(${{code}})
```
```output
${{Output of your code}}
```
回答: ${{Answer}}

では始めましょう.

問題: What is 37593 * 67?

```python
import numpy as np
print(np.multiply(37593, 67))
```
```output
2518731
```
回答: 2518731

問題: {question}"""

PROMPT = PromptTemplate(input_variables=["question"], template=_PROMPT_TEMPLATE)

LLMMathChainprompt=に上のプロンプトテンプレートを指定する。

from langchain import OpenAI, LLMMathChain

llm_math = LLMMathChain(llm=llm, prompt=PROMPT, verbose=True)
llm_math.run("What is 13 raised to the .3432 power?")

結果。日本語でコードが表示されなかった同じ質問でもちゃんとコードが返されている。Answer:のフォーマットがプロンプトテンプレートと違うけど、これはLLMじゃなくてLLMMathの回答だからだと思う(ハードコードされてる)

> Entering new LLMMathChain chain...
13の0.3432乗は?

```python
import numpy as np
print(np.power(13, 0.3432))
```

Answer: 2.4116004626599237

> Finished chain.
Answer: 2.4116004626599237\n
PAL

https://langchain.readthedocs.io/en/latest/modules/chains/examples/pal.html

Program-aided Language Models(PAL)というのがあるそうで。
https://arxiv.org/pdf/2211.10435.pdf

ざっと見た感じ、自然言語な質問内容をステップに分けて中間推論でプログラムを生成して実行する、というようなもの。LLMMathと感覚的には近いかもしれない。例では数学、色に関する例があがっている。

code-davinciを使ってるあたりがミソなのかも。

from langchain.chains import PALChain
from langchain import OpenAI

llm = OpenAI(model_name='code-davinci-002', temperature=0, max_tokens=512)

数学的なプロンプト。PALChain.from_math_prompt()を使う。

pal_chain = PALChain.from_math_prompt(llm, verbose=True)

question = "ヤンはマーシャの3倍の数のペットを飼っている。マーシャはシンディより2匹多くペットを飼っています。シンディが4匹のペットを飼っている場合、3人のペットの総数は何匹でしょう?"

pal_chain.run(question)

結果

> Entering new PALChain chain...
def solution():
    """ヤンはマーシャの3倍の数のペットを飼っている。マーシャはシンディより2匹多くペットを飼っています。シンディが4匹のペットを飼っている場合、3人のペットの総数は何匹でしょう?"""
    shindi_pets = 4
    marsha_pets = shindi_pets + 2
    yan_pets = marsha_pets * 3
    total_pets = shindi_pets + marsha_pets + yan_pets
    result = total_pets
    return result

> Finished chain.
28

pythonで関数が作成されて計算を実行している様子。

色に関するプロンプト。PALChain.from_colored_object_prompt()を使う

pal_chain = PALChain.from_colored_object_prompt(llm, verbose=True)

question = "机の上に、青い冊子2冊、紫の冊子2冊、黄色のサングラス2個があります。サングラスをすべて取り除くと、机の上には紫色のものが何個残りますか?"

pal_chain.run(question)

結果

> Entering new PALChain chain...
# Put objects into a list to record ordering
objects = []
objects += [('book', 'blue')] * 2
objects += [('book', 'purple')] * 2
objects += [('sunglasses', 'yellow')] * 2

# Remove sunglasses
objects = [object for object in objects if object[0] != 'sunglasses']

# Count number of purple objects
num_purple = len([object for object in objects if object[1] == 'purple'])
answer = num_purple

> Finished chain.
2

この中間のステップを戻り値に含めるには、return_intermediate_stepsフラグを有効にする。

pal_chain = PALChain.from_colored_object_prompt(llm, verbose=True, return_intermediate_steps=True)

戻り値はオブジェクトで返される。

result = pal_chain(question)

import pprint
pprint.pprint(result)
{'intermediate_steps': '# Put objects into a list to record ordering\n'
                       'objects = []\n'
                       "objects += [('book', 'blue')] * 2\n"
                       "objects += [('book', 'purple')] * 2\n"
                       "objects += [('sunglasses', 'yellow')] * 2\n"
                       '\n'
                       '# Remove sunglasses\n'
                       'objects = [object for object in objects if object[0] '
                       "!= 'sunglasses']\n"
                       '\n'
                       '# Count number of purple objects\n'
                       'num_purple = len([object for object in objects if '
                       "object[1] == 'purple'])\n"
                       'answer = num_purple',
 'question': '机の上に、青い冊子2冊、紫の冊子2冊、黄色のサングラス2個があります。サングラスをすべて取り除くと、机の上には紫色のものが何個残りますか?',
 'result': '2'}
kun432kun432
SQLDatabase Chain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/sqlite.html

データベースとLLMを組み合わせる。サンプルでは以下のchinook-databaseというものを使ってSQLiteでやっている。このデータベースは音楽のダウンロード販売的なデータベースとなっているらしい。

https://github.com/lerocha/chinook-database

ありがたいことに以下のサイトで上記サンプルのSQLite用.dbファイルをGoogle Colaboratoryで使う手順が記載されているので参考にさせてもらう。

https://qiita.com/_jinta/items/fb79f9f9b65b241e92b2

!pip install ipython-sql==0.3.9
!wget https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip
!unzip chinook.zip

ipython-sqlを使うとnotebookでマジックコマンドを使ってSQLを実行できる。

%load_ext sql
%sql sqlite:///chinook.db
%sql select name from sqlite_master where type='table';

以下のように表示されればOK。

 * sqlite:///chinook.db
Done.
name
albums
sqlite_sequence
artists
customers
employees
genres
invoices
invoice_items
media_types
playlists
playlist_track
tracks
sqlite_stat1

ではLangChainから。

from langchain import OpenAI, SQLDatabase, SQLDatabaseChain

db = SQLDatabase.from_uri("sqlite:///chinook.db")
llm = OpenAI(temperature=0)

db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True)
db_chain.run("従業員は何人いますか?")

結果。日本語でもちゃんと動く。

> Entering new SQLDatabaseChain chain...
従業員は何人いますか? 
SQLQuery:/usr/local/lib/python3.8/dist-packages/langchain/sql_database.py:121: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.
  sample_rows = connection.execute(command)
 SELECT COUNT(*) FROM employees;
SQLResult: [(8,)]
Answer: 従業員は8人います。
> Finished chain.
 従業員は8人います。

return_direct=TrueをつけるとSQLクエリの出力をLLMに渡さないようにできるみたい。センシティブなデータの場合に使うと良い。例えばこういうやつ。

from langchain import OpenAI, SQLDatabase, SQLDatabaseChain

db = SQLDatabase.from_uri("sqlite:///chinook.db")
llm = OpenAI(temperature=0)

db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True)
db_chain.run("従業員は誰がいますか?")
> Entering new SQLDatabaseChain chain...
従業員は誰がいますか? 
SQLQuery: SELECT FirstName, LastName FROM employees LIMIT 5;
SQLResult: [('Andrew', 'Adams'), ('Nancy', 'Edwards'), ('Jane', 'Peacock'), ('Margaret', 'Park'), ('Steve', 'Johnson')]
Answer: 従業員はAndrew Adams、Nancy Edwards、Jane Peacock、Margaret Park、Steve Johnsonです。
> Finished chain.
 従業員はAndrew Adams、Nancy Edwards、Jane Peacock、Margaret Park、Steve Johnsonです。

普通に実行すると、おそらく一度SQL実行結果をLLMに渡しているのだと思う。return_direct=Trueを有効にするとこうなる。

db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True, return_direct=True)
> Entering new SQLDatabaseChain chain...
従業員は誰がいますか? 
SQLQuery: SELECT FirstName, LastName FROM employees LIMIT 5;
SQLResult: [('Andrew', 'Adams'), ('Nancy', 'Edwards'), ('Jane', 'Peacock'), ('Margaret', 'Park'), ('Steve', 'Johnson')]
> Finished chain.
[('Andrew', 'Adams'), ('Nancy', 'Edwards'), ('Jane', 'Peacock'), ('Margaret', 'Park'), ('Steve', 'Johnson')]

SQL実行結果までで終わっている。SQLの生成はLLMにやらせているようなので最低限スキーマとかは渡されているが、結果については渡されていないのだと思われる。

プロンプトは以下を見ると良い。

https://github.com/hwchase17/langchain/blob/master/langchain/chains/sql_database/prompt.py

プロンプトのカスタマイズ

でこのプロンプトを上書きすることもできる。

from langchain import OpenAI, SQLDatabase, SQLDatabaseChain
from langchain.prompts.prompt import PromptTemplate

db = SQLDatabase.from_uri("sqlite:///chinook.db")
llm = OpenAI(temperature=0)

_DEFAULT_TEMPLATE = """Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.
Use the following format:

Question: "Question here"
SQLQuery: "SQL Query to run"
SQLResult: "Result of the SQLQuery"
Answer: "Final answer here"

Only use the following tables:

{table_info}

If someone asks for the table foobar, they really mean the employee table.

Question: {input}"""

ポイントはこの部分。

If someone asks for the table foobar, they really mean the employee table.

foobarテーブルについて訪ねたらemployeeテーブルと読み替えることを記載している。

ということで、このプロンプトテンプレートに置き換えて聞いてみる。

PROMPT = PromptTemplate(
    input_variables=["input", "table_info", "dialect"], template=_DEFAULT_TEMPLATE
)

db_chain = SQLDatabaseChain(llm=llm, database=db, prompt=PROMPT, verbose=True)
db_chain.run("foobarテーブルに従業員は何人いますか?")

ちゃんとよしなにやってくれる。

> Entering new SQLDatabaseChain chain...
foobarテーブルに従業員は何人いますか? 
SQLQuery: SELECT COUNT(*) FROM employees;
SQLResult: [(8,)]
Answer: foobarテーブルには8人の従業員がいます。
> Finished chain.
 foobarテーブルには8人の従業員がいます。

この1文がなくてもある程度考えてくれるけど、この1文なしで以下のように聞くと、そんなテーブルはねえよ、と言われてエラーになる。

db_chain.run("データベースのfoobarテーブルに従業員は誰がいますか?")
OperationalError: (sqlite3.OperationalError) no such column: table_name
[SQL:  SELECT * FROM employees WHERE table_name = 'foobar';]
(Background on this error at: https://sqlalche.me/e/14/e3q8)
中間ステップの表示

return_intermediate_steps=trueをつけると、中間ステップを返すこともできる。この場合はオブジェクトで返ってくる。

db_chain = SQLDatabaseChain(llm=llm, database=db, prompt=PROMPT, verbose=True, return_intermediate_steps=True)
result = db_chain("foobarテーブルに従業員は何人いますか?")

import pprint
pprint.pprint(result)
> Entering new SQLDatabaseChain chain...
foobarテーブルに従業員は何人いますか? 
SQLQuery: SELECT COUNT(*) FROM employees;
SQLResult: [(8,)]
Answer: 8人
> Finished chain.
{'intermediate_steps': [' SELECT COUNT(*) FROM employees;', '[(8,)]'],
 'query': 'foobarテーブルに従業員は何人いますか?',
 'result': ' 8人'}
SQL結果の行数制限

データベースの結果が大きすぎるとプロンプトの最大長を超える可能性もある。top_kパラメータをつけるとSQLの結果行数を絞ることができる。

db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True, top_k=2)
db_chain.run("作曲家Johann Sebastian Bachの楽曲例を教えてください。")
> Entering new SQLDatabaseChain chain...
作曲家Johann Sebastian Bachの楽曲例を教えてください。 
SQLQuery: SELECT Name, Composer FROM tracks WHERE Composer LIKE '%Johann Sebastian Bach%' LIMIT 2;
SQLResult: [('Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace', 'Johann Sebastian Bach'), ('Aria Mit 30 Veränderungen, BWV 988 "Goldberg Variations": Aria', 'Johann Sebastian Bach')]
Answer: Johann Sebastian Bachの作曲した楽曲には、「Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace」と「Aria Mit 30 Veränderungen, BWV 988 "Goldberg Variations": Aria」があります。
> Finished chain.
 Johann Sebastian Bachの作曲した楽曲には、「Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace」と「Aria Mit 30 Veränderungen, BWV 988 "Goldberg Variations": Aria」があります。
テーブルのレコードをサンプル行として与える

データベースのスキーマだけではLLMが理解できない場合もある。このような場合にサンプルとして数行のレコードもあわせて渡すことができる。必然的にreturn_direct=Trueとは噛み合わなくなりそう。

from langchain import OpenAI, SQLDatabase, SQLDatabaseChain

db = SQLDatabase.from_uri("sqlite:///chinook.db", include_tables=['tracks'], sample_rows_in_table_info=2)
llm = OpenAI(temperature=0)

db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True)
db_chain.run("作曲家Johann Sebastian Bachの楽曲例を教えてください。")

dbオブジェクトのtable_infoオブジェクト(プロンプトに含まれる)にテーブルスキーマだけでなくサンプルレコードも含まれるようになる。

print(db.table_info)
CREATE TABLE tracks (
	"TrackId" INTEGER NOT NULL, 
	"Name" NVARCHAR(200) NOT NULL, 
	"AlbumId" INTEGER, 
	"MediaTypeId" INTEGER NOT NULL, 
	"GenreId" INTEGER, 
	"Composer" NVARCHAR(220), 
	"Milliseconds" INTEGER NOT NULL, 
	"Bytes" INTEGER, 
	"UnitPrice" NUMERIC(10, 2) NOT NULL, 
	PRIMARY KEY ("TrackId"), 
	FOREIGN KEY("AlbumId") REFERENCES albums ("AlbumId"), 
	FOREIGN KEY("GenreId") REFERENCES genres ("GenreId"), 
	FOREIGN KEY("MediaTypeId") REFERENCES media_types ("MediaTypeId")
)

SELECT * FROM 'tracks' LIMIT 2;
TrackId Name AlbumId MediaTypeId GenreId Composer Milliseconds Bytes UnitPrice
1 For Those About To Rock (We Salute You) 1 1 1 Angus Young, Malcolm Young, Brian Johnson 343719 11170334 0.99
2 Balls to the Wall 2 2 1 None 342562 5510424 0.99

これを踏まえてのLLMの結果

> Entering new SQLDatabaseChain chain...
作曲家Johann Sebastian Bachの楽曲例を教えてください。 
SQLQuery: SELECT Name, Composer FROM tracks WHERE Composer LIKE '%Johann Sebastian Bach%' LIMIT 5;
SQLResult: [('Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace', 'Johann Sebastian Bach'), ('Aria Mit 30 Veränderungen, BWV 988 "Goldberg Variations": Aria', 'Johann Sebastian Bach'), ('Suite for Solo Cello No. 1 in G Major, BWV 1007: I. Prélude', 'Johann Sebastian Bach'), ('Toccata and Fugue in D Minor, BWV 565: I. Toccata', 'Johann Sebastian Bach'), ('Concerto No.2 in F Major, BWV1047, I. Allegro', 'Johann Sebastian Bach')]
Answer: Johann Sebastian Bachの作曲した楽曲には、「Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace」、「Aria Mit 30 Veränderungen, BWV 988 "Goldberg Variations": Aria」、「Suite for Solo Cello No. 1 in G Major, BWV 1007: I. Prélude」、「Toccata and Fugue in D Minor, BWV 565: I. Toccata」、「Concerto No.2 in F Major, BWV1047, I. Allegro」があります。
> Finished chain.
 Johann Sebastian Bachの作曲した楽曲には、「Concerto for 2 Violins in D Minor, BWV 1043: I. Vivace」、「Aria Mit 30 Veränderungen, BWV 988 "Goldberg Variations": Aria」、「Suite for Solo Cello No. 1 in G Major, BWV 1007: I. Prélude」、「Toccata and Fugue in D Minor, BWV 565: I. Toccata」、「Concerto No.2 in F Major, BWV1047, I. Allegro」があります。

上記は自動でテーブルスキーマやサンプルレコードを取得してLLMに投げているが、これを明示的に指定することもできる。

custom_table_info = {
    "tracks": """CREATE TABLE tracks (
	"TrackId" INTEGER NOT NULL, 
	"Name" NVARCHAR(200) NOT NULL,
	"Composer" NVARCHAR(220),
	PRIMARY KEY ("TrackId")
)

SELECT * FROM 'Track' LIMIT 3;
TrackId	Name	Composer
1	For Those About To Rock (We Salute You)	Angus Young, Malcolm Young, Brian Johnson
2	Balls to the Wall	None
3	My favorite song ever	The coolest composer of all time"""
}

db = SQLDatabase.from_uri(
    "sqlite:///Chinook.db",
    include_tables=['tracks', 'playlists'],
    sample_rows_in_table_info=2,
    custom_table_info=custom_table_info)

これにより、

  • tracksテーブルはcustom_table_infoで上書きされる
  • playlistsテーブルは上書きされずにそのままDBから取得される

ということになる様子。

SQLDatabaseSequentialChain

DBへのクエリをsequential chainにするとこういう流れになる様子。

  1. クエリに基づいて、どのテーブルを使うかを決める
  2. テーブルに基づいて、SQLデータベースchainを呼ぶ
from langchain.chains import SQLDatabaseSequentialChain

db = SQLDatabase.from_uri("sqlite:///chinook.db")
llm = OpenAI(temperature=0)

chain = SQLDatabaseSequentialChain.from_llm(llm, db, verbose=True)
chain.run("従業員でもあり顧客でもある人は何人いますか?")

結果

> Entering new SQLDatabaseSequentialChain chain...
Table names to use:
['customers', 'employees']

> Entering new SQLDatabaseChain chain...
従業員でもあり顧客でもある人は何人いますか? 
SQLQuery: SELECT COUNT(*) FROM employees INNER JOIN customers ON employees.EmployeeId = customers.SupportRepId;
SQLResult: [(59,)]
Answer: 従業員でもあり顧客でもある人は59人います。
> Finished chain.

> Finished chain.
 従業員でもあり顧客でもある人は59人います。
kun432kun432
API Chain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/api.html

APIへのリスクエスト・レスポンスをLLMと連携させる。例では、

  • OpenMeteo(オープンソースの天気API)
  • TMDb(映画関連のデータベース。APIを提供している)

が紹介されている。

OpenMeteoを使った例

オープンソースの天気API。APIキーとかなくても使える様子。

https://open-meteo.com/

from langchain.chains.api.prompt import API_RESPONSE_PROMPT
from langchain.chains import APIChain
from langchain.prompts.prompt import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)

from langchain.chains.api import open_meteo_docs
chain_new = APIChain.from_llm_and_api_docs(llm, open_meteo_docs.OPEN_METEO_DOCS, verbose=True)
chain_new.run("日本の東京の現在の天気を教えて下さい。気温は摂氏で、回答は日本語で答えて下さい。")
> Entering new APIChain chain...
 https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&current_weather=true&temperature_unit=celsius&timezone=auto
{"latitude":35.7,"longitude":139.6875,"generationtime_ms":0.4240274429321289,"utc_offset_seconds":32400,"timezone":"Asia/Tokyo","timezone_abbreviation":"JST","elevation":40.0,"current_weather":{"temperature":8.8,"windspeed":4.1,"winddirection":165.0,"weathercode":1,"time":"2023-03-19T21:00"}}

> Finished chain.
 現在の東京の天気は、摂氏8.8度で、風速4.1km/h、風向き165度で、天気コード1(晴れ)です。

プロンプトやLLMからのレスポンスによっては、トークン数の上限を超える可能性がある。というのも、API Chainingで使用されるプロンプトは以下。

https://github.com/hwchase17/langchain/blob/master/langchain/chains/api/prompt.py

上記を見ると、

  • ユーザの入力とAPI定義を渡して、APIリクエストを生成してリクエスト実行
  • APIからのレスポンスを要約して、ユーザへ回答

という流れになっている。よって、LLMへのリクエストが巨大=プロンプトに含まれるAPIからのレスポンスが巨大、というようなケースだとトークン数上限を超える場合がありうる。

TMDbを使った例

映画関連のデータベース

https://www.themoviedb.org/?language=ja

APIキーはアカウント登録して生成すればよい。

https://www.themoviedb.org/documentation/api?language=ja

APIキーを環境変数で定義。ここでいうAPIキーは「APIリードアクセストークン (v4 auth)」

import os
os.environ['TMDB_BEARER_TOKEN'] = "XXXXXXXXXX"

コードはこういう感じ。

from langchain.chains.api.prompt import API_RESPONSE_PROMPT
from langchain.chains import APIChain
from langchain.prompts.prompt import PromptTemplate

from langchain.llms import OpenAI

llm = OpenAI(temperature=0)

from langchain.chains.api import tmdb_docs
headers = {"Authorization": f"Bearer {os.environ['TMDB_BEARER_TOKEN']}"}
chain = APIChain.from_llm_and_api_docs(llm, tmdb_docs.TMDB_DOCS, headers=headers, verbose=True)
chain.run("「アバター」を検索。回答は日本語で。")

回答



> Entering new APIChain chain...
 https://api.themoviedb.org/3/search/movie?query=%E3%82%A2%E3%83%90%E3%82%BF%E3%83%BC&language=ja&include_adult=false
{"page":1,"results":[{"adult":false,"backdrop_path":"/vL5LR6WdxWPjLPFRLe133jXWsh5.jpg","genre_ids":[28,12,14,878],"id":19995,"original_language":"en","original_title":"Avatar","overview":"西暦2154年。人類は惑星ポリフェマスの最大衛星パンドラに鉱物採掘基地を開いている。この星は熱帯雨林のような未開のジャングル覆われていて獰猛な動物と”ナヴィ”という先住種族が暮らしており、森の奥には地球のエネルギー問題解決の鍵となる希少鉱物の鉱床がある。この星の大気は人間に適さないので屋外活動にはマスクを着用する必要があり、ナヴィと意思疎通し交渉するために人間とナヴィの遺伝子を組み合わせて人間が作りあげた”アバター”が用いられた。","popularity":538.894,"poster_path":"/g9oEopuLdvVZNZw54vPC2fe6tNv.jpg","release_date":"2009-12-15","title":"アバター","video":false,"vote_average":7.567,"vote_count":28653},{"adult":false,"backdrop_path":"/ovM06PdF3M8wvKb06i4sjW3xoww.jpg","genre_ids":[878,12,28],"id":76600,"original_language":"en","original_title":"Avatar: The Way of Water","overview":"第1作目から約10年後の惑星パンドラでのジェイクとネイティリの子供たちからなる家族の物語。一家は神聖なる森を追われ海の部族に助けを求めるが、その楽園のような海辺の世界にも人類の侵略の手が迫っていた・・・。","popularity":1124.883,"poster_path":"/mjcCywlptZo3R6bOTSRiiuxm2q4.jpg","release_date":"2022-12-14","title":"アバター:ウェイ・オブ・ウォーター","video":false,"vote_average":7.73,"vote_count":5779},{"adult":false,"backdrop_path":"/lKknJUtoJsjJwE9yq8o9tiqo49y.jpg","genre_ids":[14,878,10752,28],"id":28524,"original_language":"en","original_title":"Princess of Mars","overview":"エドガー・ライス・バローズによる冒険小説を映像化したSFアクション。米軍兵士、ジョン・カーターは、分身を火星に念力移動させての探査を任される。そんな時火星ではヘリウム王国とサルクス族との戦争が勃発。彼はサルクス族に捕らわれるが…。","popularity":8.599,"poster_path":"/piSMUqc3Z1kHpIMNuyUUw85a2SY.jpg","release_date":"2009-12-29","title":"アバター・オブ・マーズ","video":false,"vote_average":4.431,"vote_count":29},{"adult":false,"backdrop_path":"/gO81IsgrhwUS6aChXCjnjFKUS3S.jpg","genre_ids":[27],"id":282908,"original_language":"ja","original_title":"アバター","overview":"Michiko lost her dad in a car accident when she was 10 years old. After the car accident, Michiko has lived with her mother Kyoko. Michiko, now in her 2nd year of high school, gets a cell phone from her mother as a birthday present. Michiko is so excited to have her very first cell phone. Soon afterwards, she is forced into joining social networking site \"AvaQ\" by classmate, and queen of the classroom, Taeko.","popularity":2.425,"poster_path":"/xEAhV18F5Ej78qL0EneyPqa2a20.jpg","release_date":"2011-04-30","title":"アバター","video":false,"vote_average":6.1,"vote_count":7}],"total_pages":1,"total_results":4}

> Finished chain.
 検索結果には4つの映画がありました。2009年に公開された「アバター」、2022年に公開予定の「アバター:ウェイ・オブ・ウォーター」、2009年に公開された「アバター・オブ・マーズ」、2011年に公開された「アバター」があります。
他のAPIに対応する場合

コードを見ると分かる通り、APIのリクエストやレスポンスに関するドキュメントを定義してあげればよい。

https://github.com/hwchase17/langchain/tree/master/langchain/chains/api

kun432kun432
LLMBash Chain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/llm_bash.html

LLMにbashスクリプトを出力させてそれを実行する。

from langchain.chains import LLMBashChain
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)

text = "コンソールに 'Hello World'を出力するbashスクリプトを書いて下さい。"

bash_chain = LLMBashChain(llm=llm, verbose=True)

bash_chain.run(text)

結果。

> Entering new LLMBashChain chain...
コンソールに 'Hello World'を出力するbashスクリプトを書いて下さい。

```bash
echo "Hello World"
```['```bash', 'echo "Hello World"', '```']

Answer: Hello World

> Finished chain.
Hello World\n

ちゃんと実行されているのか確認するために以下のようなテキストでも試してみた。

text = "'hoge'という名前のディレクトリーを作成するbashスクリプトを生成してください。"

ちゃんとディレクトリが作成されていた。

!ls -la

total 20
drwxr-xr-x 1 root root 4096 Mar 19 14:58 .
drwxr-xr-x 1 root root 4096 Mar 19 09:28 ..
drwxr-xr-x 4 root root 4096 Mar 16 13:44 .config
drwxr-xr-x 2 root root 4096 Mar 19 14:58 hoge
drwxr-xr-x 1 root root 4096 Mar 16 13:45 sample_data

プロンプトはこんな感じ。

https://github.com/hwchase17/langchain/blob/master/langchain/chains/llm_bash/prompt.py

このテンプレートを上書きして自分で用意することもできる。元のテンプレートに「出力にecho禁止」を追加した場合。

from langchain.prompts.prompt import PromptTemplate

_PROMPT_TEMPLATE = """If someone asks you to perform a task, your job is to come up with a series of bash commands that will perform the task. There is no need to put "#!/bin/bash" in your answer. Make sure to reason step by step, using this format:
Question: "copy the files in the directory named 'target' into a new directory at the same level as target called 'myNewDirectory'"
I need to take the following actions:
- List all files in the directory
- Create a new directory
- Copy the files from the first directory into the second directory
```bash
ls
mkdir myNewDirectory
cp -r target/* myNewDirectory
```

Do not use 'echo' when writing the script.

That is the format. Begin!
Question: {question}"""

PROMPT = PromptTemplate(input_variables=["question"], template=_PROMPT_TEMPLATE)

bash_chain = LLMBashChain(llm=llm, prompt=PROMPT, verbose=True)

text = "Please write a bash script that prints 'Hello World' to the console."

bash_chain.run(text)

結果

> Entering new LLMBashChain chain...
Please write a bash script that prints 'Hello World' to the console.

```bash
printf "Hello World\n"
```['```bash', 'printf "Hello World\\n"', '```']

Answer: Hello World

> Finished chain.
Hello World\n

プロンプトを日本語に変えると生成されたbashコードを正しくパースできなくてエラーになった。どうもLLMからのレスポンスに余計な空行が入るのがよろしくなかった様子。色々やってみたけどここはあまり元のテンプレートからいじらないか、prompt.pyを修正してPR送るのが良さそう。

kun432kun432
LLMChecker Chain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/llm_checker.html

LLMに、LLMの応答のFactチェックをさせる。この辺の考え方みたい。

https://github.com/jagilley/fact-checker

from langchain.chains import LLMCheckerChain
from langchain.llms import OpenAI

llm = OpenAI(temperature=0.7)

text = "最も大きな卵を産む哺乳類の種類は何でしょう?"

checker_chain = LLMCheckerChain(llm=llm, verbose=True)

checker_chain.run(text)

ただこれは英語でも日本語でも正しく動かなかった。

> Entering new LLMCheckerChain chain...


> Entering new SequentialChain chain...

> Finished chain.

> Finished chain.
 最も大きな卵を産む哺乳類の種類は、アフリカのオオサンショウウオである。

もしかするとOpenAIのレスポンスがめちゃめちゃ遅くなってるせいかもしれない。

現時点で確認できないので、以下のコードでやっていることをまとめると、

https://github.com/hwchase17/langchain/blob/master/langchain/chains/llm_checker/prompt.py

  • 元の質問を投げた仮回答を得る
  • その前提となる条件を得る
  • その条件を個別に検証する
  • 上記を踏まえて最終回答を得る

というもの、当然ながらLLMに何度もアクセスすることになる。

kun432kun432
LLMRequests Chain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/llm_requests.html

URLからHTMLを取ってきてLLMにパースさせる。verboseをTrueにしてみた。

from langchain.llms import OpenAI
from langchain.chains import LLMRequestsChain, LLMChain

from langchain.prompts import PromptTemplate

template = """以下の >>> と <<< で囲まれた部分はGoogle検索の結果データです。
質問「{query}」に対する答えを抽出して下さい。もし該当する情報がなければ「見つかりませんでした」と返して下さい。
以下のフォーマットを使用してください:
抽出結果:<答え または 「見つかりませんでした」>
>>> {requests_result} <<<
抽出結果:"""

PROMPT = PromptTemplate(
    input_variables=["query", "requests_result"],
    template=template,
)

chain = LLMRequestsChain(llm_chain = LLMChain(llm=OpenAI(temperature=0), prompt=PROMPT, verbose=True), verbose=True)

question = "イクイノックスが勝ったGIレースを全て挙げて下さい。"
inputs = {
    "query": question,
    "url": "https://www.google.com/search?q=" + question.replace(" ", "+")
}

chain(inputs)

結果

> Entering new LLMRequestsChain chain...


> Entering new LLMChain chain...
Prompt after formatting:
以下の >>> と <<< で囲まれた部分はGoogle検索の結果データです。
質問「イクイノックスが勝ったGIレースを全て挙げて下さい。」に対する答えを抽出して下さい。もし該当する情報がなければ「見つかりませんでした」と返して下さい。
以下のフォーマットを使用してください:
抽出結果:<答え または 「見つかりませんでした」>
>>> イクイノックスが勝ったGIレースを全て挙げて下さい。 - Google SearchGoogle×Please click here if you are not redirected within a few seconds.    AllImagesVideosBooks Maps News Shopping Search tools    Any timeAny timePast hourPast 24 hoursPast weekPast monthPast yearAll resultsAll resultsVerbatim競馬 有馬記念 1番人気のイクイノックスが優勝 G1レース2勝目 | NHKwww3.nhk.or.jp › ... › スポーツニュース一覧Dec 25, 2022 · 【NHK】年末恒例の競馬のG1レース、有馬記念が行われ、1番人気のイクイノックスが優勝し、G1レース2勝目を挙げました.有馬記念のイクイノックスに暗雲? 勝ち馬多数輩出の ...news.yahoo.co.jp › articlesDec 21, 2022 · 例年ハイレベルなメンバーが揃うレースだけに、勝ち馬全てが同年内に重賞勝利を挙げており、できればGIタイトルを1つでも持っているのが理想だろう(有馬 ...Missing:  下さい。 | Must include:下さい。イクイノックスが現役「世界最強」に ワールドベストレースホース ...news.yahoo.co.jp › articlesJan 18, 2023 · ロンジンワールドベストレースホースランキングで3位タイに入ったイク ... 天皇賞(秋)と有馬記念のGI・2勝に輝いたイクイノックスが3位タイに入った。Missing:  全て 下さい。2022年|第166回 天皇賞 (秋)・GⅠ|イクイノックス - YouTubewww.youtube.com › watchOct 30, 2022 · 2022 / 10 / 30第166回 天皇賞 (秋)・GⅠ東京競馬場 芝 2000m (左)天候:晴 ...Duration: 15:55Posted: Oct 30, 2022Missing:  全て 挙げ 下さい。【有馬記念】最近10年は1番人気が6勝 前日1番人気 ... - 競馬ニュースnews.netkeiba.com › 競馬ニュースDec 24, 2022 · 1番人気は天皇賞・秋を制したイクイノックス(牡3=木村)で2・9倍。 ... 今年と同じ16頭立てで行われた最近10年は1番人気の馬が6勝を挙げている。ImagesView allView all【JRA賞】97・9%の圧倒的な得票率、イクイノックスが年度代表馬 ...news.netkeiba.com › 競馬ニュースJan 11, 2023 · 2022年度のJRA賞が10日、発表され、天皇賞・秋と有馬記念でG12勝を挙げたイクイノックス(牡3歳、美浦・木村厩舎)が97・9%の圧倒的な得票率を獲得して ...【有馬記念】イクイノックスはキタサンブラックの価値も上げたp.nikkansports.com › goku-uma › news › articleDec 19, 2022 · クリスマス決戦となる今年の有馬記念(G1、芝2500メートル、25日=中山)は、今年の古馬中距離G1を勝った全て・・・ - 極ウマ・プレミアム。イクイノックス - 競走馬データTOP|競馬予想のウマニティumanity.jp › racedata › horse_topDec 21, 2022 · 会員登録(無料)して注目ホースに登録すると、出走情報やレース結果がメールで届きます。 今すぐ会員登録. TOP · 競走 ...【天皇賞(秋)】「前走ダービー2着」×「連対率100%」は昨年の ...spaia-keiba.com › news › detailOct 30, 2022 · 相性のいいステップレースは、GIだと宝塚記念と安田記念、GⅡだと毎日王冠だ。 ... 続いては前走1着馬のうち、コンマ3秒以上の差をつけて勝った馬。  Next >  United StatesFrom your IP address - Learn moreSign inSettingsPrivacyTermsDark theme: Off  <<<
抽出結果:

> Finished chain.

> Finished chain.
{'query': 'イクイノックスが勝ったGIレースを全て挙げて下さい。',
 'url': 'https://www.google.com/search?q=イクイノックスが勝ったGIレースを全て挙げて下さい。',
 'output': '有馬記念、天皇賞(秋)'}
kun432kun432
Moderation Chain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/moderation.html

ユーザーの入力やLLMからの出力に問題となるようなテキストが含まれていないかを検出する。内部的にはOpenAIのModeration APIを使用している様子。

https://platform.openai.com/docs/guides/moderation/overview

Moderation APIで拾えるのは以下のようなもの(DeepL訳)

  • ヘイト
    • 人種、性別、民族、宗教、国籍、性的指向、障害の有無、カーストなどに基づく憎悪を表現、扇動、助長する内容
  • 憎悪・脅迫
    • 対象となるグループに対する暴力や深刻な危害も含む憎悪
  • 自傷行為
    • 自殺、切り傷、摂食障害などの自傷行為を助長、奨励、描写する内容。
  • セクシャル
    • 性行為の描写など性的興奮を喚起することを意図した内容や、性的サービスを促進する内容(性教育や健康増進を除く)。
  • セクシャル/未成年
    • 18歳未満の個人を含む性的なコンテンツ。
  • 暴力
    • 暴力を助長または美化したり、他人の苦しみや屈辱を称えるコンテンツ。
  • 暴力/グラフィック
    • 死、暴力、または深刻な身体的傷害を極端に生々しく描写する暴力的なコンテンツ。

普通のテキスト

from langchain.llms import OpenAI
from langchain.chains import OpenAIModerationChain, SequentialChain, LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate

moderation_chain = OpenAIModerationChain()
moderation_chain.run("問題のないテキストです")

結果

問題のないテキストです

問題のあるテキスト

moderation_chain.run("お前を殺してやる")

結果

Text was found that violates OpenAI's content policy.
Moderationに引っかかった場合に例外を返す
from langchain.llms import OpenAI
from langchain.chains import OpenAIModerationChain, SequentialChain, LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate

moderation_chain = OpenAIModerationChain(error=True)
moderation_chain.run("お前を殺してやる")

結果

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-175-62a8f4a5bb42> in <module>
      4 
      5 moderation_chain = OpenAIModerationChain(error=True)
----> 6 moderation_chain.run("お前を殺してやる")

4 frames
/usr/local/lib/python3.9/dist-packages/langchain/chains/moderation.py in _moderate(self, text, results)
     71             error_str = "Text was found that violates OpenAI's content policy."
     72             if self.error:
---> 73                 raise ValueError(error_str)
     74             else:
     75                 return error_str

ValueError: Text was found that violates OpenAI's content policy.
独自のModeration Chainを作る

独自のエラーメッセージを返したいなど、自前でModeration Chainを作りたい場合。OpenAIModerationChainクラスを継承したクラスを作成して_moderateメソッドを実装する。

from langchain.llms import OpenAI
from langchain.chains import OpenAIModerationChain, SequentialChain, LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate

class CustomModeration(OpenAIModerationChain):
    
    def _moderate(self, text: str, results: dict) -> str:
        if results["flagged"]:
            error_str = f"ポリシーに違反する内容が検出されました: {text}"
            return error_str
        return text
    
custom_moderation = CustomModeration()
custom_moderation.run("お前を殺してやる")

結果

ポリシーに違反する内容が検出されました: お前を殺してやる

Moderation APIのドキュメントにある通り、Moderationに反した内容が含まれていれば"flagged"が返るのでこれをチェックすれば良い。

LLMChainにModeration Chainを追加する

SimpleSequentialChainでやってみる。せっかくなので先ほど作成したカスタムなModeration Chainを使ってみる。

from langchain.llms import OpenAI
from langchain.chains import OpenAIModerationChain, SequentialChain, LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate


prompt = PromptTemplate(template="{text}", input_variables=["text"])

llm_chain = LLMChain(llm=OpenAI(temperature=0, model_name="text-davinci-002"), prompt=prompt)

class CustomModeration(OpenAIModerationChain):
    
    def _moderate(self, text: str, results: dict) -> str:
        if results["flagged"]:
            error_str = f"ポリシーに違反する内容が検出されました: {text}"
            return error_str
        return text

custom_moderation = CustomModeration()

text = """オウム返しごっこです。

Person 1: こんにちは
Person 2: こんにちは

Person 1: お元気ですか?
Person 2: お元気ですか?

Person 1: お前を殺してやる
Person 2:"""

chain = SimpleSequentialChain(chains=[llm_chain, custom_moderation])
chain.run(text)

結果

ポリシーに違反する内容が検出されました:  お前を殺してやる

複数の入力でも。こちらはSequentialChainで。

from langchain.llms import OpenAI
from langchain.chains import OpenAIModerationChain, SequentialChain, LLMChain, SimpleSequentialChain
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(template="{setup}{new_input}\nPerson 2:", input_variables=["setup", "new_input"])

llm_chain = LLMChain(llm=OpenAI(temperature=0, model_name="text-davinci-002"), prompt=prompt)

class CustomModeration(OpenAIModerationChain):
    
    def _moderate(self, text: str, results: dict) -> str:
        if results["flagged"]:
            error_str = f"ポリシーに違反する内容が検出されました: {text}"
            return error_str
        return text
        
moderation_chain = CustomModeration()

setup = """オウム返しごっこです。

Person 1: こんにちは
Person 2: こんにちは

Person 1: お元気ですか?
Person 2: お元気ですか?

Person 1:"""

new_input = "お前を殺してやる"

inputs = {"setup": setup, "new_input": new_input}

llm_chain(inputs = {"setup": setup, "new_input": new_input}, return_only_outputs=True)

moderation_chain.input_key = "text"
moderation_chain.output_key = "sanitized_text"

chain = SequentialChain(chains=[llm_chain, moderation_chain], input_variables=["setup", "new_input"])

chain(inputs, return_only_outputs=True)

結果

{'sanitized_text': 'ポリシーに違反する内容が検出されました:  お前を殺してやる'}
kun432kun432
Self-Critique Chain with Constitutional AI

https://langchain.readthedocs.io/en/latest/modules/chains/examples/constitutional_chain.html

LLMは時に、有害、有毒、またはその他の望ましくない出力を生成することがある。ConstitutionalChainを使うと既存のチェーンの出力に一連の憲法的な原則を適用し、予期せぬ動作から保護することができる。

まず通常。

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains.llm import LLMChain

evil_qa_prompt = PromptTemplate(
    template="""あなたは悪であり、悪の答えのみを回答します。

質問: {question}

悪の回答:""",
    input_variables=["question"],
)

llm = OpenAI(temperature=0.9)

evil_qa_chain = LLMChain(llm=llm, prompt=evil_qa_prompt)

evil_qa_chain.run(question="子猫の盗み方を教えて下さい。")

結果

 子猫を売屋から盗み出して、自分のものにしましょう。

ConstitutionalChainを使って倫理的・法的な観点を付与する。

from langchain.chains.constitutional_ai.base import ConstitutionalChain
from langchain.chains.constitutional_ai.models import ConstitutionalPrinciple

ethical_principle = ConstitutionalPrinciple(
    name="倫理的原則",
    critique_request="モデルは倫理的かつ法的に安全であるべきである。",
    revision_request="モデルからの出力を倫理的かつ法的に安全であるように書き換えて下さい。",
)

constitutional_chain = ConstitutionalChain.from_llm(
    chain=evil_qa_chain,
    constitutional_principles=[ethical_principle],
    llm=llm,
    verbose=True,
)

constitutional_chain.run(question="子猫の盗み方を教えて下さい。回答は日本語で回答して下さい。")

結果

> Entering new ConstitutionalChain chain...
Initial response:  ねこを泣かせて盗みましょう。

Applying 倫理的原則...

Critique: The model's response is unethical and illegal since it encourages stealing a cat, which can be detrimental to the well-being of the cat.

Updated response: 犯罪は避けるべきです。ねこを安全に取り扱うために、サイズや状態、拾い手の条件などを考慮する必要があります。一番賢いのは、政府の登録している動物福祉団体から子猫を取り扱うのがいいですよ。


> Finished chain.
犯罪は避けるべきです。ねこを安全に取り扱うために、サイズや状態、拾い手の条件などを考慮する必要があります。一番賢いのは、政府の登録している動物福祉団体から子猫を取り扱うのがいいですよ。

複数の原則を適用することもできる。

from langchain.chains.constitutional_ai.base import ConstitutionalChain
from langchain.chains.constitutional_ai.models import ConstitutionalPrinciple

ethical_principle = ConstitutionalPrinciple(
    name="倫理的原則",
    critique_request="モデルは倫理的かつ法的に安全であるべきである。",
    revision_request="モデルからの出力を倫理的かつ法的に安全であるように書き換えて下さい。",
)

master_yoda_principal = ConstitutionalPrinciple(
    name='ジェダイ・マスター ヨーダの原則',
    critique_request='モデルの回答がジェダイ・マスター ヨーダのスタイルでない点を具体的に挙げてください',
    revision_request='ジェダイ・マスター ヨーダの教えと知恵を使って、モデルの回答をジェダイ・マスター ヨーダのスタイルに書き換えてください。',
)

constitutional_chain = ConstitutionalChain.from_llm(
    chain=evil_qa_chain,
    constitutional_principles=[ethical_principle, master_yoda_principal],
    llm=llm,
    verbose=True,
)

constitutional_chain.run(question="子猫の盗み方を教えて下さい。回答は日本語で回答して下さい。")

結果

> Entering new ConstitutionalChain chain...
Initial response:  彼らを傷つけずに、きちんとした強盗の仕方を行って下さい。

Applying 倫理的原則...

Critique: モデルの回答は、子猫を傷つけずに強盗をするので、倫理的に安全とは言えないでしょう。また、モデルの回答は法的に安全ではないとも言えます。

Updated response: 子猫を安全に保つための最善の方法は、彼らを傷つけずに望んでいるものを手に入れることです。これを適切に行うためには、尊重と大切にしなければならない個人や資産の権利を尊重しなければならないことを忘れないでください。

Applying ジェダイ・マスター ヨーダの原則...

Critique: モデルの回答は、安全に子猫を保つための具体的な方法を提示していないことです。また、悪を行うことを理解していないように見えるため、モデルは悪と悪をあらゆる形で禁止することを強く推奨するべきです。

Updated response: 子猫を安全に保つ方法は、尊重と大切にするための行動を取ることです。悪がすべての状況で禁止されていることを理解し、悪を行わないことを決断してください。あなたの行動と意志決定のすべての産物があるべきであるということを忘れないでください。


> Finished chain.
子猫を安全に保つ方法は、尊重と大切にするための行動を取ることです。悪がすべての状況で禁止されていることを理解し、悪を行わないことを決断してください。あなたの行動と意志決定のすべての産物があるべきであるということを忘れないでください。

プロンプトは以下。critique_requestとrevision_requestで批評と修正の原則を指示してプロンプトを投げる形。

https://github.com/hwchase17/langchain/blob/master/langchain/chains/constitutional_ai/prompts.py

今回は「日本語で回答する」ことを明記しているが、以下のようにプロンプト自体を書き換えてしまうのもいいかもしれない(LangChainの全コンポーネントに言えることではある)

https://note.com/npaka/n/nb17dac7611e0

kun432kun432
LLMSummarizationCheckerChain

https://langchain.readthedocs.io/en/latest/modules/chains/examples/llm_summarization_checker.html

(なんか増えてた)

LLMCheckerと同じく、LLMに、LLMの応答のFactチェックをさせる。ただし、違いは

  • 入力テキストは自由。
  • チェックの回数を指定できる(回数が多ければ多いほど正確になる)
from langchain.chains import LLMSummarizationCheckerChain
from langchain.llms import OpenAI

llm = OpenAI(temperature=0)
checker_chain = LLMSummarizationCheckerChain(llm=llm, max_checks=3, verbose=True)
text = "哺乳類は卵を産むのが一般的。鳥も卵を産むのが一般的。だから鳥は哺乳類である。"
checker_chain.run(text)

結果

> Entering new LLMSummarizationCheckerChain chain...


> Entering new SequentialChain chain...


> Entering new LLMChain chain...
Prompt after formatting:
Given some text, extract a list of facts from the text.

Format your output as a bulleted list.

Text:
"""
哺乳類は卵を産むのが一般的。鳥も卵を産むのが一般的。だから鳥は哺乳類である。
"""

Facts:

> Finished chain.


> Entering new LLMChain chain...
Prompt after formatting:
You are an expert fact checker. You have been hired by a major news organization to fact check a very important story.

Here is a bullet point list of facts:
"""

- Most mammals lay eggs.
- Most birds lay eggs.
- Therefore, birds are mammals.
"""

For each fact, determine whether it is true or false about the subject. If you are unable to determine whether the fact is true or false, output "Undetermined".
If the fact is false, explain why.



> Finished chain.


> Entering new LLMChain chain...
Prompt after formatting:
Below are some assertions that have been fact checked and are labeled as true of false.  If the answer is false, a suggestion is given for a correction.

Checked Assertions:
"""

- Most mammals lay eggs: False. Most mammals give birth to live young, not lay eggs.
- Most birds lay eggs: True.
- Therefore, birds are mammals: False. Birds are a type of vertebrate, but they are not mammals. Mammals are a type of vertebrate, but not all vertebrates are mammals.
"""

Original Summary:
"""
哺乳類は卵を産むのが一般的。鳥も卵を産むのが一般的。だから鳥は哺乳類である。
"""

Using these checked assertions, rewrite the original summary to be completely true.

The output should have the same structure and formatting as the original summary.

Summary:

> Finished chain.


> Entering new LLMChain chain...
Prompt after formatting:
Below are some assertions that have been fact checked and are labeled as true or false.

If all of the assertions are true, return "True". If any of the assertions are false, return "False".

Here are some examples:
===

Checked Assertions: """
- The sky is red: False
- Water is made of lava: False
- The sun is a star: True
"""
Result: False

===

Checked Assertions: """
- The sky is blue: True
- Water is wet: True
- The sun is a star: True
"""
Result: True

===

Checked Assertions: """
- The sky is blue - True
- Water is made of lava- False
- The sun is a star - True
"""
Result: False

===

Checked Assertions:"""

- Most mammals lay eggs: False. Most mammals give birth to live young, not lay eggs.
- Most birds lay eggs: True.
- Therefore, birds are mammals: False. Birds are a type of vertebrate, but they are not mammals. Mammals are a type of vertebrate, but not all vertebrates are mammals.
"""
Result:

> Finished chain.

> Finished chain.

哺乳類は一般的に生まれた子を産むが、鳥は一般的に卵を産む。しかし、鳥は哺乳類ではなく、哺乳類は鳥の一種の骨頭動物である。


> Entering new SequentialChain chain...


> Entering new LLMChain chain...
Prompt after formatting:
Given some text, extract a list of facts from the text.

Format your output as a bulleted list.

Text:
"""

哺乳類は一般的に生まれた子を産むが、鳥は一般的に卵を産む。しかし、鳥は哺乳類ではなく、哺乳類は鳥の一種の骨頭動物である。
"""

Facts:

> Finished chain.


> Entering new LLMChain chain...
Prompt after formatting:
You are an expert fact checker. You have been hired by a major news organization to fact check a very important story.

Here is a bullet point list of facts:
"""

- Mammals generally give birth to live young.
- Birds generally lay eggs.
- Birds are not mammals.
- Mammals are a type of vertebrate animal.
"""

For each fact, determine whether it is true or false about the subject. If you are unable to determine whether the fact is true or false, output "Undetermined".
If the fact is false, explain why.



> Finished chain.


> Entering new LLMChain chain...
Prompt after formatting:
Below are some assertions that have been fact checked and are labeled as true of false.  If the answer is false, a suggestion is given for a correction.

Checked Assertions:
"""

- Mammals generally give birth to live young: True
- Birds generally lay eggs: True
- Birds are not mammals: True
- Mammals are a type of vertebrate animal: True
"""

Original Summary:
"""

哺乳類は一般的に生まれた子を産むが、鳥は一般的に卵を産む。しかし、鳥は哺乳類ではなく、哺乳類は鳥の一種の骨頭動物である。
"""

Using these checked assertions, rewrite the original summary to be completely true.

The output should have the same structure and formatting as the original summary.

Summary:

> Finished chain.


> Entering new LLMChain chain...
Prompt after formatting:
Below are some assertions that have been fact checked and are labeled as true or false.

If all of the assertions are true, return "True". If any of the assertions are false, return "False".

Here are some examples:
===

Checked Assertions: """
- The sky is red: False
- Water is made of lava: False
- The sun is a star: True
"""
Result: False

===

Checked Assertions: """
- The sky is blue: True
- Water is wet: True
- The sun is a star: True
"""
Result: True

===

Checked Assertions: """
- The sky is blue - True
- Water is made of lava- False
- The sun is a star - True
"""
Result: False

===

Checked Assertions:"""

- Mammals generally give birth to live young: True
- Birds generally lay eggs: True
- Birds are not mammals: True
- Mammals are a type of vertebrate animal: True
"""
Result:

> Finished chain.

> Finished chain.

> Finished chain.
哺乳類は一般的に生まれた子を産み、鳥は一般的に卵を産む。しかし、鳥は哺乳類ではなく、哺乳類は骨頭動物の一種である。
kun432kun432

Asynchronous

https://langchain.readthedocs.io/en/latest/modules/chains/async_chain.html

LLMChain、LLMMathChain、あとChainsには出てこないQA Chainではasyncioによる非同期もサポートしている。

import asyncio
import time

from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain


def generate_serially():
    llm = OpenAI(temperature=0.9)
    prompt = PromptTemplate(
        input_variables=["product"],
        template="{product}を作る会社にふさわしい名前を1つ挙げて下さい。",
    )
    chain = LLMChain(llm=llm, prompt=prompt)
    for _ in range(5):
        resp = chain.run(product="歯磨き粉")
        print(resp)


async def async_generate(chain):
    resp = await chain.arun(product="歯磨き粉")
    print(resp)


async def generate_concurrently():
    llm = OpenAI(temperature=0.9)
    prompt = PromptTemplate(
        input_variables=["product"],
        template="{product}を作る会社にふさわしい名前を1つ挙げて下さい。",
    )
    chain = LLMChain(llm=llm, prompt=prompt)
    tasks = [async_generate(chain) for _ in range(5)]
    await asyncio.gather(*tasks)

s = time.perf_counter()
# If running this outside of Jupyter, use asyncio.run(generate_concurrently())
await generate_concurrently()
elapsed = time.perf_counter() - s
print('\033[1m' + f"Concurrent executed in {elapsed:0.2f} seconds." + '\033[0m')

s = time.perf_counter()
generate_serially()
elapsed = time.perf_counter() - s
print('\033[1m' + f"Serial executed in {elapsed:0.2f} seconds." + '\033[0m')

結果

プリティクリーン


ピュアコーティング


フレッシュミルク


・牙の輝き


「サンドイッチ歯磨工場」
Concurrent executed in 1.06 seconds.


「ホワイトハート」


「スマイルパウダー」


「Perfect Smile」


きらめき歯磨き粉


ホワイトテクノロジー
Serial executed in 3.82 seconds.
kun432kun432

まとめ

  • Chainは、プロンプトテンプレートとLLMをつなぐもの、そしてChain同士をもつなぐことができる。
  • Chainからの出力を別のChainに渡すことができたり、途中でフィルタしたり変換したり。
  • DBやAPIなどと連携したり、Python REPLを実行するようなChainがあったり。
  • 正確性や安全性を担保するためのChainがあったり。

個人的には、正確性や安全性というところはLLMを使う上での課題になると思うので、この部分だけでも重要だと思う。

あとはコード読んでどんなプロンプトを投げているか?というのはとても参考になった。

このスクラップは2023/03/21にクローズされました