LangChainの使い方 紹介編

# 1. はじめに

このところ、大規模言語モデル(以下、LLM)を応用したサービスが普及しつつあります。

その応用例の代表格にはChatGPTがあります。
当初はネット検索ができないとか、数式の計算結果が誤りがちという問題がありましたが、それぞれブラウザ拡張による対応や、内部機能のアップデートによって克服されつつあります。(計算はちょっとまだ苦手ですが)

23/2/15現在では、BingのAIチャット機能にOpenAIのGPTモデルが組み込まれるようになりました。
ユーザの質問に対して適宜ネット検索を行って情報を得て、それに基づいて参考URLつきの回答を返すようになっています。

![bing_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/5cdde21b-f89a-ac41-d1bd-1db3ae22b210.png)

ここまでLLMが強力になりつつあると、プログラムからLLMを呼び出して何らかの処理を自動化してみたり、他のアプリと組み合わせて機能強化できないか試してみたくなります。
実際にこれらを容易に実装するためのライブラリが作り出されています。それがLangChainです。

# 2. LangChainとは

LangChainとは、LLMを使った処理をパイプライン状に順次実行するライブラリです。
先に述べたような、ネット検索結果を入力情報としてLLMに回答を作らせるような処理が容易に作れます。
質問文から回答に必要なAPIをLLMを使って判断し、それらを適宜呼び出した結果を利用して回答を返す処理(agent)も作ることができます。

# 3. 基本的な使い方

基本的な使い方については、下記がよくまとまっており通読推奨です。

https://qiita.com/wwwcojp/items/c7f43c5f964b8db8a890

しかし本文にもある通り、解説内容が23/2/15現在よりも古いバージョン(0.0.39)に基づいているため、
将来的にはコード例に書かれているAPI呼び出し例を改変しないと動かなくなる可能性があることにも注意ください。

# 4. インストール

普通にpipで入ります。特にバージョンを指定しない場合、23/2/15現在は`LangChain 0.0.86`がインストールされるはずです。

```sh
pip install langchain
```

あと注意が必要なのは、**原則的にOpenAIのAPI Keyが必要**になることです。

OpenAIにログインして、画面右上のPersonalメニューから"View API keys"を選択するとkeyの一覧と新規keyの追加ができる画面に遷移します。
その画面で"Create new secret key"ボタン(下図赤枠部)をクリックすれば、keyを追加できます。

![openai_api_keys.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/386d74b1-ce7b-960d-bdcd-71b4a29362ad.png)

keyができたら、環境変数`OPENAI_API_KEY`にkeyの値を設定するか、LangChainの`OpenAI`クラスなどのコンストラクタ引数で渡して設定する必要があります。環境変数の場合、Pythonの`os.environ`で設定することも可能です。

先に述べた、「ネット検索結果を入力情報としてLLMに回答を作らせる」という使い方をしたい場合、LangChainからGoogleのSerpAPIを呼び出してネット検索することになります。
その際もAPI実行にkeyが必要になるため、OpenAIと同様にkeyの追加と設定が必要です。この他のAPI Keyが必要なサービスを使いたい場合も同様です。

場合によってはAPI Keyに加えて、APIを叩くライブラリを追加インストールする必要があります。
ただし必要なライブラリが未インストールの場合は、適宜エラーメッセージでライブラリのインストール方法を説明してくれます。あとは、それに従って大人しくインストールをすれば事足ります。

# 5. 使ってみる

例えば[先の記事](https://qiita.com/wwwcojp/items/c7f43c5f964b8db8a890)にもあったサンプルコードを動かしてみましょう。下記では`openai_api_key`引数はあえて空文字列にしていますが、実際は本当のkeyの文字列を入れてください。あるいは`os.environ['OPENAI_API_KEY']`にkeyの文字列を指定して、`openai_api_key`の引数指定を`OpenAI`クラスのコンストラクタから除去してください。(以降のサンプルについても同様)

なお、下記で`OpenAI`クラスのコンストラクタに渡している`temperature`とは、GPTの出力結果のランダム性を調整するパラメタです。ランダム性が不要で正確な情報が知りたいときは`temperature = 0`、多少の創造性・遊び心が欲しい場合は`temperature = 0.7`にするというようにニーズに合わせた調整をするとよいでしょう。

```python
# 引用元 https://qiita.com/wwwcojp/items/c7f43c5f964b8db8a890

from langchain.llms import OpenAI
llm = OpenAI(temperature=0.9,
             openai_api_key='')
print(llm(prompt="技術記事のタイトルをよしなに生成してくれるAIの名称を考えてください。"))
```

すると下記のような結果が得られます。結果はランダムに変わります。

```sh
「タイトル博士」
```

ただし、OpenAIのAPI keyを正しく設定していても下記のようなメッセージが出てレスポンスが返ってこないことがあります。

```sh
Retrying langchain.llms.openai.BaseOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised RateLimitError: You exceeded your current quota, please check your plan and billing details..
```

こうなった場合はメッセージにもある通り"billing"が必要です。
大人しくクレカをOpenAIに登録して課金しましょう。登録後1時間くらいしたらAPIが使えるようになります。

# 6. Wolfram Alphaと連携する

[先に紹介した記事](https://qiita.com/wwwcojp/items/c7f43c5f964b8db8a890)にも言及があるとおり、現状ではOpenAI APIを使って計算をしたときに誤った結果が返ってくることがあります。

実は計算用の外部ツールを使う設定をすることによって、正しい計算結果を得やすくすることができます。
たとえば下記のコードのようにWolfram Alphaと連携することができます。

下記コードに出現する`agent`とは先に述べた、「質問文から回答に必要なAPIをLLMを使って判断し、それらを適宜呼び出した結果を利用して回答を返す処理」のことです。ここでは`tools`に指定したSerpAPIWrapperやWolfram Alphaといった外部ツールの中から、実際に使うべきものを判断する処理を行います。

このコードでは[先に紹介した記事](https://qiita.com/wwwcojp/items/c7f43c5f964b8db8a890)と同じ、「日本で一番高い山の高さは何メートルですか?その高さの5乗根を求めてください。」という質問を投げています。

```python
llm = OpenAI(temperature=0,
             openai_api_key='')
search = SerpAPIWrapper(
    serpapi_api_key='')
tools = [
    Tool(
        name="Search",
        func=search.run,
        description="useful for when you need to answer questions about current events"
    )
]
tools.extend(load_tools(['wolfram-alpha'],
                        wolfram_alpha_appid=''))

agent = initialize_agent(
    tools, llm, agent="zero-shot-react-description", verbose=True)
agent.run("日本で一番高い山の高さは何メートルですか?その高さの5乗根を求めてください。")
```

実行結果は下記のようになります。適宜Wolfram Alphaを呼び出して必要な情報を調べたり、計算していることがわかります。

```sh
> Entering new AgentExecutor chain...
 I need to find the height of the highest mountain in Japan and then calculate its fifth root.
Action: Wolfram Alpha
Action Input: "What is the height of the highest mountain in Japan?"
Observation: Assumption: mountain | with | highest elevation | 
with | country | Japan | elevation 
Answer: 3776 meters
(Mount Fuji)
Thought: I now need to calculate the fifth root of 3776 meters
Action: Wolfram Alpha
Action Input: "What is the fifth root of 3776 meters?"
Observation: Assumption: (3776 meters)^(1/5) 
Answer: 5.193 m^(1/5) (meters to the 1/5)
Thought: I now know the final answer
Final Answer: 5.193 m^(1/5) (meters to the 1/5)

> Finished chain.
'5.193 m^(1/5) (meters to the 1/5)'
```

LangChainからWolfram Alphaを使うにはAPI keyの入手・設定がやはり必要になります。下記ページからすぐ入手できます。

https://products.wolframalpha.com/api/

富士山の高さを検索するのに使っているSerpApiについても専用のAPI Keyが別個に必要になります。下記のSerpApiのダッシュボードページから入手できます。

https://serpapi.com/dashboard

なお、実はコードを書かなくてもBingのAIチャットを使えば上のコードと同じく正しい回答を導出できます。

![bing_富士山.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/b2ae9ae0-5bce-6179-5d4b-6c99c73790d2.png)

# 7. ローカルの言語モデルを動かす

OpenAIを使う場合に問題になるのが、クエリに機微な情報を乗せられないことです。

ローカルで動かせるほどの軽量なLLMがあれば機微な情報も乗せられるのですが、結論から言えば本記事の初版を公開した2023年3月時点では、そのようなLLMはまだまだ使い物にならない印象でした。

例えば、ABEJA社が比較的軽量な日本語向けLLM`abeja/gpt-neox-japanese-2.7b`を作成・公開しています。[こちらの記事](https://qiita.com/syoyo/items/d0fb68d5fe1127276e2a)にある通り、下記のコードを書いてローカルでこのLLMを動かしてみました。使用したGPUはRTX 2080(VRAM 8GB)です。

```python
# 引用元: https://qiita.com/syoyo/items/d0fb68d5fe1127276e2a

model_id = "abeja/gpt-neox-japanese-2.7b"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
pipe = pipeline(
    "text-generation", model=model, tokenizer=tokenizer,
    max_new_tokens=512, device=device_num, torch_dtype=torch.float16
)
hf = HuggingFacePipeline(pipeline=pipe)
```

クエリの発行は、上記に続けて下記コードを書き足せばできます。

```python
print(hf("技術記事のタイトルをよしなに生成してくれるAIの名称を考えてください。"))
```
ですが、今のところあまり芳しくない結果が返ってきます。下記のように何も返ってこないことすらあります。

```sh
```

今のところは機微な内容をこっそりローカルのLLMに質問することはできず、公開できる内容の質問をAPI経由でOpenAIなどのLLMに渡すよりほかなさそうです。

ただしLLM周りの進歩は早いため、軽量なLLMでも所望の回答が得られやすくなるようになるのは時間の問題であろうと思います。

# 8. YOLOPandas

YOLOPandasとはLLMを介して、自然言語でPandasのデータフレームを操作できるようにするライブラリです。

https://github.com/ccurme/yolopandas

ここでの"YOLO"というのは同名の画像認識アルゴリズムとは関係なく、字義通り"You only look once"のことで、ここでは「対話実行をせず、結果だけを見せることができる」という程度の意味合いです。

YOLOPandasのGitHubに記載のあるプログラムをもとに、自然言語による操作結果だけを表示するコードを下記の通り用意しました。結果だけ表示する指定は下記コードの`yolo=True`の部分です。

```python
# 引用元: https://github.com/ccurme/yolopandas
os.environ["OPENAI_API_KEY"] = ""
df = pd.DataFrame(
    [
        {"name": "The Da Vinci Code", "type": "book",
            "price": 15, "quantity": 300, "rating": 4},
        {"name": "Jurassic Park", "type": "book",
            "price": 12, "quantity": 400, "rating": 4.5},
        {"name": "Jurassic Park", "type": "film",
            "price": 8, "quantity": 6, "rating": 5},
        {"name": "Matilda", "type": "book", "price": 5, "quantity": 80, "rating": 4},
        {"name": "Clockwork Orange", "type": None,
            "price": None, "quantity": 20, "rating": 4},
        {"name": "Walden", "type": None, "price": None,
            "quantity": 100, "rating": 4.5},
    ],
)

print(df.llm.query("What item is the least expensive?"), yolo=True)
```

なお、上記コードで作ったdfの中身は下記のようになっています。

|    | name              | type   |   price |   quantity |   rating |
|---:|:------------------|:-------|--------:|-----------:|---------:|
|  0 | The Da Vinci Code | book   |      15 |        300 |      4   |
|  1 | Jurassic Park     | book   |      12 |        400 |      4.5 |
|  2 | Jurassic Park     | film   |       8 |          6 |      5   |
|  3 | Matilda           | book   |       5 |         80 |      4   |
|  4 | Clockwork Orange  |        |     nan |         20 |      4   |
|  5 | Walden            |        |     nan |        100 |      4.5 |

コードに記載した質問文は"What item is the least expensive?"でしたが、実際にコードを実行してみると、下記の通り質問文に合致した結果が得られます。

```sh
      name  type  price  quantity  rating
3  Matilda  book    5.0        80     4.0
```

なおYOLOを無効にしていると、下記のように一旦「コードを実行してよいか？」と聞かれるようになります。ここでyを入力すると上記の結果が得られます。

```sh
run this code? y/n
```

サンプルコードの結果を見るだけだとYOLOPandasはすごいツールに思えますが、まだまだ出来としては発展途上です。例えば下記のコードの実行結果は見事に間違いです。(ダ・ヴィンチ・コードは映画化されているが、今回使っているデータフレームの中には書籍版の情報しかない)

```python
df.llm.query("What film is the highest price?", yolo=True)
```

```sh
                name  type  price  quantity  rating
0  The Da Vinci Code  book   15.0       300     4.0
```

# 9. おわりに

LLMの強力さに私も含めて世界中の技術者が驚いていると思います。

ただ、まだまだこの分野の発展は始まったばかりで、今回紹介した関連ツールもまだまだ全般的にピーキーな挙動を示す印象です。

しかしながら、新技術はえてして数年で劇的に発展するものです。
LLMに関しては色々な基礎研究・応用技術に今から目をつけておくことが、これからの技術的なアドバンテージにつながると思います。

LangChainを使っていて思ったこととして、「AIがWeb上のリソースにアクセスして便利な仕事ができるようになれば、アクセスできるリソースも増えていき、それがさらなるAI自身あるいはAIを使ったサービスの高度化につながらないか」ということがあります。
具体的に1つの例を書くと、「AIがAPIを介して消耗品をアマゾンで定期に届ける設定を自律的にできるようになれば、顧客を逃したくない楽天やアリババなどの競合他社が同様のAPIを構築するようになる。その結果、AIは同じ商品ならより安い and/or より早く届くところを見つけて自律的に購買するようになる」といったストーリーが考えられます。

21世紀初頭には「様々なWebシステムにAPIを追加して相互連携できるようにし、それぞれのシステムやアプリを高度化していこう」という構想がありました。結局この構想は、マイクロソフトやIBMなどが数年間音頭を取るも頓挫したのですが、今こそ復活が近いのではないかと思っています。

# Appendix. 参考文献

いずれも23/2/15にアクセスし、内容を確認。

<https://qiita.com/wwwcojp/items/c7f43c5f964b8db8a890>
<https://qiita.com/syoyo/items/d0fb68d5fe1127276e2a>
<https://note.com/npaka/n/ne2b421fa678a>
<https://note.com/npaka/n/nd9a4a26a8932>
<https://github.com/ccurme/yolopandas>
<https://note.com/sator926/n/n592cda4756f0>