LangChain Expression Language（LCEL）はじめの一歩 on Azure OpenAI

## はじめに
みなさんは、**LangChain Expression Language(LCEL)** をご存じでしょうか？ この記事では、つい先日、LCELを初めて知った私が、LangChain blogに書かれていたLCELのサンプルコード（Python）を**Azure OpenAI Service**を使い、試したことを書いています。

LCELでは、チェーンを **"| "** で表し、次のように表現します。
``` Python
chain = prompt | llm | output_parser
``` 
INPUT（prompt）からOUTPUT（output_parser）までの処理が、"|"をパイプ演算子のように使い書かれています。シンプルで直観的にも理解しやすい表現です。（だから、Expression Languageなんでしょうね）

LangChainは、LLM（大規模言語モデル）を利用してアプリケーションを開発するための有名なフレームワークですが、今年2024年1月に最初の安定版であるとして、LangChain v0.1.0がリリースされました。

https://blog.langchain.dev/langchain-v0-1-0/

上記LangChainブログには、古い langchain パッケージは、 
**1.langchain-core**
**2.langchain-community**
**3.langchain**
と言う3つの個別パッケージに分割されたことが書いてあります。また、**LangChain Expression Language（LCEL）** により、より簡単にチェーンを構築できるとしています。


## LCELとは
LangChain Expression Language（LCEL）は、LLMアプリケーション開発を簡素化するためのLangChainフレームワークの一部です。簡単なチェーンから少し複雑なアプリケーションのプロトタイピングまで、開発者はよりシンプルにチェーンを組むことができます。

## LCELの主な特徴
1. チェーンのシンプルな表現
2. ストリーミングのサポート
3. 非同期のサポート
4. バッチのサポート
などがあります。

以下は、Azure OpenAI Serviceを使い、試したときのサンプルコードです。

## 環境
Windows10
Python v3.11.4

APIキー等の環境変数は、試したコードと同じフォルダに".env"ファイルを作り、その中に記述しました。

``` Python
AZURE_OPENAI_TYPE = "azure"
AZURE_OPENAI_KEY = "YOUR AZURE OPENAI KEY" 
AZURE_OPENAI_VERSION = "2023-05-15"
azure_endpoint = "YOUR AZURE ENDPOINT URL"
AZURE_OPENAI_DEPLOYMENT_NAME = "YOUR AZURE DEPLOYMENT NAME"
```


## 0.基本
冒頭にも書いた基本的なチェーンの表現方法です。
``` Python
# main_basic.py

import os
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate


# 環境変数の読み込み
load_dotenv()

# API設定の変数
openai_api_key = os.getenv("AZURE_OPENAI_KEY")
azure_endpoint = os.getenv("AZURE_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_VERSION")
azure_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")

# AzureChatOpenAI
llm = AzureChatOpenAI(
    api_version=api_version,
    azure_endpoint=azure_endpoint,
    openai_api_key=openai_api_key,
    azure_deployment=azure_deployment,
    temperature=0.5,
)

prompt = ChatPromptTemplate.from_template(
    "私に、{topic}について100文字以内で説明して下さい。"
)
output_parser = StrOutputParser()

chain = prompt | llm | output_parser

result = chain.invoke({"topic": "データ連携"})

print(result)

```

## 2.ストリーミング
ストリーミングの例です。
``` Python
# main_stream.py

import os
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough


# 環境変数の読み込み
load_dotenv()

# API設定の変数
openai_api_key = os.getenv("AZURE_OPENAI_KEY")
azure_endpoint = os.getenv("AZURE_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_VERSION")
azure_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")

# AzureChatOpenAI
llm = AzureChatOpenAI(
    api_version=api_version,
    azure_endpoint=azure_endpoint,
    openai_api_key=openai_api_key,
    azure_deployment=azure_deployment,
    temperature=0.5,
)

prompt = ChatPromptTemplate.from_template(
    "私に、{topic}について、500文字以内で説明して下さい。"
)
output_parser = StrOutputParser()

chain = {"topic": RunnablePassthrough()} | prompt | llm | output_parser

for chunk in chain.stream({"topic": "データ連携"}):
    print(chunk, end="", flush=True)
```

## 3.非同期
非同期の例です。
``` Python
# main_async.py

import os
import asyncio
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
  

# 環境変数の読み込み
load_dotenv()

# API設定の変数
openai_api_key = os.getenv("AZURE_OPENAI_KEY")
azure_endpoint = os.getenv("AZURE_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_VERSION")
azure_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")

# AzureChatOpenAI
llm = AzureChatOpenAI(
    api_version=api_version,
    azure_endpoint=azure_endpoint,
    openai_api_key=openai_api_key,
    azure_deployment=azure_deployment,
    temperature=0.5,
)

prompt = ChatPromptTemplate.from_template(
    "私に、{topic}について100文字以内で説明して下さい。"
)
output_parser = StrOutputParser()

chain = prompt | llm | output_parser

# 非同期関数を定義
async def get_response(chain):
    result = await chain.ainvoke({"topic": "データ連携"})
    print(result)

# 非同期を実行
asyncio.run(get_response(chain))
``` 
## 4.バッチ
バッチの例です。
``` Python
# main_batch.py

import os
from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough


# 環境変数の読み込み
load_dotenv()

# API設定の変数
openai_api_key = os.getenv("AZURE_OPENAI_KEY")
azure_endpoint = os.getenv("AZURE_ENDPOINT")
api_version = os.getenv("AZURE_OPENAI_VERSION")
azure_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")

# AzureChatOpenAI
llm = AzureChatOpenAI(
    api_version=api_version,
    azure_endpoint=azure_endpoint,
    openai_api_key=openai_api_key,
    azure_deployment=azure_deployment,
    temperature=0.5,
)

prompt = ChatPromptTemplate.from_template(
    "私に、{topic}について、100文字以内で説明して下さい。"
)
         
output_parser = StrOutputParser()

chain = prompt | llm | output_parser

result = chain.batch( [{"topic":"データ連携"}, {"topic":"API連携"}, {"topic":"ファイル転送"}])

print(result)
``` 


## requirements.txt
requirements.txtの内容は次の通りですが、pydanticのバージョンは、v1.10.9でないと動きませんでした。もし試される場合は、ご留意いただければと思います。

``` Python
aiohttp==3.9.3
aiosignal==1.3.1
annotated-types==0.6.0
anyio==4.2.0
attrs==23.2.0
black==24.1.1
certifi==2023.11.17
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
dataclasses-json==0.6.4
distro==1.9.0
docarray==0.32.1
frozenlist==1.4.1
greenlet==3.0.3
h11==0.14.0
hnswlib==0.8.0
httpcore==1.0.2
httpx==0.26.0
idna==3.6
jsonpatch==1.33
jsonpointer==2.4
langchain==0.1.5
langchain-community==0.0.17
langchain-core==0.1.18
langchain-openai==0.0.5
langsmith==0.0.85
markdown-it-py==3.0.0
marshmallow==3.20.2
mdurl==0.1.2
multidict==6.0.5
mypy-extensions==1.0.0
numpy==1.26.3
openai==1.10.0
orjson==3.9.12
packaging==23.2
pathspec==0.12.1
platformdirs==4.2.0
protobuf==4.25.2
pydantic==1.10.9
pydantic_core==2.16.1
Pygments==2.17.2
python-dotenv==1.0.1
PyYAML==6.0.1
regex==2023.12.25
requests==2.31.0
rich==13.7.0
sniffio==1.3.0
SQLAlchemy==2.0.25
tenacity==8.2.3
tiktoken==0.5.2
tqdm==4.66.1
types-requests==2.31.0.20240125
typing-inspect==0.9.0
typing_extensions==4.9.0
urllib3==2.2.0
yarl==1.9.4
```

## さいごに
**LangChain Expression Language（LCEL）** は、LangChainフレームワークにおける強力な新機能となっています。今後、LangChainを使って開発を行う場合、一部は、このLCELによるアプローチが使われていくものと思います。私も少しずつ慣れていきたいと思います。

:::note warn
この記事に載せたサンプルコードは、基本的には次のlangChainブログを元にしています。ブログでは、基本的にOpenAI社のモデルを使うことを例として書かれていましたので、一部変更しています。本来のコードの使い方とは異なる部分もあるかもしれませんので、原ブログも参考にして頂ければと思います。
:::

https://python.langchain.com/docs/expression_language/why