Duet AI のデベロッパー向け技術ハンズオン ワークショップ ガイド Codelab

1. 目標

このワークショップの目的は、ユーザーと実務担当者に Duet AI の実践的なトレーニングを提供することです。

この Codelab では、以下について学びます。

  1. GCP プロジェクトで Duet AI を有効にし、IDE と Cloud Console で使用するように構成します。
  2. Duet AI を使用して、コードの生成、補完、説明を行います。
  3. Duet AI を使用して、アプリケーションの問題を説明し、トラブルシューティングします。
  4. IDE チャットやマルチターンのチャット、チャットとインライン コード生成の比較、コードの説明や暗唱の確認などのスマート アクションなど、Duet AI の機能。

ナラティブ

Duet AI for Developers が日々の開発でどのように実際に使用されているかを示すため、このワークショップのアクティビティはナラティブ コンテキストで行われます。

新しいデベロッパーが e コマース企業に入社しました。複数のサービスで構成されている既存の e コマース アプリケーションに新しいサービスを追加するよう指示されました。この新しいサービスでは、商品カタログの商品に関する追加情報(寸法、重量など)が提供されます。このサービスにより、商品の寸法と重量に基づいて、より安価な送料を設定できます。

デベロッパーは入社したばかりなので、コードの生成、説明、ドキュメント作成に Duet AI を使用します。

サービスがコーディングされた後、プラットフォーム管理者は Duet AI(チャット)を使用して、アーティファクト(Docker コンテナ)と、アーティファクトを GCP にデプロイするために必要なリソース(Artifact Registry、IAM 権限、コード リポジトリ、コンピューティング インフラストラクチャ(GKE、CloudRun など))の作成を支援します。

アプリケーションが GCP にデプロイされると、アプリケーション オペレーター/SRE は Duet AI(と Cloud Ops)を使用して、新しいサービスのエラーのトラブルシューティングを行います。

ペルソナ

このワークショップでは、次のペルソナについて説明します。

  1. アプリケーション デベロッパー - プログラミングとソフトウェア開発に関する知識が必要です。

この Duet AI ワークショップは、デベロッパー専用です。GCP クラウドリソースに関する知識は必要ありません。このアプリケーションを実行するために必要な GCP リソースをビルドする方法のスクリプトは、こちらで確認できます。このガイドの手順に沿って、必要な GCP リソースをデプロイできます。

2. 環境を準備する

Duet AI を有効にする

GCP プロジェクトで Duet AI を有効にするには、API(gcloud や Terraform などの IaC ツール)または Cloud コンソール UI を使用します。

Google Cloud プロジェクトで Duet AI を有効にするには、Cloud AI Companion API を有効にして、Cloud AI Companion ユーザーと Service Usage 閲覧者の Identity and Access Management(IAM)ロールをユーザーに付与します。

gcloud を使用する

Cloud Shell をアクティブにします。

PROJECT_IDUSER を構成し、Cloud AI Companion API を有効にします。

export PROJECT_ID=<YOUR PROJECT ID>
export USER=<YOUR USERNAME> # Use your full LDAP, e.g. name@example.com
gcloud config set project ${PROJECT_ID}
gcloud services enable cloudaicompanion.googleapis.com --project ${PROJECT_ID}

出力は次のようになります。

Updated property [core/project].
Operation "operations/acat.p2-60565640195-f37dc7fe-b093-4451-9b12-934649e2a435" finished successfully.

Cloud AI Companion ユーザーと Service Usage 閲覧者の Identity and Access Management(IAM)ロールを USER アカウントに付与します。Cloud Companion API は、使用する IDE とコンソールの両方の機能の背後にあります。Service Usage 閲覧者の権限は、コンソールで UI を有効にする前に簡単なチェックとして使用されます(Duet UI は、API が有効になっているプロジェクトにのみ表示されます)。

gcloud projects add-iam-policy-binding  ${PROJECT_ID} \
--member=user:${USER} --role=roles/cloudaicompanion.user

gcloud projects add-iam-policy-binding  ${PROJECT_ID} \
--member=user:${USER} --role=roles/serviceusage.serviceUsageViewer

出力は次のようになります。

...
- members:
  - user:<YOUR USER ACCOUNT>
  role: roles/cloudaicompanion.user

...
- members:
  - user:<YOUR USER ACCOUNT>
  role: roles/serviceusage.serviceUsageViewer

Cloud コンソールを使用する

API を有効にするには、Google Cloud コンソールの Cloud AI Companion API ページに移動します。

プロジェクト セレクタで、プロジェクトを選択します。

[有効にする] をクリックします。

ページが更新され、[有効] のステータスが表示されます。これで、必要な IAM ロールを持つすべてのユーザーが、選択した Google Cloud プロジェクトで Duet AI を使用できるようになりました。

Duet AI の使用に必要な IAM ロールを付与するには、[IAM] ページに移動します。

[プリンシパル] 列で、Duet AI へのアクセスを有効にするユーザーを見つけて、その行の鉛筆アイコン ✏️ [プリンシパルを編集] をクリックします。

[アクセス権を編集] ペインで、[別のロールを追加] をクリックします。

[ロールを選択] で、[Cloud AI Companion ユーザー] を選択します。

[別のロールを追加] をクリックし、[Service Usage 閲覧者] を選択します。

[保存] をクリックします。

IDE の設定

開発者は、ニーズに最適なさまざまな IDE から選択できます。Duet AI コード アシスタンスは、Visual Studio CodeJetBrains IDE(IntelliJ、PyCharm、GoLand、WebStorm など)、Cloud WorkstationsCloud Shell エディタなど、複数の IDE で利用できます。

このラボでは、Cloud Workstations または Cloud Shell エディタを使用できます。

このワークショップでは、Cloud Shell エディタを使用します。

Cloud Workstations の設定には 20 ~ 30 分かかることがあります。

すぐに使用するには、Cloud Shell エディタを使用します。

Cloud Shell の上部にあるメニューバーの鉛筆アイコン ✏️ をクリックして、Cloud Shell エディタを開きます。

Cloud Shell エディタの UI と UX は VSCode と非常によく似ています。

d6a6565f83576063.png

Ctrl(Windows の場合)/ Cmd(Mac の場合)+ ,(カンマ)をクリックして、[設定] ペインを開きます。

検索バーに「duet ai」と入力します。

[Cloudcode] › [Duet AI: Enable][Cloudcode] › [Duet AI] › [Inline Suggestions: Enable Auto] が有効になっていることを確認するか、有効にします。

111b8d587330ec74.png

下部のステータスバーで [Cloud Code - Sign In] をクリックし、ログイン ワークフローに沿って操作します。

すでにログインしている場合、ステータスバーに [Cloud Code - No project] と表示されます。

[Cloud Code - No project] をクリックすると、上部にアクション プルダウン ペインが表示されます。[Google Cloud プロジェクトを選択] をクリックします。

3241a59811e3c84a.png

プロジェクト ID の入力を開始すると、プロジェクトがリストに表示されます。

c5358fc837588fe.png

プロジェクトのリストから PROJECT_ID を選択します。

下部のステータスバーが更新され、プロジェクト ID が表示されます。表示されない場合は、Cloud Shell エディタのタブを更新する必要があります。

左側のメニューバーにある Duet AI アイコン d97fc4e7b594c3af.png をクリックすると、Duet AI のチャット ウィンドウが表示されます。[Select GCP Project] というメッセージが表示された場合は、プロジェクトをクリックして、もう一度選択します。

Duet AI のチャット ウィンドウが表示されます。

781f888360229ca6.png

3. インフラストラクチャの設定

d3234d237f00fdbb.png

GCP で新しい配送サービスを実行するには、次の GCP リソースが必要です。

  1. データベースを含む Cloud SQL インスタンス。
  2. コンテナ化されたサービスを実行する GKE クラスタ。
  3. Docker イメージを保存する Artifact Registry。
  4. コード用の Cloud Source Repositories。

Cloud Shell ターミナルで次のリポジトリのクローンを作成し、次のコマンドを実行して、GCP プロジェクトにインフラストラクチャを設定します。

# Set your project
export PROJECT_ID=<INSERT_YOUR_PROJECT_ID>
gcloud config set core/project ${PROJECT_ID}

# Enable Cloudbuild and grant Cloudbuild SA owner role 
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format 'value(projectNumber)')
gcloud services enable cloudbuild.googleapis.com
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com --role roles/owner

# Clone the repo
git clone https://github.com/duetailabs/dev.git ~/duetaidev
cd ~/duetaidev

# Run Cloudbuild to create the necessary resources
gcloud builds submit --substitutions=_PROJECT_ID=${PROJECT_ID}

# To destroy all GCP resources, run the following
# gcloud builds submit --substitutions=_PROJECT_ID=${PROJECT_ID} --config=cloudbuild_destroy.yaml

4. Python Flask サービスの開発

9745ba5c70782e76.png

作成するサービスは、最終的に次のファイルで構成されます。これらのファイルは今すぐ作成する必要はありません。以下の手順に沿って、1 つずつ作成します。

  1. package-service.yaml - 高さ、幅、重量、特別な取り扱い手順などのデータを含む、荷物サービス用の Open API 仕様。
  2. data_model.py - package-service API 仕様のデータモデル。product_details DB に packages テーブルも作成します。
  3. connect_connector.py - CloudSQL 接続(エンジン、セッション、ベース ORM を定義します)
  4. db_init.py - packages テーブルにサンプルデータを生成します。
  5. main.py - product_id に基づいて packages データからパッケージの詳細を取得する GET エンドポイントを備えた Python Flask サービス。
  6. test.py - 単体テスト
  7. requirement.txt - Python の要件
  8. Dockerfile - このアプリケーションをコンテナ化する

演習中に問題が発生した場合は、この Codelab の付録に最終ファイルがすべて記載されていますので、参照してください。

前のステップでは、Cloud Source Repositories を作成しました。リポジトリのクローンを作成します。クローン作成されたリポジトリ フォルダにアプリケーション ファイルをビルドします。

Cloud Shell ターミナルで次のコマンドを実行して、リポジトリのクローンを作成します。

cd ~
gcloud source repos clone shipping shipping
cd ~/shipping 

Cloud Shell エディタの左側のメニューから Duet AI チャット サイドバーを開きます。アイコンは 8b135a000b259175.png のような形をしています。これで、Duet AI をコード アシスタントとして使用できるようになりました。

package-service.yaml

ファイルを開いていない状態で、配送サービスの Open API 仕様を生成するよう Duet に依頼します。

プロンプト 1: 数値のプロダクト ID を指定して、配送と荷物に関する情報を提供するサービスの OpenAPI yaml 仕様を生成します。サービスには、荷物の高さ、幅、奥行き、重量、特別な取り扱いに関する指示を含める必要があります。

ba12626f491a1204.png

生成されたコード ウィンドウの右上に 3 つのオプションが表示されます。

コードをCOPY 71194556d8061dae.pngしてファイルに貼り付けることができます。

コードを ADD df645de8c65607a.png して、エディタで現在開いているファイルに貼り付けることができます。

または、新しいファイルにコードを OPEN a4c7ed6d845df343.png することもできます。

OPEN をクリック a4c7ed6d845df343.png 新しいファイルにコードを貼り付けます。

CTRL/CMD + s をクリックしてファイルを保存し、package-service.yaml というファイル名でアプリケーション フォルダに保存します。[OK] をクリックします。

f6ebd5b836949366.png

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

さまざまなプロンプトを試して、Duet AI の回答を確認することもできます。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

data_model.py

次に、OpenAPI 仕様に基づいて、サービスのデータモデル Python ファイルを作成します。

package-service.yaml ファイルを開いた状態で、次のプロンプトを入力します。

プロンプト 1: python sqlalchemy ORM を使用して、この API サービスのデータモデルを生成します。データベース テーブルを作成する別の関数とメイン エントリ ポイントも追加します。

b873a6a28bd28ca1.png

生成された各部分を見てみましょう。Duet AI はあくまでアシスタントです。コードの作成を迅速に行うことはできますが、生成されたコンテンツをレビューし、理解しながら進める必要があります。

まず、種類 BasePackage という クラスがあります。これは、次のように packages データベースのデータモデルを定義します。

class Package(Base):
    __tablename__ = 'packages'

    id = Column(Integer, primary_key=True)
    product_id = Column(String(255))
    height = Column(Float)
    width = Column(Float)
    depth = Column(Float)
    weight = Column(Float)
    special_handling_instructions = Column(String(255))

次に、次のようにデータベースにテーブルを作成する関数が必要です。

def create_tables(engine):
    Base.metadata.create_all(engine)

最後に、create_tables 関数を実行して CloudSQL データベースに実際にテーブルを作成するメイン関数が必要です。

if __name__ == '__main__':
    from sqlalchemy import create_engine

    engine = create_engine('sqlite:///shipping.db')
    create_tables(engine)

    print('Tables created successfully.')

main 関数は、ローカルの sqlite データベースを使用してエンジンを作成しています。CloudSQL を使用するには、変更する必要があります。これは後で行います。

OPEN a4c7ed6d845df343.png を使用して、以前と同じように新しいファイル ワークフローでコードを作成します。コードを data_model.py という名前のファイルに保存します(名前にアンダースコアが含まれており、ダッシュではないことに注意してください)。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

connect-connector.py

CloudSQL コネクタを作成します。

data_model.py ファイルを開いた状態で、次のプロンプトを入力します。

プロンプト 1: cloud-sql-python-connector ライブラリを使用して、Postgres の Cloud SQL インスタンスの接続プールを初期化する関数を生成します。

ed05cb6ff85d34c5.png

レスポンスでは cloud-sql-python-connector ライブラリを使用していません。同じチャット スレッドに詳細を追加することで、プロンプトを調整して Duet にヒントを与えることができます。

別のプロンプトを使用してみましょう。

プロンプト 2: cloud-sql-python-connector ライブラリを使用する必要があります。

d09095b44dde35bf.png

cloud-sql-python-connector ライブラリを使用していることを確認します。

OPEN a4c7ed6d845df343.png を使用して、以前と同じように新しいファイル ワークフローでコードを作成します。コードを connect_conector.py という名前のファイルに保存します。pg8000 ライブラリを手動でインポートする必要がある場合があります。以下のファイルをご覧ください。

Duet AI のチャット履歴を削除し、connect_connector.py ファイルを開いた状態で、アプリケーションで使用する DB enginesessionmakerbase ORM を生成します。

プロンプト 1: connect_with_connector メソッドを使用してエンジン、sessionmaker クラス、Base ORM を作成する

6e4214b72ab13a63.png

レスポンスでは、engineSessionBaseconnect_connector.py ファイルに追加されることがあります。

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

さまざまなプロンプトを試して、Duet AI の回答のバリエーションを確認することもできます。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

data_model.py を更新する

CloudSQL データベースにテーブルを作成するには、前の手順で作成したエンジン(connect_connector.py ファイル内)を使用する必要があります。

Duet AI のチャット履歴を削除します。data_model.py ファイルを開きます。次のプロンプトを試してください。

プロンプト 1: メイン関数で、connect_connector.py からエンジンをインポートして使用する

2e768c9b6c523b9a.png

connect_connector(CloudSQL の場合)から engine をインポートするレスポンスが表示されます。create_table は(デフォルトの sqlite ローカル DB ではなく)そのエンジンを使用します。

data_model.py ファイルを更新します。

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

さまざまなプロンプトを試して、Duet AI のさまざまな回答を確認することもできます。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

requirements.txt

アプリケーションの requirements.txt ファイルを作成します。

connect_connector.py ファイルと data_model.py ファイルの両方を開き、次のプロンプトを入力します。

プロンプト 1: このデータモデルとサービス用の pip 要件ファイルを生成する

プロンプト 2: 最新バージョンを使用して、このデータモデルとサービスの pip 要件ファイルを生成する

69fae373bc5c6a18.png

名前とバージョンが正しいことを確認します。たとえば、上記のレスポンスでは、google-cloud-sql-connecter の名前とバージョンの両方が正しくありません。バージョンを手動で修正し、次のような requirements.txt ファイルを作成します。

cloud-sql-python-connector==1.2.4
sqlalchemy==1.4.36
pg8000==1.22.0

コマンド ターミナルで次のコマンドを実行します。

pip3 install -r requirements.txt

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

CloudSQL でパッケージ テーブルを作成する

CloudSQL データベース コネクタの環境変数を設定します。

export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export DB_USER=evolution
export DB_PASS=evolution
export DB_NAME=product_details

次に、data_model.py を実行します。

python data_model.py

出力は次のようになります(コードをチェックして、実際に想定される出力を確認してください)。

Tables created successfully.

CloudSQL インスタンスに接続し、データベースが作成されていることを確認します。

gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details

パスワード(evolution)を入力したら、テーブルを取得します。

product_details=> \dt

出力は次のようになります。

           List of relations
 Schema |   Name   | Type  |   Owner   
--------+----------+-------+-----------
 public | packages | table | evolution
(1 row)

データモデルとテーブルの詳細を確認することもできます。

product_details=> \d+ packages

出力は次のようになります。

                                                                        Table "public.packages"
            Column             |       Type        | Collation | Nullable |               Default                | Storage  | Compression | Stats target | Description 
-------------------------------+-------------------+-----------+----------+--------------------------------------+----------+-------------+--------------+-------------
 id                            | integer           |           | not null | nextval('packages_id_seq'::regclass) | plain    |             |              | 
 product_id                    | integer           |           | not null |                                      | plain    |             |              | 
 height                        | double precision  |           | not null |                                      | plain    |             |              | 
 width                         | double precision  |           | not null |                                      | plain    |             |              | 
 depth                         | double precision  |           | not null |                                      | plain    |             |              | 
 weight                        | double precision  |           | not null |                                      | plain    |             |              | 
 special_handling_instructions | character varying |           |          |                                      | extended |             |              | 
Indexes:
    "packages_pkey" PRIMARY KEY, btree (id)
Access method: heap

\q」と入力して Cloud SQL を終了します。

db_init.py

次に、packages テーブルにサンプルデータを追加します。

Duet AI のチャット履歴を削除します。data_model.py ファイルを開いた状態で、次のプロンプトを試してみましょう。

プロンプト 1: 10 個のサンプル パッケージ行を作成して packages テーブルに commit する関数を生成する

プロンプト 2: connect_connector のセッションを使用して、10 個のサンプル パッケージ行を作成し、それらを packages テーブルに commit する関数を生成します。

34a9afc5f04ba5.png

OPEN a4c7ed6d845df343.png を使用して、以前と同じように新しいファイル ワークフローでコードを作成します。コードを db_init.py という名前のファイルに保存します。

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

さまざまなプロンプトを試して、Duet AI のさまざまな回答を確認することもできます。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

サンプル パッケージ データの作成

コマンドラインから db_init.py を実行します。

python db_init.py

出力は次のようになります。

Packages created successfully.

CloudSQL インスタンスに再度接続し、サンプルデータが packages テーブルに追加されていることを確認します。

CloudSQL インスタンスに接続し、データベースが作成されていることを確認します。

gcloud sql connect ${INSTANCE_NAME} --user=evolution --database=product_details

パスワード(evolution)を入力したら、packages テーブルからすべてのデータを取得します。

product_details=> SELECT * FROM packages;

出力は次のようになります。

 id | product_id | height | width | depth | weight |   special_handling_instructions   
----+------------+--------+-------+-------+--------+-----------------------------------
  1 |          0 |     10 |    10 |    10 |     10 | No special handling instructions.
  2 |          1 |     10 |    10 |    10 |     10 | No special handling instructions.
  3 |          2 |     10 |    10 |    10 |     10 | No special handling instructions.
  4 |          3 |     10 |    10 |    10 |     10 | No special handling instructions.
  5 |          4 |     10 |    10 |    10 |     10 | No special handling instructions.
  6 |          5 |     10 |    10 |    10 |     10 | No special handling instructions.
  7 |          6 |     10 |    10 |    10 |     10 | No special handling instructions.
  8 |          7 |     10 |    10 |    10 |     10 | No special handling instructions.
  9 |          8 |     10 |    10 |    10 |     10 | No special handling instructions.
 10 |          9 |     10 |    10 |    10 |     10 | No special handling instructions.
(10 rows)

\q」と入力して Cloud SQL を終了します。

main.py

data_model.py ファイル、package-service.yaml ファイル、connect_connector.py ファイルを開いた状態で、アプリケーションの main.py を作成します。

プロンプト 1: python flask ライブラリを使用して、このサービスに http rest エンドポイントを使用する実装を作成します

プロンプト 2: python flask ライブラリを使用して、このサービスに http rest エンドポイントを使用する実装を作成します。connect_conector.py から SessionMaker をインポートして、パッケージ データに使用します。

プロンプト 3: python flask ライブラリを使用して、このサービスに http rest エンドポイントを使用する実装を作成します。data_model.py から Package を、connect_conector.py から SessionMaker をインポートして、パッケージ データに使用します。

プロンプト 4: python flask ライブラリを使用して、このサービスに http rest エンドポイントを使用する実装を作成します。packages データ用に、data_model.py から Package を、connect_conector.py から SessionMaker をインポートして使用します。app.run にホスト IP 0.0.0.0 を使用する

6d794fc52a90e6ae.png

main.py の要件を更新します。

プロンプト: main.py の要件ファイルを作成する

1cc0b318d2d4ca2f.png

これを requirements.txt ファイルに追加します。Flask バージョン 3.0.0 を使用してください。

OPEN a4c7ed6d845df343.png を使用して、以前と同じように新しいファイル ワークフローでコードを使用します。コードを main.py という名前のファイルに保存します。

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

5. アプリケーションのテストと実行

要件をインストールします。

pip3 install -r requirements.txt

main.py を実行します。

python main.py

出力は次のようになります。

 * Serving Flask app 'main'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://10.88.0.3:5000
Press CTRL+C to quit

2 番目のターミナルから、/packages/<product_id> エンドポイントをテストします。

curl localhost:5000/packages/1

出力は次のようになります。

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

サンプルデータ内の他の商品 ID をテストすることもできます。

ターミナルで「CTRL_C」と入力して、実行中の Docker コンテナを終了します。

単体テストの生成

main.py ファイルを開いた状態で、単体テストを生成します。

プロンプト 1: 単体テストを生成します。

e861e5b63e1b2657.png

OPEN a4c7ed6d845df343.png を使用して、以前と同じように新しいファイル ワークフローでコードを作成します。コードを test.py という名前のファイルに保存します。

test_get_package 関数では、product_id を定義する必要があります。手動で追加できます。

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

Duet AI サイドバーの上部にあるゴミ箱アイコン f574ca2c1e114856.png をクリックして、Duet AI のチャット履歴をリセットします。

単体テストの実施

単体テストを実行します。

python test.py

出力は次のようになります。

.
----------------------------------------------------------------------
Ran 1 test in 1.061s

OK

Cloud Shell エディタで開いているすべてのファイルを閉じ、上部のステータスバーにあるゴミ箱アイコン 1ecccfe10d6c540.png をクリックしてチャット履歴をクリアします。

Dockerfile

このアプリケーションの Dockerfile を作成します。

main.py を開き、次のプロンプトを試します。

プロンプト 1: このアプリケーションの Dockerfile を生成します。

プロンプト 2: このアプリケーションの Dockerfile を生成します。すべてのファイルをコンテナにコピーします。

9c473caea437a5c3.png

また、INSTANCE_CONNECTION_NAMEDB_USERDB_PASSDB_NAMEENVARS を設定する必要があります。手動で実行できます。Dockerfile は次のようになります。

FROM python:3.10-slim

WORKDIR /app

COPY . ./

RUN pip install -r requirements.txt

# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details

CMD ["python", "main.py"]

OPEN a4c7ed6d845df343.png を使用して、以前と同じように新しいファイル ワークフローでコードを作成します。コードを Dockerfile という名前のファイルに保存します。

最終的なファイルは、この Codelab の付録セクションにあります。変更されていない場合は、手動で適切な変更を行います。

アプリケーションをローカルで実行する

Dockerfile を開いた状態で、次のプロンプトを試してみましょう。

プロンプト 1: この Dockerfile を使用してコンテナをローカルで実行するにはどうすればよいですか?

570fd5c296ca8c83.png

画面上の指示に沿って操作します。

# Build
docker build -t shipping .
# And run
docker run -p 5000:5000 -it shipping

出力は次のようになります。

 * Serving Flask app 'main'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://172.17.0.2:5000
Press CTRL+C to quit

2 つ目のターミナル ウィンドウから、コンテナにアクセスします。

curl localhost:5000/packages/1

出力は次のようになります。

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

コンテナ化されたアプリケーションが動作している。

ターミナルで「CTRL_C」と入力して、実行中の Docker コンテナを終了します。

Artifact Registry でのコンテナ イメージのビルド

コンテナ イメージをビルドして Artifact Registry に push します。

cd ~/shipping
gcloud auth configure-docker us-central1-docker.pkg.dev
docker build -t us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping .
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping

アプリケーション コンテナが us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping に配置され、GKE にデプロイできるようになりました。

6. アプリケーションを GKE クラスタにデプロイする

このワークショップの GCP リソースを構築したときに、GKE Autopilot クラスタが作成されました。GKE クラスタに接続します。

gcloud container clusters get-credentials gke1 \
    --region=us-central1

Kubernetes のデフォルト サービス アカウントに Google サービス アカウントでアノテーションを付けます。

kubectl annotate serviceaccount default iam.gke.io/gcp-service-account=cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com

出力は次のようになります。

serviceaccount/default annotated

k8s.yaml ファイルを準備して適用します。

cp ~/duetaidev/k8s.yaml_tmpl ~/shipping/.
export INSTANCE_NAME=$(gcloud sql instances list --format='value(name)')
export INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe ${INSTANCE_NAME} --format="value(connectionName)")
export IMAGE_REPO=us-central1-docker.pkg.dev/${PROJECT_ID}/shipping/shipping
envsubst < ~/shipping/k8s.yaml_tmpl > k8s.yaml
kubectl apply -f k8s.yaml

出力は次のようになります。

deployment.apps/shipping created
service/shipping created

Pod が実行され、Service に外部ロードバランサの IP アドレスが割り当てられるまで待ちます。

kubectl get pods
kubectl get service shipping

出力は次のようになります。

# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
shipping-f5d6f8d5-56cvk   1/1     Running   0          4m47s
shipping-f5d6f8d5-cj4vv   1/1     Running   0          4m48s
shipping-f5d6f8d5-rrdj2   1/1     Running   0          4m47s

# kubectl get service shipping
NAME       TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
shipping   LoadBalancer   34.118.225.125   34.16.39.182   80:30076/TCP   5m41s

GKE Autopilot クラスタの場合は、リソースの準備が整うまでしばらく待ちます。

EXTERNAL-IP アドレスを介してサービスにアクセスします。

export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1

出力は次のようになります。

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

7. 追加演習: アプリケーションのトラブルシューティング

cloudsqlsa サービス アカウントから CloudSQL クライアント IAM ロールを削除します。これにより、CloudSQL データベースへの接続でエラーが発生します。

gcloud projects remove-iam-policy-binding ${PROJECT_ID} \
    --member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

配送 Pod を再起動します。

kubectl rollout restart deployment shipping

Pod が再起動したら、shipping サービスへのアクセスを再度試みます。

export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1 

出力は次のようになります。

...
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

[Kubernetes Engine] > [ワークロード] に移動して、ログを調べます。

d225b1916c829167.png

shipping デプロイをクリックし、[ログ] タブをクリックします。

1d0459141483d6a7.png

ステータスバーの右側にある [ログ エクスプローラで表示 ] df8b9d19a9fe4c73.png アイコンをクリックします。新しいログ エクスプローラ ウィンドウが開きます。

e86d1c265e176bc4.png

Traceback エラー エントリのいずれかをクリックし、[このログエントリの説明を確認する] をクリックします。

d6af045cf03008bc.png

エラーの説明を読むことができます。

次に、Duet AI を使用してエラーのトラブルシューティングを行います。

次のプロンプトを試してください。

プロンプト 1: このエラーのトラブルシューティングをサポートして

9288dd6045369167.png

プロンプトにエラー メッセージを入力します。

プロンプト 2: Forbidden: 認証された IAM プリンシパルに API リクエストを行う権限がないようです。GCP プロジェクトで「Cloud SQL Admin API」が有効になっており、IAM プリンシパルに「Cloud SQL クライアント」ロールが付与されていることを確認します。

f1e64fbdc435d31c.png

そして。

プロンプト 3: gcloud を使用して Google サービス アカウントに Cloud SQL クライアント ロールを割り当てるにはどうすればよいですか?

bb8926b995a8875c.png

Cloud SQL クライアントのロールを cloudsqlsa に割り当てます。

gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member="serviceAccount:cloudsqlsa@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

しばらくしてから、もう一度アプリケーションにアクセスしてみてください。

export EXTERNAL_IP=$(kubectl get svc shipping --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl http://${EXTERNAL_IP}/packages/1

出力は次のようになります。

{"depth":10.0,"height":10.0,"special_handling_instructions":"No special handling instructions.","weight":10.0,"width":10.0}

Cloud Loggingログ エクスプローラログ説明ツール機能で Duet AI を使用して、問題をトラブルシューティングできました。

8. まとめ

おめでとうございます!この Codelab は終了です。

この Codelab では、以下のことを学びました。

  1. GCP プロジェクトで Duet AI を有効にし、IDE と Cloud Console で使用するように構成します。
  2. Duet AI を使用して、コードの生成、補完、説明を行います。
  3. Duet AI を使用して、アプリケーションの問題を説明し、トラブルシューティングします。
  4. IDE チャットやマルチターンのチャット、チャットとインライン コード生成の比較、コードの説明や暗唱の確認などのスマート アクションなど、Duet AI の機能。

9. 付録

package-service.yaml

swagger: "2.0"
info:
 title: Shipping and Package Information API
 description: This API provides information about shipping and packages.
 version: 1.0.0
host: shipping.googleapis.com
schemes:
 - https
produces:
 - application/json
paths:
 /packages/{product_id}:
   get:
     summary: Get information about a package
     description: This method returns information about a package, including its height, width, depth, weight, and any special handling instructions.
     parameters:
       - name: product_id
         in: path
         required: true
         type: integer
         format: int64
     responses:
       "200":
         description: A successful response
         schema:
           type: object
           properties:
             height:
               type: integer
               format: int64
             width:
               type: integer
               format: int64
             depth:
               type: integer
               format: int64
             weight:
               type: integer
               format: int64
             special_handling_instructions:
               type: string
       "404":
         description: The product_id was not found

data_model.py

from sqlalchemy import Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base

from connect_connector import engine

Base = declarative_base()

class Package(Base):
    __tablename__ = 'packages'

    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, nullable=False)
    height = Column(Float, nullable=False)
    width = Column(Float, nullable=False)
    depth = Column(Float, nullable=False)
    weight = Column(Float, nullable=False)
    special_handling_instructions = Column(String, nullable=True)

def create_tables():
    Base.metadata.create_all(engine)

if __name__ == '__main__':
    create_tables()

    print('Tables created successfully.')

connect_connector.py

import os

from google.cloud.sql.connector import Connector, IPTypes
import sqlalchemy

# You may need to manually import pg8000 and Base as follows
import pg8000
from sqlalchemy.ext.declarative import declarative_base


def connect_with_connector() -> sqlalchemy.engine.base.Engine:
   """Initializes a connection pool for a Cloud SQL instance of Postgres."""
   # Note: Saving credentials in environment variables is convenient, but not
   # secure - consider a more secure solution such as
   # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
   # keep secrets safe.
   instance_connection_name = os.environ[
       "INSTANCE_CONNECTION_NAME"
   ]  # e.g. 'project:region:instance'
   db_user = os.environ["DB_USER"]  # e.g. 'my-database-user'
   db_pass = os.environ["DB_PASS"]  # e.g. 'my-database-password'
   db_name = os.environ["DB_NAME"]  # e.g. 'my-database'

   ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

   connector = Connector()

   def getconn() -> sqlalchemy.engine.base.Engine:
       conn: sqlalchemy.engine.base.Engine = connector.connect(
           instance_connection_name,
           "pg8000",
           user=db_user,
           password=db_pass,
           db=db_name,
           ip_type=ip_type,
       )
       return conn

   pool = sqlalchemy.create_engine(
       "postgresql+pg8000://",
       creator=getconn,
       # ...
   )
   return pool

# Create a connection pool
engine = connect_with_connector()

# Create a sessionmaker class to create new sessions
SessionMaker = sqlalchemy.orm.sessionmaker(bind=engine)

# Create a Base class for ORM
# You may need to manually fix the following
Base = declarative_base()

db_init.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from connect_connector import engine

from data_model import Package

def create_packages():
    # Create a session
    session = sessionmaker(bind=engine)()

    # Create 10 sample packages
    for i in range(10):
        package = Package(
            product_id=i,
            height=10.0,
            width=10.0,
            depth=10.0,
            weight=10.0,
            special_handling_instructions="No special handling instructions."
        )

        # Add the package to the session
        session.add(package)

    # Commit the changes
    session.commit()

if __name__ == '__main__':
    create_packages()

    print('Packages created successfully.')

main.py

from flask import Flask, request, jsonify

from data_model import Package
from connect_connector import SessionMaker

app = Flask(__name__)

session_maker = SessionMaker()

@app.route("/packages/<int:product_id>", methods=["GET"])
def get_package(product_id):
  """Get information about a package."""

  session = session_maker

  package = session.query(Package).filter(Package.product_id == product_id).first()

  if package is None:
    return jsonify({"message": "Package not found."}), 404

  return jsonify(
      {
          "height": package.height,
          "width": package.width,
          "depth": package.depth,
          "weight": package.weight,
          "special_handling_instructions": package.special_handling_instructions,
      }
  ), 200

if __name__ == "__main__":
  app.run(host="0.0.0.0")

test.py

import unittest

from data_model import Package
from connect_connector import SessionMaker

from main import app

class TestPackage(unittest.TestCase):

    def setUp(self):
        self.session_maker = SessionMaker()

    def tearDown(self):
        self.session_maker.close()

    def test_get_package(self):
        """Test the `get_package()` function."""

        package = Package(
        product_id=11, # Ensure that the product_id different from the sample data
        height=10,
        width=10,
        depth=10,
        weight=10,
        special_handling_instructions="Fragile",
        )

        session = self.session_maker

        session.add(package)
        session.commit()

        response = app.test_client().get("/packages/11")

        self.assertEqual(response.status_code, 200)

        self.assertEqual(
            response.json,
            {
                "height": 10,
                "width": 10,
                "depth": 10,
                "weight": 10,
                "special_handling_instructions": "Fragile",
            },
        )

if __name__ == "__main__":
    unittest.main()

requirements.txt

cloud-sql-python-connector==1.2.4
sqlalchemy==1.4.36
pg8000==1.22.0
Flask==3.0.0
gunicorn==20.1.0
psycopg2-binary==2.9.3

Dockerfile

FROM python:3.10-slim

WORKDIR /app

COPY . ./

RUN pip install -r requirements.txt

# Add these manually for your project
ENV INSTANCE_CONNECTION_NAME=YOUR_INSTANCE_CONNECTION_NAME
ENV DB_USER=evolution
ENV DB_PASS=evolution
ENV DB_NAME=product_details

CMD ["python", "main.py"]