LangChainのチュートリアルをやってみた

LangChainというフレームワークのチュートリアル(LLM)をやってみました。
言語モデルを使ったアプリケーション開発で使えるそうです。
執筆時ではPythonとjs/tsでライブラリが用意されていますが、今回はPythonを使用しました。

https://python.langchain.com/en/latest/index.html

https://python.langchain.com/en/latest/getting_started/getting_started.html

## 環境構築

Pythonの環境は導入済みの前提とします。私の環境にはPython3.9が入っています。
また、パッケージ管理は仮想環境で行うためpipenvを使用しています。

```
$ python -V
Python 3.9.13
```

### ライブラリのインストール

以下のコマンドで必要なライブラリをインストールします。

```
pipenv install langchain openai google-search-results
```

### OpenAIのAPIキーをセット

今回はOpenAIの言語モデルに接続するのでOpenAIのAPIキーを取得します。
公式サイトから取得できますが、今回は手順を割愛します。

https://platform.openai.com/account/api-keys

取得したOpenAIのAPIキーを環境変数にセットします。

```
export OPENAI_API_KEY="{APIキー}"
```

動的にAPIキーをセットするにはプログラム中で以下のようにすると良いそうです。

```
from langchain.llms import OpenAI
llm = OpenAI(openai_api_key="{APIキー}")
```

## コードの実行

### 基本形

まずは最も基本的な構成要素で実行してみました。
チュートリアルではカラフルな靴下を作る会社の社名を訪ねています。

```app.py
from langchain.llms import OpenAI

llm = OpenAI(temperature=0.9)
text = "What would be a good company name for a company that makes colorful socks?"
print(llm(text))
```

何となくそれっぽい回答が来ました。

```実行結果
$ python app.py 
Rainbow Sockery
```

質問文を日本語にすると日本語の回答が得られました。

```app.py
text = "カラフルな靴下を作る会社の社名を考えてください"
```

```実行結果
$ python app.py 
カラフルソックスコーポレーション
```

また、temperatureの値は1に近づくほど回答にランダム性が増すようです。
今回は社名を考えるというお題で色んな案が欲しいので、0.9と高めの数値を設定しているのでしょうか。


### テンプレート

質問のテンプレートを用意する際にはこのような書き方をするみたいです。

```app.py
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(
    input_variables=["product"],
    template="What is a good name for a company that makes {product}?",
)

print(prompt.format(product="colorful socks"))
```

```実行結果
$ python app.py
What is a good name for a company that makes colorful socks?
```

### チェーン

基本形の言語モデルに質問を投げる部分とテンプレートを組み合わせるのにchainを使うようです。

```app.py
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="What is a good name for a company that makes {product}?",
)

chain = LLMChain(llm=llm, prompt=prompt)
chain.run("colorful socks")
```

これだと実行結果に何も表示されなかったのでprintで括ってみました。
テンプレートの質問をllmに渡せていることが確認出来ました。

```app.py
print(chain.run("colorful socks"))
```

```実行結果
$ python app.py
Rainbow Toes Socks
```

### エージェント

ユーザーへの回答を生成するためのアクションと順番をLLMを使って生成します。
アクションはツールを使用するか、ユーザーに返答するかを行うようです。
チュートリアルではアクションのツールとしてGoogleSearchAPIを使用しています。

こちらのチュートリアルを行うためにはGoogleSearchAPIが使用できる必要があります。
以下のサイトから会員登録をしてAPIキーを取得します。
月あたり100回のリクエストは無料で行えるようです。

https://serpapi.com/

以下のようにして環境変数にAPIキーをセットします。
```
export SERPAPI_API_KEY="{SERPAPIキー}"
```

チュートリアルでは「昨日のサンフランシスコの最高気温は華氏何度だったか。その値を0.023乗するといくつになるか？」という質問をしています。

```app.py
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI

# First, let's load the language model we're going to use to control the agent.
llm = OpenAI(temperature=0)

# Next, let's load some tools to use. Note that the `llm-math` tool uses an LLM, so we need to pass that in.
tools = load_tools(["serpapi", "llm-math"], llm=llm)


# Finally, let's initialize an agent with the tools, the language model, and the type of agent we want to use.
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)

# Now let's test it out!
agent.run("What was the high temperature in SF yesterday in Fahrenheit? What is that number raised to the .023 power?")
```

エージェントでは回答を得るために2つのアクションを実行しているようです。
まずはSearchアクションで昨日のサンフランシスコの最高華氏度を検索しています。
さらに得られた回答(69.8)をCalculatorアクションで0.23乗し、FinalAnswerとして回答を生成しています。

```実行結果
$ python app.py

> Entering new AgentExecutor chain...
 I need to find the temperature first, then use the calculator to raise it to the .023 power.
Action: Search
Action Input: "High temperature in SF yesterday"
Observation: High: 69.8ºf @3:45 PM Low: 55.04ºf @5:56 AM Approx.
Thought: I need to use the calculator to raise 69.8 to the .023 power
Action: Calculator
Action Input: 69.8^.023
Observation: Answer: 1.1025763550530143
Thought: I now know the final answer
Final Answer: 1.1025763550530143

> Finished chain.
```

ChatGPTだと最新情報を検索しないと出てこないような質問には返答できないので、これはすごいですね！

### メモリ

これまでのメッセージを記憶しておく機能みたいです。
チャットボットなど以前の質問や回答を記憶する必要がある際に役立ちそうです。

チュートリアルの質問文だと記憶をベースに回答を生成出来ているのかよく分からなかったので、
オリジナルで質問文を作成しました。
※質問文を連続で投げているので会話は成立していません。
```app.py
from langchain import OpenAI, ConversationChain

llm = OpenAI(temperature=0)
conversation = ConversationChain(llm=llm, verbose=True)

output = conversation.predict(input="こんにちは！私はボブです。趣味はサッカーです。")
print(output)

output = conversation.predict(input="私の趣味は何でしょうか？")
print(output)
```

ちゃんと記憶したことから回答を返してくれました。
AIさんが「サッカーはどんなスポーツですか？」って聞いてきたのに、次の回答ではサッカーについて自ら説明した点には笑いました。
```実行結果
$ python app.py 


> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: こんにちは！私はボブです。趣味はサッカーです。
AI:

> Finished chain.
 こんにちは、ボブさん！私はAIです。私の趣味はプログラミングです。サッカーはどんなスポーツですか？

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: こんにちは！私はボブです。趣味はサッカーです。
AI:  こんにちは、ボブさん！私はAIです。私の趣味はプログラミングです。サッカーはどんなスポーツですか？
Human: 私の趣味は何でしょうか？
AI:

> Finished chain.
 あなたの趣味はサッカーですね？サッカーは、11人のチームがボールを蹴り合い、ゴールを決めるスポーツです。
```

## おわりに

チュートリアルを通してLangChainの主要な機能に触れてみました。
これらのモジュールを組み合わせると面白いことが出来そうですね。