🖼️

Bedrock(Claude3)+Lambda+Hono+LangChainでマルチモーダルなアプリを試す

2024/03/07に公開

はじめに

ChatGPT4越えとも噂されるほど性能の高い、Claude3が話題です。

https://www.anthropic.com/news/claude-3-family

Claude3は3種類発表されており、Haiku、Sonnet、Opusのうち中間のスペックであるSonnetはAWSでも既に使うことができます。

https://aws.amazon.com/jp/blogs/news/unlocking-innovation-aws-and-anthropic-push-the-boundaries-of-generative-ai-together/

せっかくなのでAWSでサンプルアプリを作ります。デモとしても動かしたいので、Honoを使って簡単なインターフェースを用意してLambdaでホスティングしましょう。

Setup

Hono公式の手順を参考にCDK、Hono環境を用意します。

https://hono.dev/getting-started/aws-lambda

mkdir my-app
cd my-app
npx cdk init app -l typescript
npm i hono
mkdir lambda
touch lambda/index.tsx

JSXを扱うので、以下のConfigも追加しましょう。

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx"
  }
}

Code

それでは早速、HonoとLangChainで書いてみましょう。/で返したFormから/analyzeへ画像をPOSTし、結果をhtmlとして返します。※もっとスマートにやるべきですが……

lambda/index.tsx
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にtextimage_urlを渡すことで、マルチモーダルを実現しています。
  • 注意点として、JS版のLangChainではClaude3を扱えるのは「Bedrock Chat」だけです。

https://github.com/langchain-ai/langchainjs/issues/4650

Deploy

それではCDKでDeployしていきましょう。
HonoがAWS Lambdaのリクエストに対応しているため、LambdaのデプロイとRole設定、エンドポイント設定を入れるだけで動かせます。

lib/index.tsx
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で守るのが一般的なので、この辺を参考に設定するとよさそうです。

https://iret.media/88185

動作確認

URLにアクセスすると、素朴な画面が出てくるので画像をアップロードします。
例として、https://zenn.dev/mediakit にあるZenn.devのロゴをアップロードしてみます。

ボタンを押して数秒待つと、結果から画像を分析できていることがわかります。

ちなみにDeepLで翻訳した結果がこれ。ロゴを分析できていることがわかります。

この画像は、「Zenn」という企業やブランドのロゴまたはワードマークを表示しています。ワードマークは「Zenn」というスタイルのテキストで構成され、「Z」の文字は太い黒のフォントで、残りの「enn」の文字は明るい黒のフォントで表現されています。ワードマークの前には、ロゴデザインの一部と思われる青い斜線またはスラッシュのグラフィックエレメントがあります。

まとめ

ということで、AWS Onlyでもサーバレスでマルチモーダルなアプリが作れる時代が来たようです。RAGやAgentにも早く対応してくれたら嬉しい!

Discussion