6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS AmplifyとAWS×フロントエンド #AWSAmplifyJPAdvent Calendar 2024

Day 18

続ついにGA 🎉 AWS Amplify AI Kitをためしてみた 🚀 ~ Tools・ナレッジ・Nova Canvas 全部盛り編 ~

Last updated at Posted at 2024-12-18

AWS Amplify AI Kitをためしてみた🚀 続編

前回はAWS Amplify AI KitのAWSサンプルを利用してチャットアプリを作成しました。モデルをNovaに切り替えるなどカスタマイズをしてAmplify AI Kitをためしました。

今回も進化したAmplify AI Kitの機能を試していきたいと思います。前回と同じく下記のAWS Sampleから動作を確認しています。

このサンプルには2つのアプリケーションがあります。

  1. claude-ai
  2. story-teller

今回は後編としてstory-tellerアプリをためしてみます。

story-teller

このアプリケーションは下記の技術で構成されています。

  • Amplify Gen2
  • React Router
  • shadcn/ui
  • Tailwind

claude-aiアプリはNext.jsが利用されていましたが、こちらはReactとReact Routerの組み合わせですね。UIにはshadcn/uiやTailwindが利用されています。こちらの実装を見るだけでも参考になりますね。

事前準備

今回は機能が多いので複数の事前準備が必要となります。

  1. News API のAPI Keyの取得
  2. Amazon Bedrock Knowledge Baseの作成

このアプリではToolsによってAIモデルが複数のAPIを呼び分け、Amazon Bedrock KnowledgeやNews APIが実行されます。

News API Keyの取得方法やAmazon Bedrock Knowledge Baseの作成については本記事では割愛します。

インストール

ディレクトリに移動します。

cd story-teller

パッケージをインストールします。

npm i

Amplify Gen2のsandboxのSecretにNews API Keyを登録します。

npx ampx sandbox secret set NEWS_API_KEY

このコマンドを入力すると次の設問でAPI Keyを入力します。Amplify Gen2のSecretの詳細についてはこちらを参考にしてください。

コードの編集

dataリソースの編集

Amazon Bedrock Knowledge Baseを呼び出すコードを書き換えます。

// amplify/backend.ts
const KB_REGION = "us-east-1"; // ← ナレッジを作成したリージョンを指定

const KB_ID = "ナレッジベースのID";// ← ナレッジのIDを設定
// amplify/data/kbResolver.js
export function request(ctx) {
  const { input } = ctx.args;
  return {
    resourcePath: "/knowledgebases/ナレッジベースのID/retrieve",//← ナレッジのIDを設定
    method: "POST",
    params: {
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        retrievalQuery: {
          text: input,
        },
      }),
    },
  };
}

export function response(ctx) {
  return JSON.stringify(ctx.result.body);
}

backendとdataリソースのAppSyncのリゾルバを変更します。

generateImage functionsの編集

このアプリは画像生成が実装されているいます。モデルをNova Canvasに切り替えます。

まずはリーソース定義のモデルIDとリージョンの定数を変更します。

// amplify/functions/generateImage/resource.ts
import { defineFunction } from "@aws-amplify/backend";

export const MODEL_ID = "amazon.nova-canvas-v1:0";
export const REGION = "us-east-1";

export const generateImage = defineFunction({
  name: "generateImage",
  entry: "./handler.ts",
  environment: {
    MODEL_ID,
    REGION,
  },
  timeoutSeconds: 500,
});

次にLambdaに記述されている、APIリクエストのbodyをNova Canvasに合わせて変更します。

// amplify/functions/generateImage/handler.ts
import {
  BedrockRuntimeClient,
  InvokeModelCommand,
} from "@aws-sdk/client-bedrock-runtime";
import type { Schema } from "../../data/resource";
import { env } from "$amplify/env/generateImage";

export const handler: Schema["generateImage"]["functionHandler"] = async (
  event
) => {
  const client = new BedrockRuntimeClient({ region: env.REGION });
  const res = await client.send(
    new InvokeModelCommand({
      modelId: env.MODEL_ID,
      contentType: "application/json",
      accept: "application/json",
      body: JSON.stringify({
        taskType: "TEXT_IMAGE",
        textToImageParams: { text: event.arguments.prompt },
        imageGenerationConfig: {
          cfgScale: 8,
          seed: 0,
          quality: "standard",
          width: 512,
          height: 512,
          numberOfImages: 1,
        },
      }),
    })
  );

  const jsonString = new TextDecoder().decode(res.body);
  const output = JSON.parse(jsonString);

  return output.images;
};

バックエンドのデプロイ

あとはsandbox環境を構築するのみ!

npx ampx sandbox

これで準備完了です。

完成したアプリ

アプリを起動します。

npm run dev

amplify-ai-kit-02-01.png

ログイン画面は色合いが気になりますが、気にせずユーザー登録してログインします。

amplify-ai-kit-02-02.png

いい感じのUIに遷移します。このアプリではAIにストーリーを考えてもらえるようです。ちなみにモデルはClaude 3 Sonnetです。

amplify-ai-kit-02-03.png

チャットベースでストーリーを作る画面もあります。

amplify-ai-kit-02-04.png

では「犬と猫の大戦争」というストーリーを作ってもらいましょう。

amplify-ai-kit-02-05.png

おお。いい感じにストーリーが出来上がりました。Novaが画像も作ってくれています。タイトルも生成してくれますね。おもしろい。

amplify-ai-kit-02-06.png

今度は別のプロンプトを入力してみます。

amplify-ai-kit-02-07.png

なんか絵面とストーリーの雰囲気が違います😆まあいいでしょう。

今度は会話ベースでストーリーを考えてもらいます。

amplify-ai-kit-02-08.png

toolsに登録しているAPIが呼び出されていますね。生成AIアプリらしいおもしろい体験ができます。

次はナレッジベースをテストします。ナレッジにはClaudeのWebアプリで作成したAWSを学ぶ3人のエンジニア(妄想)の物語のドキュメントを登録しています。内容は下記のとおりです。

クラウドへの夢 〜3人のエンジニアの物語(要約版)

クラウドソリューションズ社で働く3人のエンジニア - 新人の佐藤美咲、インフラ専門の田中健一、ベテランの山本理恵が、写真管理システムのクラウド移行プロジェクトに取り組む。それぞれがLambda、ECS、セキュリティの知見を活かしながら成長し、新たな目標に向かって歩み始める物語。AWSの可能性と共に歩む彼らの成長物語。

amplify-ai-kit-02-09.png

News APIで取り出した情報とナレッジの情報を組み合わせて壮大なストーリーが誕生しました。ずっと遊べるけど。本質とズレるのでこれぐらいにしときます。

Amplify AI Kitの使われ方

このアプリでは2つのAIルートが利用されていて、Conversationルートを使用してチャット機能を構成しており、Generationルートで単一のストーリを生成しています。AIルートに関してはこちらを参考に。

toolsに3つのAPIを登録し、画像生成とニュース取得、ナレッジベースの利用を行っています。

これらは下記のdataリソースに記載されています。

// amplify/data/resource.ts
import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
import { generateImage } from "../functions/generateImage/resource";
import { getNews } from "../functions/getNews/resource";

const schema = a.schema({
  Story: a
    .model({
      title: a.string().required(),
      story: a.string().required(),
    })
    .authorization((allow) => [allow.authenticated()]),
  chat: a
    .conversation({
      aiModel: a.ai.model("Claude 3 Sonnet"),
      systemPrompt:
        "You are a story telling finder. You will assist " +
        "the user in finding a story that matches the story string, " +
        "title string or id.",

      tools: [
        a.ai.dataTool({
          name: "listStories",
          description:
            "This lists all stories from the Story model. " +
            "Use it to find stories with the story field " +
            "and display them to user.",
          model: a.ref("Story"),
          modelOperation: "list",
        }),
        a.ai.dataTool({
          name: "getNews",
          description:
            "Help generate a story prompt using " +
            "the current news.  User will provide a category",
          query: a.ref("getNews"),
        }),
        a.ai.dataTool({
          name: "knowledgeBase",
          description:
            "Used to search a knowledge base of style " +
            "dictionary documentation. Use it to help create story prompts",
          query: a.ref("knowledgeBase"),
        }),
      ],
    })
    .authorization((allow) => allow.owner()),
  summarizer: a
    .generation({
      aiModel: a.ai.model("Claude 3 Sonnet"),
      systemPrompt:
        "You are a helpful assistant that summarizes stories. " +
        "Give a concise summary of the supplied story. " +
        "The summary should be one or two sentences long",
      inferenceConfiguration: {
        temperature: 0.7,
        topP: 1,
        maxTokens: 400,
      },
    })
    .arguments({
      story: a.string(),
    })
    .returns(
      a.customType({
        summary: a.string(),
      })
    )
    .authorization((allow) => [allow.authenticated()]),
  generateStory: a
    .generation({
      aiModel: a.ai.model("Claude 3 Sonnet"),
      systemPrompt:
        "Generate a story and a title that's fun and exciting, " +
        "leave it off in a cliff hanger. The story should be a " +
        "fun magical story. The title should be interesting and " +
        "short.",
    })
    .arguments({
      description: a.string(),
    })
    .returns(
      a.customType({
        story: a.string().required(),
        title: a.string().required(),
      })
    )
    .authorization((allow) => allow.authenticated()),
  generateImage: a
    .query()
    .arguments({
      prompt: a.string(),
    })
    .returns(a.string().array())
    .handler(a.handler.function(generateImage))
    .authorization((allow) => [allow.authenticated()]),
  getNews: a
    .query()
    .arguments({
      category: a.string(),
    })
    .returns(
      a.customType({
        title: a.string(),
        description: a.string(),
      })
    )
    .authorization((allow) => allow.authenticated())
    .handler(a.handler.function(getNews)),
  knowledgeBase: a
    .query()
    .arguments({ input: a.string() })
    .handler(
      a.handler.custom({
        dataSource: "KnowledgeBaseDataSource",
        entry: "./kbResolver.js",
      })
    )
    .returns(a.string())
    .authorization((allow) => [allow.authenticated()]),
});
export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: "userPool",
  },
});

まとめ

Amplify AI Kitできること全部盛りのアプリでした。生成AIアプリは面白いですね。アプリのできることの幅が広がります。Amplify AI Kitを使うことでバックエンドの構築が簡素化され、アプリ機能の実装に注力できますね。

本格的な実装になれば、別の手段も必要になると思いますが、PoCや初期フェーズなどでは十分活躍できそうな気がします。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?