Bedrock(Claude3)+Lambda+Hono+LangChainでマルチモーダルなアプリを試す
はじめに
ChatGPT4越えとも噂されるほど性能の高い、Claude3が話題です。
Claude3は3種類発表されており、Haiku、Sonnet、Opusのうち中間のスペックであるSonnetはAWSでも既に使うことができます。
せっかくなのでAWSでサンプルアプリを作ります。デモとしても動かしたいので、Honoを使って簡単なインターフェースを用意してLambdaでホスティングしましょう。
Setup
Hono公式の手順を参考にCDK、Hono環境を用意します。
mkdir my-app
cd my-app
npx cdk init app -l typescript
npm i hono
mkdir lambda
touch lambda/index.tsx
JSXを扱うので、以下のConfigも追加しましょう。
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
Code
それでは早速、HonoとLangChainで書いてみましょう。/
で返したFormから/analyze
へ画像をPOSTし、結果をhtmlとして返します。※もっとスマートにやるべきですが……
import { Hono } from "hono";
import { handle } from "hono/aws-lambda";
import { logger } from "hono/logger";
import { HumanMessage } from "@langchain/core/messages";
import { BedrockChat } from "@langchain/community/chat_models/bedrock";
const app = new Hono();
app.use(logger());
const Layout = (props: any) => (
<html>
<head>
<meta charset="UTF-8" />
<title>{props.title}</title>
</head>
<body>{props.children}</body>
</html>
);
app.get("/", (c) => {
return c.render(
<Layout title="Image Analysis">
<h1>Image Analysis</h1>
<form action="/analyze" method="post" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*" required />
<button type="submit">Analyze</button>
</form>
</Layout>,
);
});
app.post("/analyze", async (c) => {
const body = await c.req.parseBody();
const image = body["image"];
if (!image || Array.isArray(image) || typeof image === "string") {
return c.text("No image uploaded or invalid image", 400);
}
const imageData = await image.arrayBuffer();
const imageUrl = `data:${image.type};base64,${Buffer.from(imageData).toString("base64")}`;
const model = new BedrockChat({
model: "anthropic.claude-3-sonnet-20240229-v1:0",
region: "us-east-1",
});
const message = new HumanMessage({
content: [
{ type: "text", text: "What's in this image?" },
{ type: "image_url", image_url: { url: imageUrl } },
],
});
const res = await model.invoke([message]);
const analysis = res.content;
return c.render(
<Layout title="Image Analysis Result">
<h1>Image Analysis Result</h1>
<img src={imageUrl} alt="Uploaded Image" />
<p>{analysis}</p>
<a href="/">Analyze another image</a>
</Layout>,
);
});
export const handler = handle(app);
Tips
-
HumanMessage
のContentにtext
とimage_url
を渡すことで、マルチモーダルを実現しています。 - 注意点として、JS版のLangChainではClaude3を扱えるのは「Bedrock Chat」だけです。
Deploy
それではCDKでDeployしていきましょう。
HonoがAWS Lambdaのリクエストに対応しているため、LambdaのデプロイとRole設定、エンドポイント設定を入れるだけで動かせます。
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
export class MyApp2Stack extends cdk.Stack {
public readonly edgeFn: lambda.Function;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const fn = new NodejsFunction(this, "origin", {
entry: "lambda/index.tsx",
handler: "handler",
runtime: lambda.Runtime.NODEJS_20_X,
timeout: cdk.Duration.seconds(120),
});
fn.role?.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonBedrockFullAccess"),
);
const fnUrl = fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
});
new cdk.CfnOutput(this, 'ServiceUrl', { value: fnUrl.url });
}
}
Tips
-
addFunctionUrl.authType
は全公開になっているので、デモが終わったら以下の設定でアクセス制御しましょう。lambda.FunctionUrlAuthType.AWS_IAM
-
実運用ではエンドポイントをCloudFront+WAFで守るのが一般的なので、この辺を参考に設定するとよさそうです。
動作確認
URLにアクセスすると、素朴な画面が出てくるので画像をアップロードします。
例として、https://zenn.dev/mediakit にあるZenn.devのロゴをアップロードしてみます。
ボタンを押して数秒待つと、結果から画像を分析できていることがわかります。
ちなみにDeepLで翻訳した結果がこれ。ロゴを分析できていることがわかります。
この画像は、「Zenn」という企業やブランドのロゴまたはワードマークを表示しています。ワードマークは「Zenn」というスタイルのテキストで構成され、「Z」の文字は太い黒のフォントで、残りの「enn」の文字は明るい黒のフォントで表現されています。ワードマークの前には、ロゴデザインの一部と思われる青い斜線またはスラッシュのグラフィックエレメントがあります。
まとめ
ということで、AWS Onlyでもサーバレスでマルチモーダルなアプリが作れる時代が来たようです。RAGやAgentにも早く対応してくれたら嬉しい!
Discussion