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

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

前回はAWS Amplify AI Kitの[AWSサンプル](https://github.com/aws-samples/amplify-ai-examples)を利用してチャットアプリを作成しました。モデルを[Nova](https://docs.aws.amazon.com/nova/latest/userguide/what-is-nova.html)に切り替えるなどカスタマイズをしてAmplify AI Kitをためしました。

https://qiita.com/MK_Tech/items/c407664631ebf09b0251

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

https://github.com/aws-samples/amplify-ai-examples

このサンプルには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](https://newsapi.org) のAPI Keyの取得
2. [Amazon Bedrock Knowledge Base](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html)の作成

このアプリでは[Tools](https://docs.amplify.aws/react/ai/concepts/tools/)によってAIモデルが複数のAPIを呼び分け、Amazon Bedrock KnowledgeやNews APIが実行されます。

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

### インストール
ディレクトリに移動します。
```sh
cd story-teller
```
パッケージをインストールします。
```sh
npm i
```
Amplify Gen2のsandboxのSecretにNews API Keyを登録します。
```sh
npx ampx sandbox secret set NEWS_API_KEY
```
このコマンドを入力すると次の設問でAPI Keyを入力します。Amplify Gen2のSecretの詳細については[こちら](https://docs.amplify.aws/react/deploy-and-host/fullstack-branching/secrets-and-vars/)を参考にしてください。

### コードの編集
#### dataリソースの編集
Amazon Bedrock Knowledge Baseを呼び出すコードを書き換えます。

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

const KB_ID = "ナレッジベースのID";// ← ナレッジのIDを設定
```

```tsx
// 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とリージョンの定数を変更します。
```tsx
// 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に合わせて変更します。

```tsx
// 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環境を構築するのみ!

```sh
npx ampx sandbox
```

これで準備完了です。

## 完成したアプリ

アプリを起動します。
```sh
npm run dev
```

![amplify-ai-kit-02-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/5ed3018c-abd8-2158-a5fe-c0b1a75b98d7.png)

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

![amplify-ai-kit-02-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/10155bf8-dd57-d050-1219-b787adf79102.png)

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

![amplify-ai-kit-02-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/544d8101-60b0-fcf4-4697-f720811198ad.png)

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

![amplify-ai-kit-02-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/6e0dd45b-2423-aa24-089a-8cdaf9a6748f.png)

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

![amplify-ai-kit-02-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/fab59ddf-6b28-4cba-060f-cb0c472e1f87.png)

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

![amplify-ai-kit-02-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/738f46f4-6038-3e88-0337-5a555c7c1214.png)

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

![amplify-ai-kit-02-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/21cfe68a-ffa6-fdea-bcba-d3ac18a81718.png)

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

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

![amplify-ai-kit-02-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/8e43af8b-a452-63b0-5f3b-b87a3535ced9.png)

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

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

<details><summary>クラウドへの夢 〜3人のエンジニアの物語（要約版）</summary>

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

![amplify-ai-kit-02-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831737/eff47639-0b58-decf-fa9c-e5d177b6024a.png)

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

## Amplify AI Kitの使われ方

このアプリでは2つのAIルートが利用されていて、Conversationルートを使用してチャット機能を構成しており、Generationルートで単一のストーリを生成しています。AIルートに関しては[こちら](https://docs.amplify.aws/react/ai/concepts/architecture/)を参考に。

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

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

```tsx
// 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や初期フェーズなどでは十分活躍できそうな気がします。