これを読んだらわかった気になれるLangChain

2023/10/01
改めて動作確認した記事を投稿していますのでこちらも参照ください。

https://qiita.com/moritalous/items/60bcae924812a18d65b0


---

LangChainのクイックスタートガイドを日本語に翻訳しながらやってみました。

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

実行結果も記載しますので、これを読んだらクイックスタートをやった気になれます

## 事前準備

VSCodeのdevcontainer (ubuntu:jammy)上にipynbを作って試しました。

* `python3-pip`をインストール
* VSCodeの拡張をインストール
    * `Python`：https://marketplace.visualstudio.com/items?itemName=ms-python.python
    * `Jupyter`：https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter
* `LangChain-Quickstart.ipynb`ファイルを新規作成して実施

---

ここから先がクイックスタートの内容です。

---

## インストール方法 (Installation)

始めるには、次のコマンドを使用してLangChainをインストールしてください。

```python
%pip install langchain
```

## 環境設定 (Environment Setup)

LangChainを使用する場合、通常は1つ以上のモデルプロバイダ、データストア、APIなどとの統合が必要です。

この例では、OpenAIのAPIを使用するため、まず彼らのSDKをインストールする必要があります。

```python
%pip install openai
```

次に、環境変数を設定する必要があります。

```python:python
import os
os.environ["OPENAI_API_KEY"] = "..."
```

## 言語モデルアプリケーションの構築：LLMs (Building a Language Model Application: LLMs)

LangChainをインストールし、環境を設定したので、言語モデルアプリケーションの構築を開始できます。

LangChainは、言語モデルアプリケーションの構築に使用できる多くのモジュールを提供しています。これらのモジュールは組み合わせてより複雑なアプリケーションを作成することもできますし、単独でシンプルなアプリケーションに使用することもできます。

## LLMs：言語モデルからの予測を取得する (LLMs: Get predictions from a language model)

LangChainの最も基本的なビルディングブロックは、入力に対してLLM（言語モデル）を呼び出すことです。簡単な例を通じて、これを行う方法を見てみましょう。この目的のために、企業が何を製造しているかに基づいて会社名を生成するサービスを構築していると想像しましょう。

これを行うために、まずLLMラッパーをインポートする必要があります。

```python:python
from langchain.llms import OpenAI
```

次に、任意の引数でラッパーを初期化します。この例では、出力をよりランダムにしたいと思うので、高い温度で初期化します。

```python:python
llm = OpenAI(temperature=0.9)
```

これで、いくつかの入力に対して呼び出すことができます！

```python:python
text = "カラフルなソックスを製造する会社にとって、どのような会社名が良いでしょうか？"
print(llm(text))
```

```
・Happy Feet
・Colorful Knees
・Kaleidoscope Socks
・Sock Lane
・Socktastic
```

LLMをLangChain内で使用する方法の詳細については、[LLMの入門ガイド](https://python.langchain.com/en/latest/modules/models/llms/getting_started.html)を参照してください。

## プロンプトテンプレート：LLMsのプロンプトを管理する (Prompt Templates: Manage prompts for LLMs)

LLMを呼び出すことは素晴らしい最初のステップですが、それは始まりに過ぎません。通常、LLMをアプリケーションで使用する場合、ユーザーの入力を直接LLMに送信することはありません。代わりに、おそらくユーザーの入力を受け取り、プロンプトを構築してそれをLLMに送信します。

例えば、前の例では、渡したテキストはハードコーディングされ、カラフルなソックスを製造する会社の名前を求めるものでした。この架空のサービスでは、ユーザーが会社の業務内容を説明する入力のみを受け取り、その情報でプロンプトを作成する必要があります。

LangChainを使用すると、これは簡単に実現できます！

まず、プロンプトのテンプレートを定義しましょう。

```python:python
from langchain.prompts import PromptTemplate

promptJ = PromptTemplate(
    input_variables=["product"],
    template="{product}を製造する会社には、どのような名前が良いでしょうか？",
)
```

さて、これがどのように機能するか見てみましょう！`.format`メソッドを呼び出して、それをフォーマットすることができます。

```python:python
print(promptJ.format(product="カラフルなソックス"))
```
```
カラフルなソックスを製造する会社には、どのような名前が良いでしょうか？
```

詳細については、[プロンプトの入門ガイド](https://python.langchain.com/en/latest/modules/prompts/chat_prompt_template.html)をチェックしてください。

## チェーン：LLMsとプロンプトを組み合わせてマルチステップのワークフローを作成する (Chains: Combine LLMs and prompts in multi-step workflows)

これまでは、PromptTemplateやLLMのプリミティブそれ自体を使用して作業してきました。しかし、実際のアプリケーションは、単一のプリミティブではなく、それらの組み合わせです。

LangChainでは、チェーンはリンクから構成されます。リンクは、LLMなどのプリミティブまたは他のチェーンである場合があります。

最も基本的なタイプのチェーンはLLMChainであり、PromptTemplateとLLMで構成されています。

前の例を拡張して、ユーザーの入力を受け取り、PromptTemplateでフォーマットし、そのフォーマットされた応答をLLMに渡すLLMChainを構築することができます。

```python:python
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI(temperature=0.9)
promptJ = PromptTemplate(
    input_variables=["product"],
    template="{product}を製造する会社には、どのような名前が良いでしょうか？",
)
```

ここで、ユーザーの入力を受け取り、それを使ってプロンプトをフォーマットし、その後LLMに送信する非常にシンプルなチェーンを作成できます。

```python:python
from langchain.chains import LLMChain
chainJ = LLMChain(llm=llm, prompt=promptJ)
```

それでは、productを指定してそのチェーンを実行できます！

```python:python
chainJ.run("カラフルなソックス")
```

```
'\n\n1. Rainbow Socks \n2. Socktastic \n3. Crazy Socks \n4. Cheery Feet \n5. Sock Zoo \n6. Fun Socks \n7. Sock Heaven \n8. Colorful Comfort \n9. Colorful Creations \n10.Socktopia'
```

はい、これが最初のチェーン、LLMチェーンです。これはよりシンプルなタイプのチェーンの1つですが、その動作原理を理解することは、より複雑なチェーンでの作業に役立ちます。

詳細については、[チェーンの入門ガイド](https://python.langchain.com/en/latest/modules/chains/getting_started.html)をチェックしてください。

## エージェント：ユーザーの入力に基づいてダイナミックにチェーンを呼び出す (Agents: Dynamically Call Chains Based on User Input)

これまで見てきたチェーンは、あらかじめ決められた順序で実行されます。

ただし、エージェントは異なります。エージェントは、どのアクションをどの順序で実行するかを決定するためにLLMを使用します。アクションは、ツールを使用してその出力を観察するか、ユーザーに戻ることのいずれかです。

正しく使用すると、エージェントは非常に強力なツールになります。このチュートリアルでは、最もシンプルで最上位のAPIを介してエージェントを簡単に使用する方法を説明します。

エージェントをロードするためには、以下の概念を理解する必要があります。

* ツール（Tool）：特定の役割を果たす関数です。Google検索、データベースの検索、Python REPL、他のチェーンなどが含まれます。ツールのインターフェースは現在、文字列を入力とし、文字列を出力とする関数であることが期待されています。

* LLM：エージェントのバックエンドとなる言語モデルです。

* エージェント（Agent）：使用するエージェントです。これは、サポートされているエージェントクラスを参照する文字列である必要があります。このノートブックでは、最もシンプルで最上位のAPIに焦点を当てているため、標準のサポートされているエージェントの使用方法のみがカバーされています。カスタムエージェントを実装したい場合は、カスタムエージェントのドキュメントを参照してください（近日公開予定）。

**エージェント（Agents）**：サポートされているエージェントとその仕様のリストについては、こちらを参照してください。

**ツール（Tools）**：事前定義されたツールとその仕様のリストについては、こちらを参照してください。

この例では、SerpAPIのPythonパッケージもインストールする必要があります。


> **注釈：**  
[SerpApi](https://serpapi.com/)とは、Google、Bing、Baidu、Yandex、Yahoo、Home depot、Ebayなどからの検索結果をスクレイピング及び解析することができるAPIです1. SerpApiはリアルタイムAPIであり、Google検索結果にアクセスするためのものです。
Freeプランもあります。（100 searches / month、No Legal US Shield）

```python
%pip install google-search-results
```

```python:python
import os
os.environ["SERPAPI_API_KEY"] = "..."
```

それでは、始めましょう！

> **注釈：**  
Exceptionが発生したため、[こちらのイシュー](https://github.com/hwchase17/langchain/issues/3071)を参考に、このあとのスクリプトは`llm = OpenAI(temperature=0)`を`llm = OpenAI(temperature=0.9)`に変更しています。

```python:python
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.9)

# 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("昨日のサンフランシスコの最高気温は華氏でいくつでしたか？その数値を0.023乗した結果は何ですか？")
```

```
> Entering new AgentExecutor chain...
 I need to find out what the highest temperature was in San Francisco yesterday, and use a calculator to compute the result of raising the temperature to the power of 0.023.
Action: Search
Action Input: "San Francisco Highest Temperature Yesterday"
Observation: High: 62.96ºf @2:56 PM Low: 53.6ºf @5:50 AM Approx.
Thought: I need to use a calculator to figure out what is 0.023 to the power of 62.96
Action: Calculator
Action Input: 0.023^62.96
Observation: Answer: 7.151292728589393e-104
Thought: I now know the final answer
Final Answer: The result of 0.023 raised to the power of the highest temperature yesterday in San Francisco is 7.151292728589393e-104.

> Finished chain.
```
```
'The result of 0.023 raised to the power of the highest temperature yesterday in San Francisco is 7.151292728589393e-104.'
```

## メモリ：チェーンとエージェントに状態を追加する (Memory: Add State to Chains and Agents)

これまでのチェーンやエージェントはすべて状態を持たないものでした。しかし、しばしばチェーンやエージェントには「メモリ」の概念が必要な場合があります。これによって、前回のやり取りに関する情報を記憶し、それを活用してより良い会話を行うことができます。これは「短期記憶」の一種です。より複雑な例としては、チェーンやエージェントが時間の経過とともに重要な情報を記憶することを想像することができます。これは「長期記憶」の一種です。後者について具体的なアイデアを得るためには、[この素晴らしい論文](https://memprompt.com/)を参照してください。

LangChainには、このような目的のために特別に作成された複数のチェーンが用意されています。このノートブックでは、その中の1つのチェーン（`ConversationChain`）を2種類の異なるメモリと共に使用する方法を説明します。

デフォルトでは、`ConversationChain`にはシンプルなメモリがあり、すべての前回の入力/出力を記憶し、渡されたコンテキストに追加します。promptを表示するために`verbose=True`を設定して、このチェーンの使用方法を見てみましょう。

```python:python
from langchain import OpenAI, ConversationChain

llm = OpenAI(temperature=0.9)
conversationJ = ConversationChain(llm=llm, verbose=True)

outputJ = conversationJ.predict(input="やあ！")
print(outputJ)
```

```
> 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.
 やあ！お元気ですか？今日の天気はどうですか？
```

```python:python
outputJ = conversationJ.predict(input="今日は晴れです。AIとの会話は面白いですね。")
print(outputJ)
```
```
> 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:  やあ！お元気ですか？今日の天気はどうですか？
Human: 今日は晴れです。AIとの会話は面白いですね。
AI:

> Finished chain.
 そうですね！会話は楽しいものですね。私はこれまでお話ししたことを覚えていますか？
```

## 言語モデルアプリケーションの構築：チャットモデル (Building a Language Model Application: Chat Models)

同様に、LLM（Language Model）の代わりにチャットモデルを使用することもできます。チャットモデルは、言語モデルのバリエーションです。チャットモデルは内部で言語モデルを使用しますが、公開されるインターフェースは少し異なります。単なる「テキストを入力し、テキストを出力する」APIではなく、入出力として「チャットメッセージ」を扱うインターフェースを公開します。

チャットモデルのAPIは比較的新しいものですので、まだ正確な抽象化方法を見つけている最中です。


## チャットモデルからのメッセージ補完の取得 (Get Message Completions from a Chat Model)

チャットモデルに1つまたは複数のメッセージを渡すことで、チャットの補完を取得することができます。応答はメッセージとなります。LangChainでは現在、`AIMessage`、`HumanMessage`、`SystemMessage`、`ChatMessage`の4種類のメッセージがサポートされています。`ChatMessage`は任意の役割パラメータを受け取ります。ほとんどの場合、`HumanMessage`、`AIMessage`、`SystemMessage`の扱いになるでしょう。

```python:python
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

chat = ChatOpenAI(temperature=0.9)
```

単一のメッセージを渡すことで、補完を取得することができます。

```python:python
chat([HumanMessage(content="この文章を日本語からフランス語に翻訳してください。私はプログラミングが大好きです。")])
```

```
AIMessage(content="J'aime beaucoup la programmation.", additional_kwargs={}, example=False)
```

OpenAIのgpt-3.5-turboやgpt-4モデルでは、複数のメッセージも渡すことができます。

```python:python
messagesJ = [
    SystemMessage(content="あなたは日本語からフランス語に翻訳する役立つアシスタントです。"),
    HumanMessage(content="私はプログラミングが大好きです。")
]
chat(messagesJ)
```
```
AIMessage(content="J'adore la programmation.", additional_kwargs={}, example=False)
```

`generate`を使用して、複数のメッセージセットに対して補完を生成することもできます。これにより、追加の`message`パラメータを持つ`LLMResult`が返されます。

```python:python
batch_messages = [
    [
        SystemMessage(content="あなたは日本語からフランス語に翻訳する役立つアシスタントです。"),
        HumanMessage(content="私はプログラミングが大好きです。")
    ],
    [
        SystemMessage(content="あなたは日本語からフランス語に翻訳するのに役立つアシスタントです。"),
        HumanMessage(content="私は人工知能が大好きです。")
    ],
]
resultJ = chat.generate(batch_messages)
resultJ
```
```
LLMResult(generations=[[ChatGeneration(text='Je suis passionné par la programmation.', generation_info=None, message=AIMessage(content='Je suis passionné par la programmation.', additional_kwargs={}, example=False))], [ChatGeneration(text="J'adore l'intelligence artificielle.", generation_info=None, message=AIMessage(content="J'adore l'intelligence artificielle.", additional_kwargs={}, example=False))]], llm_output={'token_usage': {'prompt_tokens': 118, 'completion_tokens': 21, 'total_tokens': 139}, 'model_name': 'gpt-3.5-turbo'})
```

このLLMResultからトークンの使用状況などを復元することができます。

```python:python
resultJ.llm_output['token_usage']
```

```
{'prompt_tokens': 118, 'completion_tokens': 20, 'total_tokens': 138}
```

## チャットのプロンプトテンプレート (Chat Prompt Templates)

LLMと同様に、`MessagePromptTemplate`を使用してテンプレートを利用することができます。`MessagePromptTemplate`から`ChatPromptTemplate`を構築することもできます。`ChatPromptTemplate`の`format_prompt`を使用することができます。これにより、`PromptValue`が返されます。PromptValueは、llmまたはchatモデルへの入力としてフォーマットされた値を使用するかどうかに応じて、文字列または`Message`オブジェクトに変換することができます。

利便性のために、template上で公開された`from_template`メソッドがあります。このテンプレートを使用する場合、以下のようになります:

```python:python
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

chat = ChatOpenAI(temperature=0.9)

template = "あなたは{input_language}から{output_language}に翻訳するのに役立つアシスタントです。"
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

# get a chat completion from the formatted messages
chat(chat_prompt.format_prompt(input_language="Japanese", output_language="English", text="私はプログラミングが大好きです。").to_messages())
```

```
AIMessage(content='I love programming.', additional_kwargs={}, example=False)
```

## チャットモデルを使用したチェーン (Chains with Chat Models)

上記のセクションで説明したLLMChainは、チャットモデルでも使用することができます。

```python:python
from langchain.chat_models import ChatOpenAI
from langchain import LLMChain
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

chat = ChatOpenAI(temperature=0)

template = "あなたは{input_language}から{output_language}に翻訳するのに役立つアシスタントです。"
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

chain = LLMChain(llm=chat, prompt=chat_prompt)
chain.run(input_language="日本語", output_language="英語", text="私はプログラミングが大好きです。")
```
```
'I love programming.'
```

## チャットモデルを使用したエージェント (Agents with Chat Models)

エージェントはチャットモデルでも使用することができます。エージェントを初期化するには、エージェントタイプとして`AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTIO`を使用します。

```python:python
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI
from langchain.llms import OpenAI

# First, let's load the language model we're going to use to control the agent.
chat = ChatOpenAI(temperature=0.9)

# Next, let's load some tools to use. Note that the `llm-math` tool uses an LLM, so we need to pass that in.
llm = OpenAI(temperature=0.9)
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, chat, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)

# Now let's test it out!
agent.run("Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?")
```

```
> Entering new AgentExecutor chain...
Thought: I should use a search engine to find who is Olivia Wilde's boyfriend and then use a calculator to raise his current age to the 0.23 power.
Action:
```
{
  "action": "Search",
  "action_input": "Olivia Wilde boyfriend"
}
```

Observation: Olivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.
Thought:Now that I know Olivia Wilde's boyfriend is Harry Styles, I should use a search engine to find out his current age.
Action:
```
{
  "action": "Search",
  "action_input": "Harry Styles current age"
}
```

Observation: 29 years
Thought:Now I should use a calculator to raise Harry Styles's current age to the 0.23 power.
Action:
...
Thought:I now know the answer to the question.
Final Answer: Approximately 2.17.

> Finished chain.
```

> **注釈：**  
日本語にするとエラーになったので英語のままとしています。

## メモリ：チェーンとエージェントに状態を追加する (Memory: Add State to Chains and Agents)

チェーンやチャットモデルで初期化されたエージェントとともにメモリを使用することができます。LLMの場合との主な違いは、すべての以前のメッセージを1つの文字列にまとめようとするのではなく、個別のメモリオブジェクトとして保持することができる点です。

```python:python
from langchain.prompts import (
    ChatPromptTemplate, 
    MessagesPlaceholder, 
    SystemMessagePromptTemplate, 
    HumanMessagePromptTemplate
)
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

promptJ = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("以下は、人間とAIの間の友好的な会話です。AIはおしゃべりで、コンテキストから多くの具体的な詳細を提供します。AIが質問の答えを知らない場合は、正直に知らないと伝えます。"),
    MessagesPlaceholder(variable_name="history"),
    HumanMessagePromptTemplate.from_template("{input}")
])

llmJ = ChatOpenAI(temperature=0.9)
memoryJ = ConversationBufferMemory(return_messages=True)
conversationJ = ConversationChain(memory=memoryJ, prompt=prompt, llm=llmJ)

conversationJ.predict(input="こんにちは！")
```
```
'こんにちは！どういったお話しをしましょうか？'
```

```python:python
conversationJ.predict(input="今日は晴れです。")
```
```
'そうですね、晴れの日が続いていますね。現在の場所によっては、平均気温はどのくらいでしょうか？'
```

```python:python
conversationJ.predict(input="今日は28度もあります。平均気温よりも高いです。")
```
```
'なるほど、それは暑いですね！28度は、多くの場所で夏の最高気温と同じくらいです。暑さ対策はしっかりとしているといいですね。'
```