AWS Amplify AI Kitをためしてみた🚀 続編
前回はAWS Amplify AI KitのAWSサンプルを利用してチャットアプリを作成しました。モデルをNovaに切り替えるなどカスタマイズをしてAmplify AI Kitをためしました。
今回も進化したAmplify AI Kitの機能を試していきたいと思います。前回と同じく下記のAWS Sampleから動作を確認しています。
このサンプルには2つのアプリケーションがあります。
- claude-ai
- story-teller
今回は後編としてstory-tellerアプリをためしてみます。
story-teller
このアプリケーションは下記の技術で構成されています。
- Amplify Gen2
- React Router
- shadcn/ui
- Tailwind
claude-aiアプリはNext.jsが利用されていましたが、こちらはReactとReact Routerの組み合わせですね。UIにはshadcn/uiやTailwindが利用されています。こちらの実装を見るだけでも参考になりますね。
事前準備
今回は機能が多いので複数の事前準備が必要となります。
- News API のAPI Keyの取得
- 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
ログイン画面は色合いが気になりますが、気にせずユーザー登録してログインします。
いい感じのUIに遷移します。このアプリではAIにストーリーを考えてもらえるようです。ちなみにモデルはClaude 3 Sonnetです。
チャットベースでストーリーを作る画面もあります。
では「犬と猫の大戦争」というストーリーを作ってもらいましょう。
おお。いい感じにストーリーが出来上がりました。Novaが画像も作ってくれています。タイトルも生成してくれますね。おもしろい。
今度は別のプロンプトを入力してみます。
なんか絵面とストーリーの雰囲気が違います😆まあいいでしょう。
今度は会話ベースでストーリーを考えてもらいます。
toolsに登録しているAPIが呼び出されていますね。生成AIアプリらしいおもしろい体験ができます。
次はナレッジベースをテストします。ナレッジにはClaudeのWebアプリで作成したAWSを学ぶ3人のエンジニア(妄想)の物語のドキュメントを登録しています。内容は下記のとおりです。
クラウドへの夢 〜3人のエンジニアの物語(要約版)
クラウドソリューションズ社で働く3人のエンジニア - 新人の佐藤美咲、インフラ専門の田中健一、ベテランの山本理恵が、写真管理システムのクラウド移行プロジェクトに取り組む。それぞれがLambda、ECS、セキュリティの知見を活かしながら成長し、新たな目標に向かって歩み始める物語。AWSの可能性と共に歩む彼らの成長物語。
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や初期フェーズなどでは十分活躍できそうな気がします。