every Tech Blog

株式会社エブリーのTech Blogです。

Android Studio での Gemini 連携について

はじめに

こんにちは、DELISH KITCHEN でクライアントエンジニアを担当している kikuchi です。

昨今 AI がますます普及し業務で AI を活用する事例も増えてきましたが、Google が提供している Gemini が Android Studio の一機能として提供されていることをご存知でしょうか?
2024/9/13 時点ではまだプレビュー版ではありますが無料で公開されていますので、今回は実際に使用した際の環境構築手順、使用感などをまとめてみたいと思います。

Gemini の詳細は今回割愛しますが、Gemini は大きく分けて無料版の Gemini、有料版の Gemini Advanced と分かれており、現時点では Android Studio は無料版の Gemini と連動しているようです。

事前準備

Android Studio と Gemini を連携する場合は Google アカウントが必須となりますので、事前に作成をしておいてください。
なお、プレビュー版では Gemini Advanced への加入状況は反映されないため、Gemini Advanced への加入は不要です。

環境構築手順

  1. 公式サイト から Canary 版 (2024/9/13 時点では Ladybug) をダウンロードし、Android Studio を起動します

  2. プロジェクトを生成後、右上にある Gemini のアイコンをクリックします

  3. 「Log in to Google」をクリックするとブラウザが起動するためブラウザで認証を通し、完了後に「Next」をクリックします

  4. プライバシーポリシーを確認し、「Next」をクリックします

  5. 利用規約を確認し、チェックを付けて「Next」をクリックします

  6. プライバシー設定を確認し、任意の項目にチェックを付けて「Finish」をクリックします

以上で Gemini の機能を使用できる準備が完了しました。

操作方法について

操作方法はコメント入力欄に質問内容を入力し 「Submit」 をクリックすると AI が回答をしてくれるという形で、通常のブラウザ上で操作するものと大きな違いはありません。
以下は 「ログイン画面のソースファイルとレイアウトファイルを作成して」 と質問した時の回答です。

AI のサービスを使用したことがある方は違和感なく使用できるかと思います。

ファイル操作

Android Studio の Gemini では通常のブラウザ上で使用するようなチャット形式でやり取りが出来る点に加え、生成したソースコードに対して特殊な操作が行えるようになります。

上記は実際に Android Studio 上の Gemini で生成したソースコードの直下に表示されるアイコンですが、左から順に

  1. Copy → ソースコードをコピーする
  2. Insert at Cursor → カーソルが当たっている箇所に該当のソースを挿入する
  3. Insert in New Kotlin File → ソースコードを新規ファイルとして生成する (レイアウトファイルの場合はレイアウトファイルとして新規で生成する)
  4. Explore in Playground → 生成されたコードを Playground 上で確認する

といった操作が行えます。
1、2、4 についてはソースコードのコピーやコピー&ペーストを一括で実施してくれる機能となりますが、3 についてもう少し細かく動きを見てみたいと思います。

Insert in New Kotlin File の実施例

実際に回答結果で出力されたソースファイルの「Insert in New Kotlin File」を実行してみたいと思います。
ファイルを追加したいディレクトリを選択した状態で「Insert in New Kotlin File」のアイコンをクリックします。

実行後、ファイルが自動的に追加された上、androidx.appcompat:appcompat:1.7.0 のライブラリが不足していることを検知し、追加するかの提案もしてくれます。

「Add」 をクリックすると build.gradle に追加してくれるだけでなく、Version Catalog でライブラリを管理している場合は libs.versions.toml にも変更を加えてくれます。

1 つ注意点としては、AndroidManifest.xml には自動的に定義が追加されないため、手動で activity の定義を追加する必要があります。

1 つ手動操作が必要なものの、ソースコードはファイル生成からライブラリの導入までスムーズに行うことが出来ました。
では引き続きレイアウトファイルの生成を行ってみます。

レイアウトファイルの場合はアイコンの説明が「Insert in New Layout File」になるため、そちらのアイコンをクリックします。

レイアウトファイルの場合はここで 2 つほど問題が発生してしまいます。追加後の状態は以下の画像のようになります。

問題の 1 つ目、どのディレクトリを選択していても res ディレクトリ直下に「new.xml」というファイル名で生成されてしまうため、手動でリネームとパス移動が必要になります。
そして 2 つ目、自動生成されたレイアウトファイルは ConstraintLayout を使用していますが、このプロジェクトでは androidx.constraintlayout:constraintlayout のライブラリを設定しておらず、
こちらはソースコードの場合と違ってライブラリが不足していることを検知しませんでした。
レイアウトファイルの自動生成については、手動操作、エラーの解析、ライブラリの追加が必要となってしまいます。

本項でファイルの自動生成を試してみましたが、ソースファイルの生成は容易なものの、レイアウトファイルの生成にはまだまだ課題がありました。

コード補完

前項ではチャット形式でのやり取りについて説明しましたが、実際のソースコードのドキュメント作成や補完を行えるコード補完の機能についても触れたいと思います。
先ほど自動生成したファイルのメソッドで右クリックをすると以下の機能を選択できます。

こちらは上から順に

  1. Document Function "<メソッド名>" → メソッドの KDoc を作成する
  2. Comment Code → 処理にコメントを付ける
  3. Explain Code → 処理の解説をする
  4. Suggest Improvements → 処理の改善案を提示する
  5. Rethink variable names → 無反応のため不明 (直訳だと変数名の再定義)

となっており、どれも該当のソースコードに特殊なプレフィックスを付けてチャット欄に貼り付けるものでした。
最終的には通常のチャット形式でのやり取りになるため、3 のみ試してみたいと思います。

Explain Code の実施例

実際にメソッドの部分で Explain Code を選択すると以下の警告が表示されます。

解析のために Gemini のサーバーにソースコードを送信してよいかという質問になります。機密情報を含むソースコードの取り扱いには注意が必要なため、適切な判断をする必要があります。
今回は先程 Gemini で自動生成したソースコードのため、そのまま送信をしてみます。

プレフィックスとして Explain the following code: というものが付いて、後は丸々ソースコードがチャット欄に貼り付けられる、という挙動になりました。
あとは「submit」をクリックします。

ソースコードの解析結果がチャット欄に出力されました。
こちらはチャット欄で「コードを解析して」といった文章を入力したり、ソースコードをコピー&ペーストする手間を省く効果が期待できそうです。

注意点

おそらく 1 時間に 10 回程度質問のやり取りをしたところで上限に達してしまいました。1 時間程度待てば再度使用できるようになりましたが、ここは現時点で無料版を使用しているための制限になるかと思います。

実際の使用例

色々と試した中で一番便利に感じたのは、ファイル操作の「Insert at Cursor (カーソルの位置に自動挿入)」と「Insert in New Kotlin File (ファイル自動生成)」を組み合わせて使用する形でした。
具体的な例を記載してみます。

  1. 「ログを出力する Utility クラスを作成して」と質問し、出力結果で「Insert in New Kotlin File」を実行しファイルを自動生成する

  2. 「verbose のログを出力するメソッドを作成して」と質問し、出力結果で「Insert at Cursor」を実行しクラスを拡張する

手動で実装することなくチャットによるやり取りのみでクラスの生成、クラスの拡張を行うことが出来ました。
ビジネスロジックを含むようなクラスの生成は難しいかもしれませんが、Utility クラスや data クラスなど単純なメソッドの組み合わせになるようなクラスであればこちらの方が速く実装できる可能性があります。

まとめ

今回 Android Studio の Gemini 機能を使用してみましたが、ブラウザと Android Studio を行き来する手間もなくなり、ファイルの自動生成など Android Studio の操作の手間をかなり軽減し、
また最低限の知識さえあればソースコードをほぼ書かずに実行ができる状態まで作れるため、開発のサポートとしては非常に強力だと感じました。

ただし、前述の通りまだプレビュー版のため、無料版の Gemini を使用していることで回答が返るまでに 10 秒程度かかってしまう点、レイアウトファイルの操作は完全に自動化されていないなど、
まだまだ課題も目立っています。
今後どう改修されていくかは分かりませんが、結局は Android 開発の正しい知識は必須であることには変わりないため、あくまで開発の補助に留めて使用するのがベストかと感じました。

今回紹介した内容が少しでも皆さまのお役に立てれば幸いです。

【2024最新】AWS Data Firehoseを使った際の4つの問題とその解決策

【2024最新】AWS Data Firehoseを使った際の4つの問題とその解決策
【2024最新】AWS Data Firehoseを使った際の4つの問題とその解決策

背景

こんにちは、開発本部 DELISH KITCHEN Retail HUB NetSuperグループに所属するフルスタックエンジニアをやらせていただいています、ホーク🦅アイ👁️です。2024/2/9、Amazon Kinesis Data Firehose から Amazon Data Firehoseに名称変更されてから半年ほど経過しておりますが最新の設定情報などが公開されていることが少ないと感じたため今回記事を書くに至りました。

前提

今回の記事を書くにあたっての前提条件は以下のようになっております。

  • ECS Fargate上にWEBアプリケーションコンテナが存在し、そのコンテナは標準出力にアクセスログを出力している
    • アクセスログは、JSON形式ログとスペース区切り形式ログが混在している

      # JSON形式ログ例
      {"message": "access", "key": "value"}
      # スペース区切り形式ログ例
      127.0.0.1 - - [09/Sep/2024:01:54:49 +0000] "GET /healthcheck HTTP/1.1" 200 236 "-" "curl/7.74.0" 
      
  • TaskDefinitionで logDriverawslogsに設定しその標準出力はCloudWatch Logsに常に転送されている
  • 主な作業を調査・実施した年月日は2024年7月12日

要件

要件1

  • WEBアプリケーションログはJSON形式ログとスペース区切り形式ログが混在しているのでそれぞれを別のS3プレフィックスに保存したい

要件2

  • JSON形式ログは{unique_code}/日付でパーティションをして1行ずつ改行されるようにS3保存させたい
  • スペース区切り形式ログはノンパーティションで1行ずつ改行されるようにS3保存させたい
  • 但し、これらは自前のLambdaを使わずに行うこと!

実装手段

要件1に対する実装

  • CloudWatch Logsのサブスクリプションフィルターを使うとログの種類を2つに分けることができます。

    • コンソール上にも注意書きがありますがサブスクリプションフィルターはLog Groupごとに2個までしか設定できないので注意してください。以下、Terraformで3つ目を適用した時のエラーです。

      Error: putting CloudWatch Logs Subscription Filter (XXXX): operation error CloudWatch Logs: PutSubscriptionFilter, exceeded maximum number of attempts, 25, https response error StatusCode: 400, RequestID: XXXX, LimitExceededException: Resource limit exceeded.
      
  • Terraformを使って反映

# pointだけ絞ってピックアップ
resource "aws_cloudwatch_log_subscription_filter" "application_log" {
  name            = "application-log"
  role_arn        = var.subscription_filter_webserver_arn
  log_group_name  = aws_cloudwatch_log_group.webserver.name
  destination_arn = var.kinesis_firehose_stream_applicationlog_arn
  filter_pattern  = "{$.message = \"access\"}"
}

resource "aws_cloudwatch_log_subscription_filter" "access_log" {
  name            = "access-log"
  role_arn        = var.subscription_filter_webserver_arn
  log_group_name  = aws_cloudwatch_log_group.webserver.name
  destination_arn = var.kinesis_firehose_stream_accesslog_arn
  filter_pattern  = "\" \""
}
  • スペース区切り形式ログのfilter_patternは半角スペース1文字で可能でTerraformで記述するときはダブルクォーテーションで括る必要があります。

要件2に対する実装

  • Terraformを使って反映
# pointだけ絞ってピックアップ
dynamic_partitioning_configuration {
  enabled = "true"
}

processing_configuration {
  enabled = "true"

  processors {
    type = "RecordDeAggregation"
    parameters {
      parameter_name  = "SubRecordType"
      parameter_value = "JSON"
    }
  }

  processors {
    type = "MetadataExtraction"
    parameters {
      parameter_name  = "JsonParsingEngine"
      parameter_value = "JQ-1.6"
    }
    parameters {
      parameter_name  = "MetadataExtractionQuery"
      parameter_value = "if .context.unique_code then {unique_code: .context.unique_code} else {unique_code: \"NONE\"} end"
    }
  }

  processors {
    type = "Decompression"
    parameters {
      parameter_name  = "CompressionFormat"
      parameter_value = "GZIP"
    }
  }

  processors {
    type = "CloudWatchLogProcessing"
    parameters {
      parameter_name  = "DataMessageExtraction"
      parameter_value = "true"
    }
  }

  processors {
    type = "AppendDelimiterToRecord"
  }
}
  • JSON形式ログの方は、Dynamic Partitioning(動的パーティショニング)を使えばJSONパースが行われてS3のプレフィックスにパーティションされます。この際、1ログごとに付与される改行コードがなくなってしまいます。そこで改めてDestination(送信先)データに改行コードを付与するためにProcessorsのAppendDelimiterToRecord typeを設定する必要があります。Terraformで適用するとコンソール上では「New line delimiter(改行の区切り文字)」がenabled(有効)になります。一方で、スペース区切り形式ログの方はDynamic Partitioningを使う必要がないのでTransform and convert records(レコードを変換および転換)機能を行うだけで改行コードが付与されているので改めてAppendDelimiterToRecordを設定する必要はありません。

問題

問題1

  • Dynamic PartitioningのJSONパースにおいて指定したパーティションキーにNULL値があるときパーティション作成に失敗していた

      "errorCode":"DynamicPartitioning.MetadataExtractionFailed","errorMessage":"partitionKeys values must not be null or empty"
    

問題2

  • Firehose自体のエラーログに以下のエラーメッセージが出てS3保存に失敗していた

      errorCode":"DynamicPartitioning.MetadataExtractionFailed","errorMessage":"Non UTF-8 record provided. Only UTF-8 encoded data supported"
    

問題3

  • terraform applyを実行すると以下のエラーメッセージが出て適用に失敗した

      operation error Firehose: UpdateDestination, https response error StatusCode: 400, RequestID: d7b3d246-ecb3-a51f-88ba-1557ca6eae2a, InvalidArgumentException: Enabling source decompression is not supported for existing stream with no Lambda function attached.
    
    • Terraform provider hashicorpのバージョンを5.44->5.58(当時の最新版)に上げるも変化なし

問題4

  • Athenaでクエリ発行するときに、0件ヒットになってしまう
    • JSONコンテンツのみデータ取得できていない感じ
      • 結論、S3に保存しているデータはGZIP圧縮されているが拡張子が.jsonなのでそれを認識できていないため解凍せずにそのまま読み込もうとしている様子
      • 拡張子を.gzにしたら読み込めた
      • プレーンテキスト状態の.jsonファイルと圧縮状態の.gzファイルを混在しても両方同時に読み込めていた

解決策

問題1に対する解決策

  • JQのif-then-else制御構文を使うことでNULL値を特定の固定文字列に置換してしまえば解決できました。以下のようにTerraformでparameter_value値に記述して適用します。尚、この値は長文なので実際にコンソール上で設定画面のDynamic partitioning keys表示を見ても文字列全文は出てこないですが正しい挙動になるので問題ありません。
parameter_value = "if .context.unique_code then {unique_code: .context.unique_code} else {unique_code: \"NONE\"} end"

問題2に対する解決策

  • 原因調査をするにあたって、ChatGPTに質問してその情報を足がかりに進めることにしました。

    • 質問スクリプト

      "errorCode":"Lambda.ProcessingFailedStatus","errorMessage":"ProcessingFailed status set for record" というエラーが出て先ほど教えたJSONデータログをutf-8変換に失敗しました。なぜでしょうか?
      
    • 回答

      ・Firehoseに渡す前にLambda関数でutf-8に変換しておく
      
  • ChatGPTの回答を受けて「結局Lambdaを間に挟まないとダメなのか?!」と疑問視しつつ、該当するBluePrintのLambda関数をReadingするとそもそもCloudWatch LogsからFirehoseに渡る過程でサブスクリプションフィルターを利用するとデータ構造が生ログではなくなっていることが判明しました。つまり、Firehoseに渡るデータは何もしないと圧縮データかつJSON構造変更、そして元ログデータ自体がBASE64エンコード化していました。故に、解凍、パース、デコード処理が必要になるということでした。
{
  "messageType": "DATA_MESSAGE",
  "owner": "123456789012",
  "logGroup": "log_group_name",
  "logStream": "log_stream_name",
  "subscriptionFilters": [
    "subscription_filter_name"
  ],
  "logEvents": [
    {
      "id": "01234567890123456789012345678901234567890123456789012345",
      "timestamp": 1510109208016,
      "message": "log message 1"
    },
    {
      "id": "01234567890123456789012345678901234567890123456789012345",
      "timestamp": 1510109208017,
      "message": "log message 2"
    }
    ...
  ]
}
  • 参考)Transform source data in Amazon Data Firehose - Amazon Data Firehose
  • 便利なサブスクリプションフィルターを使わない選択肢はないので、これを使いつつパースするFirehoseの新たな機能が2024年2月27日にリリースされたようでその機能を使うことで自前でLambda関数を用意せずにFirehoseの機能だけで解決させることができました。
  • 具体的には、コンソールで言うところの特定Data Firehoseのconfigurationページを開いて「Transform and convert records」→「Decompress source records from Amazon CloudWatch Logs」と「Extract message fields only from log events」をONにします。
  • ちなみに、 Firehose新機能の内部構造は結局Lambda関数を使っているようです(メッセージ抽出機能自体は追加料金はありませんがおそらくCloudWatch Logsからのソースレコード解凍機能部における追加料金はLambda関数料金分なのではないかと推測)。
    Amazon Data Firehose Firehose streams Configuration
    Amazon Data Firehose Firehose streams Configuration

問題3に対する解決策

  • 結論としては、Terraformでresourceのattributeであるprocessing_configuration内でDecompressionとCloudWatchLogProcessingの2つのtypeのみを設定すれば解決策2の設定が適用されます。しかし原因はよくわからないですがTerraformでDecompressionとCloudWatchLogProcessingを設定せずにFirehoseリソースを新規作成した後に設定変更という形で前述の2つのtype設定をTerraformで更新適用しようとするとエラーになった(plan時はエラーは出ない)ので途中で変更する場合は工夫が必要ということが判明しました。以下、具体的な工夫手順です。
    • まず適当に既存のLambda関数を用意した上でそのLambdaを使って変換処理を行う記述も追記して適用
    • その後、Lambda関数による変換記述をコメントアウトなどしてそのprocessors部分だけ削除更新するterraform applyを実施することが可能なのでそれを適用
processors {
  type = "Lambda"
  parameters {
    parameter_name  = "LambdaArn"
    parameter_value = "arn:aws:lambda:${region}:${account_id}:function:${parse_func_name}:$LATEST"
  }
}

問題4に対する解決策

  • FirehoseでS3に転送する時に再圧縮して保存させているのでそのファイルが圧縮されたファイルであることをAthenaに認識させるためにはContent-Typeではなく拡張子を.gzにすることであると判明したため、その設定をTerraformに施し適用します。
resource "aws_kinesis_firehose_delivery_stream" "app_server_log" {
  name        = "app-server-log"
  destination = "extended_s3"

  extended_s3_configuration {
    bucket_arn          = var.access_logs_arn
    role_arn            = var.iam_role_kinesis_stream_app_server_arn
    buffering_interval  = 300
    buffering_size      = 64
    compression_format  = "GZIP"
    custom_time_zone    = "Asia/Tokyo"
    file_extension      = ".json.gz" # <= この部分!
  • ここで、元々の経緯は、圧縮しているにもかかわらず。.json拡張子にしていた理由としてChromeブラウザでS3コンソールからダウンロードを実行した時、自動で圧縮ファイルを認識して解凍してローカルディスクに保存する。その時に拡張子.gzのままだとファイルを開いたときにエラーが出て開けないのでファイル名変更で拡張子を手動で.gz部分を削除して.json拡張子にさせてから開かないといけないという手間が発生することに起因します。

総括

結論

  • Amazon Data Firehose解凍機能のみを使ってCloudWatch LogsのログメッセージをS3に転送すること自体は他の記事でもありました。しかし、本記事のようにJSON形式ログの変換を追ってTerraformを使ってまとめている記事はなかったように思います。
  • Amazon Data Firehose解凍機能を使うべき理由は、公式DOCによるLambdaで提供しているblueprints関数テンプレートが非推奨(deprecated)となっていることにあります。ただし、2024年9月執筆時点では、未だLambda関数を作成するコンソールで該当のblueprintsを選択および作成できます。
  • Dynamic Partitioningを使う際に、JQのif-then構文を駆使してパーティションキーを柔軟に設定できました。
  • Amazon Data Firehose解凍機能をTerraformで適用する場合、注意点があることがわかりました。

現状の課題

  • Amazon Data Firehose解凍機能が有償である点ですが、blueprintsのLambdaを設置した時の料金と比較しても大差がないくらい安い点で現状は採用しております。もし完全に無償でこの辺りを構築する場合は、そもそもECS FargateでFluentdなどの外部ログエージェントを使って直接S3に転送するアーキテクチャを採用することになると思います。

みなさまの快適なログライフを!

参考

Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入した話

Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入した話

はじめに

株式会社エブリーでソフトウェアエンジニアをしている桝村です。

本記事では、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介します。

この記事のゴールは、以下を想定しています。

  • Nitro の概要や、Nuxt 2 への Nitro 導入のメリットを把握する
  • Nuxt 2 への Nitro 導入における変更点や考慮すべきポイントを把握する

Nuxt 3 へのアップデートに関連して、Vuex の Pinia への移行については、以下の記事で詳しく紹介しています。

tech.every.tv

サーバーエンジン Nitro とは

サーバーエンジン Nitro とは、様々な環境で軽量な Web サーバーを構築できるライブラリのことです。

Vue や Nuxt 開発メンバーが中心のプロジェクト unjs が開発・メンテナンスしており、Nuxt 3 へデフォルトで組み込まれています。

UnJS は Unified JavaScript Tools の略で、JavaScript の開発をより効率的かつ柔軟に行うために設計された、一連のオープンソースライブラリおよびツールを提供しているプロジェクトです。

同様のライブラリとして、Nuxt 2 との互換性がある Express.js や Koa.js, Fastify などがあります。

詳しくは、以下のリンクをご参照ください。

nitro.unjs.io

Nuxt での Nitro の採用

ここでは、Nuxt での Nitro の採用について、概要やメリットを整理します。

Nuxt での Server の構成や役割

Nitro が採用された Nuxt の Server の構成は以下のようになっています。

Nuxt Server Structure
Nuxt の Server 構成

nuxt.com

  • Server Engine: アプリケーションのサーバーを動作させるための基盤となる技術
  • Nuxt: Vue.js や SSR、状態管理 などの機能を提供する高レベルのフレームワーク
  • Nitro: 軽量でポータブルな出力を生成するライブラリ
  • h3: 軽量で高速な HTTP サーバーのライブラリ。Nitro の基盤技術

また、Nuxt の Server 側では以下をはじめとした責務を担っています。

  • サーバーのビルド・起動設定
  • API のルーティング初期化
  • HTTP リクエストの処理
  • 初期 HTML のレンダリング
  • 静的なサーバーサイドコンテンツの生成 (ex. サイトマップ)
  • etc...

上記を踏まえると、Nuxt での Nitro の採用は、大きな変更であることが想定できます。

Nuxt への Nitro 導入のメリット

Nuxt への Nitro 導入には以下のようなメリットがあります。

  • 高速なサーバーレスポンス
  • ハイブリッドレンダリングのサポート
  • ホットリロードが高速

詳しくは、以下のリンクをご参照ください。

nuxt.com

実際の Nitro 導入による効果については、後述の結果と振り返りで紹介しております。

Nuxt 2 への Nitro の導入における変更点

前提

今回は、以下の技術スタックを持つ本番運用中のアプリケーションへの導入を想定しております。

  • Node.js v20.14.0
  • nuxt v2.17.2
  • @nuxt/bridge v3.0.1
  • express v4.17.1

本アプリケーションは、Nuxt Bridge を利用して Nuxt 2 の状態で Nuxt 3 への移行を進めており、今回は移行の一つであるサーバーエンジン Nitro の導入を行いました。

Nuxt Bridge とは、Nuxt 3 と上方互換性があり、Nuxt 3 の機能の一部を Nuxt 2 で利用できるようにするためのライブラリです。

nuxt.com

以降の内容は、公式の移行ガイドを参考にしながらも、実際に対応を進める中でハマったポイントや気づきを中心に紹介していきます。

開発サーバーの起動

Nuxt が Nitro を利用して開発サーバーを起動するには、CLI コマンド nuxi のインストール・利用が必要です。

前提として、nuxi のインストール・利用には、Node.js のバージョン 18.0.0 以上が必要そうでした。

WARN Current version of Node. js (16.18.0) is unsupported and might cause issues.
Please upgrade to a compatible version >= 18.0.0.

その上で、基本的には、以下のリンクを参考に進めていくことができます。

nuxt.com

また、開発サーバーの起動設定について、コマンド nuxt では server オプションを利用していましたが、コマンド nuxi では devServer オプションの利用が必要なのもポイントでした。

export default defineNuxtConfig({
- server: {
+ devServer: {
    port: 3002,
  }
})

nuxt.com

加えて、コマンド nuxt ではファイルの内容をバッファとして読み込んで渡す仕様でしたが、コマンド nuxi ではファイルのパスを文字列として直接渡す仕様に変更になっていました。より設定が簡潔になったと言えるでしょう。

export default defineNuxtConfig({
  devServer: {
    port: 3002,
    https: {
-     key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
-     cert: fs.readFileSync(path.resolve(__dirname, 'server.crt'))
+     key: './server.key',
+     cert: './server.crt'
    }
  }
})

デプロイメント

nuxi では build コマンドを実行することで、.output ディレクトリにアプリケーションのビルド成果物を出力します。この成果物がサーバーを起動するためのエントリーポイントとなります。

また、デフォルトではポート 3000 でサーバーが起動するのと、上述の devServer オプションを参照しないので カスタマイズでポートを変更したい場合は、以下のように環境変数を利用してポートを指定する必要がありました。

PORT=3002 node .output/server/index.mjs

nuxt.com

エンドポイント・ミドルウェアの設定

前提として、Nuxt 2 では express を利用してエンドポイントやミドルウェアを設定していたので、Nitro の導入にあたっては Nitro (h3) の API を利用するように書き換えることが基本的な方針でした。

Express.js から Nitro (h3) へ書き換えする場合

Nitro では h3 の defineEventHandler によりアプリケーションロジックを定義します。

コンテキストにあたる event インスタンスを受け取って、ロジックを実行する関数を定義することができます。

Express.js
// server/api/test.ts
export default (req, res, next) => {
  // ... Do whatever you want here
  next();
}
Nitro (h3)
// server/api/test.ts
import { defineEventHandler } from "h3";

export default defineEventHandler(async (event) => {
  // ... Do whatever you want here
});

nuxt.com

以下は、具体的な書き換え例になります。

Express.js
import urlParse from "url-parse";

export default (req, res, next) => {
  const host = req.headers.host;
  const parsedUrl = new URL(`https://${host}${req.originalUrl}`);
  const pathname = parsedUrl.pathname;

  if (pathname.match(/.+\/$/)) {
    parsedUrl.pathname = pathname.replace(/\/$/, "");
    res.writeHead(301, { Location: urlParse(parsedUrl).href });
    res.end();
  } else {
    next();
  }
};
Nitro (h3)
import { defineEventHandler, sendRedirect, getRequestURL, getRequestHost } from "h3";

export default defineEventHandler((event) => {
  const host = getRequestHost(event);
  const parsedUrl = new URL(getRequestURL(event), `https://${host}`);
  const pathname = parsedUrl.pathname;

  if (pathname.match(/.+\/$/)) {
    parsedUrl.pathname = pathname.replace(/\/$/, "");
    return sendRedirect(event, `https://${host}${parsedUrl.pathname}`, 301);
  }
});

express では、リクエストオブジェクトから直接情報を取得しているのに対して、h3 では getRequestHostgetRequestURL のような関数を使用してリクエストから情報を取得しています。

それにより、関数の抽象化を通じてコードの可読性と保守性を向上させることに重きを置いていたり、Nuxt 3 で設計を刷新しようとしていることが垣間見えます。

Express.js のコードをそのまま利用する場合

express のコードでも、fromNodeMiddleware() で変換することで Nitro (h3) でそのまま利用することができました。

Express.js
// server/api/index.js
import express from "express";
const app = express();

app.get("/api/test", (req, res) => {
  res.send("Hello World!");
});

export default app;
Nitro (h3)
// server-middleware/api/index.js
import express from "express";
import { fromNodeMiddleware } from "h3";

app.get("/api/test", (req, res) => {
  res.send("Hello World!");
});

export default fromNodeMiddleware(app);

これにより、徐々に express から h3 ベースへコードの書き換えを進めることが可能です。

ただし、Nuxt として推奨されている機能ではないことは留意しておくと良さそうです。

nuxt.com

結果と振り返り

結果

Nuxt 3 への Nitro 導入した結果、体感の部分もありますが、今回のアプリケーションでは以下のような効果が得られました。

#・ホットリロード: 約 20% 高速化
#・開発サーバーの起動時間:約 30% 高速化
#・サーバーのレスポンスタイム: 約 5% 高速化

サーバーのレスポンスタイムについて、今回は開発スピードを優先して主に express のコードをそのまま利用する方針で進めたので、モジュールの変換に伴うオーバーヘッドが影響している可能性があります。

なので、Nitro (h3) へ完全移行できるとさらに改善が見込めるかもしれません。

振り返り

サーバーエンジン Nitro の導入に際して、Nuxt のサーバーの基盤技術の刷新でもあり、変更点が多くありました。

一方で、開発サーバーの起動時間やホットリロードの高速化など、開発効率の向上が期待できることもわかりました。

今後は、Nitro (h3) への完全移行を進めることで、更なるパフォーマンス向上や開発効率の向上を図っていきたいと考えています。

おわりに

今回は、Nuxt 3 へのアップデートに向けて、Nuxt Bridge を使用して Nuxt 2 のアプリケーションへサーバーエンジン Nitro を導入したので、実施内容やそれによって得られた知見について紹介しました。

これから Nuxt Bridge を使用して Nuxt 2 のアプリケーションへ Nitro の導入を検討している方にとって、参考になれば幸いです。

Databricks GenieではじめるText-to-SQL

はじめに

こんにちは。
株式会社エブリーの開発本部データ&AIチーム(DAI)でデータエンジニアをしている吉田です。

今回は、Text-to-SQLを実現するDatabricks Genieを紹介します。

Databricks Genie

Databricks Genieは、自然言語を利用してデータ分析が行えるサービスです。
あらかじめデータ、サンプルクエリ、Genieへの指示を登録しておくことで、Genieに対して自然言語でクエリを投げることができます。
これにより、SQLに詳しくない人でもデータ分析を行うことができます。

AI/BI Genie Space とは何ですか?

Genieを利用する

Genieを利用するためには、以下の手順が必要です。

  1. 利用するデータをUnity Catalogに登録する
  2. Genie Spaceを作成する
  3. Genieをチューニングする

今回は弊社が提供しているレシピ動画サービス、DELISH KITCHENのデータを模したサンプルデータを用意し、Genieを利用してみます。
サンプルデータはランダムに生成したもので、実際のデータとは異なります。

サンプルデータをUnity Catalogに登録する

サンプルデータは、以下のような構造です。

テーブル名 カラム 説明
user_master id
age
gender
recipe_master id
recipe_name
is_premium プレミアムレシピかどうか
viewed_video event_date
user_id
recipe_id
seconds 動画視聴時間
referrer_screen 直前に見た画面

Unity Catalogへの登録

Genieではカタログのメタデータを利用してクエリを生成するため、テーブル/カラムのコメントを登録しておく必要があります。
登録にはAI Generate機能を利用することでデータの内容から適切なコメントを生成できるため、利用すると便利です。

コメントの自動生成

Genie Spaceを作成する

Genie Spaceは、Genieの利用者がデータ分析を行うためのスペースです。
使用するテーブルやサンプルクエリ、使用するコンピュートリソースなどを指定して作成します。

Genie Spaceの初期設定

Genieをチューニングする

Genieに対して、クエリの生成精度を向上させるためのチューニングを行えます。
ドメイン知識を追加したり、回答形式を指定する、あらかじめ質問とSQLをセットで登録し学習させるなど、Genieの精度を向上させる方法があります。

チューニング

Genieでデータ分析をする

実際にGenieに対して、日本語で質問を投げてみます。
クエリの実行後、Show Generated Codeをクリックすると、Genieが生成したクエリを確認できます。

最初はシンプルな質問を投げてみます。

回答

よさそうです。
次はテーブルのJoinが発生する質問を投げてみます。

回答

よさそうです。
さらに複数のJoinが発生する質問を投げてみます。

回答

こちらも良さそうです。
では簡単な変換を伴う質問を投げてみます。

回答

うまくいきませんでした。
このように質問が正確に理解されない、または誤ったクエリを生成することがあります。
そういった場合はチューニングを行うことで精度を向上できます。
例えば以下のようなクエリを質問とセットで登録することで、Genieに正しいクエリを生成するよう学習させられます。

クエリと質問の登録によるチューニング

この状態で同様の質問をしてみます。

回答

今度は正常にクエリが生成されました。
このようにチューニングを行うことで、Genieの精度が向上します。

まとめ

今回はDatabricks Genieを利用して、自然言語でデータ分析を行う方法を紹介しました。
Databricks Genieを利用することで、SQLに詳しくない人でも質問を入力するだけでデータ分析を行うことができます。
これにより、データ分析の敷居が下がりデータ活用が進むことが期待されます。

Amazon BedrockのAdvanced parsing optionsの挙動を確認する

はじめに

こんにちは。DELISH KITCHEN開発部の村上です。

直近は社内でAmazon Bedrockを使った RAG基盤の構築をしています。その中でちょうど先月AWSから発表された advanced RAG機能 の中のAdvanced parsing optionsを検証も兼ねて使用する機会があったので紹介します。

Advanced parsing optionsとは

Knowledge baseではS3や他のデータコネクターを指定し、データソースを作成、同期することによってOpensearch ServerlessといったベクトルDBにデータを格納しています。データソースはさまざまなファイル形式をサポートしていますが、今まではサポートしている形式であってもその解析精度に課題が残るものもありました。

今回のアップデートで追加されたAdvanced parsing optionsは有効化することによって、今まで課題であったPDFファイルのテーブルや表、グラフなど非テキスト情報も基盤モデルを使って解析してベクトルDBに埋め込むことができるようになります。

設定可能な値は二つのみでほぼ有効化のみですぐに試すことができます。

  • 使用する基盤モデル
    • 『Claude 3 Sonnet v1』 or 『Claude 3 Haiku v1 』
  • parserの指示プロンプト
    • 英語のデフォルトプロンプトが設定済み

長いので折り畳みますが、デフォルトプロンプトはこのように記述されています。

プロンプト内容

Transcribe the text content from an image page and output in Markdown syntax (not code blocks). Follow these steps:

1. Examine the provided page carefully.

2. Identify all elements present in the page, including headers, body text, footnotes, tables, visualizations, captions, and page numbers, etc.

3. Use markdown syntax to format your output:
    - Headings: # for main, ## for sections, ### for subsections, etc.
    - Lists: * or - for bulleted, 1. 2. 3. for numbered
    - Do not repeat yourself

4. If the element is a visualization
    - Provide a detailed description in natural language
    - Do not transcribe text in the visualization after providing the description

5. If the element is a table
    - Create a markdown table, ensuring every row has the same number of columns
    - Maintain cell alignment as closely as possible
    - Do not split a table into multiple tables
    - If a merged cell spans multiple rows or columns, place the text in the top-left cell and output ' ' for other
    - Use | for column separators, |-|-| for header row separators
    - If a cell has multiple items, list them in separate rows
    - If the table contains sub-headers, separate the sub-headers from the headers in another row

6. If the element is a paragraph
    - Transcribe each text element precisely as it appears

7. If the element is a header, footer, footnote, page number
    - Transcribe each text element precisely as it appears

Output Example:

A bar chart showing annual sales figures, with the y-axis labeled "Sales ($Million)" and the x-axis labeled "Year". The chart has bars for 2018 ($12M), 2019 ($18M), 2020 ($8M), and 2021 ($22M).
Figure 3: This chart shows annual sales in millions. The year 2020 was significantly down due to the COVID-19 pandemic.

# Annual Report

## Financial Highlights

* Revenue: $40M
* Profit: $12M
* EPS: $1.25


| | Year Ended December 31, | |
| | 2021 | 2022 |
|-|-|-|
| Cash provided by (used in): | | |
| Operating activities | $ 46,327 | $ 46,752 |
| Investing activities | (58,154) | (37,601) |
| Financing activities | 6,291 | 9,718 |

Here is the image.

これまでとの挙動の比較

実際にデフォルトの有効化されていない状態と比較しながら、Advanced parsingがどのようにPDFを解析しているのかを確認していきます。 今回はサンプルデータとして情報通信白書令和5年版のPDFをS3に入れて解析を行っています。

以下は使用している設定値です。

  • 基盤モデル: Claude 3 Sonnet v1
  • parserの指示プロンプト: デフォルト

グラフの解析

まず、p9の棒グラフを解析してみます。

PDFの中のグラフは、デフォルトの設定だと以下のように解析されました。デフォルトでも大きく崩れてはいませんが、それぞれの文字同士のつながりがわかりにくく、ひとつのグラフとして解釈するのは難しいかもしれません。

違法・有害情報センターへの相談件数の推移   0   1,000   2,000   3,000   4,000   5,000   6,000   7,000   平成22 平成23 平成24 平成25 平成26 平成27 平成28 平成29 平成30 令和元 令和2 令和4令和3 (年度)   (件)   1,337 1,560 2,386   2,927 3,400   5,200 5,251 5,598 5,085 5,198 5,407   6,329 5,745   (出典)総務省「令和4年度インターネット上の違法・有害情報対応相談業務等請負業務報告書(概要版)」

Advanced parsingだとこのグラフはマークダウン形式で解析され、それぞれの年と数値の関係がわかりやすく表現されています。(※これ以降ではわかりやすく改行コードで改行を入れていますが、実際には一行にまとまっています。)

## 違法・有害情報センターへの相談件数の推移\n
| 年度 | 件数 |\n
|-|-|\n
| 平成22 | 1,337 |\n
| 平成23 | 1,560 |\n
| 平成24 | 2,386 |\n
| 平成25 | 2,927 |\n
| 平成26 | 3,400 |\n
| 平成27 | 5,200 |\n
| 平成28 | 5,251 |\n
| 平成29 | 5,598 |\n
| 平成30 | 5,085 |\n
| 令和元 | 5,198 |\n
| 令和2 | 5,407 |\n
| 令和3 | 6,329 |\n
| 令和4 | 5,745 |\n
(出典) 総務省「令和4年度インターネット上の違法・有害情報対応相談業務等請負業務報告書(概要版)」\n

他にシンプルな円グラフでも同じような検証を行いましたが、同じ結果でAdvanced parsingの方がより構造化されて解釈がしやすくなっていました。では、もう少し複雑なものだとどうでしょうか。

こちらはp7にある似たような折れ線グラフですが、先ほどの棒グラフと違い、細かい各年度の数値が書かれていません。

これをデフォルト設定で読み込むと、先ほどと同じくテキストは読み込みますが、大事な中身のグラフに対する内容が抜け落ちてしまっています。

主要プラットフォーマーの売上高の推移   0   100   200   300   400   500   600 (10億ドル)   Google Amazon Meta Apple   Microsoft Baidu Alibaba Tencent Holdings   2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022(年)   (出典)Statistaデータを基に作成

一方のAdvanced parsingだとそれぞれの数値が詳細に出されていないことを判断して、無駄なテキスト情報を省き、グラフが示唆する内容を簡単にまとめて解説しています。惜しいのは内容の抜粋になってしまっているので、RAGとして質問した時にそれ以上の回答はできなそうです。ただ、現状はデフォルトプロンプトを使っているので、カスタマイズすることによって精度向上は期待できるかもしれません。

## 主要プラットフォーマーの売上高の推移\n
この画像は、主要プラットフォーマー企業の過去10年間の売上高の推移を示すグラフです。縦軸は売上高(10億ドル)、横軸は年を表しています。グラフには、Google、Amazon、Meta、Apple、Microsoft、Baidu、Alibaba、Tencent Holdingsの売上高の推移が示されています。全体的に右肩上がりの傾向が見られ、特にGoogleとAmazonの売上高の伸びが顕著です。\n
(出典) Statistaデータを基に作成\n

次にp9にあるよりテキスト情報が多く、それぞれの対応関係を正しく把握しないといけないようなものを見てみます。

デフォルト設定では同じように構造化されていない文字の抽出だけで複雑になった分だけよりわかりにくくなっています。

インターネット上の偽・誤情報への接触頻度   毎日、またはほぼ毎日 最低週1回 月に数回 ほとんどない 頻度はわからない   一度も見たことがない そもそも何がフェイクニュースなのかがわからない   19.1 12.0 16.6 18.6 20.2   2.9   10.7   19.5 19.5 21.7 10.7 22.2 5.0   1.4   令和3年度インターネット上のメディア (SNSやブログなど)   令和3年度まとめサイト

Advanced parsingだと先ほどのようにマークダウン形式で解析され、一見すると綺麗に整ったように感じられます。 しかし、よく見るとそれぞれに対応する数値が違っていたり、欠損が目立っており正しく解析はできていないようで一部はハルシネーションにつながりそうな結果となりました。

## インターネット上の偽・誤情報への接触頻度\n
| | 令和3年度インターネット上のメディア(SNSやブログなど) | 令和3年度まとめサイト |\n
|-|-|-|\n
| 毎日、またはほぼ毎日 | 19.1% | 10.7% |\n
| 最低週1回 | 12.0% | 19.5% |\n
| 月に数回 | 16.6% | 19.5% |\n
| ほとんどない | 18.6% | 21.7% |\n
| 頻度はわからない | 20.2% | 10.7% |\n
| 一度も見たことがない | 2.9% | 22.2% |\n
| そもそも何がフェイクニュースなのかがわからない | | 5.0% |\n
| | | 1.4% |\n
(出典) 総務省「令和3年版 国内外における偽情報に関する意識調査」

画像の解析

p39の埋め込まれた画像を解析してみます。

デフォルト設定では画像は完全な非テキスト情報となり、その部分だけが情報として抜け落ちてしまいました。

図表2-1-4-1 校務・学習データの可視化(Microsoft)   (出典)Microsoft

一方でAdvanced parsingでは、画像内で表現されていることを解析して、説明が加えられています。内容も大きく異なることを言っているわけではなく、かなりいい精度で解析ができています。

## 図表2-1-4-1 校務・学習データの可視化(Microsoft) この図は、Microsoftによる校務・学習データの可視化の概要を示しています。左側には、Microsoft Teamsやアンケート・出欠管理システムなどの学校で利用されるシステムが列挙されています。中央には、これらのシステムから収集されたデータが蓄積されていることが示されています。右側には、蓄積されたデータを可視化し、学校全体、クラス、児童・生徒個人のレベルで分析できることが示されています。

表の解析

p7の表を解析してみます。

デフォルト設定の結果は今までのグラフと同じく、文字の抽出のみです。

プラットフォーマーが取得するデータ項目   データ項目 プラットフォーム   Google Facebook Amazon Apple 名前 〇 〇 〇 〇   ユーザー名 - - 〇 -   IPアドレス 〇 〇 〇 〇   検索ワード 〇 - 〇 〇   コンテンツの内容 - 〇 - -   コンテンツと広告表示の対応関係 〇 〇 - -   アクティビティの時間や頻度、期間 〇 〇 - 〇   購買活動 〇 - 〇 -   コミュニケーションを行った相手 〇 〇 - -   サードパーティーアプリ等でのアクティビティ 〇 - - -   閲覧履歴 購買活動 〇 - 〇 -   コミュニケーションを行った相手 〇 〇 - -   サードパーティーアプリ等でのアクティビティ 〇 - - -   閲覧履歴 〇 - 〇 -   (出典)Security.org「The Data Big Tech Companies Have On You」 より、一部抜粋して作成

Advanced parsingでは、マークダウンでそのまま表形式を表現することでPDFと同じ構造を保てています。

## プラットフォーマーが取得するデータ項目\n
| データ項目 | Google | Facebook | Amazon | Apple |\n
|-|-|-|-|-|\n
| 名前 | ◯ | ◯ | ◯ | ◯ |\n
| ユーザー名 | - | - | ◯ | - |\n
| IPアドレス | ◯ | ◯ | ◯ | ◯ |\n
| 検索ワード | ◯ | - | ◯ | ◯ |\n
| コンテンツの内容 | - | ◯ | - | - |\n
| コンテンツと広告表示の対応関係 | ◯ | ◯ | - | - |\n
| アクティビティの時間や頻度、期間 | ◯ | ◯ | - | ◯ |\n
| 購買活動 | ◯ | - | ◯ | - |\n
| コミュニケーションを行った相手 | ◯ | ◯ | - | - |\n
| サードパーティーアプリ等でのアクティビティ | ◯ | - | - | - |\n
| 閲覧履歴 | ◯ | - | ◯ | - |\n
(出典) Security.org「The Data Big Tech Companies Have On You」より、一部抜粋して作成\n

テキストの解析

基本的にAdvanced parsingはこれまで述べてきた非テキスト情報の解析が大きな特徴になっていますが、テキスト情報でもどのような影響があるのかを最後にこちらの目次を解析した結果で確認します。

デフォルト設定では、チャンキングの設定により途中で切れてしまっていますが、表示されている通りに抽出を行っているため無駄にトークンを消費していそうです。

第1章 データ流通の進展 第1節 データ流通を支える通信インフラの高度化・・・・2   ■1  固定通信・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・2 ■2  移動通信・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・2 第2節 データ流通とデジタルサービスの進展・・・・・・・・5   ■1  片方向のデータ発信・ (Web1.0時代:1990年代~2000年代前半)・・・・・5   ■2

一方のAdvanced parsingでは書かれている内容を解析して、その意味を失わない範囲でマークダウンに整形しており、無駄がありません。 また、マークダウンで構造も表現できているため、文章の関係もこちらの方がわかりやすいです。

### 第1章 データ流通の進展 #### 第1節 データ流通を支える通信インフラの高度化 * 1  固定通信 * 2  移動通信 #### 第2節 データ流通とデジタルサービスの進展 * 1  片方向のデータ発信 * (Web1.0時代:1990年代~2000年代前半) * 2  双方向のデータ共有 * (Web2.0時代:2000年代後半~)

運用上の注意点

以上のようにAdvanced parsing optionを有効化することによって完璧ではないものの全体の精度の向上が見込めそうなことがわかりました。一方で使っていく中で以下のような注意点があります。

  • 基盤モデルで前処理を行う都合上、デフォルト設定よりモデルの利用コストが増加する
  • ベクトルDBへの同期処理が遅いため、リアルタイムに文書を処理したい場合に適していない
  • デフォルトプロンプトが英語だからなのか、そのままデフォルトで使うと一部の解析結果が英語になってしまう可能性がある
  • データサイズ、読み込みファイル数に制限がある

特にフルにこの機能をRAG基盤で活用するにあたって大きな障壁に感じたのは、読み込みファイル数の制限です。現状では解析できるファイルの最大数は100ファイルで申請による調整もできません。工夫したとしてもナレッジベースあたりのデータソース数は5つなので、分割して全て使ったとしても500ファイル程度で上限に達してしまいます。もう一つの解決策としてファイルサイズの上限まで複数のファイルを繋げて、ファイル数を削減することですが、その手間を考えるとAWS側での今後の引き上げを期待したいところです。

まとめ

今回はここ1ヶ月ほどで出たAdvanced parsing optionsを活用したKnowledge baseの精度向上に関して、その挙動を見ていきながら機能を紹介しました。

リリースではこの他にもチャンキング戦略のアップデートやクエリ分割の機能など多くのアップデートがあり、Amazon Bedrockの改善のスピード感とAWSがかなり力を入れていることを日々感じています。現在同じようにRAG基盤を構築されている方はぜひ追加された機能も試してみてください。