見出し画像

langchain 0.1.11 一通り確認(1)

langchain 0.1.11 一通り確認(1)
公式サイトのcookbook Prompt+LLMにあるサンプルコードを一通り試す

こんにちは、makokonです。
皆さんlangchainを使っていますか。
2023年12月14日にv0.1.0へのメジャーアップデートと思ったら、もうv0.1.11になっていますね。今のところ使っていて、いきなり従来のプログラムが誤作動するような大きな変更にぶつかっていませんが、いちいち使い方が古いと責められては、気持ちがもやもやします。
一度、公式ページのコードを一通り実行して、もやもやを消しておこうと思います。やっぱり自分の環境で動いてもらわないとね。
ですから、基本的に公式ページの焼き直しなので、そんなに新しい報告はないです。むしろ自分用の備忘録です。


準備

python  仮想環境


まずは、環境の準備をしましょう。
お試しということもあり、pythonの仮想環境を用意します。
今回はlinux上で試すことにしたので、こんな感じ

$ mkdir langchain-0.1.11
$ python -m venv langchain-0.1.11
$ cd langchain-0.1.11/
$ source bin/activate

$ cat /etc/issue
Ubuntu 22.04.3 LTS \n \l
$ python --version
Python 3.10.12

$ pip list
Package    Version
---------- -------
pip        22.0.2
setuptools 59.6.0
$ pip install --upgrade pip
$ pip list
Package    Version
---------- -------
pip        24.0
setuptools 59.6.0

 とりあえずpipも新しくして、準備完了

コーディング


Prompt + LLM

まずは基本ですね。
PromptTemplate / ChatPromptTemplate -> LLM / ChatModel -> OutputParser
これを試してみます。なお、LLMはOPENAIを利用します。

$ pip install –upgrade –quiet langchain langchain-openai

sample00.py

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI()
chain = prompt | model

# result
print(chain.invoke({"foo": "bears"}))


prompt = ChatPromptTemplate.from_template("{foo}に関するジョークを日本超で話して")
chain = prompt | model

# result
print(chain.invoke({"foo": "カラーテレビ"}))

$ python sample00.py
content="Why don't bears like fast food?\n\nBecause they can't catch it!"
content='「なぜカラーテレビは色が出るのか知ってる?それは、黒と白が一緒に仲良く しているからさ!」'

実行結果

"bears"に関するジョークを返してくれました。
日本語のプロンプトと回答も当然ですが問題ないですね。
ジョークの出来は悪いけど
なお、今回はpromptが固定なので、promptを変更したときはchainも再定義が必要です。ご注意を。

Attaching Stop Sequences

ストップシーケンスの設定のサンプルもありますね。ストップシーケンスとは処理を停止するための指示やデータを示すものです。
サンプルは改行コードをストップシーケンスに設定しています。temperature=0.0にして、その挙動を確認してみましょう。

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI(temperature = 0.0)
chain = prompt | model

# result
print(chain.invoke({"foo": "bears"}))
# test stop sequences
chain = prompt | model.bind(stop=["\n"])
for i in range(5):
    print(chain.invoke({"foo": "bears"}))

content="Why did the bear break up with his girlfriend? \n\nBecause he couldn't bear the relationship any longer!"
content='Why did the bear break up with his girlfriend? '
content='Why did the bear bring a flashlight to the party? '
content='Why did the bear break up with his girlfriend? '
content='Why did the bear break up with his girlfriend? '
content='Why did the bear break up with his girlfriend? '

実行結果

後ろの5つの応答には、改行コードが含まれていません。質問文で切れているので、改行コードが発生したところで処理を停止したのでしょう。
なお、temperature=0.0でも回答に若干のゆらぎはある模様。

関数呼び出し


from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI(temperature = 0.0)

functions = [
    {
        "name": "joke",
        "description": "A joke",
        "parameters": {
            "type": "object",
            "properties": {
                "setup": {"type": "string", "description": "The setup for the joke"},
                "punchline": {
                    "type": "string",
                    "description": "The punchline for the joke",
                },
            },
            "required": ["setup", "punchline"],
        },
    }
]
chain = prompt | model.bind(function_call={"name": "joke"}, functions=functions)

print(chain.invoke({"foo": "bears"}, config={}))

$ python sample00-01.py
content='' additional_kwargs={'function_call': {'arguments': '{"setup":"Why don't bears like fast food?","punchline":"Because they can't catch it!"}', 'name': 'joke'}}

実行結果

問題なく動いていますね。なお、function_callがどういうものかについては下記リンクを読むとイメージが掴めるかと思います。今回は動いてOKで終わりです。以前と使い方もほぼ同じなので、ちょっと安心しました。

PromptTemplate + LLM + OutputParser

出力パーサーも利用可能です。出力パーサーは出力を扱いやすい形式にしてくれます。今回は普通に文字列に変更してみます。

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI(temperature = 0.0)
chain = prompt | model

# result
print(chain.invoke({"foo": "bears"}))

# output parser

from langchain_core.output_parsers import StrOutputParser

print("use output parser:")
chain = prompt | model | StrOutputParser()
print(chain.invoke({"foo": "bears"}))

$ python sample00-02.py
content='Why did the bear bring a flashlight to the party? \n\nBecause he heard it was going to be a "beary" good time!'
use output parser:
Why did the bear break up with his girlfriend?

Because he couldn't bear the relationship any longer!

実行結果

出力結果に"content= XXXXX"やらが、普通に文字列だけになって、読みやすくなりましたね。

関数出力も出力パーサー

関数出力に対しても適切な整形ができます。
このサンプルでは、必要な出力'setpu','punchline'のみをjson形式で出力します。
また、出力するキー(例えばsetup)だけを指定することもできます。

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI(temperature = 0.0)

functions = [
    {
        "name": "joke",
        "description": "A joke",
        "parameters": {
            "type": "object",
            "properties": {
                "setup": {"type": "string", "description": "The setup for the joke"},
                "punchline": {
                    "type": "string",
                    "description": "The punchline for the joke",
                },
            },
            "required": ["setup", "punchline"],
        },
    }
]
chain = prompt | model.bind(function_call={"name": "joke"}, functions=functions)

print(chain.invoke({"foo": "bears"}, config={}))

print("user output parser:")
# output parser
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser

chain = (
    prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonOutputFunctionsParser()
)

print(chain.invoke({"foo": "bears"}))
print("set output key")
from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser

chain = (
    prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)


print(chain.invoke({"foo": "bears"}))

$ python sample00-03.py
content='' additional_kwargs={'function_call': {'arguments': '{"setup":"Why don\'t bears like fast food?","punchline":"Because they can\'t catch it!"}', 'name': 'joke'}}

user output parser:
{'setup': "Why don't bears like fast food?", 'punchline': "Because they can't catch it!"}

set output key
Why don't bears like fast food?

実行結果

確かに見やすいし、この出力を再利用することを考えると圧倒的に使いやすいですよね。

Simplifying input

RunnableParallelを使えば、入力をシンプルにすることもできます。
今まで毎回、chain.invoke({"foo":"bears"}))とか入れてましたけど、"foo"なのは決まり切っている(ような使い方も多い)のだから、省略して勝手に入力辞書を作って欲しいですよね。

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI(model="gpt-4-turbo-preview",temperature = 0.0)

functions = [
    {
        "name": "joke",
        "description": "A joke",
        "parameters": {
            "type": "object",
            "properties": {
                "setup": {"type": "string", "description": "The setup for the joke"},
                "punchline": {
                    "type": "string",
                    "description": "The punchline for the joke",
                },
            },
            "required": ["setup", "punchline"],
        },
    }
]

print("use runnablePassthrough:")
from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser
from langchain_core.runnables import RunnableParallel, RunnablePassthrough

map_ = RunnableParallel(foo=RunnablePassthrough())
chain = (
    map_
    | prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)



print(chain.invoke("bears"))

$ python sample00-04.py
use runnablePassthrough:
Why don't bears wear socks?

実行結果

このように"foo"に対してパススルー設定を加えて、最初にチェインすることで、無事入力を省略することができました。

これで、prompt + LLM に含まれるサンプルコードはすべて、そのまま動くし、従来からの利用方法と大きく変わるところはありませんでした。
一安心です。
今回の確認部分はlangchain-coreに集約されているので、楽ちんでしたね。

存外長くなったので、別のサンプルコードは次の機会に検証したいと思います。





この記事が気に入ったらサポートをしてみませんか?