Slide 1

Slide 1 text

2024/07/31 第6回 FlutterGakkai AWS AmplifyとBedrockを 活用した生成AIアプリの開発 KDDIアジャイル開発センター株式会社 平井友樹

Slide 2

Slide 2 text

自己紹介 平井友樹 (Tomoki Hirai) @AllJokin KDDIアジャイル開発センター株式会社 / KDDI株式会社 エンジニアとしてアジャイルの内製開発支援を担当 React, Flutter, AWS, GCPなど KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 1

Slide 3

Slide 3 text

生成AI テキスト生成 画像生成 KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 2

Slide 4

Slide 4 text

Flutter x 生成AI KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 3

Slide 5

Slide 5 text

google_generative_ai Googleの生成AIであるGeminiをFlutterから簡単に呼び出すためのライブラリ。 Using the Google AI SDK for Dart (Flutter) to call the Google AI Gemini API directly from your app is recommended for prototyping only. → 商用では非推奨なので使えない…。API Keyを管理するためのバックエンドを構築がめんどくさい…。 import 'package:google_generative_ai/google_generative_ai.dart'; const apiKey = ...; void main() async { final model = GenerativeModel( model: 'gemini-1.5-flash-latest', apiKey: apiKey, ); final prompt = 'Write a story about a magic backpack.'; final content = [Content.text(prompt)]; final response = await model.generateContent(content); }; KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 4

Slide 6

Slide 6 text

AWS AmplifyとAmazon Bedrock を使おう! KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 5

Slide 7

Slide 7 text

AWS Amplify (Gen2) フルスタックのWebアプリケーションやモバイルアプリケーションを素早く構築、デプロイするためのツール Typescriptによるバックエンド定義 開発者ごとのクラウドサンドボックス環境 Gitブランチと連携した自動デプロイ AWS CDKによるバックエンドのカスタマイズ KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 6

Slide 8

Slide 8 text

Typescriptによるバックエンド定義 Typescriptによるリソース定義によってGraphQLスキーマとDynamoDBのテーブルを自動生成 GraphQLスキーマ定義 DynamoDBテーブル id content 1 test 2 test import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; const schema = a.schema({ Todo: a.model({ content: a.string(), }).authorization(allow => [allow.owner()]), }); export type Schema = ClientSchema; export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: 'userPool', }, }); type Todo { id: ID! content: String! } KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 7

Slide 9

Slide 9 text

サンドボックス環境 開発者ごとに独立したサンドボックス環境を利用可能。変更後のデプロイも高速。 KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 8

Slide 10

Slide 10 text

Amazon Bedrock AWSが提供する生成AIサービス 様々な生成AIモデルをサーバレスなAPIで呼び出すことができる KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 9

Slide 11

Slide 11 text

作成したサンプルアプリケーション 料理の写真をFlutterアプリで撮影し、生成AIで料理名とカロリーを推定する https://github.com/tm-hirai/flutter_amplify_bedrock https://qiita.com/allJokin/items/dd1a267bdce88a79d0cc AWS Cloud User { "food": カレー, "calorie": 800 }    AWS Amplify Amazon Bedrock AWS AppSync AWS Lambda KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 10

Slide 12

Slide 12 text

バックエンド実装 KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 11

Slide 13

Slide 13 text

生成AIを呼び出すバックエンドの作成 フロントエンドから送信された料理画像を生成AIで処理し、カロリーを推定するバックエンドを作成 Amplifyのファイル構成に従い、以下のファイルを作成する 使用するサービスは以下の3つ。 AppSync: サーバーレスでマネージドなGraphQLサーバー Lambda: Node.js、Python、Rubyなどをサーバーレスに実行できる Faas (Function as a Service) Bedrock: 生成AIサービス amplify/ data/ calorieCalculation.ts Lambda関数の定義 resource.ts スキーマの定義 backend.ts バックエンドの定義 KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 12

Slide 14

Slide 14 text

Lambda関数の実装 Lambda関数で実行されるTypescriptのコード。Base64形式の画像をBedrockに送信する。 export const handler: Schema["calorieCalculation"]["functionHandler"] = async (event,_ ) => { const client = new BedrockRuntimeClient({ region: "us-east-1" }); // base64形式の画像 const base64String = event.arguments.base64String; const input = { modelId: process.env.MODEL_ID, contentType: "application/json", accept: "application/json", body: prompt, // 次スライドに記載 } as InvokeModelCommandInput; const command = new InvokeModelCommand(input); const response = await client.send(command); const data = JSON.parse(Buffer.from(response.body) .toString()); return data.content[0].text; }; KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 13

Slide 15

Slide 15 text

Bedrockに送信するプロンプト 日本語より英語の方がトークン数(課金単位)が少なくて低コストなので、英語で作成 システムプロンプト 入力画像 命令文 system: "You are a calorie analysis expert capable of estimating the calories of any dish shown in an image." { type: "image", source: {type: "base64", media_type: "image/jpeg", data: base64String} } { type: "text", text: `Estimate calories of the food in this image. Use JSON format with "food" (dish name in Japanese) and "calorie" (in kcal) as keys. Do not output anything other than JSON. { "food": "寿司", "calorie": 300 } ` } KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 14

Slide 16

Slide 16 text

スキーマ定義 Lambda関数の設定とGraphQLスキーマ定義する。フロントから呼び出す際の引数や返り値、認証方式などを定義する。 export const MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"; // Lambdaの定義 export const calorieCalculationFunction = defineFunction({ entry: "./calorieCalculation.ts", environment: { MODEL_ID, }, runtime: 20, timeoutSeconds: 10, }); const schema = a.schema({ calorieCalculation: a .query().arguments({ base64String: a.string().required() }).returns(a.string()) .authorization((allow) => [allow.publicApiKey()]) .handler(a.handler.function(calorieCalculationFunction)), }); export type Schema = ClientSchema; export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: "apiKey", apiKeyAuthorizationMode: { expiresInDays: 30 } }, }); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 15

Slide 17

Slide 17 text

バックエンド定義 defineBackend によってバックエンドが生成される。 import { defineBackend } from "@aws-amplify/backend"; import { data, MODEL_ID, calorieCalculationFunction } from "./data/resource"; import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; export const backend = defineBackend({ data, calorieCalculationFunction, }); // LambdaからBedrockを呼び出すための権限を付与 backend.calorieCalculationFunction.resources.lambda.addToRolePolicy( new PolicyStatement({ effect: Effect.ALLOW, actions: ["bedrock:InvokeModel"], resources: [`arn:aws:bedrock:us-east-1::foundation-model/${MODEL_ID}`], }) ); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 16

Slide 18

Slide 18 text

デプロイ 以下のコマンドでサンドボックス環境にデプロイ可能。GraphQLスキーマとフロントの設定ファイルが生成される。 アプリ側で読み込むdartファイル GraphQLのスキーマ npx ampx sandbox --outputs-format dart --outputs-out-dir lib --outputs-version 0 const amplifyConfig = '''{ "UserAgent": "@aws-amplify/client-config/1.0.4", "Version": "1.0", "api": { "plugins": { "awsAPIPlugin": { "data": { "endpointType": "GraphQL", "endpoint": "https://xxx.appsync-api. ap-northeast-1.amazonaws.com/graphql", "region": "ap-northeast-1", "authorizationType": "API_KEY", "apiKey": "xxx" } }}}}'''; type Query { calorieCalculation(base64String: String!): String @aws_iam @aws_api_key } KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 17

Slide 19

Slide 19 text

Flutterアプリ実装 KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 18

Slide 20

Slide 20 text

アプリの概要 1. カメラで写真を撮影 2. 撮影した画像を加工 3. 画像をアップロード KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 19

Slide 21

Slide 21 text

1.カメラで写真を撮影 camera パッケージを使ってカメラ機能を実装 void main() async { WidgetsFlutterBinding.ensureInitialized(); // 利用可能なカメラの取得 final cameras = await availableCameras(); runApp(MyApp( cameras: cameras, )); } // コントローラー初期化 void initState() { super.initState(); controller = CameraController(widget.cameras[0], ResolutionPreset.max, enableAudio: false); controller.initialize().then((_) { if (!mounted) { return; } setState(() {}); }); } Widget build(BuildContext context) { // プレビューウィジット return CameraPreview(controller); } // 撮影 controller.takePicture().then((XFile file) async {}); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 20

Slide 22

Slide 22 text

2.撮影した画像を加工 1. 端末によって縦横比が異なるので、正方形に切り取る 2. 解像度が高いとコストが大きいので、500x500にリサイズする ①画像ファイルを    正⽅形に整形 ②500x500に  リサイズ 元画像 正⽅形画像 500x500 tokens = (width px * height px)/750 画像サイズ トークン数 コスト/1枚 コスト/1000枚 200x200 約54 約$0.00016 約$0.16 500x500 約333 約$0.001 約$1 1000x1000 約1334 約$0.004 約$4.00 KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 21

Slide 23

Slide 23 text

2.撮影した画像を加工 image パッケージを使って500x500の画像を作成する copyCrop : 画像を切り取る copyResize : 画像のサイズを変更する final cmd = img.Command() ..image(image) ..copyCrop(x: 0, y: 0, width: image.width, height: image.width) ..copyResize(width: 500, height: 500); final processedImage = await cmd.getImageThread(); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 22

Slide 24

Slide 24 text

3.画像をアップロードして推定結果を受け取る base64形式の画像を引数に設定してGraphQLのリクエストを実行する const graphQLDocument = ''' query calorieCalculation(\$base64String: String!) { calorieCalculation(base64String: \$base64String) } '''; // imageのオブジェクトをjpg形式でbase64エンコード final base64String = base64Encode(img.encodeJpg(_image!)); final request = GraphQLRequest( document: graphQLDocument, variables: { "base64String": base64String }, authorizationMode: APIAuthorizationType.apiKey); /// API実行 final response = await Amplify.API.query(request: request).response; Map jsonMap = json.decode(response.data!); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 23

Slide 25

Slide 25 text

デモ KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 24

Slide 26

Slide 26 text

まとめ AWS Amplify x Amazon Bedrockで 簡単に生成AIアプリケーションを作成可能 ぜひ試してみてください! KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 25