langchain(v0.13)でAzure OpenAIを使うまでの導入

## はじめに

langchainが安定版であるバージョン0.1Xをリリースしたようなので、以前書いたコードをリファクタしようとしました。
すると非推奨の警告メッセージがたくさん出てきたり、どのドキュメントが最新なのか非常に分かりづらかったので、備忘録として書き残します。

## 事前準備

### ライブラリ

`pip install`してください

```
pip install langchain, langchain-openai
```

```txt: requirements.txt
langchain==0.1.3
langchain-openai==0.0.3
langchain-community==0.0.15
langchain-core==0.1.15
langsmith==0.0.83
```

### 環境変数

[Azure側で準備](https://www.desknets.com/neo/download/doc/chatgpt/chatgpt_azure.html)してから、以下の環境変数を入れてください。

```.env
AZURE_OPENAI_API_KEY = "APIキー"
AZURE_OPENAI_ENDPOINT = "エンドポイント"
```

## 書く

### インポート

`AzureChatOpenAI`は`langchain_openai`からインポートしてください。
`langchain.chat_models`は非推奨になっています。

```python
from langchain.schema import HumanMessage
from langchain_openai import AzureChatOpenAI
```

### モデル用意

`AzureChatOpenAI`でLLMを用意します。
2024年1月現在、APIのバージョン名は`2023-12-01-preview`が最新みたいです。

APIのバージョン名は[ここ](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/api-version-deprecation)に載っているようです。

```python
model = AzureChatOpenAI(
    openai_api_version="APIのバージョン名",
    azure_deployment="デプロイ名",
)
```

### 呼び出し

`invoke`で呼び出します。
`run`は非推奨になっています。そのまま`invoke`に書き換えればOKです。

```python
message = "こんにちは"

res = model.invoke(message)

print(res)
```

### 結果

ちゃんと帰ってきました。

```
content='こんにちは！お元気ですか？何かお手伝いできることはありますか？'
```

## Chainを使う

langchainと言えば！の機能、Chainも使ってみます。
基本的に以前と書き方は変わりません。LCEL使ってないのは許してください…
```python
from os.path import join, dirname
from dotenv import load_dotenv
import langchain
from langchain_openai import AzureChatOpenAI
from langchain.chains import LLMChain
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain.chains import SequentialChain
from pydantic import BaseModel, Field
from typing import List

langchain.verbose = True

# フォーマット
## 食材と数
class Foods(BaseModel):
    name: str = Field(description="食材の名前")
    count: str = Field(description="量")

## 買い物リスト
class BuyList(BaseModel):
    buyList: List[Foods]

# ローカル用環境変数呼び出し
dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)

# LLM用意
model = AzureChatOpenAI(
    openai_api_version="2023-12-01-preview",
    azure_deployment="デプロイ名",
)

template1 = """
{recipe}のレシピを簡潔に教えてください。
食材とその個数は必ず教えてください。

"""

template2 = """
以下のレシピに必要な食材とその個数をまとめて教えてください。
### レシピ
{res1}

{format_instructions}
"""

# 実行関数
def generate_questions(recipe: str):
    # 命令1: 1回目
    ## プロンプト作成
    prompt1 = PromptTemplate(
        template=template1,
        input_variables=["recipe"],
    )
    ## chain作成
    chain1 = LLMChain(llm=model, prompt=prompt1, output_key="res1")

    # 命令1: 2回目
    ## Parser作成
    parser = PydanticOutputParser(pydantic_object=BuyList)
    ## プロンプト作成
    prompt2 = PromptTemplate(
        template=template2,
        input_variables=["res1"],
        partial_variables={"format_instructions": parser.get_format_instructions()},
    )
    ## chain作成
    chain2 = LLMChain(llm=model, prompt=prompt2)

    # chains作成
    chains = SequentialChain(
        chains=[chain1, chain2],
        input_variables=["recipe"],
    )

    # chains実行
    answer = chains.invoke({"recipe":recipe})
    
    # JSON整形
    s = "```json\n"
    answer = answer['text'].strip(s)

    # 表示
    print(answer)
    print(type(answer))

    return answer

if __name__ == "__main__": 
    recipe = "カレー"
    output = generate_questions(recipe)
```

### 結果
```
> Entering new SequentialChain chain...

> Entering new LLMChain chain...
Prompt after formatting:

カレーのレシピを簡潔に教えてください。
食材とその個数は必ず教えてください。

> Finished chain.

> Entering new LLMChain chain...
Prompt after formatting:

以下のレシピに必要な食材とその個数をまとめて教えてください。
### レシピ
材料:
- 玉ねぎ: 1個
- にんじん: 1本
- じゃがいも: 2個
- 鶏肉or牛肉: 300g
- カレールー: 1箱
- 水: 500ml
- サラダ油: 大さじ1
- 塩: 小さじ1/2

作り方:
1. 食材を準備する。玉ねぎ、にんじん、じゃがいもをそれぞれみじん切りにする。肉は食べやすい大き
さに切る。
2. 鍋にサラダ油を熱し、玉ねぎを炒める。
3. 玉ねぎが透明になったら肉を加えて炒める。
4. にんじん、じゃがいもを加えて炒め、水を加えて煮る。
5. カレールーを加え、溶かしてから塩で味を調える。
6. 全体が煮えたら完成です。ご飯と一緒にお召し上がりください。

# Parser用プロンプトのため割愛
The output should be formatted as a JSON instance that conforms to the JSON schema below.     

> Finished chain.

> Finished chain.
{
  "buyList": [
    {"name": "玉ねぎ", "count": "1個"},
    {"name": "にんじん", "count": "1本"},
    {"name": "じゃがいも", "count": "2個"},
    {"name": "鶏肉or牛肉", "count": "300g"},
    {"name": "カレールー", "count": "1箱"},
    {"name": "水", "count": "500ml"},
    {"name": "サラダ油", "count": "大さじ1"},
    {"name": "塩", "count": "小さじ1/2"}
  ]
}
<class 'str'>
```

しっかりChain出来ていることが確認できます。
Parserも効いており、指定したJSON形式に変換されています。

## おわりに

langchainは便利ですが、更新が非常に速いので追いつくのが大変でした。
公式ドキュメントすら最新化が追い付いてないので、恐ろしいですね…

また、`AzureChatOpenAI`のパラメータである`openai_api_version`の参照先が本当に分からなくて調べるのに時間がかかりました。
Azureの画面上か、モデル詳細に記載しておいてほしいです、切実に

## 参照ドキュメント

[langchain 0.13](https://api.python.langchain.com/en/stable/langchain_api_reference.html)
[AzureChatOpenAI](https://python.langchain.com/docs/integrations/chat/azure_chat_openai)
[langcahin_openai](https://api.python.langchain.com/en/latest/openai_api_reference.html#)