過去にQiitaに投稿した内容のアーカイブです。
少し前にLangChain開発元から新しいツールとしてLangServeというものがリリースされています。
https://blog.langchain.dev/introducing-langserve/
LangServe is the easiest and best way to deploy any any LangChain chain/agent/runnable.
生成系AIを使ったAPIを簡単に作成し利用できる仕組みで、プロダクション環境でどんどんLangChainを使ってねというメッセージと捉えました。
イメージとしてはこんな仕組みです。
FastAPI上で動作し、API仕様が定められている感じです。
- Invoke API 単一の入力で処理を実行する
- Batch API 複数の入力で処理を実行する
- Stream API 単一の入力で処理を行い、結果をストリームで返却する
- Stream_log API 単一の入力で処理を行い、結果だけでなく途中の経過もストリームで返却する
ローカルでお試し実行
まずはローカル環境で動作させてみます。
LangServeのインストール
pydanticはv2ではなくv1を使うため、バージョン指定でインストールしています。 生成系AIにAmazon Bedrockを使用するため、Boto3もインストールしています。
pip install langserve[server] pydantic==1.10.13 boto3
サーバー側ロジックを記述
こちらを参考にしました。
add_routes
を使って、BedrockChat
モデルを使用する/bedrock
のパスを定義しています。server.py#!/usr/bin/env python
from fastapi import FastAPI
from langchain_community.chat_models import BedrockChat
from langserve import add_routes
app = FastAPI(
title="LangServe",
version="1.0",
description="LangChain Server",
)
add_routes(
app,
BedrockChat(model_id="anthropic.claude-instant-v1"),
path="/bedrock",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)なんと、これだけw
起動
python server.py
でLangServeを起動します。素敵なログが出力されます。INFO: Started server process [7582]
INFO: Waiting for application startup.
__ ___ .__ __. _______ _______. _______ .______ ____ ____ _______
| | / \ | \ | | / _____| / || ____|| _ \ \ \ / / | ____|
| | / ^ \ | \| | | | __ | (----`| |__ | |_) | \ \/ / | |__
| | / /_\ \ | . ` | | | |_ | \ \ | __| | / \ / | __|
| `----./ _____ \ | |\ | | |__| | .----) | | |____ | |\ \----. \ / | |____
|_______/__/ \__\ |__| \__| \______| |_______/ |_______|| _| `._____| \__/ |_______|
LANGSERVE: Playground for chain "/bedrock/" is live at:
LANGSERVE: │
LANGSERVE: └──> /bedrock/playground/
LANGSERVE:
LANGSERVE: See all available routes at /docs/
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)ブラウザで
http://127.0.0.1:8000/docs
にアクセスすると、Swagger UIでOpenAPIドキュメントが表示されます。先程紹介したものよりもいくつか追加でAPIが定義されました。
この画面上からAPIのテスト実行も可能です。
もちろんREST APIなので、表示されるcurlリクエストで呼び出すことも可能です。
curl -X 'POST' \
'http://127.0.0.1:8000/bedrock/invoke' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"input": "ポエムを作ってください。",
"config": {},
"kwargs": {}
}'{
"output": {
"content": " はい、以下は簡単なポエムです。\n\nひとゆびの距離 \n\n夜の静けさにさびしく\n窓の外は星空を照らす\n目の前はひとつの指\n地球の行方はわからない\n\nたったひとつの指の先\n無限な宇宙が広がる\n星々はとても遠いのに\nひとゆびのずれにかかる\n\n指を動かせば景色が変わる\n新しい場所が見えてくる\nでも今はここにいる\nひとゆび先で世界は広がる\n\n簡単なポエムですが、ひとゆびの距離に宇宙の広さを重ね合わせました。内容や表現が不十分な部分があると思いますが、ご要",
"additional_kwargs": {},
"type": "ai",
"example": false
},
"callback_events": [],
"metadata": {
"run_id": "117fae40-1ed6-4858-bca0-c44bc2707c71"
}
}
- プレイグラウンド
http://127.0.0.1:8000/bedrock/playground/
にアクセスすると、プレイグラウンド画面が表示されます。(すごい!)
ここでもAPIの動作確認ができます。
ドキュメントによるとプレイグラウンドの画面をカスタマイズすることもできるようです。
クライアントから呼び出す
APIにできたので、ブラウザから呼び出してみようと思います。
とても単純なHTMLを作成しました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
async function call() {
const response = await fetch("http://127.0.0.1:8000/bedrock/invoke", {
method: "POST",
body: JSON.stringify({
"input": "ポエムを作ってください。"
})
});
console.log((await response.json()).output.content);
}
call();
</script>
</body>
</html>
index.htmlのディレクトリで簡易HTTPサーバーを起動します。
python -m http.server 8080
サーバー側とクライアント側でポート番号が異なるため、CORSリクエストになります。 サーバー側でCORSを有効化してサーバーを再起動します。
#!/usr/bin/env python
from fastapi import FastAPI
+ from fastapi.middleware.cors import CORSMiddleware
from langchain_community.chat_models import BedrockChat
from langserve import add_routes
app = FastAPI(
title="LangServe",
version="1.0",
description="LangChain Server",
)
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["http://127.0.0.1:8080"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
add_routes(
app,
BedrockChat(model_id="anthropic.claude-instant-v1"),
path="/bedrock",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
ブラウザでhttp://127.0.0.1:8080/
にアクセスすると、consoleに出力されました。
はい、簡単なポエムを作ってみます。
白い雪は降り注ぎに
木々はゆっくりと眠りにつく
星が光り夜は更けて
静かに闇が訪れる
いかがでしょうか。内容はシンプルでテーマは自然の季節の移り変わりを詠んでいます。ポエムの作成は人工知能の限界がある部分なので、改善点があれば教えていただければ幸いです。自由な表現は難しい技術なのです。
Streming呼び出しもあるのですが、ちょっとうまくいきませんでした。。
ドキュメントによるとLangChain.jsのversion 0.0.166以降で、以下のような呼び出しが可能なようです。(未確認)
import {RemoteRunnable} from "langchain/runnables/remote";
const chain = new RemoteRunnable({
url: `http://localhost:8000/joke/`,
});
const result = await chain.invoke({
topic: "cats",
});
Lambda化
ローカルでの動作が確認できたので、Lambda化します。
AWS製のAWS Lambda Web Adapter
というFastAPIのLambda化を行う便利なライブラリーがあります。これを使います。
https://github.com/awslabs/aws-lambda-web-adapter
こんなイメージです。
examples/fastapi-response-streaming-zip
にサンプルが用意されていますので、これを参考に作成します。
tree fastapi-response-streaming-zip
fastapi-response-streaming-zip
├── README.md
├── __init__.py
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── requirements.txt
│ └── run.sh
├── samconfig.toml
└── template.yaml
1 directory, 8 files
AWS Lambda Web Adapterは、レイヤーで指定されています。
レイヤーを指定する だけ です。すごいですね。
FastAPIFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: app/
Handler: run.sh
Runtime: python3.12
MemorySize: 256
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
AWS_LWA_INVOKE_MODE: response_stream
PORT: 8000
+ Layers:
+ - !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:18
FunctionUrlConfig:
AuthType: NONE
InvokeMode: RESPONSE_STREAM
LangServeをレイヤーにして追加しましょう。
langserve[server]
pydantic==1.10.13
Pythonのバージョンを3.12にし、Bedrockの権限も付与します。
FastAPIFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: app/
Handler: run.sh
- Runtime: python3.11
+ Runtime: python3.12
MemorySize: 256
+ Policies:
+ - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
Environment:
Variables:
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
AWS_LWA_INVOKE_MODE: response_stream
PORT: 8000
Layers:
- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:18
+ - !Ref LangServeLayer
FunctionUrlConfig:
AuthType: NONE
InvokeMode: RESPONSE_STREAM
+ LangServeLayer:
+ Type: AWS::Serverless::LayerVersion
+ Properties:
+ ContentUri: langserve-layer/
+ CompatibleRuntimes:
+ - python3.12
+ Metadata:
+ BuildMethod: python3.12
+ BuildArchitecture: x86_64
LambdaのPython 3.12ランタイムはBoto3のバージョンが1.28.72
のため、Bedrockに対応しています。そのため、レイヤーなどでboto3を追加インストールする必要はありません。
main.py
に先程のserver.py
の内容を反映します。
#!/usr/bin/env python
from fastapi import FastAPI
from langchain_community.chat_models import BedrockChat
from langserve import add_routes
app = FastAPI(
title="LangServe",
version="1.0",
description="LangChain Server",
)
add_routes(
app,
BedrockChat(model_id="anthropic.claude-instant-v1"),
path="/bedrock",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
ビルドしてデプロイします。
sam build
sam deploy --guided
デプロイできたらFunction URLにアクセスします。
curl -X 'POST' \
'https://xxxxx.lambda-url.ap-northeast-1.on.aws/bedrock/invoke' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"input": "ポエムを作ってください。",
"config": {},
"kwargs": {}
}'
{
"output": {
"content": " はい、思いつきました。\n\n雨のポエム\n\n空は暗い雲に覆われて\n静かに降り注ぐ雨\n一筋の光も見えません\n\n道の上は水まみれ\n傘をさして歩く人\n靴がぬかるみに沈み込んで\n\n窓から外を見下ろす\n心地よいひまわりの時間\n本をめくりながら過ごす\n\n雨はじわじわと弱まって\n虹が現れるかもしれない\n希望を持とうではないか\n\n人生にも雨期があるというのに\nその先には必ず晴れが開いていく\n信じて待つ時が来る",
"additional_kwargs": {},
"type": "ai",
"example": false
},
"callback_events": [],
"metadata": {
"run_id": "8e17ccdf-bc21-4867-a20f-dd1b1f841103"
}
}
ローカルと同じように動作しました。
また、すごいことに{Function URLエンドポイント}/docs
でOpenAPIのドキュメントページも表示できます!!
すごいっす!AWS Lambda Web Adapter!!
LangServeで作成されるAPIを限定するにはこのような指定を、
add_routes(
app,
BedrockChat(model_id="anthropic.claude-instant-v1"),
path="/bedrock",
enabled_endpoints=["invoke", "batch"]
)
OpenAPIのドキュメントを無効化したい場合はこのような指定をすると良さそうです。
app = FastAPI(
title="LangServe",
version="1.0",
description="LangChain Server",
docs_url=None,
redoc_url=None,
openapi_url=None
)