Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AWS AmplifyとBedrockを活用した生成AIアプリの開発@第6回 FlutterG...

AWS AmplifyとBedrockを活用した生成AIアプリの開発@第6回 FlutterGakkai

Tomoki Hirai

December 19, 2024
Tweet

More Decks by Tomoki Hirai

Other Decks in Programming

Transcript

  1. 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
  2. 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<typeof schema>; export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: 'userPool', }, }); type Todo { id: ID! content: String! } KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 7
  3. 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
  4. 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. <example> { "food": "寿司", "calorie": 300 } </example>` } KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 14
  5. スキーマ定義 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<typeof schema>; export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: "apiKey", apiKeyAuthorizationMode: { expiresInDays: 30 } }, }); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 15
  6. バックエンド定義 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
  7. デプロイ 以下のコマンドでサンドボックス環境にデプロイ可能。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
  8. 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
  9. 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
  10. 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
  11. 3.画像をアップロードして推定結果を受け取る base64形式の画像を引数に設定してGraphQLのリクエストを実行する const graphQLDocument = ''' query calorieCalculation(\$base64String: String!) {

    calorieCalculation(base64String: \$base64String) } '''; // imageのオブジェクトをjpg形式でbase64エンコード final base64String = base64Encode(img.encodeJpg(_image!)); final request = GraphQLRequest<String>( document: graphQLDocument, variables: <String, String>{ "base64String": base64String }, authorizationMode: APIAuthorizationType.apiKey); /// API実行 final response = await Amplify.API.query(request: request).response; Map<String, dynamic> jsonMap = json.decode(response.data!); KDDI Agile Development Center Corporation KDDIアジャイル開発センター株式会社 23