From 6ae3a509084200f8f263413eb18338ca35d8b74d Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Tue, 20 May 2025 10:21:54 +0900 Subject: [PATCH 01/23] Update README.md Line 64, not only on Linux, but also on macOS, need to set permission. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dab9652..5d9c0f2 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ azd init ``` ### Grant permissions to azd hooks scripts -If you are deploying the solution on linux OS, grant the following permissions to `predeploy.sh` +If you are deploying the solution on linux OS and macOS, grant the following permissions to `predeploy.sh` ```sh cd azd-hooks sudo chmod +x predeploy.sh From 67b9343b2556e88e537aedc9a50912bea83774fc Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Tue, 20 May 2025 17:38:15 +0900 Subject: [PATCH 02/23] Added hands-on contents in Japanese --- docs/workshop/ja/01-Introduction.md | 22 ++++ docs/workshop/ja/02-Prerequisites.md | 48 +++++++++ docs/workshop/ja/03-Integration.md | 86 ++++++++++++++++ docs/workshop/ja/04-Repository.md | 40 ++++++++ docs/workshop/ja/05-Provisioning.md | 58 +++++++++++ docs/workshop/ja/06-Post-provisioning.md | 52 ++++++++++ docs/workshop/ja/07-WhyPostgreSQL.md | 125 +++++++++++++++++++++++ docs/workshop/ja/08-Wrapup.md | 32 ++++++ 8 files changed, 463 insertions(+) create mode 100644 docs/workshop/ja/01-Introduction.md create mode 100644 docs/workshop/ja/02-Prerequisites.md create mode 100644 docs/workshop/ja/03-Integration.md create mode 100644 docs/workshop/ja/04-Repository.md create mode 100644 docs/workshop/ja/05-Provisioning.md create mode 100644 docs/workshop/ja/06-Post-provisioning.md create mode 100644 docs/workshop/ja/07-WhyPostgreSQL.md create mode 100644 docs/workshop/ja/08-Wrapup.md diff --git a/docs/workshop/ja/01-Introduction.md b/docs/workshop/ja/01-Introduction.md new file mode 100644 index 0000000..2b033be --- /dev/null +++ b/docs/workshop/ja/01-Introduction.md @@ -0,0 +1,22 @@ +# はじめに + +**AgenticShop**は、Azure Database for PostgreSQLを活用したマルチエージェントの小売体験を示すソリューションアーキテクチャです 。このリポジトリのアプリケーションは、電子機器ガジェットのショッピング体験をAIによって高度化したデモとなっており、Azure上でワンクリックデプロイできるハンズオン教材として位置付けられています。アプリケーション名にある「Agentic(エージェンティック)」とは、複数のAIエージェントが対話し協調してタスクをこなすアプリケーションを意味します。大規模言語モデル(LLM)を用いたエージェントが自律的に計画立案し、必要に応じてデータベースやAPIなどのツールを利用しながらユーザ要求を満たすのが特徴です。 + +## アプリケーションのアーキテクチャ + +このソリューションの全体アーキテクチャは、フロントエンド、バックエンド、データベースおよびAIサービスから構成されています。フロントエンドはユーザーにショッピングUIを提供し、バックエンドはAIエージェントのワークフローを実行します。バックエンドではPython製のエージェントオーケストレーター(LlamaIndexライブラリを使用)が稼働し、Azure Database for PostgreSQLからのデータ取得やAzure OpenAIサービスへの問い合わせなど複数のタスクをエージェントが順次処理します。また、アプリケーションのデバッグ・解析用に**Arize Phoenix**と呼ばれるトレーシング機能も組み込まれており、エージェントの内部動作(どのようなクエリを発行し、どんな回答を得たか)を可視化できるようになっています。Azure Database for PostgreSQLは単なるデータストアに留まらず、後述する拡張機能(AI、ベクトル、グラフ機能)を利用して**知的なクエリ処理**を行う重要な役割を担っています。 + +## Agenticなアプリケーションの特徴と実装 + +このアプリケーションの最大の特徴は、複数のAIエージェントが連携してユーザーにパーソナライズされたショッピング情報を提供する点です。例えば、あるエージェントはユーザープロファイルに基づいて好みに合いそうな製品を選定し、別のエージェントは製品に関するデータベース検索(自然言語クエリをSQLに変換しベクトル検索)を行い、さらに別のエージェントがレビュー要約や特徴抽出を担う、といった分担が考えられます。このマルチエージェントワークフローにより、製品のパーソナライズドな詳細情報提示や高度なユーザーエクスペリエンスが実現されています。エージェント間の調停やタスクの流れはLlamaIndexによって制御され、LLMが各ステップで適切なアクション(データベースへの問い合わせや別エージェントへの引き継ぎ)を選択できるよう実装されています。総じて、AgenticShopはAIエージェントがバックエンドで自律的に動作する次世代のアプリケーション構築手法を示す例となっており、Azure上でそれを構築・体験できるようになっています。 + +## Key Features(主な機能) +本ソリューションアクセラレータには以下のような特徴があります +- **ユーザープロファイルに基づく製品詳細提示**: 各ユーザーの属性や嗜好に合わせて、関連性の高い製品情報や説明が自動生成・表示されます。 +- **向上したユーザーエクスペリエンス**: AIを活用したチャットボット的な対話や要約表示、レコメンデーションにより、従来の通販サイトよりリッチな体験を提供します。 +- **マルチエージェントワークフロー**: 複数のエージェントが協調して検索・要約・レコメンドなど複数タスクをこなし、シームレスな処理を実現します。 +- **デバッグパネルによる可視化**: Arize Phoenixを用いたエージェント動作のトレース機能があり、エージェントがどのようにトリガーされどんな応答を生成したかを確認できます。 + +以上が全体の概要です。このハンズオンでは、以上の特徴を備えたAgenticShopアプリケーションをAzure上にデプロイし、実際にその機能を体験しながら内部でどのような処理が行われているか学んでいきます。 + +[次へ](02-Prerequisites.md) diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md new file mode 100644 index 0000000..f1eb7bd --- /dev/null +++ b/docs/workshop/ja/02-Prerequisites.md @@ -0,0 +1,48 @@ +# 事前準備と必要要件 + +本セクションでは、ハンズオンを開始する前に準備すべき環境や確認事項について説明します。参加者は**PostgreSQLの基礎知識**を有し、**Azure上でのAIアプリ開発の経験**があることを想定しています。その上で、必要なソフトウェアのインストールやAzureサブスクリプションの設定を事前に済ませておきましょう。 + +## ハンズオンに必要なもの +- **Azureサブスクリプション**: Azureの有効なサブスクリプションが必要です(所有者またはリソース作成権限を持つこと)。Azure OpenAI サービスを利用するため、このサービスへのアクセス権がサブスクリプションに含まれていることを確認してください(場合によっては利用申請が必要です)。 +- **開発マシンとインターネット接続**: ハンズオンを実施するPC(Windows, macOS, Linuxいずれも可)と安定したインターネット接続が必要です。Azure CLI等を使用するため、ローカル環境にターミナル/コマンドプロンプトが利用可能であることを確認してください。 + +## 事前にインストールしておくべきソフトウェア + +以下のソフトウェアを事前にインストールしてください。バージョンはできるだけ最新の安定版を推奨します。 + +- **Azure Developer CLI (`azd`)**: Azureの開発者向けCLIツールです。インストール手順は[Azure Developer CLI のインストール方法](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-linux)を参照してください。 + +- **Azure CLI**: Azureのコマンドラインツールです。インストール手順は[Azure CLI をインストールする方法](https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli?view=azure-cli-latest)を参照してください。既にインストール済みであればバージョンの確認(`az --version`)を行い、必要に応じてアップデートしてください。 + +- **Azure CLI拡張機能(rdbms-connect)**: Azure CLIにPostgreSQLサーバーへの一時的トンネリング接続等を追加する拡張です。インストールは`az extension add --name rdbms-connect`で行います。インストール後、`az extension list`で有効になっていることを確認してください。 + +- **Python (3.8以上)**: デプロイ後のアプリケーションやスクリプトの動作に利用します。Pythonがインストールされていない場合は[公式サイト](https://www.python.org/downloads/windows/)から入手してください。Windowsの場合、インストール時に「Add Python to PATH」にチェックを入れておくと便利です。 + +- **PowerShell Core (Windowsユーザーのみ)**: WindowsでLinux向けスクリプトを実行する場合に必要です。事前に[PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5)をインストールしておいてください。 + +インストール後、それぞれのコマンドがパスに通っていることを確認しましょう。例えば`azd version`や`az --version`を実行してバージョン情報が表示されれば準備OKです。 + +## Azureリージョンの選定条件 + +本ハンズオンでデプロイするリソースにはAzure Database for PostgreSQLやAzure OpenAIサービスなどが含まれます。**Azure OpenAI**サービスは利用可能なリージョンが限られているため、**「Azure OpenAIが利用可能なリージョン」を選択することが重要**です。また、**Azure Database for PostgreSQLにおける一部拡張機能(後述のApache AGEなど)**は新規作成したサーバでサポートされるプレビュー機能です。Azure Developer CLI (`azd`) 実行時に、インフラリソース用リージョンとAzure OpenAI用リージョンの2つを尋ねられます。原則として以下の条件を満たすリージョンを選びましょう。 + +- **Azure OpenAI対応リージョン**: East USやSouth Central US、西ヨーロッパ、東日本など、Azure OpenAIリソースを作成できるリージョンを指定します。自分のサブスクリプションでOpenAIのサービス作成が許可されているリージョンか事前に確認してください。 + +- **データベースと近接した場所**: パフォーマンスの観点から、Azure Database for PostgreSQLを配置するリージョンと物理的に近いリージョンをAzure OpenAIに選ぶのが望ましいです。例えば、東日本リージョンにDBを作成した場合、Azure OpenAIも日本または近隣のリージョンを選択します(ただしOpenAIサービスが限られるため妥協が必要な場合もあります)。 + +- **プレビュー機能の利用**: Apache AGE拡張は新規作成のPostgreSQLサーバでPG 13〜16の場合に利用可能で、既存サーバには有効化できません。`azd up`では自動的に新しいPostgreSQLサーバを作成しますが、念のためAzure Portalでプレビュー機能利用に関する記載がないか確認すると安心です。 + +## クォータの確認 + +Azure OpenAIサービスを利用するには、モデルごとの使用上限(クォータ)がサブスクリプションに割り当てられている必要があります。特に本ハンズオンで用いる**GPT-4**モデルや**Embedding**モデルは要求クォータが比較的大きいです。デフォルト構成では以下の程度のスループットが必要となります。 + +- **GPT-4 モデル**: 1分あたり150kトークン程度の処理能力 (150K TPM)。Azure OpenAIの「モデル使用上限 (Requests per minute)」がこれを満たす必要があります。 + +- **text-embedding-ada-002 (埋め込みモデル)**: 1分あたり120kトークン程度 (120K TPM)。 + +ご自身のAzureサブスクリプションのOpenAIクォータは、Azure Portalの「クォータ + 制限」ページや`az openai admin quota show`コマンドで確認できます。不足している場合は、Azure OpenAIリソースの作成時に上限引き上げのリクエストを申請してください。 + +> [!NOTE] 注意 +> `azd up`実行時のプロンプトでも、上記モデルのデプロイ容量 (デフォルト値) をパラメータとして設定するようになっています。必要に応じてリポジトリ内の infra/main.parameters.json を編集することで、GPT-4や埋め込みモデルのデプロイスケールを調整できます(クォータ節約のためにTPSを下げる等)。 + +[前へ](01-Introduction.md) | [次へ](03-Introduction.md) diff --git a/docs/workshop/ja/03-Integration.md b/docs/workshop/ja/03-Integration.md new file mode 100644 index 0000000..159c061 --- /dev/null +++ b/docs/workshop/ja/03-Integration.md @@ -0,0 +1,86 @@ +# Azure Database for PostgreSQLへのAI・ベクトル・グラフ機能の統合 + +Azure Database for PostgreSQLを単なるリレーショナルデータベースではなく、**AI(人工知能)・機械学習(ML)・ベクトル検索・グラフデータ**を統合したプラットフォームとして活用することで、より高度なデータ検索・分析が可能になります。本セクションでは、その理由と、本ハンズオンで使用する各種拡張機能の概要および有効化手順について説明します。 + +## 拡張機能の概要: azure_ai、pgvector、Apache AGE + +- **azure_ai拡張機能 (プレビュー)**: Azure Database for PostgreSQLのFlexibleサーバ向けに提供されている新機能で、データベース内から直接Azureの各種AIサービス(Azure OpenAIやAzure Cognitive Servicesなど)を呼び出すことを可能にします。これにより、SQLクエリの中でテキストの埋め込みベクトル生成やLLMによる文章生成・要約、テキストの分析(感情分析やキーフレーズ抽出など)が行えるようになります。言い換えると、Azureの強力なAIモデルをPostgreSQL内にシームレスに統合し、**データベースがAIの機能を直接利用できる**ようになる拡張です。azure_ai拡張をインストールすると、データベース内に専用のスキーマ(`azure_ai`や`azure_openai`、`azure_cognitive`)が作成され、そこに各種AIサービスを呼び出す関数が提供されます。例えば、`azure_openai.create_embeddings()`で埋め込みベクトルを生成したり、`azure_ai.generate()`でLLMによるテキスト生成を行ったり、`azure_ai.rank()`で文章の関連度評価(リランキング)を行うことができます。 + +- **pgvector拡張機能**: PostgreSQLにオープンソースの**ベクトル類似検索機能**を追加する拡張です。テキストや画像などから生成された特徴ベクトルをデータベース内にそのまま保存し、高速な近似近傍検索(ANN)が可能となります。pgvectorを使うことで、ベクトルデータベース専用の製品を使わずともPostgreSQLで埋め込みベクトルの格納・検索ができ、RDB内の他のリレーショナルデータと組み合わせたクエリも容易になります。たとえばSQLの中で特殊な演算子<=>を使ってベクトル間の距離(類似度)を計算することができ、あるクエリベクトルに対して最も距離の近い(=内容が類似した)上位N件のレコードを取得するといった操作がシンプルに書けます。Azure Database for PostgreSQLではpgvector拡張がサポートされており、埋め込みベクトルとAzure OpenAIの組み合わせでRAG(Retrieval Augmented Generation)パターンが実装できます。 + +- **Apache AGE拡張機能**: PostgreSQLにグラフデータベースの機能を追加する拡張で、**Apache AGE (Apache Graph Extension)** と呼ばれます。Apache AGEを導入することで、PostgreSQL上でノードとエッジからなるグラフ構造を表現・保存し、**openCypherクエリ言語**でグラフに対する問い合わせが可能になります。これにより、従来はNeo4jなど専用のグラフDBを使っていたような複雑なリレーションシップの解析を、PostgreSQL一つで実現できるようになります。例えば、製品とユーザ、レビューをノードとして登録し、関係性(「ユーザが製品を購入した」「レビューが製品について言及した」等)をエッジとしてモデル化すれば、あるユーザが高く評価している特徴を持つ製品をグラフ経由で探索するといった高度なクエリが書けます。Azure Database for PostgreSQLへのApache AGE導入により、**リレーショナルとグラフの統合**が容易になり、別サービスを併用するコストも削減できます。 + +以上の3つの拡張機能を組み合わせることで、Azure Database for PostgreSQLは以下のような強力な機能を備えます。 + +- SQLで直接LLMに質問・文章生成をさせたり、テキストの分析結果を取得できる(azure_ai)。 + +- 埋め込みベクトルを扱うことで意味ベースの検索(類義語や関連コンテキストを考慮した検索)が可能になる(pgvector)。 +- 従来の行・列データに加えてノード・エッジ形式のデータを保持し、パス探索など関係性のクエリを実行できる(AGE)。 + +本ハンズオンのシナリオ(ショッピングサイト)では、**製品説明文やレビューをベクトル化**しておき検索に利用し、**LLMを使った検索結果の関連度評価(リランキング)や要約**を行い、さらに**製品・レビュー・特徴キーワードをグラフ化**して複雑な質問に答える——といった流れを全てPostgreSQL上で実現しています。データがすべて同一のPostgreSQL内に収まり、AI推論呼び出しもデータベース層で行えるため、シンプルで一貫したアプリケーション構成になっているのがポイントです。 + +## 拡張機能を有効化する手順 + +それでは、上記拡張をAzure Database for PostgreSQLで利用可能にする手順を概説します。それぞれPostgreSQLサーバー側で**拡張の有効化(インストール)**を行う必要があります。 + +### azure_ai拡張の有効化手順 + +1. **拡張の許可リスト追加**: Azure Database for PostgreSQLでは、カスタム拡張を使う際にサーバーパラメータ`azure.extensions`にその拡張名を登録する必要があります。Azure Portalの「サーバー パラメーター」設定画面かAzure CLIを使って、当該PostgreSQLサーバーの許可リストに`azure_ai`を追加します。追加後、`SHOW azure.extensions;`コマンドで反映されたか確認してください。 + +2. **拡張のインストール (DBごと)**: 許可リスト設定後、対象のPostgreSQLデータベースに接続し、次のSQLを実行します。 + +```sql +CREATE EXTENSION IF NOT EXISTS azure_ai; +``` + +データベースごとに上記コマンドを実行する必要があります(複数データベースで使いたい場合、それぞれで実行)。 + +3. **認証情報の設定**: `azure_ai`拡張がインストールされると、Azure OpenAIやCognitive Servicesを呼び出すためのエンドポイントURLやAPIキーを設定する関数群が利用できます。例えば、Azure OpenAIの場合はデータベース内の関数 `azure_openai.set_openai_endpoint('エンドポイントURL')` や `azure_openai.set_openai_key('キー')` のようなコマンドで、自分のAzure OpenAIリソースのHTTPエンドポイントとAPIキーを登録します。これらの設定値はデータベース内のテーブルに暗号化保存され、拡張機能の各種関数はこの設定を使ってAzureサービスを呼び出します。 + +> [!NOTE] 注意 +> 本リポジトリのデプロイスクリプトでは、Azure OpenAIリソースの作成後に自動で上記設定関数を実行し、エンドポイントとキーをPostgreSQLに登録しています(手動で設定する必要はありません)。 + +### pgvector拡張の有効化手順 + +1. **拡張の許可リスト確認/追加**: pgvector拡張はPostgreSQLコミュニティ由来の拡張機能です。Azure Database for PostgreSQLでは多くの場合デフォルトで許可されていますが、念のため`SHOW azure.extensions;`で`vector`(※後述)拡張が含まれるか確認します。含まれていない場合は`azure_ai`と同様に許可リストに`vector`を追加します。 + +> [!NOTE] 注意 +> 拡張名は`pgvector`ではなく`vector`として扱います。PostgreSQL上でも`CREATE EXTENSION vector;`と記述する点に注意してください。 + +2. **拡張のインストール**: データベースに接続し、以下のSQLを実行します。 + +```sql +CREATE EXTENSION IF NOT EXISTS vector; +``` + +これで`pgvector`が使えるようになります。インストール後は、ベクトル型(デフォルトでは`vector(1536)`など次元数指定した型)をテーブルで使ったり、`<=>`演算子が利用可能になります。 + +3. **ベクトル類似検索の利用**: 例えば、製品説明文から生成したベクトルを格納するカラム(例: `description_emb vector(1536)`)に対し、ユーザーの問い合わせ文から生成したベクトルとの距離を計算するクエリが書けます。距離計算には`<=>`という演算子を使い、値が小さいほど類似度が高いことを意味します。詳細な使用方法は後述の「ベクトルに対するクエリ」の項で説明します。 + +### Apache AGE拡張の有効化手順 + +1. **新規サーバであることの確認**: 前述の通り、Apache AGE拡張は既存のAzure DB for PostgreSQLサーバには導入できず、新しく作成したPG 13〜16対応サーバでのみプレビュー利用できます。`azd`によるデプロイではサーバ作成が自動で行われるため特別な操作は不要ですが、もし既存サーバに対して手動で導入する場合は、新たにサーバを作り直す必要があります。 + +2. **拡張の許可リスト追加**: サーバーパラメータの許可リストに`age`を追加します。Portalの場合「`AGE (preview)`」等の表記でON/OFFを切り替えられるかもしれません。 + +3. **拡張のインストール**: データベースで以下のSQLを実行します。 + +```sql +CREATE EXTENSION IF NOT EXISTS age CASCADE; +``` + +`CASCADE`オプションにより依存する`ag_catalog`スキーマ等も含めて作成されます。インストールが成功すると、グラフ操作用の関数群(特に`cypher()`関数)が使えるようになります。 + +4. **グラフの作成**: AGEではデータベース内にグラフを作成してからノード・エッジを追加します。グラフは論理的なコンテナ名で、例えば: + +```sql +SELECT * FROM ag_catalog.create_graph('shop_graph'); +``` + +のようにSQLから関数を呼ぶことで`shop_graph`という名のグラフを作成できます。あとはこのグラフ名を指定してノードやエッジを作成・クエリします。 + +5. **openCypherクエリの実行**: AGE拡張では、`cypher('グラフ名', $$ $$)`という関数呼び出しでCypherクエリを実行します。詳しくは後述の「グラフデータに対するクエリ」の項で例を示しますが、SQL文の中でサブクエリとしてCypherを呼び出し、その結果をテーブルのように取り扱うことができます。これにより、グラフの問い合わせ結果とリレーショナルなテーブルを結合することも容易にできます。 + +以上の手順によって、Azure Database for PostgreSQL上でAI・ベクトル・グラフの各機能が使えるようになります。本ハンズオンのデプロイスクリプトでは、デプロイ時に自動でこれら拡張の有効化SQLを実行する処理(`azd-hooks`のプリデプロイスクリプト)が組み込まれているため、参加者が手動でSQLを実行する必要はありません。ただし、仕組みを理解するために、Portal等で対象サーバの`azure.extensions`パラメータが設定され拡張がインストール済みになっていること、`azure_ai`や`age`スキーマがデータベース内に作成されていることなどを確認してみると良いでしょう。 + +[前へ](02-Prerequisites.md) | [次へ](04-Repository.md) diff --git a/docs/workshop/ja/04-Repository.md b/docs/workshop/ja/04-Repository.md new file mode 100644 index 0000000..d1abba2 --- /dev/null +++ b/docs/workshop/ja/04-Repository.md @@ -0,0 +1,40 @@ +# リポジトリのフォークとローカルクローン + +ハンズオンの実施にあたり、まず教材となるGitHub上のリポジトリを自分のアカウントに**フォーク(コピー)し、そのフォークをローカル環境にクローン**する手順を行います。これにより、自分専用のリポジトリとして設定やコードを書き換えても問題ない環境が用意できます。 + +## GitHub上でリポジトリをフォークする手順 + +1. **リポジトリページへアクセス**: ブラウザで[GitHubの該当リポジトリページ](https://github.com/rioriost/postgres-agentic-shop)を開きます。 + +2. **Forkボタンをクリック**: リポジトリの右上付近にある「Fork」ボタンをクリックします。フォーク先として自分のGitHubアカウントを選択し、リポジトリ名はデフォルトのままで構いません(必要に応じて変更も可能ですが、本ハンズオンでは特に変更不要です)。 + +3. **フォークの完了確認**: 数秒待つと、自分のアカウント配下にリポジトリがフォーク(コピー)されます。ブラウザ上でURLが github.com/<あなたのユーザ名>/postgres-agentic-shop になっていることを確認してください。 + +> [!NOTE] 注意 +> フォークは必須ではありませんが、演習中にコードに変更を加える可能性がある場合や、自分の環境で進めたい場合に安心です。そのまま公式リポジトリを直接クローンしても動作上は問題ありません。講師から指示があればそれに従ってください。 + +## gitコマンドでローカルにレポジトリをクローンする手順 + +1. **クローンURLの取得**: フォークしたリポジトリのページで、緑色の「Code」ボタンを押し、表示されるクローン用URLをコピーします。HTTPSでもSSHでも構いません(HTTPSなら `https://github.com/<あなたのユーザ名>/postgres-agentic-shop.git` の形式になります)。 + +2. **ターミナルを開く**: ローカルPCで作業用のディレクトリ(例: `C:\work` や `~/projects`)に移動し、ターミナル(WindowsならPowerShellやコマンドプロンプト、macOS/Linuxならターミナル)を開きます。 + +3. クローン実行: 以下のコマンドを実行してリポジトリをクローンします。 + +```sh +git clone https://github.com/<あなたのユーザ名>/postgres-agentic-shop.git +``` + +※上記URL部分はコピーした自分のフォークのURLに置き換えてください。 + +4. **ディレクトリへ移動**: クローンが完了すると、新たに`postgres-agentic-shop`というフォルダが作成されます。その中に移動します。 + +```sh +cd postgres-agentic-shop +``` + +5. **クローン内容の確認**: `ls`コマンド(Windowsなら`dir`)で中身を確認し、`README.md`や`azure.yaml`ファイル、ソースコードのディレクトリ構成が見られることを確認してください。以降の作業はこのリポジトリディレクトリ内で行います。 + +以上でリポジトリの取得は完了です。エディタでコードを開いたり、`README`を一読して全体像を掴んでおくと良いでしょう。特に、本プロジェクトでは**Azure Developer CLI (azd)**を使ってデプロイしますので、`azure.yaml`や`infra`ディレクトリ内のテンプレートにどんなリソースが定義されているか目を通しておくと理解が深まります。 + +[前へ](03-Integration.md) | [次へ](05-Provisioning.md) diff --git a/docs/workshop/ja/05-Provisioning.md b/docs/workshop/ja/05-Provisioning.md new file mode 100644 index 0000000..1a82c9b --- /dev/null +++ b/docs/workshop/ja/05-Provisioning.md @@ -0,0 +1,58 @@ +# Azure Developer CLIを使ったリソースプロビジョニング + +ローカルにクローンしたリポジトリには、Azure上にインフラおよびアプリケーションコードをデプロイするための設定が含まれています。ここでは**Azure Developer CLI (azd)** を用いて、必要なAzureリソースのプロビジョニングとアプリケーションのデプロイを行います。所要時間はおおよそ20〜30分程度ですが、Azureのリソース作成状況によっては変動します。 + +## `azd up`コマンドによるプロビジョニング + +Azure Developer CLIを使うと、シングルコマンドでインフラの構築からデプロイまで実行できます。以下の手順で進めましょう。 + +1. **Azure CLIへのログイン確認**: 事前準備でAzure CLIをインストール済みですが、念のためログイン状態を確認します。ターミナル上で `az account show` を実行し、サブスクリプション情報が表示されればログイン済みです。未ログインの場合は `az login` を実行してブラウザ認証を完了してください。加えて、`azd`自体もAzureへの認証が必要ですので、`azd auth login`を実行しておきます(ブラウザが自動起動しAzure認証画面が表示されます。うまくいかない場合 `azd auth login --use-device-code` を使用)。 + +2. **環境の初期化 (`azd init`)**: (※既定では必須ではありませんが推奨)`azd init` コマンドを実行し、このプロジェクト用の環境名を設定します。環境名は英数字とハイフンの組み合わせで、他と被らないユニークな名前を付けます(例: `agentic-shop-env1`)。環境名はAzureリソース名の一部としても使われます。ここで`azd init`を実行すると`azure.yaml`に基づきローカルに環境構成が作成されます。 + +3. **デプロイ実行**: 準備が整ったら、いよいよ `azd up` コマンドを実行します。このコマンドは以下の処理を自動で行います。 + +- Bicepテンプレート(`infra`ディレクトリ内)を用いてAzure上にリソースグループおよび必要な各種リソースを作成。 +- 作成したリソースに対して、ポストデプロイのスクリプト(`azd-hooks/predeploy.sh`)を実行して、データベース拡張の有効化やAzure OpenAI設定の書き込みを実施。 +- アプリケーションのコード(バックエンド/APIやフロントエンド)をビルドし、それぞれ指定されたAzureサービス(例: App ServiceやStatic Web Appsなど)にデプロイ。 + +> [!NOTE] 注意 +> Linux及びmacOS環境でデプロイする際は、`azd-hooks/predeploy.sh`に実行権限を付与しておく必要があります。 +> +> `chmod +x azd-hooks/predeploy.sh` +> +> を実行しておいてください(Windowsの場合は不要です)。PowerShellでスクリプト実行ポリシーに関するエラーが出た場合は、一時的に `PowerShell -ExecutionPolicy Bypass -Scope Process` を実行してから再度 `azd up` を試してください。 + +4. **プロンプトへの入力**: `azd up`を実行すると、最初にいくつか入力を求められます。具体的には「デプロイ先のAzureサブスクリプション」「デプロイするAzureリージョン(ロケーション)」「Azure OpenAIモデルのデプロイ先リージョン」などです。ここで、前セクションで検討したリージョンを選択してください。Azure OpenAIについては利用可能なリージョンのみ候補に出ます。また、今回新規に作成するリソースグループ名も求められる場合があります。適宜入力しましょう。 + +> [!CAUTION] 警告 +> OpenAIモデルのクォータに関する警告が表示されることがあります。「指定リージョンに十分なGPT-4のTPMがない」等のメッセージが出た場合は、前述のクォータ条件を満たすように設定を変更するかリージョンを変更する必要があります。 + +5. **デプロイ進行のモニタ**: コマンドを実行すると、ターミナル上に各ステップの進行状況が表示されます。内部ではTerraformのように状態を追跡しつつ、順次リソースが構築されます。特にAzure Database for PostgreSQLやAzure OpenAIリソースの構築には数分単位の時間がかかります。気長に待ちましょう。Azure Portalを開いてリソースグループを確認すると、リアルタイムでリソースが増えていくのが確認できます。 + +6. **デプロイ完了の確認**: 全ての処理が完了すると、`azd`はデプロイ結果の概要を表示します。成功した場合、デプロイした各サービスのエンドポイントURLやリソースの名前などが一覧表示されるはずです(例えばWebアプリのURLなど)。 + +## プロビジョニングで作成される主なリソースと各ステップの説明 + +`azd up`によって自動的に構築・デプロイされる主なAzureリソースと、プロビジョニングプロセス内の各ステップについて簡単に説明します。 + +- **Resource Group(リソースグループ)**: 指定した名前で新規に作成されます。ハンズオン全体のリソースをまとめるコンテナです。不要になったらこのリソースグループごと削除することで関連リソースを一括削除できます。 + +- **Azure Database for PostgreSQL – Flexible Server**: PostgreSQLデータベースサーバが作成されます。本プロジェクトの中核データベースであり、製品情報やレビューなどのテーブルデータ、および拡張機能によるベクトル・グラフデータを格納します。BicepテンプレートでSKUやストレージサイズ、管理ユーザ名などが定義されています(デフォルトでは安価なプランが選ばれているはずです)。 + +- **Azure OpenAI Service**: GPT-4やEmbeddingモデルを使用するためのAzure OpenAIリソースが作成されます。デプロイ時にモデル名やSKUも自動設定されます。例えばGPT-4(あるいはGPT-4-32k)と、text-embedding-ada-002のデプロイがセットアップされます。これにより、データベースからAzure OpenAIにリクエストを送信できるようになります。 + +- **アプリケーションホスティング(App Service / Function / Container Apps 等)**: バックエンドのアプリケーションコードをホストするためのコンピュートリソースが作られます。Pythonで書かれたAPI(エージェントのオーケストレーションロジック)と、TypeScript/JavaScriptで書かれたフロントエンドが存在します。Azure Developer CLIは`azure.yaml`に記載されたサービス定義に基づき、適切なホスティング先を構築・デプロイします。例えば、フロントエンドが静的WebアプリであればAzure Static Web Appsが、バックエンドがPythonならAzure FunctionsやApp Service(Web App)がデプロイされているかもしれません。 + +- **接続設定・シークレット**: 構築された各リソース間の接続情報(例えばデータベースの接続文字列、OpenAIのAPIキーなど)は、Azure Developer CLIによりアプリの設定に組み込まれます。セキュリティ上、本来はAzure Key Vaultなどに格納すべき情報ですが、現時点のテンプレートでは環境変数等で直接App Serviceに設定している可能性があります(今後Key Vault統合予定)。 + +- **拡張機能のセットアップ**: PostgreSQLサーバが起動後、`azd-hooks/predeploy.sh` スクリプトが実行されます。このスクリプト内で、前セクションで述べた `CREATE EXTENSION` 文の実行や、Azure OpenAIエンドポイント・キーをデータベースに登録する `azure_ai.set_setting` 関数の呼び出しなどが行われます。Azure CLIの`rdbms-connect`拡張がここで活躍し、スクリプト内で `az postgres flexible-server connect` コマンドによりPostgreSQLに接続してSQL実行しています。 + +- **デプロイ後処理**: インフラ構築とアプリの配置が終わると、Azure Developer CLIは「デプロイ完了」としてエンドポイント情報を表示します。例えば「WebアプリURL: …」、「Azure OpenAIリソース名: …」などが出力されます。これらは`azure.yaml`で`output`として定義されている項目です。参加者はこの情報をメモするか、そのまま次の検証手順で利用します。 + +もしプロビジョニング中にエラーが発生した場合、エラーメッセージを確認してください。よくある問題としては、環境名に使用不可な文字が含まれていてバリデーションエラーが出る、Azure権限不足でリソース作成が拒否される、既存のリソース名と衝突する、といったことがあります。その場合はメッセージに従い対応するか、`azd up`を再実行すれば再試行されます。 + +> [!NOTE] トラブルシューティング +> DescriptionAzure Developer CLIに関する一般的なトラブルシューティングは[公式ドキュメント](https://github.com/Azure-Samples/postgres-agentic-shop)を参照してください。 + +[前へ](04-Repository.md) | [次へ](06-Post-provisioning.md) diff --git a/docs/workshop/ja/06-Post-provisioning.md b/docs/workshop/ja/06-Post-provisioning.md new file mode 100644 index 0000000..d3164d6 --- /dev/null +++ b/docs/workshop/ja/06-Post-provisioning.md @@ -0,0 +1,52 @@ +# プロビジョニング後の確認ポイント + +デプロイが完了したら、構築されたリソースやアプリケーションが正しく動作しているか確認します。本セクションでは**Azureポータル上で確認すべき主なリソース**と、それらの**役割**、および**アプリケーションの動作確認方法**について説明します。 + +## 主なAzureリソースとそれぞれの役割 + +デプロイされたリソースグループ内に、以下のような主要コンポーネントが存在するはずです。それぞれのリソースと、本ハンズオンアプリケーション内での役割を把握しておきましょう。 + +- **Azure Database for PostgreSQL (Flexible Server)**: +役割: アプリケーションのデータストア。リレーショナルデータ(ユーザ、製品、レビュー等)を保存するとともに、`azure_ai`・`pgvector`・`Apache AGE`拡張を利用したAI処理・ベクトル検索・グラフ分析を実行します。 + +確認内容: Azureポータルの「Azure Database for PostgreSQL」の項目で、新規サーバが作成されていることを確認します。設定->サーバーパラメータで`azure.extensions`に`azure_ai`,`vector`,`age`が含まれていること、接続情報(ホスト名、ユーザ名など)が想定通りか確認しましょう。データベースには初期データとして製品・レビュー情報がロードされているはずです(Arizeなど外部サービス接続情報もテーブルに含まれる可能性あり)。 + +- **Azure OpenAI リソース**: +役割: GPT-4などのモデルをホストする認知サービスリソース。PostgreSQL側からの`azure_ai`経由の呼び出しや、バックエンドコードからのAPI呼び出しに応答します。 + +確認内容: ポータルの「Azure OpenAI」から該当リソースを開き、モデルのデプロイ状況を確認します(「モデルのデプロイ」メニューで、例えば `gpt-4` や `text-embedding-ada-002` が Deploy 状態になっていればOKです)。また、キーとエンドポイントURIを確認し、PostgreSQL側に設定されたものと一致していることも後で検証できます。 + +- **バックエンド アプリ (例: Azure App Service または Function)**: +役割: Python製のアプリケーションロジックがデプロイされているホスティング環境です。ユーザーからの要求(例えば商品検索のクエリ)を受け取り、必要に応じてデータベースに問い合わせたりLLMエージェントを起動して応答を生成します。マルチエージェントのオーケストレーションが行われる中心的存在です。 + +確認内容: ポータルの「App Service」や「Function App」を確認し、新しく作成されたリソースを探します。ネーミングは環境名などが含まれているはずです。App Serviceの場合、「デプロイセンター」や「構成 > アプリケーション設定」を見て、データベース接続文字列やOpenAIエンドポイントなどが環境変数として設定されていることを確認しましょう。また、「ログストリーム」を有効にし、後述のアプリテスト時にログが出力されるかを見るのも有益です。 + +- **フロントエンド アプリ (例: Azure Static Web Apps あるいはStorageの静的サイト)**: +役割: ユーザーインターフェースをホストします。今回TypeScriptのコードが含まれていたため、おそらくReactやVue等のシングルページアプリ(SPA)がビルドされ、静的ファイルがデプロイされていると考えられます。ユーザーはこのフロントエンドを通じて検索クエリを入力したり結果を閲覧します。フロントエンドからバックエンドAPIへの呼び出しもここで行われます。 + +確認内容: Static Web Appsの場合、ポータルの対象リソースを開き、「URL」を確認します。このURLが後述の動作確認で使用するWebアプリのURLになります。Storageアカウントの静的サイトホスティングの場合、エンドポイント域名が表示されています。これらに実際にアクセスしてみて、ページが表示されるか確認します。 + +- **その他リソース**: +Application Insights / Log Analytics: ログ収集用に設定されている可能性があります。バックエンドのログがここに送られていれば、障害調査等で参照できます。 + +Virtual Network / NSG: データベースをセキュアにするためにVNet内に配置されている場合、そのネットワーク関連リソースがあるかもしれません。ただ、Azure Database for PostgreSQL Flexibleはパブリックアクセスでも構築できるので、テンプレート次第です。必要に応じて確認してください。 + +以上のリソースが揃って初めてアプリケーションは動作します。それぞれの役割を理解することで、万一問題が発生した場合にどのコンポーネントを調査すべきか判断しやすくなります。 + +## アプリケーションの動作確認 + +リソースのデプロイが完了したら、実際にアプリケーション(AgenticShop)が期待通り動いているかを確認しましょう。確認すべきポイントを順に説明します。 + +1. **フロントエンドへのアクセス**: 前述のフロントエンドURL(例: `https://<ランダム名>.azurestaticapps.net` やカスタムドメイン)をブラウザで開きます。AgenticShopのトップページが表示されるはずです。画面には検索バーやカテゴリ選択、もしくはチャットボット風のUIがあるかもしれません。デザインは電子ガジェットのショッピングサイトを模していると想定されます。 + +2. **製品情報の表示**: アプリ上で適当な操作をしてみます。例えば商品カテゴリーを選択すると、そのカテゴリの製品一覧やおすすめ商品が表示されるか確認します。AgenticShopではユーザプロファイルに基づくパーソナライズ機能 があるため、ユーザがログインする仕組みや、デフォルトユーザとして何か固定のプロフィールがあるかもしれません。もしログイン機能があれば用意されたテストユーザでログインします(ドキュメントを確認)。 + +3. **検索とAI応答**: 検索バーに質問文を入力してみます(例:「音質が良くてバッテリー長持ちのヘッドフォンはありますか?」など)。入力して送信すると、バックエンドでLLMエージェントが動作し、PostgreSQLからベクトル検索で製品とレビューを取得し、Azure OpenAIで要約・応答を生成する一連のフローが実行されます。その結果、画面上に該当する製品のリストや説明が表示されたり、チャットボットの回答として「おすすめのヘッドフォンは○○です。それは…(理由)」のような出力が得られることを確認します。リランクの効果も確認してみましょう。同じ質問をした際に、単にキーワード検索する場合と比較してより関連性の高い結果が出ているか(例えばノイズキャンセルに関する言及がちゃんと評価されているか)を検証します。 + +4. **グラフ機能の検証**: いくつか複雑な質問を試してみます。例えば「軽くて防水機能があり、音質レビュー評価の高いヘッドフォンは?」といった複合条件の質問です。この質問は製品スペック上の特徴(軽量=デザイン特徴、耐水=機能特徴)と、レビュー内容(音質について高評価か)を組み合わせています。AgenticShopではデータベース内でこのような質問に答えるため、製品とレビューの特徴をグラフデータ化し、さらにLLMの判断も加えて結果を絞り込んでいます。画面上ではトップ3に該当する製品がリストアップされ、各製品の特徴サマリやレビュー要約が表示されることを期待します。実際にそのような結果が得られれば、グラフ + AIが正しく機能している証拠です。 + +5. **デバッグパネルの確認(任意)**: Arize Phoenixによるデバッグパネル機能が含まれている場合、UI上に「Debug」や「Trace」といったボタンやリンクがあるかもしれません。それを開くと、エージェントの内部動作ログ(例えば各ステップでどんなクエリを発行し、どのような応答が得られたか、信頼度はどうか等)が一覧できる可能性があります。このパネルが利用できれば、ハンズオン参加者がエージェントの流れを追いやすくなるので、ぜひ一緒に確認してみてください。 + +以上のように、実際にアプリケーションを操作しながら各機能が期待通り動いているかテストします。特にデータベースの役割が正しく果たされているか(ベクトル検索結果が妥当か、グラフクエリが使われているか)は、Azure Portalの「クエリ パフォーマンス洞察」や`pg_stat_statements`拡張を見れば確認できるでしょう。興味があればバックエンドのログ(App Serviceの場合はLog StreamやApplication Insightsログ)も見てみましょう。Azure OpenAIに投げたプロンプトや応答内容、エラーがあればその内容などが記録されているはずです。 + +[前へ](05-Provisioning.md) | [次へ](07-WhyPostgreSQL.md) diff --git a/docs/workshop/ja/07-WhyPostgreSQL.md b/docs/workshop/ja/07-WhyPostgreSQL.md new file mode 100644 index 0000000..741cfbd --- /dev/null +++ b/docs/workshop/ja/07-WhyPostgreSQL.md @@ -0,0 +1,125 @@ +# PostgreSQL上でのベクトル・リランク・グラフクエリの詳細 + +このセクションでは、AgenticShopソリューション内で実際に使用されているPostgreSQLのテーブル構成と、ベクトル検索・リランク・グラフデータクエリの具体例について説明します。PostgreSQLに拡張機能を組み込むことで可能になった高度なクエリを、どのように使っているかを理解しましょう。 + +## テーブル一覧とスキーマ + +AgenticShopで扱うデータは主に「製品」と「レビュー」に関するものです。リポジトリのデータセット(Agentic Shopデータセット)には、ヘッドフォン、スマートウォッチ、タブレットの3カテゴリについて、製品と対応するユーザーレビューが含まれています。それぞれに対応して、PostgreSQL上には以下のテーブルが存在します。 + +**products テーブル(製品情報)**: 製品の基本情報を格納するテーブルです。主なカラムは以下の通りです: +- id: 製品ID(整数, 主キー) +- name: 製品名(テキスト) +- category: 製品カテゴリ(テキスト、例: “headphones”, “smartwatches”, “tablets”) +- description: 製品の説明文(テキスト、自由記述の詳細な説明) +- description_emb: 製品説明文の埋め込みベクトル(vector型)。Azure OpenAIのEmbeddingモデルで生成した1536次元程度のベクトルが格納されています。 +- features: 製品の特徴をまとめたJSONデータ(jsonb型)。LLMを用いて説明文から抽出した特徴量(例えばデザイン、バッテリー性能、防水性能など)がキーと値の形で格納されています。後述のグラフ分析で利用。 + +また、`description_emb`カラムにはベクトル検索を効率化するためのインデックスが作成されています(`pgvector`はInner ProductやCosine距離用のインデックスをサポート)。 + +**reviews テーブル(レビュー情報)**: ユーザーの製品レビューを格納するテーブルです。主なカラムは以下の通りです: +- id: レビューID(整数, 主キー) +- product_id: レビュー対象の製品ID(整数, 外部キーでproducts.idを参照) +- review_text: レビュー本文(テキスト、ユーザーが書いた自由形式の内容) +- review_text_emb: レビュー本文の埋め込みベクトル(vector型)。こちらも説明文同様にEmbeddingモデルでベクトル化したもの。 +- features: レビューから抽出した特徴をまとめたJSONデータ(jsonb型)。例えば「soundQuality: positive/negative」や「battery: great battery life」といったキー値が含まれます。 +`review_text_emb`にもベクトルインデックスが作成されています。これにより、レビュー内容を意味的に検索する処理が高速化されています。 + +以上2つのテーブルが中心データとなります。リレーションとして、`reviews.product_id`が`products.id`に対する外部キーとなっており、製品とレビューを紐づけています(1製品に複数レビューが対応)。 + +さらに、AgenticShopではApache AGEを用いて上記製品・レビュー・特徴をグラフ化しています。グラフ化に際して以下の概念がノード・エッジとして扱われます。 +**ノード**: +- productノード: 製品1件につき1ノード。属性として製品IDや名前、カテゴリ、特徴JSONなどを保持。 +- reviewノード: レビュー1件につき1ノード。属性としてレビューID、テキスト、特徴JSONなどを保持。 +- featureノード: 製品やレビューから抽出される特徴語(例: “battery life”, “soundQuality”, “waterResistance”, “design”など)ごとにノード。属性に`name`(特徴名)を持つ。 + +**エッジ**: +- (:product)-[:HAS_REVIEW]->(:review): 製品ノードからそのレビューノードへのエッジ。 +- (:product)-[:HAS_FEATURE]->(:feature): 製品ノードから、その製品説明に含まれる特徴ノードへのエッジ。例えば製品説明に「water resistant(防水)」の話があれば、その製品と特徴「waterResistance」を結ぶエッジ。 +- (:review)-[:MENTIONS_FEATURE {sentiment: ...}]->(:feature): レビューノードから、レビュー本文で言及されている特徴ノードへのエッジ。エッジのプロパティ`sentiment`にはその言及がポジティブかネガティブかなど評価が入ります。 + +これらのグラフデータは、PostgreSQLの`AGE`拡張により`ag_catalog`内に管理されています。実際に`products`や`reviews`テーブルから特徴を抽出してグラフノード・エッジを作成する処理は、デプロイ時のスクリプトやバックエンドの初期化コードで実行されています(例えば全製品について特徴ノードを作り、`HAS_FEATURE`エッジを張る`INSERT`文、全レビューについて`MENTIONS_FEATURE`エッジを張る`INSERT`文など)。 + +## ベクトル・リランク・グラフデータに対するクエリ + +AgenticShopでは、上記のデータと拡張機能を駆使して高度なクエリを実行しています。ここでは代表的なクエリの例を挙げ、その内容を解説します。 + +**ベクトル類似検索クエリ(Semantic Similarity Search)**: + +ユーザーの質問や興味に合致する製品やレビューを見つけるために、埋め込みベクトルによる類似検索を行います。例えば「通話品質がクリアでバッテリーの評判が良いヘッドフォンは?」という問いに対し、システムはまず製品説明から類似する製品トップ10を探し、その製品に紐づくレビューの中から類似するレビュートップ10を見つける、といった二段階検索を行います。SQL例としては以下のようになります(簡略化しています): + +```sql +WITH potential_products AS ( + SELECT id, name, description + FROM products + WHERE category = 'headphones' + ORDER BY description_emb <=> azure_openai.create_embeddings('text-embedding-ada-002', 'good clear calling')::vector + ASC + LIMIT 10 +) +SELECT p.id AS product_id, r.id AS review_id, p.name, p.description, r.review_text +FROM potential_products p +LEFT JOIN reviews r USING (product_id) +ORDER BY r.review_text_emb <=> azure_openai.create_embeddings('text-embedding-ada-002', 'good battery life')::vector +ASC +LIMIT 10; +``` + +ここでは、まず`products`テーブルからカテゴリ`headphones`の中で "good clear calling" というフレーズに近い説明文を持つ製品をベクトル距離`<=>`でソートしています。`azure_openai.create_embeddings('text-embedding-ada-002', '...')` はAzure OpenAI経由でクエリ文をベクトル化する関数です(`azure_ai`拡張により提供)。この結果上位10件の製品を`potential_products`として、次にそれらのレビューを`reviews`テーブルから結合し、今度は "good battery life" に近いレビュー文を持つものをベクトル距離でソートしています。最終的に関連性の高いレビューとその製品が取得できます。このように、SQL内で直接埋め込み生成とベクトル比較を行っている点が大きな特徴です。 + +**ドキュメント再ランク(リランキング)クエリ**: +ベクトル検索で得られた結果をさらにLLMによる評価で並べ替えるのがリランキングです。Azure AI拡張の `azure_ai.rank()` 関数は、与えたクエリとドキュメント集合に対して、LLM(もしくはクロスエンコーダーモデル)で関連度スコアを算出し、最もクエリに合致するものから順にランキングを返します。例えば先ほどのレビュー集合に対し「どのレビューが『通話の明瞭さを重視しているか』」を評価するクエリは次のようになります。 + +```sql +WITH reviews(id, text) AS ( + VALUES + (1, 'The product has a great battery life.'), + (2, 'Noise cancellation does not work as advertised. Avoid this product.'), + (3, 'Good design, but a bit heavy. Not recommended for travel.'), + (4, 'Music quality is good but call quality could have been better.') +) +SELECT rr.rank, rr.id, r.text AS review +FROM azure_ai.rank( + 'clear calling capability that blocks out background noise', + ARRAY(SELECT text FROM reviews ORDER BY id), + ARRAY(SELECT id FROM reviews ORDER BY id) + ) AS rr +JOIN reviews r ON r.id = rr.id +ORDER BY rr.rank ASC; +``` + +上記の例では4つのレビュー文を入力とし、「周囲の雑音を遮断してクリアな通話ができるか」という問い合わせとの関連度でランク付けしています。結果は`rank`値が低いほど関連度が高いものとして返り(1位が`rank=1`として出力)、レビューID2「ノイズキャンセルが期待通りに機能しない」が最も該当する(=通話品質に課題が言及されている)と評価されています。このようにLLMがテキストの意味を総合的に判断して順位付けしてくれるため、ベクトル距離だけでは拾いきれない微妙な文脈も考慮した結果調整が可能になります。AgenticShopでは、このリランキングを検索結果の表示順最適化や、ユーザー質問に一番答えているレビュー抽出などに利用しています。 + +**グラフクエリ(openCypherを用いた問合せ)**: +製品・レビュー・特徴をグラフ構造にした利点は、複雑な条件を関係性で絞り込めることにあります。Cypherクエリではパターンマッチを使って、例えば「ヘッドフォンカテゴリの製品で、デザインに関する特徴を持ち、さらに防水機能の特徴も持ち、かつその製品のレビュー群に音質に関するポジティブな言及があるもの」を探すことができます。この条件は非常に複雑ですが、Cypherでは以下のように記述できます: + +```sql +MATCH (p:product {category: 'headphones'}) + -[:HAS_FEATURE]->(:feature {name: 'design'}) + WITH p +MATCH (p)-[:HAS_FEATURE]->(:feature {name: 'waterResistance'}) + WITH p +MATCH (p)-[:HAS_REVIEW]->(r:review) + -[:MENTIONS_FEATURE {sentiment: 'positive'}]->(:feature {name: 'soundQuality'}) +RETURN p.id, r.id; +``` + +これはSQLからは `cypher('products_reviews_features_graph', $$ <上記クエリ> $$)` と呼び出して実行されます。結果として該当する製品IDとレビューIDの組を取得できます。AgenticShopでは、この結果に基づいてさらにSQL側で後処理を行っています。例えば、上記で得た製品とレビューの詳細情報を通常のテーブル(`products`, `reviews`)から引き出し、製品説明中に「軽量」であるかをLLMに判定させて不適合品を除外(`azure_ai.is_true`関数の利用)、最終的に製品ごとのレビュー数をカウントしつつLLMで要約文を生成する(`azure_ai.generate`関数の利用)――という具合です。一連の最終クエリはかなり長いものになりますが、SQLとCypherとAI関数を組み合わせて一回の問合せで結果を集約しています。このクエリにより、「軽量かつ防水で音質レビュー評価が高いヘッドフォン」の上位3つが要約付きで得られます。 + +実際の結果例では、製品IDと名前、それぞれの製品特徴サマリ(デザイン・防水の要約)とレビュー要約、レビュー件数が出力されています。LLMに要約させることで、ユーザーには「このヘッドフォンは軽量設計で防水性能があります。レビューでは音質が高く評価されました。」のように簡潔に特徴が伝えられるわけです。 + +以上、AgenticShopで用いられる高度なクエリについて見てきました。 + +まとめると、ベクトル検索は関連アイテムの絞り込みに、リランキングはLLMの理解による結果精度向上に、グラフクエリは複数条件を組み合わせた関係性抽出に、それぞれ威力を発揮しています。それらをPostgreSQL内で一貫して使えるため、データ移動のコストも無く複雑な質問に答えられるのです。 + +ハンズオンでは、実際にPostgreSQLに接続して上記のようなクエリを試すことも推奨します。`psql`やAzure Data Studio等でデータベースに接続し、`SELECT * FROM products LIMIT 5;`でデータを覗いてみたり、簡単なCypherクエリを実行してみましょう。たとえば、全製品ノード数とレビューノード数を数えるクエリ: + +```sql +SELECT * +FROM cypher('products_reviews_features_graph', $$ + MATCH (p:product) RETURN count(p) +$$) AS t(count bigint); +``` + +などを試すと、グラフにデータが入っていることが確認できます。 + +[前へ](06-Post-provisioning.md) | [次へ](08-Wrapup.md) diff --git a/docs/workshop/ja/08-Wrapup.md b/docs/workshop/ja/08-Wrapup.md new file mode 100644 index 0000000..e12bc04 --- /dev/null +++ b/docs/workshop/ja/08-Wrapup.md @@ -0,0 +1,32 @@ +# プロビジョニングしたリソースの削除 + +ハンズオンが終了し、不要となったリソースは確実に削除しましょう。Azure上に作成したリソースを放置すると予期せぬコストが発生する可能性があります。ここではAzure Developer CLIを使ったクリーンアップ手順を紹介します。 + +## リソースの削除手順 + +Azure Developer CLIでは、デプロイした環境を一括で削除するコマンドが用意されています。特に今回のようにリソースグループごと作成した場合、そのリソースグループと中の全リソースをまとめて消去できます。 + +- **削除コマンドの実行**: プロジェクトのディレクトリ(postgres-agentic-shop)内で、以下のコマンドを実行します。 + +```sh +azd down --purge +``` + +これにより、`azd up`で作成された全リソースが削除されます。`--purge`オプションを付けることで、Azure OpenAIのような「アカウント」リソースも含め完全に削除されます。通常の`azd down`ではリソースグループ内の削除のみですが、`--purge`を付けると例えばOpenAIリソースからモデルのデプロイ解除→リソース削除まで行われます。削除には数分かかることがありますが、完了すると「削除成功」のようなメッセージが表示されます。 + +- **削除結果の確認**: 念のためAzureポータルでリソースグループ一覧を確認し、該当のリソースグループが消えていることを確認してください。`azd`の環境もローカルに記録が残っていますが、再利用しない場合は `.azure` ディレクトリごと削除しておいて構いません。 + +- **代替手段**: Azure Developer CLIを使用しない場合、Azureポータルからリソースグループを手動で削除しても同様です。その場合、Azure OpenAIリソースのみリソースグループ外に存在する可能性がありますが、今回は同じRG内に作成されているはずなので、一括削除できる見込みです。ポータルでリソースグループを削除する際は、リソースグループ名の入力確認がありますので指示に従ってください。 + +> [!CAUTION] 警告 +> 一度削除を行うと、データベース内のデータや接続情報、ログなど全て失われます。ハンズオン中に何か成果物(例: エクスポートした特徴データや分析結果)がある場合は事前にバックアップしておいてください。もっとも、本ハンズオンでは特に保持すべきデータはありませんので、そのまま削除して問題ありません。 + +以上で後片付けは完了です。Azure上に不要なリソースを残さない習慣は、コスト管理上とても重要ですので、ハンズオンの最後には必ずこの手順を実施するようにしましょう。 + +これで、GitHubリポジトリ postgres-agentic-shop を用いたAzure上でのAI統合アプリケーション開発ハンズオンの全工程が終了です。 + +お疲れ様でした! + +今回学んだように、Azure Database for PostgreSQLにAI拡張や`pgvector`、Apache AGEを組み合わせることで、シンプルながら強力なAIアプリケーション基盤を構築できます。ぜひ現場のプロジェクトでも応用してみてください。各セクションで触れた技術要素について更に深掘りしたい場合、公式ドキュメントや関連ブログ記事なども参照すると理解が深まるでしょう。 + +[前へ](07-WhyPostgreSQL.md) From ca0b5f0baa5c77680c7f108b9c24601bd9efd0c3 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Thu, 22 May 2025 14:30:42 +0900 Subject: [PATCH 03/23] Fixed wordings, wrong link --- docs/workshop/ja/01-Introduction.md | 5 +++-- docs/workshop/ja/02-Prerequisites.md | 6 +++--- docs/workshop/ja/03-Integration.md | 5 ++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/workshop/ja/01-Introduction.md b/docs/workshop/ja/01-Introduction.md index 2b033be..7a36760 100644 --- a/docs/workshop/ja/01-Introduction.md +++ b/docs/workshop/ja/01-Introduction.md @@ -1,6 +1,6 @@ # はじめに -**AgenticShop**は、Azure Database for PostgreSQLを活用したマルチエージェントの小売体験を示すソリューションアーキテクチャです 。このリポジトリのアプリケーションは、電子機器ガジェットのショッピング体験をAIによって高度化したデモとなっており、Azure上でワンクリックデプロイできるハンズオン教材として位置付けられています。アプリケーション名にある「Agentic(エージェンティック)」とは、複数のAIエージェントが対話し協調してタスクをこなすアプリケーションを意味します。大規模言語モデル(LLM)を用いたエージェントが自律的に計画立案し、必要に応じてデータベースやAPIなどのツールを利用しながらユーザ要求を満たすのが特徴です。 +**AgenticShop**は、Azure Database for PostgreSQLを活用したマルチエージェントの小売体験を示すソリューションアクセラレータです。このリポジトリのアプリケーションは、電子機器ガジェットのショッピング体験をAIによって高度化したデモとなっており、Azure上でワンクリックデプロイできるハンズオン教材として位置付けられています。アプリケーション名にある「Agentic(エージェンティック)」とは、複数のAIエージェントが対話し協調してタスクをこなすアプリケーションを意味します。大規模言語モデル(LLM)を用いたエージェントが自律的に計画立案し、必要に応じてデータベースやAPIなどのツールを利用しながらユーザ要求を満たすのが特徴です。 ## アプリケーションのアーキテクチャ @@ -8,9 +8,10 @@ ## Agenticなアプリケーションの特徴と実装 -このアプリケーションの最大の特徴は、複数のAIエージェントが連携してユーザーにパーソナライズされたショッピング情報を提供する点です。例えば、あるエージェントはユーザープロファイルに基づいて好みに合いそうな製品を選定し、別のエージェントは製品に関するデータベース検索(自然言語クエリをSQLに変換しベクトル検索)を行い、さらに別のエージェントがレビュー要約や特徴抽出を担う、といった分担が考えられます。このマルチエージェントワークフローにより、製品のパーソナライズドな詳細情報提示や高度なユーザーエクスペリエンスが実現されています。エージェント間の調停やタスクの流れはLlamaIndexによって制御され、LLMが各ステップで適切なアクション(データベースへの問い合わせや別エージェントへの引き継ぎ)を選択できるよう実装されています。総じて、AgenticShopはAIエージェントがバックエンドで自律的に動作する次世代のアプリケーション構築手法を示す例となっており、Azure上でそれを構築・体験できるようになっています。 +このアプリケーションの最大の特徴は、複数のAIエージェントが連携してユーザーにパーソナライズされたショッピング情報を提供する点です。例えば、あるエージェントはユーザープロファイルに基づいて好みに合いそうな製品を選定し、別のエージェントは製品に関するデータベース検索(自然言語クエリをSQLに変換しベクトル検索)を行い、さらに別のエージェントがレビュー要約や特徴抽出を担う、といった分担をしています。このマルチエージェントワークフローにより、製品のパーソナライズドな詳細情報提示や高度なユーザーエクスペリエンスが実現されています。エージェント間の調停やタスクの流れはLlamaIndexによって制御され、LLMが各ステップで適切なアクション(データベースへの問い合わせや別エージェントへの引き継ぎ)を選択できるよう実装されています。総じて、AgenticShopはAIエージェントがバックエンドで自律的に動作する次世代のアプリケーション構築手法を示す例となっており、Azure上でそれを構築・体験できるようになっています。 ## Key Features(主な機能) + 本ソリューションアクセラレータには以下のような特徴があります - **ユーザープロファイルに基づく製品詳細提示**: 各ユーザーの属性や嗜好に合わせて、関連性の高い製品情報や説明が自動生成・表示されます。 - **向上したユーザーエクスペリエンス**: AIを活用したチャットボット的な対話や要約表示、レコメンデーションにより、従来の通販サイトよりリッチな体験を提供します。 diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index f1eb7bd..a53f360 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -28,7 +28,7 @@ - **Azure OpenAI対応リージョン**: East USやSouth Central US、西ヨーロッパ、東日本など、Azure OpenAIリソースを作成できるリージョンを指定します。自分のサブスクリプションでOpenAIのサービス作成が許可されているリージョンか事前に確認してください。 -- **データベースと近接した場所**: パフォーマンスの観点から、Azure Database for PostgreSQLを配置するリージョンと物理的に近いリージョンをAzure OpenAIに選ぶのが望ましいです。例えば、東日本リージョンにDBを作成した場合、Azure OpenAIも日本または近隣のリージョンを選択します(ただしOpenAIサービスが限られるため妥協が必要な場合もあります)。 +- **データベースと近接した場所**: パフォーマンスの観点から、Azure Database for PostgreSQLを配置するリージョンと物理的に近いリージョンをAzure OpenAIに選ぶのが望ましいです。例えば、東日本リージョンにDBを作成した場合、Azure OpenAIも東日本または近隣のリージョンを選択します(ただしOpenAIサービスが限られるため妥協が必要な場合もあります)。 - **プレビュー機能の利用**: Apache AGE拡張は新規作成のPostgreSQLサーバでPG 13〜16の場合に利用可能で、既存サーバには有効化できません。`azd up`では自動的に新しいPostgreSQLサーバを作成しますが、念のためAzure Portalでプレビュー機能利用に関する記載がないか確認すると安心です。 @@ -43,6 +43,6 @@ Azure OpenAIサービスを利用するには、モデルごとの使用上限 ご自身のAzureサブスクリプションのOpenAIクォータは、Azure Portalの「クォータ + 制限」ページや`az openai admin quota show`コマンドで確認できます。不足している場合は、Azure OpenAIリソースの作成時に上限引き上げのリクエストを申請してください。 > [!NOTE] 注意 -> `azd up`実行時のプロンプトでも、上記モデルのデプロイ容量 (デフォルト値) をパラメータとして設定するようになっています。必要に応じてリポジトリ内の infra/main.parameters.json を編集することで、GPT-4や埋め込みモデルのデプロイスケールを調整できます(クォータ節約のためにTPSを下げる等)。 +> `azd up`実行時のプロンプトでも、上記モデルのデプロイ容量 (デフォルト値) をパラメータとして設定するようになっています。必要に応じてリポジトリ内の `infra/main.parameters.json` を編集することで、GPT-4や埋め込みモデルのデプロイスケールを調整できます(クォータ節約のためにTPSを下げる等)。 -[前へ](01-Introduction.md) | [次へ](03-Introduction.md) +[前へ](01-Introduction.md) | [次へ](03-Integration.md) diff --git a/docs/workshop/ja/03-Integration.md b/docs/workshop/ja/03-Integration.md index 159c061..208baab 100644 --- a/docs/workshop/ja/03-Integration.md +++ b/docs/workshop/ja/03-Integration.md @@ -6,14 +6,13 @@ Azure Database for PostgreSQLを単なるリレーショナルデータベース - **azure_ai拡張機能 (プレビュー)**: Azure Database for PostgreSQLのFlexibleサーバ向けに提供されている新機能で、データベース内から直接Azureの各種AIサービス(Azure OpenAIやAzure Cognitive Servicesなど)を呼び出すことを可能にします。これにより、SQLクエリの中でテキストの埋め込みベクトル生成やLLMによる文章生成・要約、テキストの分析(感情分析やキーフレーズ抽出など)が行えるようになります。言い換えると、Azureの強力なAIモデルをPostgreSQL内にシームレスに統合し、**データベースがAIの機能を直接利用できる**ようになる拡張です。azure_ai拡張をインストールすると、データベース内に専用のスキーマ(`azure_ai`や`azure_openai`、`azure_cognitive`)が作成され、そこに各種AIサービスを呼び出す関数が提供されます。例えば、`azure_openai.create_embeddings()`で埋め込みベクトルを生成したり、`azure_ai.generate()`でLLMによるテキスト生成を行ったり、`azure_ai.rank()`で文章の関連度評価(リランキング)を行うことができます。 -- **pgvector拡張機能**: PostgreSQLにオープンソースの**ベクトル類似検索機能**を追加する拡張です。テキストや画像などから生成された特徴ベクトルをデータベース内にそのまま保存し、高速な近似近傍検索(ANN)が可能となります。pgvectorを使うことで、ベクトルデータベース専用の製品を使わずともPostgreSQLで埋め込みベクトルの格納・検索ができ、RDB内の他のリレーショナルデータと組み合わせたクエリも容易になります。たとえばSQLの中で特殊な演算子<=>を使ってベクトル間の距離(類似度)を計算することができ、あるクエリベクトルに対して最も距離の近い(=内容が類似した)上位N件のレコードを取得するといった操作がシンプルに書けます。Azure Database for PostgreSQLではpgvector拡張がサポートされており、埋め込みベクトルとAzure OpenAIの組み合わせでRAG(Retrieval Augmented Generation)パターンが実装できます。 +- **pgvector拡張機能**: PostgreSQLにオープンソースの**ベクトル類似検索機能**を追加する拡張です。テキストや画像などから生成された特徴ベクトルをデータベース内にそのまま保存し、高速な近似近傍検索(ANN)が可能となります。pgvectorを使うことで、ベクトルデータベース専用の製品を使わずともPostgreSQLで埋め込みベクトルの格納・検索ができ、RDB内の他のリレーショナルデータと組み合わせたクエリも容易になります。たとえばSQLの中で特殊な演算子`<=>`を使ってベクトル間の距離(類似度)を計算することができ、あるクエリベクトルに対して最も距離の近い(=内容が類似した)上位N件のレコードを取得するといった操作がシンプルに書けます。Azure Database for PostgreSQLではpgvector拡張がサポートされており、埋め込みベクトルとAzure OpenAIの組み合わせでRAG(Retrieval Augmented Generation)パターンが実装できます。 - **Apache AGE拡張機能**: PostgreSQLにグラフデータベースの機能を追加する拡張で、**Apache AGE (Apache Graph Extension)** と呼ばれます。Apache AGEを導入することで、PostgreSQL上でノードとエッジからなるグラフ構造を表現・保存し、**openCypherクエリ言語**でグラフに対する問い合わせが可能になります。これにより、従来はNeo4jなど専用のグラフDBを使っていたような複雑なリレーションシップの解析を、PostgreSQL一つで実現できるようになります。例えば、製品とユーザ、レビューをノードとして登録し、関係性(「ユーザが製品を購入した」「レビューが製品について言及した」等)をエッジとしてモデル化すれば、あるユーザが高く評価している特徴を持つ製品をグラフ経由で探索するといった高度なクエリが書けます。Azure Database for PostgreSQLへのApache AGE導入により、**リレーショナルとグラフの統合**が容易になり、別サービスを併用するコストも削減できます。 以上の3つの拡張機能を組み合わせることで、Azure Database for PostgreSQLは以下のような強力な機能を備えます。 - SQLで直接LLMに質問・文章生成をさせたり、テキストの分析結果を取得できる(azure_ai)。 - - 埋め込みベクトルを扱うことで意味ベースの検索(類義語や関連コンテキストを考慮した検索)が可能になる(pgvector)。 - 従来の行・列データに加えてノード・エッジ形式のデータを保持し、パス探索など関係性のクエリを実行できる(AGE)。 @@ -59,7 +58,7 @@ CREATE EXTENSION IF NOT EXISTS vector; ### Apache AGE拡張の有効化手順 -1. **新規サーバであることの確認**: 前述の通り、Apache AGE拡張は既存のAzure DB for PostgreSQLサーバには導入できず、新しく作成したPG 13〜16対応サーバでのみプレビュー利用できます。`azd`によるデプロイではサーバ作成が自動で行われるため特別な操作は不要ですが、もし既存サーバに対して手動で導入する場合は、新たにサーバを作り直す必要があります。 +1. **新規サーバであることの確認**: 前述の通り、Apache AGE拡張は既存のAzure Database for PostgreSQLサーバには導入できず、新しく作成したPG 13〜16対応サーバでのみプレビュー利用できます。`azd`によるデプロイではサーバ作成が自動で行われるため特別な操作は不要ですが、もし既存サーバに対して手動で導入する場合は、新たにサーバを作り直す必要があります。 2. **拡張の許可リスト追加**: サーバーパラメータの許可リストに`age`を追加します。Portalの場合「`AGE (preview)`」等の表記でON/OFFを切り替えられるかもしれません。 From 42b446a75fb18393681c0a124c1c83f9328888fd Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Thu, 22 May 2025 15:06:39 +0900 Subject: [PATCH 04/23] Small fixes --- docs/workshop/ja/02-Prerequisites.md | 2 ++ docs/workshop/ja/05-Provisioning.md | 6 +++--- docs/workshop/ja/06-Post-provisioning.md | 4 ++-- docs/workshop/ja/07-WhyPostgreSQL.md | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index a53f360..102bd2f 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -20,6 +20,8 @@ - **PowerShell Core (Windowsユーザーのみ)**: WindowsでLinux向けスクリプトを実行する場合に必要です。事前に[PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5)をインストールしておいてください。 +- **psql もしくは pgAdmin**: PostgreSQLに接続し、テーブルやデータの確認・操作に利用します。psqlはPostgreSQLのパッケージに含まれているので、[PostgreSQLのダウンロードページ](https://www.postgresql.org/download/)から、pgAdminは[pgAdminのダウンロードページ](https://www.pgadmin.org/download/)から入手可能です。 + インストール後、それぞれのコマンドがパスに通っていることを確認しましょう。例えば`azd version`や`az --version`を実行してバージョン情報が表示されれば準備OKです。 ## Azureリージョンの選定条件 diff --git a/docs/workshop/ja/05-Provisioning.md b/docs/workshop/ja/05-Provisioning.md index 1a82c9b..01cd834 100644 --- a/docs/workshop/ja/05-Provisioning.md +++ b/docs/workshop/ja/05-Provisioning.md @@ -4,11 +4,11 @@ ## `azd up`コマンドによるプロビジョニング -Azure Developer CLIを使うと、シングルコマンドでインフラの構築からデプロイまで実行できます。以下の手順で進めましょう。 +Azure Developer CLIを使うと、単一のコマンドでインフラの構築からデプロイまで実行できます。以下の手順で進めましょう。 1. **Azure CLIへのログイン確認**: 事前準備でAzure CLIをインストール済みですが、念のためログイン状態を確認します。ターミナル上で `az account show` を実行し、サブスクリプション情報が表示されればログイン済みです。未ログインの場合は `az login` を実行してブラウザ認証を完了してください。加えて、`azd`自体もAzureへの認証が必要ですので、`azd auth login`を実行しておきます(ブラウザが自動起動しAzure認証画面が表示されます。うまくいかない場合 `azd auth login --use-device-code` を使用)。 -2. **環境の初期化 (`azd init`)**: (※既定では必須ではありませんが推奨)`azd init` コマンドを実行し、このプロジェクト用の環境名を設定します。環境名は英数字とハイフンの組み合わせで、他と被らないユニークな名前を付けます(例: `agentic-shop-env1`)。環境名はAzureリソース名の一部としても使われます。ここで`azd init`を実行すると`azure.yaml`に基づきローカルに環境構成が作成されます。 +2. **環境の初期化 (`azd init`)**: (※既定では必須ではありませんが推奨)`azd init` コマンドを実行し、このプロジェクト用の環境名を設定します。環境名は英数字の組み合わせで、他と被らないユニークな名前を付けます(例: `agshopenv1`)。環境名はAzureリソース名の一部としても使われます。ここで`azd init`を実行すると`azure.yaml`に基づきローカルに環境構成が作成されます。 3. **デプロイ実行**: 準備が整ったら、いよいよ `azd up` コマンドを実行します。このコマンドは以下の処理を自動で行います。 @@ -36,7 +36,7 @@ Azure Developer CLIを使うと、シングルコマンドでインフラの構 `azd up`によって自動的に構築・デプロイされる主なAzureリソースと、プロビジョニングプロセス内の各ステップについて簡単に説明します。 -- **Resource Group(リソースグループ)**: 指定した名前で新規に作成されます。ハンズオン全体のリソースをまとめるコンテナです。不要になったらこのリソースグループごと削除することで関連リソースを一括削除できます。 +- **Resource Group(リソースグループ)**: 指定した名前で新規に作成されます。ハンズオン全体のリソースをまとめるコンテナです。 - **Azure Database for PostgreSQL – Flexible Server**: PostgreSQLデータベースサーバが作成されます。本プロジェクトの中核データベースであり、製品情報やレビューなどのテーブルデータ、および拡張機能によるベクトル・グラフデータを格納します。BicepテンプレートでSKUやストレージサイズ、管理ユーザ名などが定義されています(デフォルトでは安価なプランが選ばれているはずです)。 diff --git a/docs/workshop/ja/06-Post-provisioning.md b/docs/workshop/ja/06-Post-provisioning.md index d3164d6..4b8bd3a 100644 --- a/docs/workshop/ja/06-Post-provisioning.md +++ b/docs/workshop/ja/06-Post-provisioning.md @@ -37,9 +37,9 @@ Virtual Network / NSG: データベースをセキュアにするためにVNet リソースのデプロイが完了したら、実際にアプリケーション(AgenticShop)が期待通り動いているかを確認しましょう。確認すべきポイントを順に説明します。 -1. **フロントエンドへのアクセス**: 前述のフロントエンドURL(例: `https://<ランダム名>.azurestaticapps.net` やカスタムドメイン)をブラウザで開きます。AgenticShopのトップページが表示されるはずです。画面には検索バーやカテゴリ選択、もしくはチャットボット風のUIがあるかもしれません。デザインは電子ガジェットのショッピングサイトを模していると想定されます。 +1. **フロントエンドへのアクセス**: 前述のフロントエンドURL(例: `https://<ランダム名>.azurestaticapps.net` やカスタムドメイン)をブラウザで開きます。AgenticShopのトップページが表示されるはずです。画面には検索バーやカテゴリ選択、もしくはチャットボット風のUIがあるかもしれません。デザインは電子ガジェットのショッピングサイトを模しています。 -2. **製品情報の表示**: アプリ上で適当な操作をしてみます。例えば商品カテゴリーを選択すると、そのカテゴリの製品一覧やおすすめ商品が表示されるか確認します。AgenticShopではユーザプロファイルに基づくパーソナライズ機能 があるため、ユーザがログインする仕組みや、デフォルトユーザとして何か固定のプロフィールがあるかもしれません。もしログイン機能があれば用意されたテストユーザでログインします(ドキュメントを確認)。 +2. **製品情報の表示**: アプリ上で適当な操作をしてみます。例えば商品カテゴリーを選択すると、そのカテゴリの製品一覧やおすすめ商品が表示されるか確認します。AgenticShopではユーザプロファイルに基づくパーソナライズ機能があります。 3. **検索とAI応答**: 検索バーに質問文を入力してみます(例:「音質が良くてバッテリー長持ちのヘッドフォンはありますか?」など)。入力して送信すると、バックエンドでLLMエージェントが動作し、PostgreSQLからベクトル検索で製品とレビューを取得し、Azure OpenAIで要約・応答を生成する一連のフローが実行されます。その結果、画面上に該当する製品のリストや説明が表示されたり、チャットボットの回答として「おすすめのヘッドフォンは○○です。それは…(理由)」のような出力が得られることを確認します。リランクの効果も確認してみましょう。同じ質問をした際に、単にキーワード検索する場合と比較してより関連性の高い結果が出ているか(例えばノイズキャンセルに関する言及がちゃんと評価されているか)を検証します。 diff --git a/docs/workshop/ja/07-WhyPostgreSQL.md b/docs/workshop/ja/07-WhyPostgreSQL.md index 741cfbd..4ad5b3e 100644 --- a/docs/workshop/ja/07-WhyPostgreSQL.md +++ b/docs/workshop/ja/07-WhyPostgreSQL.md @@ -87,7 +87,7 @@ JOIN reviews r ON r.id = rr.id ORDER BY rr.rank ASC; ``` -上記の例では4つのレビュー文を入力とし、「周囲の雑音を遮断してクリアな通話ができるか」という問い合わせとの関連度でランク付けしています。結果は`rank`値が低いほど関連度が高いものとして返り(1位が`rank=1`として出力)、レビューID2「ノイズキャンセルが期待通りに機能しない」が最も該当する(=通話品質に課題が言及されている)と評価されています。このようにLLMがテキストの意味を総合的に判断して順位付けしてくれるため、ベクトル距離だけでは拾いきれない微妙な文脈も考慮した結果調整が可能になります。AgenticShopでは、このリランキングを検索結果の表示順最適化や、ユーザー質問に一番答えているレビュー抽出などに利用しています。 +上記の例では4つのレビュー文を入力とし、「周囲の雑音を遮断してクリアな通話ができるか」という問い合わせとの関連度でランク付けしています。結果は`rank`値が小さいほど関連度が高いものとして返り(1位が`rank=1`として出力)、レビューID2「ノイズキャンセルが期待通りに機能しない」が最も該当する(=通話品質に課題が言及されている)と評価されています。このようにLLMがテキストの意味を総合的に判断して順位付けしてくれるため、ベクトル距離だけでは拾いきれない微妙な文脈も考慮した結果調整が可能になります。AgenticShopでは、このリランキングを検索結果の表示順最適化や、ユーザー質問に一番答えているレビュー抽出などに利用しています。 **グラフクエリ(openCypherを用いた問合せ)**: 製品・レビュー・特徴をグラフ構造にした利点は、複雑な条件を関係性で絞り込めることにあります。Cypherクエリではパターンマッチを使って、例えば「ヘッドフォンカテゴリの製品で、デザインに関する特徴を持ち、さらに防水機能の特徴も持ち、かつその製品のレビュー群に音質に関するポジティブな言及があるもの」を探すことができます。この条件は非常に複雑ですが、Cypherでは以下のように記述できます: @@ -111,7 +111,7 @@ RETURN p.id, r.id; まとめると、ベクトル検索は関連アイテムの絞り込みに、リランキングはLLMの理解による結果精度向上に、グラフクエリは複数条件を組み合わせた関係性抽出に、それぞれ威力を発揮しています。それらをPostgreSQL内で一貫して使えるため、データ移動のコストも無く複雑な質問に答えられるのです。 -ハンズオンでは、実際にPostgreSQLに接続して上記のようなクエリを試すことも推奨します。`psql`やAzure Data Studio等でデータベースに接続し、`SELECT * FROM products LIMIT 5;`でデータを覗いてみたり、簡単なCypherクエリを実行してみましょう。たとえば、全製品ノード数とレビューノード数を数えるクエリ: +ハンズオンでは、実際にPostgreSQLに接続して上記のようなクエリを試すことも推奨します。`psql`や`pgAdmin`等でデータベースに接続し、`SELECT * FROM products LIMIT 5;`でデータを覗いてみたり、簡単なCypherクエリを実行してみましょう。たとえば、全製品ノード数とレビューノード数を数えるクエリ: ```sql SELECT * From 13de33ad198cca70c051f60b962c9b72b3363a3c Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 26 May 2025 14:02:27 +0900 Subject: [PATCH 05/23] Fixed table schemas --- docs/workshop/ja/01-Introduction.md | 2 ++ docs/workshop/ja/07-WhyPostgreSQL.md | 32 ++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/workshop/ja/01-Introduction.md b/docs/workshop/ja/01-Introduction.md index 7a36760..c144357 100644 --- a/docs/workshop/ja/01-Introduction.md +++ b/docs/workshop/ja/01-Introduction.md @@ -2,6 +2,8 @@ **AgenticShop**は、Azure Database for PostgreSQLを活用したマルチエージェントの小売体験を示すソリューションアクセラレータです。このリポジトリのアプリケーションは、電子機器ガジェットのショッピング体験をAIによって高度化したデモとなっており、Azure上でワンクリックデプロイできるハンズオン教材として位置付けられています。アプリケーション名にある「Agentic(エージェンティック)」とは、複数のAIエージェントが対話し協調してタスクをこなすアプリケーションを意味します。大規模言語モデル(LLM)を用いたエージェントが自律的に計画立案し、必要に応じてデータベースやAPIなどのツールを利用しながらユーザ要求を満たすのが特徴です。 +このソリューションアクセラレータの説明は、[動画で公開](https://build.microsoft.com/en-US/sessions/BRK211)されています。 + ## アプリケーションのアーキテクチャ このソリューションの全体アーキテクチャは、フロントエンド、バックエンド、データベースおよびAIサービスから構成されています。フロントエンドはユーザーにショッピングUIを提供し、バックエンドはAIエージェントのワークフローを実行します。バックエンドではPython製のエージェントオーケストレーター(LlamaIndexライブラリを使用)が稼働し、Azure Database for PostgreSQLからのデータ取得やAzure OpenAIサービスへの問い合わせなど複数のタスクをエージェントが順次処理します。また、アプリケーションのデバッグ・解析用に**Arize Phoenix**と呼ばれるトレーシング機能も組み込まれており、エージェントの内部動作(どのようなクエリを発行し、どんな回答を得たか)を可視化できるようになっています。Azure Database for PostgreSQLは単なるデータストアに留まらず、後述する拡張機能(AI、ベクトル、グラフ機能)を利用して**知的なクエリ処理**を行う重要な役割を担っています。 diff --git a/docs/workshop/ja/07-WhyPostgreSQL.md b/docs/workshop/ja/07-WhyPostgreSQL.md index 4ad5b3e..c382e85 100644 --- a/docs/workshop/ja/07-WhyPostgreSQL.md +++ b/docs/workshop/ja/07-WhyPostgreSQL.md @@ -6,38 +6,42 @@ AgenticShopで扱うデータは主に「製品」と「レビュー」に関するものです。リポジトリのデータセット(Agentic Shopデータセット)には、ヘッドフォン、スマートウォッチ、タブレットの3カテゴリについて、製品と対応するユーザーレビューが含まれています。それぞれに対応して、PostgreSQL上には以下のテーブルが存在します。 -**products テーブル(製品情報)**: 製品の基本情報を格納するテーブルです。主なカラムは以下の通りです: +**product テーブル(製品情報)**: 製品の基本情報を格納するテーブルです。主なカラムは以下の通りです: - id: 製品ID(整数, 主キー) - name: 製品名(テキスト) - category: 製品カテゴリ(テキスト、例: “headphones”, “smartwatches”, “tablets”) +- price: 価格(整数) +- brand: ブランド名(可変長テキスト) - description: 製品の説明文(テキスト、自由記述の詳細な説明) -- description_emb: 製品説明文の埋め込みベクトル(vector型)。Azure OpenAIのEmbeddingモデルで生成した1536次元程度のベクトルが格納されています。 -- features: 製品の特徴をまとめたJSONデータ(jsonb型)。LLMを用いて説明文から抽出した特徴量(例えばデザイン、バッテリー性能、防水性能など)がキーと値の形で格納されています。後述のグラフ分析で利用。 +- specifications: 製品の特徴をまとめたJSONデータ(jsonb型)。LLMを用いて説明文から抽出した特徴量(例えばデザイン、バッテリー性能、防水性能など)がキーと値の形で格納されています。後述のグラフ分析で利用。 -また、`description_emb`カラムにはベクトル検索を効率化するためのインデックスが作成されています(`pgvector`はInner ProductやCosine距離用のインデックスをサポート)。 +**data_embeddings_products テーブル(製品の埋め込み情報)**: 製品の説明文の埋め込みを格納するテーブルです。 +- embedding: 製品説明文の埋め込みベクトル(vector型)。Azure OpenAIのEmbeddingモデルで生成した1536次元程度のベクトルが格納されています。 + +また、`embedding`カラムにはベクトル検索を効率化するためのインデックスが作成されています(`pgvector`はInner ProductやCosine距離用のインデックスをサポート)。 **reviews テーブル(レビュー情報)**: ユーザーの製品レビューを格納するテーブルです。主なカラムは以下の通りです: - id: レビューID(整数, 主キー) - product_id: レビュー対象の製品ID(整数, 外部キーでproducts.idを参照) - review_text: レビュー本文(テキスト、ユーザーが書いた自由形式の内容) -- review_text_emb: レビュー本文の埋め込みベクトル(vector型)。こちらも説明文同様にEmbeddingモデルでベクトル化したもの。 -- features: レビューから抽出した特徴をまとめたJSONデータ(jsonb型)。例えば「soundQuality: positive/negative」や「battery: great battery life」といったキー値が含まれます。 -`review_text_emb`にもベクトルインデックスが作成されています。これにより、レビュー内容を意味的に検索する処理が高速化されています。 + +**data_embeddings_reviews テーブル(レビューの埋め込み情報)**: ユーザーの製品レビューの埋め込みを格納するテーブルです。主なカラムは以下の通りです: +- embedding: レビュー本文の埋め込みベクトル(vector型)。 +このカラムにもベクトルインデックスが作成されています。これにより、レビュー内容を意味的に検索する処理が高速化されています。 以上2つのテーブルが中心データとなります。リレーションとして、`reviews.product_id`が`products.id`に対する外部キーとなっており、製品とレビューを紐づけています(1製品に複数レビューが対応)。 さらに、AgenticShopではApache AGEを用いて上記製品・レビュー・特徴をグラフ化しています。グラフ化に際して以下の概念がノード・エッジとして扱われます。 **ノード**: -- productノード: 製品1件につき1ノード。属性として製品IDや名前、カテゴリ、特徴JSONなどを保持。 -- reviewノード: レビュー1件につき1ノード。属性としてレビューID、テキスト、特徴JSONなどを保持。 -- featureノード: 製品やレビューから抽出される特徴語(例: “battery life”, “soundQuality”, “waterResistance”, “design”など)ごとにノード。属性に`name`(特徴名)を持つ。 +- Productノード: 製品1件につき1ノード。属性として製品IDや名前、カテゴリ、特徴JSONなどを保持。 +- Reviewノード: レビュー1件につき1ノード。属性としてレビューID、テキスト、特徴JSONなどを保持。 +- Featureノード: 製品やレビューから抽出される特徴語(例: “battery life”, “soundQuality”, “waterResistance”, “design”など)ごとにノード。属性に`name`(特徴名)を持つ。 **エッジ**: -- (:product)-[:HAS_REVIEW]->(:review): 製品ノードからそのレビューノードへのエッジ。 -- (:product)-[:HAS_FEATURE]->(:feature): 製品ノードから、その製品説明に含まれる特徴ノードへのエッジ。例えば製品説明に「water resistant(防水)」の話があれば、その製品と特徴「waterResistance」を結ぶエッジ。 -- (:review)-[:MENTIONS_FEATURE {sentiment: ...}]->(:feature): レビューノードから、レビュー本文で言及されている特徴ノードへのエッジ。エッジのプロパティ`sentiment`にはその言及がポジティブかネガティブかなど評価が入ります。 +- (:Product)-[:REVIEWS]->(:Review): 製品ノードからそのレビューノードへのエッジ。 +- (:Product)-[:HAS_FEATURE]->(:Feature): 製品ノードから、その製品説明に含まれる特徴ノードへのエッジ。例えば製品説明に「water resistant(防水)」の話があれば、その製品と特徴「waterResistance」を結ぶエッジ。 -これらのグラフデータは、PostgreSQLの`AGE`拡張により`ag_catalog`内に管理されています。実際に`products`や`reviews`テーブルから特徴を抽出してグラフノード・エッジを作成する処理は、デプロイ時のスクリプトやバックエンドの初期化コードで実行されています(例えば全製品について特徴ノードを作り、`HAS_FEATURE`エッジを張る`INSERT`文、全レビューについて`MENTIONS_FEATURE`エッジを張る`INSERT`文など)。 +これらのグラフデータは、PostgreSQLの`AGE`拡張により`ag_catalog`内に管理されています。実際に`products`や`reviews`テーブルから特徴を抽出してグラフノード・エッジを作成する処理は、デプロイ時のスクリプトやバックエンドの初期化コードで実行されています(例えば全製品について特徴ノードを作り、`HAS_FEATURE`エッジを張る`INSERT`文、全レビューについて`REVIEWS`エッジを張る`INSERT`文など)。 ## ベクトル・リランク・グラフデータに対するクエリ From 0a7e7e7ad10bc97d95487b6d3920a13fec9bdce4 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Thu, 12 Jun 2025 20:05:17 +0900 Subject: [PATCH 06/23] Fixed bicep errors --- .../host/container-apps-env-registry.bicep | 46 +++++++++++++++---- infra/core/host/container-registry.bicep | 8 ++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/infra/core/host/container-apps-env-registry.bicep b/infra/core/host/container-apps-env-registry.bicep index b021bde..9ca7bd4 100644 --- a/infra/core/host/container-apps-env-registry.bicep +++ b/infra/core/host/container-apps-env-registry.bicep @@ -10,6 +10,7 @@ param containerRegistryAdminUserEnabled bool = false module containerAppsEnvironment 'container-apps-environment.bicep' = { name: '${name}-container-apps-environment' + scope: resourceGroup() params: { name: containerAppsEnvironmentName location: location @@ -17,14 +18,27 @@ module containerAppsEnvironment 'container-apps-environment.bicep' = { } } -module containerRegistry 'container-registry.bicep' = { - name: '${name}-container-registry' - scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() +// ACR: for local RG +module containerRegistryLocal 'container-registry.bicep' = if (empty(containerRegistryResourceGroupName)) { + name: 'acr-local' + scope: resourceGroup() params: { - name: containerRegistryName - location: location - adminUserEnabled: containerRegistryAdminUserEnabled - tags: tags + name : containerRegistryName + location : location + adminUserEnabled : containerRegistryAdminUserEnabled + tags : tags + } +} + +// ACR: for cross RG +module containerRegistryCrossRG 'container-registry.bicep' = if (!empty(containerRegistryResourceGroupName)) { + name: 'acr-cross' + scope: resourceGroup(containerRegistryResourceGroupName) + params: { + name : containerRegistryName + location : location + adminUserEnabled : containerRegistryAdminUserEnabled + tags : tags } } @@ -32,6 +46,18 @@ output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain output environmentName string = containerAppsEnvironment.outputs.name output environmentId string = containerAppsEnvironment.outputs.id -output registryLoginServer string = containerRegistry.outputs.loginServer -output registryName string = containerRegistry.outputs.name -output registryid string = containerRegistry.outputs.id +// switch output +var loginServerOut = empty(containerRegistryResourceGroupName) + ? containerRegistryLocal.outputs.loginServer + : containerRegistryCrossRG.outputs.loginServer +output registryLoginServer string = loginServerOut + +var loginServerName = empty(containerRegistryResourceGroupName) + ? containerRegistryLocal.outputs.name + : containerRegistryCrossRG.outputs.name +output registryName string = loginServerName + +var loginServerId = empty(containerRegistryResourceGroupName) + ? containerRegistryLocal.outputs.id + : containerRegistryCrossRG.outputs.id +output registryid string = loginServerId diff --git a/infra/core/host/container-registry.bicep b/infra/core/host/container-registry.bicep index e808f21..267abd2 100644 --- a/infra/core/host/container-registry.bicep +++ b/infra/core/host/container-registry.bicep @@ -70,6 +70,14 @@ param trustPolicy object = { @description('Zone redundancy setting') param zoneRedundancy string = 'Disabled' +@description('Optional override for the resource group. If empty, uses the module\'s resource group.') +param targetResourceGroupName string = '' // Add parameter for receiving + +// define a scope when built +var targetScope = empty(targetResourceGroupName) + ? resourceGroup() + : resourceGroup(targetResourceGroupName) + resource containerRegistry 'Microsoft.ContainerRegistry/registries@2024-11-01-preview' = { name: name location: location From 099454af1910fe3e672a00e339d01fcc7a5e9982 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 16 Jun 2025 11:23:44 +0900 Subject: [PATCH 07/23] Added instruction to install bash --- docs/workshop/ja/02-Prerequisites.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index 102bd2f..0f3451f 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -19,6 +19,14 @@ - **Python (3.8以上)**: デプロイ後のアプリケーションやスクリプトの動作に利用します。Pythonがインストールされていない場合は[公式サイト](https://www.python.org/downloads/windows/)から入手してください。Windowsの場合、インストール時に「Add Python to PATH」にチェックを入れておくと便利です。 - **PowerShell Core (Windowsユーザーのみ)**: WindowsでLinux向けスクリプトを実行する場合に必要です。事前に[PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5)をインストールしておいてください。 +インストール後、以下のコマンドを実行してbashが動作することを確認してください。 +```sh +bash --version +``` +もしエラーになる場合は、WSLのインストールなど、bashが動作する環境を構築してください。 +```sh +wsl --install +``` - **psql もしくは pgAdmin**: PostgreSQLに接続し、テーブルやデータの確認・操作に利用します。psqlはPostgreSQLのパッケージに含まれているので、[PostgreSQLのダウンロードページ](https://www.postgresql.org/download/)から、pgAdminは[pgAdminのダウンロードページ](https://www.pgadmin.org/download/)から入手可能です。 From ab9a30f50ebb388a6915ee3fc20326ab95c7fcda Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 16 Jun 2025 12:26:51 +0900 Subject: [PATCH 08/23] Update azure.yaml --- azure.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure.yaml b/azure.yaml index 6cf986a..3f16707 100644 --- a/azure.yaml +++ b/azure.yaml @@ -4,7 +4,7 @@ description: Agentic Shop metadata: template: retail-solution-accelerator@0.0.1 requiredVersions: - azd: ">= 1.15.0" + azd: ">= 1.14.0" workflows: up: @@ -70,4 +70,4 @@ services: module: arize host: containerapp docker: - remoteBuild: true \ No newline at end of file + remoteBuild: true From 75e17e44ae465a096996cce1afc7a4db75749465 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 16 Jun 2025 12:32:59 +0900 Subject: [PATCH 09/23] Update 05-Provisioning.md --- docs/workshop/ja/05-Provisioning.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/workshop/ja/05-Provisioning.md b/docs/workshop/ja/05-Provisioning.md index 01cd834..ed4255d 100644 --- a/docs/workshop/ja/05-Provisioning.md +++ b/docs/workshop/ja/05-Provisioning.md @@ -2,6 +2,9 @@ ローカルにクローンしたリポジトリには、Azure上にインフラおよびアプリケーションコードをデプロイするための設定が含まれています。ここでは**Azure Developer CLI (azd)** を用いて、必要なAzureリソースのプロビジョニングとアプリケーションのデプロイを行います。所要時間はおおよそ20〜30分程度ですが、Azureのリソース作成状況によっては変動します。 +> [!NOTE] 注意 +> CloudShellによるプロビジョニングでは、az config set extension.dynamic_install_allow_preview=true を実行しておいてください。 + ## `azd up`コマンドによるプロビジョニング Azure Developer CLIを使うと、単一のコマンドでインフラの構築からデプロイまで実行できます。以下の手順で進めましょう。 From 7560a605cf381e5d473e3e1cf7ef7bab242a8240 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 16 Jun 2025 13:41:08 +0900 Subject: [PATCH 10/23] Added GraphRAG explanation --- docs/workshop/ja/02-Prerequisites.md | 1 + docs/workshop/ja/05-Provisioning.md | 3 -- docs/workshop/ja/08-Wrapup.md | 3 +- docs/workshop/ja/09-GraphRAG.md | 65 ++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 docs/workshop/ja/09-GraphRAG.md diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index 0f3451f..43f6042 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -5,6 +5,7 @@ ## ハンズオンに必要なもの - **Azureサブスクリプション**: Azureの有効なサブスクリプションが必要です(所有者またはリソース作成権限を持つこと)。Azure OpenAI サービスを利用するため、このサービスへのアクセス権がサブスクリプションに含まれていることを確認してください(場合によっては利用申請が必要です)。 - **開発マシンとインターネット接続**: ハンズオンを実施するPC(Windows, macOS, Linuxいずれも可)と安定したインターネット接続が必要です。Azure CLI等を使用するため、ローカル環境にターミナル/コマンドプロンプトが利用可能であることを確認してください。 +- **CloudShell**: 開発マシンの用意が難しい場合、AzureポータルのCloudShellでもハンズオンの実行が可能です。`az extension add --name rdbms-connect`を事前に実行しておいてください。 ## 事前にインストールしておくべきソフトウェア diff --git a/docs/workshop/ja/05-Provisioning.md b/docs/workshop/ja/05-Provisioning.md index ed4255d..01cd834 100644 --- a/docs/workshop/ja/05-Provisioning.md +++ b/docs/workshop/ja/05-Provisioning.md @@ -2,9 +2,6 @@ ローカルにクローンしたリポジトリには、Azure上にインフラおよびアプリケーションコードをデプロイするための設定が含まれています。ここでは**Azure Developer CLI (azd)** を用いて、必要なAzureリソースのプロビジョニングとアプリケーションのデプロイを行います。所要時間はおおよそ20〜30分程度ですが、Azureのリソース作成状況によっては変動します。 -> [!NOTE] 注意 -> CloudShellによるプロビジョニングでは、az config set extension.dynamic_install_allow_preview=true を実行しておいてください。 - ## `azd up`コマンドによるプロビジョニング Azure Developer CLIを使うと、単一のコマンドでインフラの構築からデプロイまで実行できます。以下の手順で進めましょう。 diff --git a/docs/workshop/ja/08-Wrapup.md b/docs/workshop/ja/08-Wrapup.md index e12bc04..37fee99 100644 --- a/docs/workshop/ja/08-Wrapup.md +++ b/docs/workshop/ja/08-Wrapup.md @@ -29,4 +29,5 @@ azd down --purge 今回学んだように、Azure Database for PostgreSQLにAI拡張や`pgvector`、Apache AGEを組み合わせることで、シンプルながら強力なAIアプリケーション基盤を構築できます。ぜひ現場のプロジェクトでも応用してみてください。各セクションで触れた技術要素について更に深掘りしたい場合、公式ドキュメントや関連ブログ記事なども参照すると理解が深まるでしょう。 -[前へ](07-WhyPostgreSQL.md) + +[前へ](07-WhyPostgreSQL.md) | [次へ](09-GraphRAG.md) diff --git a/docs/workshop/ja/09-GraphRAG.md b/docs/workshop/ja/09-GraphRAG.md new file mode 100644 index 0000000..635a88f --- /dev/null +++ b/docs/workshop/ja/09-GraphRAG.md @@ -0,0 +1,65 @@ +# GraphRAG Integration + +## グラフRAGの知識グラフ構築(Graph Extraction) + +AgenticShopのバックエンドではGraphRAGの一環として、製品レビューから知識グラフを構築しています。具体的には、PostgreSQLにApache AGE拡張を導入し、製品とその特徴(機能)をノードとして、レビュー内の言及関係をエッジとして表現したグラフを構築します。この処理はcreate_apache_age_graph.pyで実行されており、PostgreSQL上でAGEのcreate_graph関数を用いてグラフを生成しています(リテール用のグラフスキーマを作成)。また、generate_sentiments_for_reviews.pyで各レビュー中の特定の特徴に対するポジティブ/ネガティブな言及を分析し、これをグラフのエッジ情報として活用できる形でデータベースに格納しています。すなわち、製品ノードと特徴ノードを繋ぐエッジに、レビューでその特徴が言及された回数や感情(肯定/否定)の属性を持たせ、製品と特徴の関連知識グラフをデータベース内に構築しています。このグラフ抽出工程により、非構造なテキストから製品間の関係(共通の特徴やレビュー評価)を構造化データとして抽出しています。これによってバックエンドはPostgreSQLをリレーショナルDBとグラフDBの両方として利用し、複雑な関係クエリを可能にしています。 + +## Apache AGEによるグラフ統合とCypherクエリ + +Apache AGE拡張を介して、バックエンドはPostgreSQL上でCypherクエリ(OpenCypher言語)を実行できるようになっています。Alembicマイグレーション内でCREATE EXTENSION ageおよびSELECT * FROM create_graph('graph_name')を実行し、グラフスキーマをPostgreSQL内に作成します。また、製品・特徴ノードやエッジ挿入もSQLから実行されます。たとえば、特徴リスト(features.csv)やレビューの分析結果から、各製品に対して「特徴Xがポジティブに言及された」ことを表すエッジをMATCH ... CREATE文で挿入しています。Apache AGEではCypherクエリをSQLのFROM句で呼び出すハイブリッドクエリが可能であり、本プロジェクトでもこれを活用しています。実際、PostgreSQLとAGEの統合により、従来のSQLフィルタとグラフクエリを組み合わせた問合せを発行できます。例えば、「特定製品群の中から、ある特徴について肯定的な言及数が多い製品を探す」ような処理を、一度のSQL+Cypherハイブリッドクエリで実行できます。Apache AGE統合のメリットは、LLM用の知識グラフを別のグラフDBに置く必要がなく、PostgreSQL内でリレーショナルデータとグラフデータを一貫して操作できる点です。 + +具体的なCypherクエリ発行箇所としては、バックエンドのサービス層でCypher関数付きSQLを実行しています。たとえば、特徴と感情に基づく製品フィルタリングでは、内部で以下のようなクエリを組み立てています(擬似コード): + +```sql +SELECT p.id, g.count +FROM products p +JOIN LATERAL cypher('retail_graph', $$ + MATCH (p:Product)-[m:MENTION {sentiment: "positive"}]->(f:Feature {name: "<特徴名>"}) + RETURN COUNT(m) as count +$$) AS g(count) +ON g.count > 0 +WHERE p.category = '<カテゴリ>' +ORDER BY g.count DESC; +``` + +上記のように、PostgreSQLのSQL文にcypher('graph_name', $$ ... $$)を組み込み、グラフ内のエッジ数などを算出して結果をリレーショナルクエリに組み入れています。このようなOpenCypherクエリとSQLの混合により、通常のフィルタ(例: カテゴリ)とグラフ構造上の指標(例: 言及数)を同時に考慮した検索が可能になっています。 + +## 実行時におけるグラフRAGの活用(Graph Query Generation) + +エンドユーザから質問や要求があった際、バックエンドではグラフクエリ生成を行い、知識グラフを用いた情報検索を実施します。GraphRAGのアルゴリズム上、これは「グラフクエリを生成し、LLMへの文脈情報として使用する」ステップに該当します。AgenticShopではユーザのクエリ内容やプロファイルに応じて、適切なグラフ問合せを発行しています。例えばユーザが「バッテリー寿命に関する評価が高いスマートフォンは?」と質問した場合、バックエンドのUserQueryAgentが以下のような処理を行います。 +1. ベクトル検索で該当カテゴリーの製品候補を取得します(pgvectorを用いた類似度検索)。 +2. グラフクエリ生成: 候補製品群に対し、「指定の特徴に対するポジティブレビュー数」を計算するCypher問合せを組み立てます。コード上はfetch_product_with_feature_and_sentiment_countという関数で実装されており、引数に製品IDリスト・感情種別・特徴名を受け取ってグラフクエリを実行します。この関数内で前述のようなCypher+SQLを発行し、各製品についてその特徴のポジティブ言及数を取得します。 +3. 結果のランキング: 返ってきた各製品の言及数に基づき、製品リストを並べ替えます(言及数が多い=その特徴で評価の高い製品として順位を上げる)。GraphRAGではグラフ上の**“プロミネンス”**(重要度)のシグナルを活用して検索精度を上げるのが狙いであり、本ケースでは「ポジティブレビューの多さ」がプロミネンス指標になっています。 +4. LLMへのコンテキスト提供: 最終的に上位N件の製品を選び、それぞれに対して関連するレビュー内容を要約したテキストを生成します(後述のエージェントワークフロー参照)。このとき、GraphRAGにより選ばれた重要度の高い製品やレビューが文脈としてLLMに与えられるため、ユーザの質問意図に沿った正確な回答が得られやすくなります。 + +コード上では、UserQueryAgent.query_reviews_with_sentimentメソッド内で上記ロジックが確認できます。以下はその一部抜粋で、特徴が指定された場合にグラフクエリ関数を呼び出している箇所です。 + +```python +if feature: + product_ids_for_query = [product.id for product in results] + product_ids_with_count = await self.fetch_product_with_feature_and_sentiment_count( + product_ids_for_query, sentiment, feature[0] + ) + # (返り値のproduct_ids_with_countを用いて製品リストをフィルタ/ソート) +``` + +ここでfeature[0]は特徴名(例:「バッテリー」)を示し、product_ids_for_queryは対象となる製品IDのリストです。この関数呼び出しにより、グラフデータベース上で該当製品群における指定特徴の言及数を集計し、結果がproduct_ids_with_countに格納されます。その後の処理で、この情報を使ってresults中の製品オブジェクトを並べ替え、回答用の上位製品を決定しています。 + +## マルチエージェントワークフローにおけるグラフ活用 + +AgenticShopはLlamaIndexベースのマルチエージェントフローを採用しており、検索結果の後処理や要約生成にもGraphRAGの成果を活かしています。バックエンドのmulti_agent_workflow.pyでは、run_workflows_in_backgroundという関数でバックグラウンドタスクとしてエージェントによる追加処理を起動します。このワークフローでは、ユーザごとのプロフィール情報や、グラフクエリで得られた知識を元に、各製品の詳細な説明やレビュー要約をLLMに生成させます。具体的には: +- パーソナライズド要約: ユーザの嗜好(プロフィールで好む特徴や重視点)に合わせ、各製品のレビューから relevant なポイントを抽出・要約します。GraphRAGで構築した製品-特徴グラフは、どの製品がユーザの関心特徴について高評価かを示す指針となります。エージェントはこの指針を使い、該当するレビューをベクトル検索(vector_store_reviews_embeddings)やグラフから探し出し、LLMに渡します。例えば「バッテリー寿命重視」のユーザには、その特徴に関するポジティブレビューが多い製品をグラフ経由で特定し、そのレビュー内容を重点的に要約するという流れです。 +- プロンプト内での指示: backend/src/agents/prompts.pyを見ると、エージェントへの指示として「ユーザの嗜好に最も関連するレビューからインサイトを要約せよ」といった内容が含まれています。さらに「ユーザ嗜好そのものを要約結果にせず、あくまで製品に言及せよ」「製品カテゴリと無関係な嗜好は無視せよ」等、出力品質を高めるための細かなガイドラインも記載されています。これらはGraphRAGから得たグラフ構造を前提に、LLMが適切に情報を取捨選択できるよう設計されたプロンプトです。例えばグラフ上で無関係と判定される特徴(スマートウォッチに音質は無関係等)は要約に含めない指示となっています。 + +以上のように、グラフ構造は情報検索と要約の両面で活用されています。検索段階ではグラフから得たスコア(例: 特徴の言及数)で候補をランキングし、要約段階ではグラフが示す関連性に沿って適切なレビュー内容を抽出しています。 + +## GraphRAGのアーキテクチャ上の役割と利点 + +AgenticShopにおけるGraphRAGは、バックエンドの情報検索パイプラインを高度化する中核技術として機能しています。その役割とメリットは以下の通りです。 +- 精度向上とランキング強化: 単純なベクトル検索ではテキスト類似度のみの評価となりますが、GraphRAGにより知識グラフ上の関係性を考慮できます。例えば「レビュー件数が多い」「ある特徴で高評価されている」といった情報はグラフから得られる重要なシグナルです。Legal Copilotの例では判例の被引用数をプロミネンス指標として精度向上しましたが、AgenticShopでは特徴に対する肯定的レビュー数を類似指標としてランキングに反映しています。これにより、ユーザの質問に対しより適切な(ユーザの求める観点で優れた)製品が上位に来るようになります。実際、GraphRAG導入により情報検索パイプラインの精度(リコール向上など)が飛躍的に改善することが報告されています。 +- リレーショナルデータとグラフデータの融合: GraphRAGはPostgreSQL内で動作するため、ユーザプロフィールや製品マスタなどリレーショナル情報と、レビュー由来のグラフ情報を一本化して扱えます。これによりシステム構成がシンプルになり、外部グラフDBとの同期を取る必要がありません。AgenticShopのアーキテクチャ上も、Azure Database for PostgreSQL一つでベクトル検索(pgvector)、全文検索、そしてグラフ検索を担っており、データ一貫性と管理容易性の面で大きな利点があります。 +- LLM応答質の向上: GraphRAGが提供する文脈(知識グラフに基づくコンテキスト)は、LLMの回答内容をより信頼性の高いものにします。例えばユーザが関心を持つ製品特徴について、グラフから得た「裏付けのある情報(レビュー評価や頻出するメリット)」を提示できるため、LLMは具体的で妥当性の高い回答を生成できます。これはRAGの目的である幻覚の抑制と正確性向上に直結します。AgenticShopではエージェントが複数の知識ソース(ベクトルDB・グラフDB)を横断して情報収集することで、よりユーザ意図にマッチした有用な回答(製品推薦や説明)を実現しています。 + +総括すると、AgenticShopバックエンドのbackendディレクトリにはGraphRAGの概念が技術的に組み込まれており、Apache AGEによる知識グラフ構築とCypherクエリ活用、LLMエージェントへのグラフ情報供給を通じて、マルチエージェントAIの回答精度とユーザ体験を大幅に向上させています。GraphRAGは本ソリューションアーキテクチャのキーストーンとなっており、今後の拡張においてもグラフデータを活用した高度な検索・推論が容易に行える土台を提供しています。各種リテールドメイン特有の関係性(製品-特徴-レビュー-ユーザ嗜好)をモデル化したこのグラフ活用アプローチは、従来のRAG手法では得られなかった洞察を引き出し、最終的なエンドユーザへの回答品質を押し上げています。 + +[前へ](08-Wrapup.md) From ed369c1eb96c4cb1153891ee5726cb0113d43f7f Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 16 Jun 2025 13:54:09 +0900 Subject: [PATCH 11/23] Added Docker Desktop --- docs/workshop/ja/02-Prerequisites.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index 43f6042..0e20310 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -17,6 +17,8 @@ - **Azure CLI拡張機能(rdbms-connect)**: Azure CLIにPostgreSQLサーバーへの一時的トンネリング接続等を追加する拡張です。インストールは`az extension add --name rdbms-connect`で行います。インストール後、`az extension list`で有効になっていることを確認してください。 +- **Docker Desktop**: Docker Desktopがインストールされていない場合は[公式サイト](https://www.docker.com/ja-jp/get-started/)から入手してください。 + - **Python (3.8以上)**: デプロイ後のアプリケーションやスクリプトの動作に利用します。Pythonがインストールされていない場合は[公式サイト](https://www.python.org/downloads/windows/)から入手してください。Windowsの場合、インストール時に「Add Python to PATH」にチェックを入れておくと便利です。 - **PowerShell Core (Windowsユーザーのみ)**: WindowsでLinux向けスクリプトを実行する場合に必要です。事前に[PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5)をインストールしておいてください。 From bbbeb2ee4e555236b45585431d7c11be085df92e Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Mon, 16 Jun 2025 14:38:16 +0900 Subject: [PATCH 12/23] Removed Docker Desktop --- docs/workshop/ja/02-Prerequisites.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index 0e20310..43f6042 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -17,8 +17,6 @@ - **Azure CLI拡張機能(rdbms-connect)**: Azure CLIにPostgreSQLサーバーへの一時的トンネリング接続等を追加する拡張です。インストールは`az extension add --name rdbms-connect`で行います。インストール後、`az extension list`で有効になっていることを確認してください。 -- **Docker Desktop**: Docker Desktopがインストールされていない場合は[公式サイト](https://www.docker.com/ja-jp/get-started/)から入手してください。 - - **Python (3.8以上)**: デプロイ後のアプリケーションやスクリプトの動作に利用します。Pythonがインストールされていない場合は[公式サイト](https://www.python.org/downloads/windows/)から入手してください。Windowsの場合、インストール時に「Add Python to PATH」にチェックを入れておくと便利です。 - **PowerShell Core (Windowsユーザーのみ)**: WindowsでLinux向けスクリプトを実行する場合に必要です。事前に[PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5)をインストールしておいてください。 From 75b8a66fae7342e7614450477f31f5a5c8e65502 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Wed, 18 Jun 2025 10:26:43 +0900 Subject: [PATCH 13/23] Added English documents for hands-on lab --- README.md | 10 +- docs/workshop/en/01-Introduction.md | 25 ++++ docs/workshop/en/02-Prerequisites.md | 61 +++++++++ docs/workshop/en/03-Integration.md | 83 +++++++++++++ docs/workshop/en/04-Repository.md | 40 ++++++ docs/workshop/en/05-Provisioning.md | 61 +++++++++ docs/workshop/en/06-Post-provisioning.md | 50 ++++++++ docs/workshop/en/07-WhyPostgreSQL.md | 117 ++++++++++++++++++ docs/workshop/en/08-GraphRAG.md | 64 ++++++++++ docs/workshop/en/09-Wrapup.md | 32 +++++ docs/workshop/ja/02-Prerequisites.md | 26 ++-- docs/workshop/ja/05-Provisioning.md | 29 +++-- docs/workshop/ja/06-Post-provisioning.md | 20 ++- docs/workshop/ja/07-WhyPostgreSQL.md | 2 +- .../ja/{09-GraphRAG.md => 08-GraphRAG.md} | 2 +- .../ja/{08-Wrapup.md => 09-Wrapup.md} | 4 +- 16 files changed, 582 insertions(+), 44 deletions(-) create mode 100644 docs/workshop/en/01-Introduction.md create mode 100644 docs/workshop/en/02-Prerequisites.md create mode 100644 docs/workshop/en/03-Integration.md create mode 100644 docs/workshop/en/04-Repository.md create mode 100644 docs/workshop/en/05-Provisioning.md create mode 100644 docs/workshop/en/06-Post-provisioning.md create mode 100644 docs/workshop/en/07-WhyPostgreSQL.md create mode 100644 docs/workshop/en/08-GraphRAG.md create mode 100644 docs/workshop/en/09-Wrapup.md rename docs/workshop/ja/{09-GraphRAG.md => 08-GraphRAG.md} (99%) rename docs/workshop/ja/{08-Wrapup.md => 09-Wrapup.md} (96%) diff --git a/README.md b/README.md index 5d9c0f2..ba8abf1 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,11 @@ Retail solution accelerator provides the following features: ## Architecture Diagram ![AZURE ARCHITECTURE](https://github.com/user-attachments/assets/f3ca0e0d-0c93-4c0f-be5d-958eace3a138) +## Solution Accelerator Deployment +### CloudShell +This solution is designed to be deployable via CloudShell. If you prefer to deploy from your local development machine, please follow the steps below. -## Solution Accelerator Deployment ### Prerequisites The following serve as prerequisites for deployment of this solution: 1. [Azure Developer Cli](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-linux) @@ -76,12 +78,12 @@ pwsh -NoProfile -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolic ``` ### Solution Deployment -Run the following command to provision the resources. +Run the following command to provision the resources. ```sh azd up ``` -Once this command is executed, the prompt asks for subscription for deployment, two locations i.e. one for location of solution accelerator resources and other for location of Azure OpenAI models and the resource group to create. +Once this command is executed, the prompt asks for subscription for deployment, two locations i.e. one for location of solution accelerator resources and other for location of Azure OpenAI models and the resource group to create. Make sure that you have enough Azure OpenAI model quota in the region of deployment. The Azure OpenAI quota required for this solution is listed below. This configuration can be changed from `main.parameters.json` file in `infra` directory using following parameters. The deployment might take some time and will provide progress of deployment in terminal as well as on Azure Portal. - **GPT-4o:** 150K TPM - `AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY` - **text-embedding-ada-002:** 120K TPM - `AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY` @@ -93,7 +95,7 @@ Make sure that you have enough Azure OpenAI model quota in the region of deploym 4. When `The resource entity provisioning state is not terminal` error occurs, restart the deployment using `azd up` command. ## Tear Down -To destroy all the resources that have been created in the step above as well as remove any accounts deployed by the solution accelerator, use the following command: +To destroy all the resources that have been created in the step above as well as remove any accounts deployed by the solution accelerator, use the following command: ```sh azd down --purge ``` diff --git a/docs/workshop/en/01-Introduction.md b/docs/workshop/en/01-Introduction.md new file mode 100644 index 0000000..9f4fb0d --- /dev/null +++ b/docs/workshop/en/01-Introduction.md @@ -0,0 +1,25 @@ +# Introduction + +**AgenticShop** is a solution accelerator that demonstrates a multi-agent retail experience utilizing Azure Database for PostgreSQL. The application in this repository is a demo that enhances the shopping experience for electronic gadgetry with AI, positioned as hands-on material that can be deployed with one click on Azure. The term "Agentic" in the application name refers to applications where multiple AI agents interact and collaborate to accomplish tasks. Agents using Large Language Models (LLMs) autonomously plan and meet user demands while using tools such as databases and APIs as needed. + +An explanation of this solution accelerator is [available in video](https://build.microsoft.com/en-US/sessions/BRK211). + +## Application Architecture + +The overall architecture of this solution consists of a frontend, backend, database, and AI services. The frontend provides a shopping UI to the user, and the backend executes the AI agent's workflow. In the backend, a Python-based agent orchestrator (using the LlamaIndex library) operates, and multiple tasks such as data retrieval from Azure Database for PostgreSQL and inquiries to Azure OpenAI service are processed sequentially by the agent. In addition, a tracing feature called **Arize Phoenix** is incorporated for debugging and analysis of the application, allowing visualization of the internal operation of the agent (what queries were issued and what answers were obtained). Azure Database for PostgreSQL plays a crucial role in performing **intelligent query processing** using extensions (AI, vector, graph features) beyond just being a data store. + +## Features and Implementation of Agentic Applications + +The biggest feature of this application is that multiple AI agents collaborate to provide personalized shopping information to the user. For example, one agent selects products that seem to match the user's profile, another agent performs database searches for product information (converting natural language queries to SQL and performing vector searches), and yet another agent handles review summaries and feature extraction. This multi-agent workflow enables personalized product detail presentation and advanced user experience. The mediation between agents and the flow of tasks are controlled by LlamaIndex, and the LLM is implemented to select the appropriate action (inquiries to the database or handovers to other agents) at each step. Overall, AgenticShop is an example of a next-generation application construction method where AI agents operate autonomously in the backend, and it is built and experienced on Azure. + +## Key Features + +This solution accelerator has the following features: +- **Product detail presentation based on user profiles**: Relevant product information and descriptions are automatically generated and displayed according to each user's attributes and preferences. +- **Improved user experience**: Provides a richer experience than traditional online shopping sites through AI-powered chatbot-like interactions, summary displays, and recommendations. +- **Multi-agent workflow**: Multiple agents collaborate to perform multiple tasks such as search, summary, and recommendation, achieving seamless processing. +- **Visualization with a debug panel**: There is a trace function for agent operation using Arize Phoenix, and you can check how the agent was triggered and what response it generated. + +That's an overview. In this hands-on, we will deploy the AgenticShop application with the above features on Azure, and while experiencing its functions, we will learn about the internal processes. + +[Next](02-Prerequisites.md) \ No newline at end of file diff --git a/docs/workshop/en/02-Prerequisites.md b/docs/workshop/en/02-Prerequisites.md new file mode 100644 index 0000000..cbaeb7d --- /dev/null +++ b/docs/workshop/en/02-Prerequisites.md @@ -0,0 +1,61 @@ +# Preparations and Requirements + +This section explains the environment and points to check before starting the hands-on. It is assumed that participants have **basic knowledge of PostgreSQL** and **experience in AI application development on Azure**. With that in mind, let's complete the installation of necessary software and the setting of Azure subscription in advance. + +## What you need for the hands-on +- **Azure Subscription**: You need a valid Azure subscription (with owner or resource creation rights). To use the Azure OpenAI service, make sure that access to this service is included in your subscription (application may be required depending on the case). +- **CloudShell**: If it is difficult to prepare a development machine, you can also execute the hands-on in the CloudShell of the Azure portal. Please execute `az extension add --name rdbms-connect` in advance. +- **Development machine and internet connection**: You need a PC (Windows, macOS, Linux are all acceptable) to conduct the hands-on and a stable internet connection. Make sure that a terminal/command prompt is available in your local environment for using Azure CLI, etc. + +## Software to install in advance + +If you are using a development machine instead of CloudShell, please install the following software in advance. We recommend the latest stable version as much as possible. + +- **Azure Developer CLI (`azd`)**: This is a CLI tool for Azure developers. For installation instructions, please refer to [How to Install Azure Developer CLI](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-linux). + +- **Azure CLI**: This is a command line tool for Azure. For installation instructions, please refer to [How to Install Azure CLI](https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli?view=azure-cli-latest). If you have already installed it, check the version (`az --version`) and update it if necessary. + +- **Azure CLI Extension (rdbms-connect)**: This is an extension that adds temporary tunneling connections to PostgreSQL servers, etc., to Azure CLI. Install it with `az extension add --name rdbms-connect`. After installation, confirm that it is enabled with `az extension list`. + +- **PowerShell Core (Windows users only)**: This is necessary when running Linux-oriented scripts on Windows. Please install [PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5) in advance. +After installation, execute the following command to confirm that bash is working. +```sh +bash --version +``` +If an error occurs, please set up an environment where bash can work, such as installing WSL. +```sh +wsl --install +``` + +After installation, make sure that each command is in the path. For example, if you execute `azd version` or `az --version` and the version information is displayed, you are ready. + +## Criteria for selecting Azure region + +The resources to be deployed in this hands-on include Azure Database for PostgreSQL and Azure OpenAI services. It is important to select a region where **Azure OpenAI** is available, as the regions where this service can be used are limited. Also, some extensions in **Azure Database for PostgreSQL (such as Apache AGE mentioned later)** are preview features supported on newly created servers. When executing Azure Developer CLI (`azd`), you will be asked for two regions: one for infrastructure resources and one for Azure OpenAI. As a rule, choose a region that meets the following conditions. + +- **Azure OpenAI compatible region**: Specify a region where you can create Azure OpenAI resources, such as East US, South Central US, West Europe, Japan East, etc. Please check in advance whether your subscription allows the creation of OpenAI services in the region. + +- **Location close to the database**: From a performance perspective, it is desirable to choose a region for Azure OpenAI that is physically close to the region where Azure Database for PostgreSQL is located. For example, if you create a DB in the Japan East region, you will choose Azure OpenAI in the Japan East or a nearby region (however, compromises may be necessary because the OpenAI service is limited). + +- **Regions confirmed to work with CloudShell**: The following regions have been confirmed to work by deploying with CloudShell. + - Japan East (took 30 mins) + - Australia East (took 15 mins) + - South India (took 16 mins) + - Korea Central (Failed due to lack of text-embedding-ada-002) + +- **Use of preview features**: The Apache AGE extension is available on newly created PostgreSQL servers and cannot be enabled on existing servers. `azd up` automatically creates a new PostgreSQL server, but for peace of mind, check the Azure Portal for any mention of using preview features. + +## Check quota + +To use the Azure OpenAI service, a usage limit (quota) for each model must be allocated to your subscription. In particular, the **GPT-4o** model and **Embedding** model used in this hands-on require relatively large request quotas. The default configuration requires the following throughput. + +- **GPT-4o model**: Processing capacity of about 150k tokens per minute (150K TPM). The "Model Usage Limit (Requests per minute)" of Azure OpenAI must meet this requirement. + +- **text-embedding-ada-002 (embedding model)**: About 120k tokens per minute (120K TPM). + +You can check the OpenAI quota of your Azure subscription on the "Quota + Limit" page of the Azure Portal or with the `az openai admin quota show` command. If you are short, please request a limit increase when creating the Azure OpenAI resource. + +> [!NOTE] Note +> The deploy capacity (default value) of the above models is also set as a parameter in the prompt at the time of `azd up` execution. If necessary, you can adjust the deploy scale of GPT-4o and the embedding model by editing `infra/main.parameters.json` in the repository (e.g., lowering TPS to save quota). + +[Previous](01-Introduction.md) | [Next](03-Integration.md) diff --git a/docs/workshop/en/03-Integration.md b/docs/workshop/en/03-Integration.md new file mode 100644 index 0000000..c26e3d0 --- /dev/null +++ b/docs/workshop/en/03-Integration.md @@ -0,0 +1,83 @@ +# Integration of AI, Vector, and Graph Features into Azure Database for PostgreSQL + +By utilizing Azure Database for PostgreSQL not just as a relational database, but as a platform integrating **AI (Artificial Intelligence), Machine Learning (ML), Vector Search, and Graph Data**, more advanced data search and analysis become possible. This section explains the reasons for this and provides an overview and activation procedures for the various extensions used in this hands-on. + +## Overview of Extensions: azure_ai, pgvector, Apache AGE + +- **azure_ai extension (preview)**: This is a new feature provided for Azure Database for PostgreSQL's Flexible Server, which allows you to directly call various Azure AI services (such as Azure OpenAI and Azure Cognitive Services) from within the database. This allows you to generate embedded vectors of text, generate and summarize sentences with LLM, and analyze text (such as sentiment analysis and key phrase extraction) within SQL queries. In other words, it is an extension that seamlessly integrates Azure's powerful AI models into PostgreSQL, enabling the **database to directly utilize AI functions**. When you install the azure_ai extension, dedicated schemas (`azure_ai`, `azure_openai`, `azure_cognitive`) are created in the database, and functions to call various AI services are provided there. For example, you can generate embedded vectors with `azure_openai.create_embeddings()`, generate text with LLM with `azure_ai.generate()`, and evaluate the relevance of sentences (re-ranking) with `azure_ai.rank()`. + +- **pgvector extension**: This is an extension that adds **vector similarity search functionality** to PostgreSQL as an open source. It allows you to store feature vectors generated from text, images, etc. directly in the database and perform fast Approximate Nearest Neighbor (ANN) searches. With pgvector, you can store and search for embedded vectors in PostgreSQL without using a dedicated vector database product, and it becomes easier to query in combination with other relational data in the RDB. For example, you can use the special operator `<=>` in SQL to calculate the distance (similarity) between vectors, and you can easily write operations such as retrieving the top N records that are closest in distance (i.e., similar in content) to a certain query vector. Azure Database for PostgreSQL supports the pgvector extension, and you can implement the RAG (Retrieval Augmented Generation) pattern with a combination of embedded vectors and Azure OpenAI. + +- **Apache AGE extension**: This is an extension that adds graph database functionality to PostgreSQL, called **Apache AGE (Apache Graph Extension)**. By introducing Apache AGE, you can represent and store graph structures consisting of nodes and edges on PostgreSQL, and query graphs using the **openCypher query language**. This allows you to analyze complex relationships that traditionally required a dedicated graph DB like Neo4j, all within a single PostgreSQL. For example, if you register products, users, and reviews as nodes and model relationships (such as "a user purchased a product" or "a review mentioned a product") as edges, you can write advanced queries such as searching for products with features highly rated by a certain user via the graph. With the introduction of Apache AGE to Azure Database for PostgreSQL, **integration of relational and graph data** becomes easier, and the cost of using additional services can be reduced. + +By combining these three extensions, Azure Database for PostgreSQL has the following powerful features: + +- You can ask LLM questions directly in SQL, generate sentences, and get text analysis results (azure_ai). +- By handling embedded vectors, semantic-based search (search considering synonyms and related contexts) becomes possible (pgvector). +- In addition to traditional row and column data, you can hold data in node and edge format and execute relationship queries such as path search (AGE). + +In the scenario of this hands-on (shopping site), we realize all the flow of **vectorizing product descriptions and reviews** for use in search, **re-ranking and summarizing search results using LLM**, and **graphing products, reviews, and feature keywords** to answer complex questions, all on PostgreSQL. The point is that all data is contained within the same PostgreSQL, and AI inference calls can be made at the database layer, resulting in a simple and consistent application configuration. + +## Procedure to Enable Extensions + +Now, let's outline the procedure to make the above extensions available in Azure Database for PostgreSQL. Each requires **activation (installation) of the extension** on the PostgreSQL server side. + +### Procedure to Enable azure_ai Extension + +1. **Adding to the Extension Whitelist**: In Azure Database for PostgreSQL, when using custom extensions, you need to register the extension name in the server parameter `azure.extensions`. Using the Azure Portal's "Server Parameters" settings screen or Azure CLI, add `azure_ai` to the whitelist of the PostgreSQL server in question. After adding, check with the `SHOW azure.extensions;` command to see if it has been reflected. + +2. **Extension Installation (per DB)**: After setting the whitelist, connect to the target PostgreSQL database and execute the following SQL. + +```sql +CREATE EXTENSION IF NOT EXISTS azure_ai; +``` + +You need to execute the above command for each database (if you want to use it in multiple databases, execute it in each one). + +3. **Setting Authentication Information**: Once the `azure_ai` extension is installed, you can use a set of functions to set the endpoint URL and API key for calling Azure OpenAI and Cognitive Services. For example, for Azure OpenAI, you register your Azure OpenAI resource's HTTP endpoint and API key in the database with commands like `azure_openai.set_openai_endpoint('endpoint URL')` and `azure_openai.set_openai_key('key')`. These settings are encrypted and stored in a table in the database, and the various functions of the extension use these settings to call Azure services. + +> [!NOTE] Note +> In the deployment script of this repository, after creating the Azure OpenAI resource, the above setting functions are automatically executed and the endpoint and key are registered in PostgreSQL (you do not need to set them manually). + +### Procedure to Enable pgvector Extension + +1. **Check/Add to Extension Whitelist**: The pgvector extension is an extension derived from the PostgreSQL community. In Azure Database for PostgreSQL, it is often allowed by default, but just in case, check with `SHOW azure.extensions;` to see if the `vector` (※explained later) extension is included. If it is not included, add `vector` to the whitelist in the same way as `azure_ai`. + +> [!NOTE] Note +> The extension name is treated as `vector`, not `pgvector`. Please note that you should write `CREATE EXTENSION vector;` on PostgreSQL as well. + +2. **Extension Installation**: Connect to the database and execute the following SQL. + +```sql +CREATE EXTENSION IF NOT EXISTS vector; +``` + +This will enable the use of `pgvector`. After installation, you can use the vector type (by default, a type specified by the dimension number, such as `vector(1536)`) in the table, and the `<=>` operator becomes available. + +3. **Using Vector Similarity Search**: For example, you can write a query to calculate the distance between a vector generated from a user's query sentence and a column storing vectors generated from product descriptions (e.g., `description_emb vector(1536)`). The distance calculation uses the `<=>` operator, with smaller values indicating higher similarity. Detailed usage will be explained in the "Queries for Vectors" section later.### Enabling Apache AGE Extension + +1. **Confirming it's a new server**: As mentioned earlier, the Apache AGE extension cannot be installed on existing Azure Database for PostgreSQL servers, and can only be previewed on newly created PG 13-16 compatible servers. No special operation is required for deployment with `azd` as server creation is done automatically, but if you want to manually install it on an existing server, you need to recreate the server. + +2. **Adding to the extension whitelist**: Add `age` to the server parameter whitelist. In the Portal, you may be able to switch ON/OFF with a notation like "`AGE (preview)`". + +3. **Installing the extension**: Execute the following SQL in the database. + +```sql +CREATE EXTENSION IF NOT EXISTS age CASCADE; +``` + +The `CASCADE` option will also create dependent `ag_catalog` schemas, etc. Once installed successfully, you will be able to use a set of functions for graph operations, especially the `cypher()` function. + +4. **Creating a graph**: In AGE, you create a graph within the database and then add nodes and edges. A graph is a logical container name, for example: + +```sql +SELECT * FROM ag_catalog.create_graph('shop_graph'); +``` + +You can create a graph named `shop_graph` by calling a function from SQL like this. Then you specify this graph name to create and query nodes and edges. + +5. **Executing openCypher queries**: In the AGE extension, you execute Cypher queries with a function call like `cypher('graph name', $$ $$)`. We will show examples in the later section "Querying Graph Data", but you can call Cypher as a subquery in an SQL statement and handle the result like a table. This makes it easy to join the results of graph queries with relational tables. + +By following these steps, you will be able to use AI, vector, and graph functions on Azure Database for PostgreSQL. In this hands-on deployment script, the process to automatically execute these extension activation SQLs at deployment time (`azd-hooks` pre-deployment script) is built in, so participants do not need to manually execute SQL. However, to understand the mechanism, it would be good to check that the `azure.extensions` parameter of the target server is set in the Portal, etc., the extension is installed, and the `azure_ai` and `age` schemas are created in the database. + +[Previous](02-Prerequisites.md) | [Next](04-Repository.md) \ No newline at end of file diff --git a/docs/workshop/en/04-Repository.md b/docs/workshop/en/04-Repository.md new file mode 100644 index 0000000..5227372 --- /dev/null +++ b/docs/workshop/en/04-Repository.md @@ -0,0 +1,40 @@ +# Forking and Cloning the Repository Locally + +Before starting the hands-on, you will first **fork (copy) the repository on GitHub, which will be used as the teaching material, to your own account and clone that fork to your local environment**. This will allow you to prepare an environment where you can rewrite settings and code as your own repository without any problems. + +## How to Fork a Repository on GitHub + +1. **Access the Repository Page**: Open the [relevant repository page on GitHub](https://github.com/rioriost/postgres-agentic-shop) in your browser. + +2. **Click the Fork Button**: Click the "Fork" button located near the top right of the repository. Select your own GitHub account as the destination for the fork, and you can leave the repository name as default (you can change it if necessary, but no changes are required for this hands-on). + +3. **Confirm the Completion of the Fork**: After waiting a few seconds, the repository will be forked (copied) under your account. Please confirm that the URL on the browser is github.com//postgres-agentic-shop. + +> [!NOTE] Note +> Forking is not mandatory, but it is safe if you may make changes to the code during the exercise or if you want to proceed in your own environment. There is no operational problem even if you directly clone the official repository as it is. Please follow the instructions from the instructor. + +## How to Clone the Repository Locally with git Command + +1. **Obtain the Clone URL**: On the page of the forked repository, press the green "Code" button and copy the displayed clone URL. Either HTTPS or SSH is fine (if HTTPS, it will be in the format `https://github.com//postgres-agentic-shop.git`). + +2. **Open the Terminal**: Move to the working directory on your local PC (e.g., `C:\work` or `~/projects`), and open the terminal (PowerShell or Command Prompt for Windows, Terminal for macOS/Linux). + +3. Execute Clone: Execute the following command to clone the repository. + +```sh +git clone https://github.com//postgres-agentic-shop.git +``` + +※Please replace the above URL part with the URL of your own fork that you copied. + +4. **Move to the Directory**: Once the clone is complete, a new folder named `postgres-agentic-shop` will be created. Move into it. + +```sh +cd postgres-agentic-shop +``` + +5. **Confirm the Contents of the Clone**: Confirm the contents with the `ls` command (or `dir` for Windows), and make sure you can see the directory structure of the source code, `README.md` file, and `azure.yaml` file. All subsequent work will be done within this repository directory. + +This completes the acquisition of the repository. It would be good to open the code in an editor or read the `README` to get an overall picture. In particular, in this project, we will deploy using the **Azure Developer CLI (azd)**, so it will deepen your understanding to check what resources are defined in the `azure.yaml` and the templates in the `infra` directory. + +[Back](03-Integration.md) | [Next](05-Provisioning.md) diff --git a/docs/workshop/en/05-Provisioning.md b/docs/workshop/en/05-Provisioning.md new file mode 100644 index 0000000..05be8f9 --- /dev/null +++ b/docs/workshop/en/05-Provisioning.md @@ -0,0 +1,61 @@ +# Resource Provisioning with Azure Developer CLI + +The repository cloned locally contains settings for deploying infrastructure and application code on Azure. Here, we will use the **Azure Developer CLI (azd)** to provision the necessary Azure resources and deploy the application. The process takes approximately 20-30 minutes, but this may vary depending on the status of Azure resource creation. + +## Provisioning with the `azd up` command + +With the Azure Developer CLI, you can execute everything from infrastructure construction to deployment with a single command. Let's proceed with the following steps. + +1. **Confirm login to Azure CLI (not necessary in CloudShell)**: You have already installed Azure CLI in the preliminary preparation, but check the login status just in case. Run `az account show` on the terminal, and if the subscription information is displayed, you are logged in. If you are not logged in, execute `az login` and complete browser authentication. In addition, `azd` itself also requires authentication to Azure, so execute `azd auth login` (the browser will automatically start and the Azure authentication screen will be displayed. If it does not work, use `azd auth login --use-device-code`). + +2. **Grant execution rights to the shell script**: When deploying in a CloudShell, Linux, or macOS environment, you need to grant execution rights to `azd-hooks/predeploy.sh`. + +```sh +chmod +x azd-hooks/predeploy.sh +``` + +Also, when deploying on Windows, you may get an error about the script execution policy in PowerShell. You can temporarily bypass this with the following command. + +```sh +PowerShell -ExecutionPolicy Bypass -Scope Process +``` + +3. **Execute deployment**: Once prepared, finally execute the `azd up` command. This command automatically performs the following processes. + +- Create a resource group and various necessary resources on Azure using the Bicep template (inside the `infra` directory). +- Execute the post-deploy script (`azd-hooks/predeploy.sh`) for the created resources, enabling database extensions and writing Azure OpenAI settings. +- Build the application code (backend/API and frontend) and deploy it to the specified Azure services (e.g., App Service, Static Web Apps, etc.). + +4. **Input to the prompt**: When you execute `azd up`, you will be asked for some inputs at first. Specifically, these are "Azure subscription to deploy to", "Azure region (location) to deploy to", "Deployment region for Azure OpenAI model", etc. Here, please select the region you considered in the previous section. For Azure OpenAI, only available regions will be candidates. You may also be asked for the name of the resource group to be created. Please enter as appropriate. + +> [!CAUTION] Warning +> A warning about the OpenAI model quota may be displayed. If you get a message like "There is not enough GPT-4o TPM in the specified region", you need to change the settings or the region to meet the quota conditions mentioned above. + +5. **Monitor deployment progress**: When you execute the command, the progress of each step is displayed on the terminal. Internally, resources are built sequentially while tracking the state like Terraform. In particular, it takes several minutes to build Azure Database for PostgreSQL and Azure OpenAI resources. Please wait patiently. If you open the Azure Portal and check the resource group, you can confirm that the resources are increasing in real time. + +6. **Confirm deployment completion**: When all processes are completed, `azd` displays an overview of the deployment results. If successful, the endpoint URLs and names of the deployed services should be listed (for example, the URL of the web application). + +## Description of the main resources created by provisioning and each step + +Here is a brief explanation of the main Azure resources that are automatically built and deployed by `azd up`, and each step within the provisioning process. + +- **Resource Group**: A new one is created with the specified name. It is a container that groups all the resources for the entire hands-on. + +- **Azure Database for PostgreSQL – Flexible Server**: A PostgreSQL database server is created. This is the core database of this project, storing table data such as product information and reviews, as well as vector and graph data by extensions. The SKU, storage size, admin username, etc. are defined in the Bicep template (the default should be a cheap plan). + +- **Azure OpenAI Service**: An Azure OpenAI resource for using GPT-4 and Embedding models is created. The model name and SKU are also automatically set at deployment. For example, GPT-4o and text-embedding-ada-002 are deployed. This allows you to send requests from the database to Azure OpenAI. + +- **Container Apps**: Compute resources for hosting the backend application code are created. There are APIs written in Python (agent orchestration logic) and frontends written in TypeScript/JavaScript. Azure Developer CLI builds and deploys the appropriate hosting destination based on the service definition written in `azure.yaml`. + +- **Connection settings & secrets**: Connection information between the built resources (for example, the database connection string, OpenAI API key, etc.) is incorporated into the application settings by Azure Developer CLI. If you want to further enhance security, consider using Azure Key Vault. + +- **Extension setup**: After the PostgreSQL server starts, the `azd-hooks/predeploy.sh` script is executed. In this script, the execution of the `CREATE EXTENSION` statement mentioned in the previous section and the registration of the Azure OpenAI endpoint and key in the database by calling the `azure_ai.set_setting` function are performed. The `rdbms-connect` extension of Azure CLI plays an active role here, connecting to PostgreSQL and executing SQL in the script with the `az postgres flexible-server connect` command. + +- **Post-deployment processing**: When the infrastructure construction and application placement are finished, Azure Developer CLI displays endpoint information as "Deployment completed". For example, "Web app URL: ...", "Azure OpenAI resource name: ..." etc. are output. These are items defined as `output` in `azure.yaml`. Participants can note this information or use it directly in the next verification procedure. + +If an error occurs during provisioning, please check the error message. Common problems include validation errors due to unusable characters in the environment name, resource creation being denied due to insufficient Azure permissions, and conflicts with existing resource names. In that case, follow the message or retry by re-executing `azd up`. + +> [!NOTE] Troubleshooting +> For general troubleshooting related to Azure Developer CLI, please refer to the [official documentation](https://github.com/Azure-Samples/postgres-agentic-shop). + +[Previous](04-Repository.md) | [Next](06-Post-provisioning.md) \ No newline at end of file diff --git a/docs/workshop/en/06-Post-provisioning.md b/docs/workshop/en/06-Post-provisioning.md new file mode 100644 index 0000000..327155c --- /dev/null +++ b/docs/workshop/en/06-Post-provisioning.md @@ -0,0 +1,50 @@ +# Points to Check After Provisioning + +Once the deployment is complete, check that the built resources and applications are working correctly. This section explains the **main resources to check on the Azure portal**, their **roles**, and **how to check the operation of the application**. + +## Main Azure Resources and Their Roles + +Within the deployed resource group, there should be major components as follows. Understand the role of each resource and its role in this hands-on application. + +- **Azure Database for PostgreSQL (Flexible Server)**: +Role: Data store for the application. It stores relational data (users, products, reviews, etc.) and performs AI processing, vector search, and graph analysis using `azure_ai`, `pgvector`, and `Apache AGE` extensions. + +What to check: In the "Azure Database for PostgreSQL" section of the Azure portal, confirm that a new server has been created. In Settings -> Server Parameters, check that `azure.extensions` includes `azure_ai`, `vector`, and `age`, and that the connection information (hostname, username, etc.) is as expected. The database should be loaded with initial data such as product and review information (external service connection information such as Arize may also be included in the table). + +- **Azure OpenAI Resource**: +Role: A cognitive services resource that hosts models such as GPT-4. It responds to calls via `azure_ai` from PostgreSQL and API calls from backend code. + +What to check: Open the relevant resource from "Azure OpenAI" on the portal and check the deployment status of the model (in the "Model Deployment" menu, it's okay if, for example, `gpt-4o` or `text-embedding-ada-002` is in Deploy state). Also, you can later verify that the key and endpoint URI match those set on the PostgreSQL side. + +- **Backend App (Container Apps)**: +Role: A hosting environment where Python application logic is deployed. It receives requests from users (e.g., product search queries), queries the database as needed, and launches LLM agents to generate responses. It is the central entity where multi-agent orchestration takes place. + +What to check: Check the Container Apps on the portal and look for the newly created resource. Enabling "Log Stream" is also useful to see if logs are output during the app test mentioned later. + +- **Frontend App (Container Apps)**: +Role: Hosts the user interface. Users enter search queries and view results through this frontend. Calls to the backend API from the frontend also take place here. + +What to check: In the case of Static Web Apps, open the target resource on the portal and check the "URL". This URL will be the URL of the web app used in the operation check mentioned later. In the case of static site hosting on a Storage account, the endpoint domain is displayed. Try accessing these to see if the page is displayed. + +The application will only work when all of these resources are in place. Understanding each role makes it easier to decide which component to investigate if a problem occurs. + +## Checking the Operation of the Application + +Once the resource deployment is complete, let's check if the application (AgenticShop) is working as expected. I will explain the points to check in order. + +1. **Access to the frontend**: Open the frontend URL (e.g., `https://rt-frontend..japaneast.azurecontainerapps.io`) in your browser. The top page of AgenticShop should be displayed. The screen has a search bar and a chatbot-like UI. The design is modeled after an electronics gadget shopping site. + +> [!NOTE] Note +> If the backend does not switch from the connecting display even when accessing the frontend, the backend may not be starting up properly. In this case, stopping and restarting the backend should make it work properly. + +2. **Display of product information**: Try some operations on the app. For example, select a product category and check if the product list and recommended products for that category are displayed. AgenticShop has a personalization feature based on user profiles. + +3. **Search and AI response**: Try entering a question in the search bar (e.g., "Do you have headphones with good sound quality and long battery life?"). When you enter and send, the LLM agent operates in the backend, retrieves products and reviews with vector search from PostgreSQL, and generates a summary/response with Azure OpenAI, executing a series of flows. As a result, a list of relevant products and descriptions should be displayed on the screen, and you should get output like "The recommended headphones are ○○. It is... (reason)" as a chatbot response. Let's also check the effect of reranking. When you ask the same question, verify whether more relevant results are obtained compared to simply keyword searching (for example, whether mentions of noise cancellation are properly evaluated). + +4. **Verification of graph function**: Try some complex questions. For example, "Are there headphones that are light, have a waterproof function, and have high sound quality review ratings?" This question combines product spec features (lightweight = design feature, waterproof = functional feature) and review content (highly rated for sound quality). In AgenticShop, to answer such questions in the database, product and review features are graphed, and the results are narrowed down with the addition of LLM's judgment. On the screen, you would expect the top 3 relevant products to be listed, with a summary of each product's features and a summary of reviews. If you actually get such results, it's proof that Graph + AI is working correctly. + +5. **Checking the debug panel (optional)**: If the Arize Phoenix debug panel function is included, there may be buttons or links such as "Debug" or "Trace" on the UI. Opening it may allow you to list the internal operation logs of the agent (for example, what kind of query was issued at each step, what kind of response was obtained, how confident it was, etc.). If this panel is available, it will make it easier for hands-on participants to follow the flow of the agent, so please check it together. + +In this way, test each function while actually operating the application to see if it is working as expected. In particular, whether the role of the database is being properly fulfilled (whether the vector search results are reasonable, whether graph queries are being used) can be confirmed by looking at the "Query Performance Insight" on the Azure Portal or the `pg_stat_statements` extension. If you're interested, also take a look at the backend logs (for example, Log Stream or Application Insights logs if you're using App Service). The prompts and responses thrown to Azure OpenAI, any errors and their contents, etc., should be recorded. + +[Back](05-Provisioning.md) | [Next](07-WhyPostgreSQL.md) \ No newline at end of file diff --git a/docs/workshop/en/07-WhyPostgreSQL.md b/docs/workshop/en/07-WhyPostgreSQL.md new file mode 100644 index 0000000..96dd477 --- /dev/null +++ b/docs/workshop/en/07-WhyPostgreSQL.md @@ -0,0 +1,117 @@ +# Detailed Vector, Rerank, and Graph Queries on PostgreSQL + +This section describes the actual table configuration of PostgreSQL used in the AgenticShop solution and provides concrete examples of vector search, rerank, and graph data queries. Let's understand how we are using advanced queries made possible by integrating extensions into PostgreSQL. + +## Table List and Schema + +The data handled in AgenticShop mainly relates to "products" and "reviews". The repository's dataset (Agentic Shop dataset) includes products and corresponding user reviews for three categories: headphones, smartwatches, and tablets. Corresponding to each, the following tables exist on PostgreSQL. + +**product table (product information)**: This table stores basic product information. The main columns are as follows: +- id: Product ID (integer, primary key) +- name: Product name (text) +- category: Product category (text, e.g., "headphones", "smartwatches", "tablets") +- price: Price (integer) +- brand: Brand name (variable-length text) +- description: Product description (text, detailed free description) +- specifications: JSON data summarizing the product's features (jsonb type). Features extracted from the description using LLM (e.g., design, battery performance, water resistance, etc.) are stored in key-value pairs. Used in the graph analysis described later. + +**data_embeddings_products table (product embedding information)**: This table stores the embeddings of product descriptions. +- embedding: Embedding vector of product description (vector type). A vector of about 1536 dimensions generated by Azure OpenAI's Embedding model is stored. + +In addition, an index is created on the `embedding` column to optimize vector search (`pgvector` supports indexes for Inner Product and Cosine distance). + +**reviews table (review information)**: This table stores user product reviews. The main columns are as follows: +- id: Review ID (integer, primary key) +- product_id: Product ID of the review target (integer, references products.id as a foreign key) +- review_text: Review text (text, free-form content written by the user) + +**data_embeddings_reviews table (review embedding information)**: This table stores the embeddings of user product reviews. The main columns are as follows: +- embedding: Embedding vector of review text (vector type). +A vector index is also created on this column. This speeds up the process of semantically searching review content. + +The above two tables are the core data. As a relation, `reviews.product_id` is a foreign key to `products.id`, linking products and reviews (multiple reviews correspond to one product). + +Furthermore, in AgenticShop, products, reviews, and features are graphed using Apache AGE. The following concepts are treated as nodes and edges when graphing. +**Nodes**: +- Product nodes: One node per product. Holds attributes such as product ID, name, category, feature JSON, etc. +- Review nodes: One node per review. Holds attributes such as review ID, text, feature JSON, etc. +- Feature nodes: One node per feature word extracted from products and reviews (e.g., "battery life", "soundQuality", "waterResistance", "design", etc.). Has a `name` attribute (feature name). + +**Edges**: +- (:Product)-[:REVIEWS]->(:Review): Edge from the product node to its review node. +- (:Product)-[:HAS_FEATURE]->(:Feature): Edge from the product node to the feature node included in its product description. For example, if the product description talks about "water resistance", there is an edge connecting that product and the feature "waterResistance". + +These graph data are managed within `ag_catalog` by PostgreSQL's `AGE` extension. The process of extracting features from the `products` and `reviews` tables and creating graph nodes and edges is executed in deployment scripts and backend initialization code (for example, creating feature nodes for all products and creating `HAS_FEATURE` edges, creating `REVIEWS` edges for all reviews, etc.). + +## Queries for Vector, Rerank, and Graph Data + +In AgenticShop, we are executing advanced queries using the above data and extensions. Here, we will give examples of representative queries and explain their content. + +**Vector Similarity Search Query (Semantic Similarity Search)**: + +To find products and reviews that match the user's questions or interests, we perform similarity searches using embedding vectors. For example, in response to the question "Which headphones have clear call quality and a good reputation for battery life?", the system first searches for the top 10 similar products from product descriptions, and then finds the top 10 similar reviews from the reviews associated with those products, performing a two-stage search. The SQL example would be as follows (simplified): + +```sql +WITH potential_products AS ( + SELECT id, name, description + FROM products + WHERE category = 'headphones' + ORDER BY description_emb <=> azure_openai.create_embeddings('text-embedding-ada-002', 'good clear calling')::vector + ASC + LIMIT 10 +) +SELECT p.id AS product_id, r.id AS review_id, p.name, p.description, r.review_text +FROM potential_products p +LEFT JOIN reviews r USING (product_id) +ORDER BY r.review_text_emb <=> azure_openai.create_embeddings('text-embedding-ada-002', 'good battery life')::vector +ASC +LIMIT 10; +``` + +Here, we first sort the products in the `products` table with the category `headphones` that have a description close to the phrase "good clear calling" by vector distance `<=>`. `azure_openai.create_embeddings('text-embedding-ada-002', '...')` is a function that vectorizes the query sentence via Azure OpenAI (provided by the `azure_ai` extension). The top 10 products from this result are taken as `potential_products`, and then their reviews are joined from the `reviews` table, and this time we sort by those with review text close to "good battery life" by vector distance. Finally, we can obtain reviews and their products with high relevance. A major feature is that embedding generation and vector comparison are performed directly within SQL. + +**Document Reranking Query**: +Reranking is the process of reordering the results obtained from vector search based on an LLM evaluation. The `azure_ai.rank()` function of the Azure AI extension calculates relevance scores for the given query and document set using LLM (or a cross-encoder model) and returns the ranking in order of the most matching ones. For example, the query to evaluate "which review emphasizes the clarity of the call" against the previous set of reviews would be as follows. + +```sql +WITH reviews(id, text) AS ( + VALUES + (1, 'The product has a great battery life.'), + (2, 'Noise cancellation does not work as advertised. Avoid this product.'), + (3, 'Good design, but a bit heavy. Not recommended for travel.'), +In the above example, four review sentences are input and ranked based on their relevance to the query "Can it make clear calls that block out background noise?". The results are returned with the `rank` value being smaller the higher the relevance (with 1st place output as `rank=1`), and review ID2 "Noise canceling does not work as expected" is evaluated as the most relevant (= mentioned issues with call quality). This is because LLM ranks the text based on its overall meaning, allowing for subtle context adjustments that cannot be captured by vector distance alone. At AgenticShop, we use this reranking to optimize the display order of search results and to extract reviews that best answer user questions. + +**Graph Query (Querying with openCypher)**: +The advantage of graphing products, reviews, and features is that it allows for narrowing down complex conditions based on relationships. With Cypher queries, you can use pattern matching to, for example, find "products in the headphone category that have a feature related to design, also have a feature of water resistance, and have positive mentions about sound quality in their group of reviews". This condition is very complex, but in Cypher it can be described as follows: + +```sql +MATCH (p:product {category: 'headphones'}) + -[:HAS_FEATURE]->(:feature {name: 'design'}) + WITH p +MATCH (p)-[:HAS_FEATURE]->(:feature {name: 'waterResistance'}) + WITH p +MATCH (p)-[:HAS_REVIEW]->(r:review) + -[:MENTIONS_FEATURE {sentiment: 'positive'}]->(:feature {name: 'soundQuality'}) +RETURN p.id, r.id; +``` + +This is called and executed from SQL as `cypher('products_reviews_features_graph', $$ $$)`. As a result, you can get a set of product IDs and review IDs that meet the criteria. At AgenticShop, we perform further post-processing on the SQL side based on these results. For example, we extract detailed information about the products and reviews obtained above from regular tables (`products`, `reviews`), have LLM determine whether the product description is "lightweight" and exclude non-compliant products (using the `azure_ai.is_true` function), and finally count the number of reviews per product while generating a summary sentence with LLM (using the `azure_ai.generate` function). The final query in this series is quite long, but it aggregates the results in one query by combining SQL, Cypher, and AI functions. This query allows you to get the top three "lightweight, waterproof headphones with high sound quality review ratings" with a summary. + +In the actual result example, the product ID and name, each product feature summary (design, waterproof summary) and review summary, and the number of reviews are output. By having LLM summarize, users can be succinctly informed of features such as "These headphones are lightweight and waterproof. The sound quality was highly rated in the reviews." + +In summary, we have looked at advanced queries used in AgenticShop. + +In summary, vector search is powerful for narrowing down related items, reranking improves result accuracy through LLM's understanding, and graph queries extract relationships by combining multiple conditions. Because they can all be used consistently within PostgreSQL, there is no cost of data movement and complex questions can be answered. + +In the hands-on, we recommend trying out queries like the above by actually connecting to PostgreSQL. Connect to the database with `psql` or `pgAdmin`, peek at the data with `SELECT * FROM products LIMIT 5;`, and try running a simple Cypher query. For example, a query to count the number of all product nodes and review nodes: + +```sql +SELECT * +FROM cypher('products_reviews_features_graph', $$ + MATCH (p:product) RETURN count(p) +$$) AS t(count bigint); +``` + +By trying this, you can confirm that data is entered in the graph. + +[Previous](06-Post-provisioning.md) | [Next](08-GraphRAG.md) \ No newline at end of file diff --git a/docs/workshop/en/08-GraphRAG.md b/docs/workshop/en/08-GraphRAG.md new file mode 100644 index 0000000..512fe21 --- /dev/null +++ b/docs/workshop/en/08-GraphRAG.md @@ -0,0 +1,64 @@ +# GraphRAG Integration + +## Knowledge Graph Construction with GraphRAG (Graph Extraction) + +As part of GraphRAG, the backend of AgenticShop constructs a knowledge graph from product reviews. Specifically, we introduce the Apache AGE extension to PostgreSQL, and construct a graph that represents products and their features (functions) as nodes, and the relationships mentioned in reviews as edges. This process is executed in create_apache_age_graph.py, where the create_graph function of AGE is used on PostgreSQL to generate a graph (creating a graph schema for retail). In addition, generate_sentiments_for_reviews.py analyzes positive/negative mentions for specific features in each review, and stores this in the database in a form that can be utilized as edge information in the graph. In other words, we assign attributes such as the number of times a feature is mentioned in a review and the sentiment (positive/negative) to the edges connecting the product nodes and feature nodes, and construct a knowledge graph of product-feature relationships within the database. Through this graph extraction process, we extract relationships between products (common features and review ratings) from unstructured text as structured data. This allows the backend to use PostgreSQL as both a relational DB and a graph DB, enabling complex relationship queries. + +## Graph Integration and Cypher Query with Apache AGE + +Through the Apache AGE extension, the backend can execute Cypher queries (OpenCypher language) on PostgreSQL. In Alembic migrations, we execute CREATE EXTENSION age and SELECT * FROM create_graph('graph_name') to create a graph schema within PostgreSQL. Also, product/feature node and edge insertions are executed from SQL. For example, from the feature list (features.csv) and the analysis results of reviews, we insert edges that represent "Feature X was mentioned positively" for each product using the MATCH ... CREATE statement. Apache AGE allows hybrid queries that call Cypher queries in the FROM clause of SQL, and we are utilizing this in this project. In fact, with the integration of PostgreSQL and AGE, we can issue queries that combine traditional SQL filters and graph queries. For example, we can execute a process like "Find products with many positive mentions about a certain feature from a specific group of products" in a single SQL+Cypher hybrid query. The advantage of Apache AGE integration is that it is not necessary to place the knowledge graph for LLM in another graph DB, and we can consistently operate relational data and graph data within PostgreSQL. + +Specifically, we execute SQL with Cypher functions in the service layer of the backend. For example, in product filtering based on features and sentiments, we construct a query internally as follows (pseudo code): + +```sql +SELECT p.id, g.count +FROM products p +JOIN LATERAL cypher('retail_graph', $$ + MATCH (p:Product)-[m:MENTION {sentiment: "positive"}]->(f:Feature {name: ""}) + RETURN COUNT(m) as count +$$) AS g(count) +ON g.count > 0 +WHERE p.category = '' +ORDER BY g.count DESC; +``` + +As shown above, we incorporate cypher('graph_name', $$ ... $$) into the SQL statement of PostgreSQL, calculate the number of edges in the graph, and incorporate the results into the relational query. This combination of OpenCypher queries and SQL allows for searches that consider both normal filters (e.g., category) and indicators on the graph structure (e.g., mention count) at the same time. + +## Utilization of GraphRAG at Runtime (Graph Query Generation) + +When there are questions or requests from end users, the backend generates graph queries and conducts information retrieval using the knowledge graph. In the GraphRAG algorithm, this corresponds to the step of "generating a graph query and using it as context information for LLM". AgenticShop issues appropriate graph queries according to the content of the user's query and profile. For example, when a user asks "Which smartphone has high ratings for battery life?", the UserQueryAgent in the backend performs the following process. +1. Retrieve product candidates in the relevant category using vector search (similarity search using pgvector). +2. Graph query generation: For the candidate product group, construct a Cypher query that calculates the "number of positive reviews for the specified feature". This is implemented in a function called fetch_product_with_feature_and_sentiment_count, which takes a list of product IDs, sentiment type, and feature name as arguments and executes the graph query. This function issues Cypher+SQL as mentioned above and retrieves the number of positive mentions for that feature for each product. +3. Ranking of results: Based on the mention count for each product, rearrange the product list (the more mentions, the higher the ranking as a product with high ratings for that feature). The aim of GraphRAG is to improve search accuracy by utilizing the signal of **"prominence"** (importance) on the graph, and in this case, the "number of positive reviews" serves as a prominence indicator. +4. Providing context to LLM: Finally, select the top N products and generate a text summarizing the review content related to each product (see the agent workflow below). At this time, the products and reviews with high importance selected by GraphRAG are given to LLM as context, making it easier to obtain accurate answers that align with the user's question intent. + +In the code, you can check the above logic in the UserQueryAgent.query_reviews_with_sentiment method. The following is an excerpt from it, showing the part where the graph query function is called when a feature is specified. + +```python +if feature: + product_ids_for_query = [product.id for product in results] + product_ids_with_count = await self.fetch_product_with_feature_and_sentiment_count( + product_ids_for_query, sentiment, feature[0] + ) + # (Filter/sort the product list using the returned product_ids_with_count) +``` + +Here, feature[0] indicates the feature name (e.g., "battery"), and product_ids_for_query is a list of target product IDs. This function call aggregates the mention count of the specified feature for the relevant product group on the graph database, and the result is stored in product_ids_with_count. In the subsequent process, this information is used to rearrange the product objects in results and determine the top products for the answer. + +## Utilization of Graph in Multi-Agent Workflow + +AgenticShop adopts a multi-agent flow based on LlamaIndex, and also utilizes the results of GraphRAG for post-processing of search results and summary generation. In the backend's multi_agent_workflow.py, the function run_workflows_in_background launches additional processing by agents as a background task. In this workflow, based on user-specific profile information and knowledge obtained from graph queries, LLM generates detailed descriptions of each product and summary of reviews. Specifically: +- Personalized summary: According to the user's preferences (features and points of interest in the profile), it extracts and summarizes relevant points from the reviews of each product. The product-feature graph constructed with GraphRAG serves as a guide to which products are highly rated for the user's interest features. Using this guide, the agent finds relevant reviews from vector search (vector_store_reviews_embeddings) and the graph, and passes them to LLM. For example, for a user who values "battery life", the workflow identifies products with many positive reviews for that feature via the graph, and focuses on summarizing the content of those reviews.- Instructions in the prompt: If you look at backend/src/agents/prompts.py, it contains instructions to the agent such as "Summarize insights from the review most relevant to the user's preferences". Furthermore, it includes detailed guidelines to improve output quality, such as "Do not summarize the user's preferences themselves, but rather mention the product" and "Ignore preferences unrelated to the product category". These are prompts designed for the LLM to appropriately select and discard information, based on the graph structure obtained from GraphRAG. For example, features determined to be irrelevant on the graph (e.g., sound quality is irrelevant to a smartwatch) are instructed not to be included in the summary. + +As such, the graph structure is utilized in both information retrieval and summarization. In the search stage, candidates are ranked based on scores obtained from the graph (e.g., mention count of features), and in the summarization stage, relevant review content is extracted according to the relevance indicated by the graph. + +## Role and Advantages of GraphRAG in the Architecture + +In AgenticShop, GraphRAG functions as a core technology to sophisticate the backend information retrieval pipeline. Its roles and benefits are as follows. +- Improvement in accuracy and ranking enhancement: Simple vector search only evaluates text similarity, but with GraphRAG, relationships on the knowledge graph can be considered. For example, information such as "many reviews" and "highly rated for a certain feature" are important signals obtained from the graph. In the case of Legal Copilot, the number of times a precedent was cited was used as a prominence indicator to improve accuracy, while in AgenticShop, the number of positive reviews for a feature is reflected in the ranking as a similar indicator. This ensures that more appropriate products (superior from the user's perspective) rank higher in response to user queries. In fact, it has been reported that the introduction of GraphRAG has dramatically improved the accuracy of the information retrieval pipeline (such as improved recall). +- Fusion of relational data and graph data: Since GraphRAG operates within PostgreSQL, it can handle relational information such as user profiles and product masters, and graph information derived from reviews in a unified manner. This simplifies the system configuration and eliminates the need to synchronize with an external graph DB. In the architecture of AgenticShop, Azure Database for PostgreSQL alone handles vector search (pgvector), full-text search, and graph search, offering significant advantages in terms of data consistency and manageability. +- Improvement in LLM response quality: The context provided by GraphRAG (context based on the knowledge graph) makes the LLM's response content more reliable. For example, for product features of interest to the user, the LLM can generate specific and valid responses by presenting "substantiated information (review ratings and frequently mentioned benefits)" obtained from the graph. This directly connects to the purpose of RAG, which is to suppress hallucinations and improve accuracy. In AgenticShop, the agent collects information across multiple knowledge sources (vector DB, graph DB) to provide useful responses (product recommendations and explanations) that better match user intent. + +In summary, the concept of GraphRAG is technically incorporated into the backend directory of the AgenticShop backend, and by building a knowledge graph with Apache AGE and utilizing Cypher queries, and supplying graph information to the LLM agent, it significantly improves the accuracy of multi-agent AI responses and user experience. GraphRAG is the keystone of this solution architecture, providing a foundation for easy advanced search and inference using graph data in future expansions. This approach of utilizing graphs that model various retail domain-specific relationships (product-feature-review-user preference) brings out insights that could not be obtained with traditional RAG methods, and ultimately enhances the quality of responses to end users. + +[Previous](07-WhyPostgreSQL.md) | [Next](09-Wrapup.md) \ No newline at end of file diff --git a/docs/workshop/en/09-Wrapup.md b/docs/workshop/en/09-Wrapup.md new file mode 100644 index 0000000..e57aa92 --- /dev/null +++ b/docs/workshop/en/09-Wrapup.md @@ -0,0 +1,32 @@ +# Deleting Provisioned Resources + +Once the hands-on is over, be sure to delete any resources that are no longer needed. Leaving resources created on Azure could potentially incur unexpected costs. Here, we introduce a cleanup procedure using Azure Developer CLI. + +## Procedure for Deleting Resources + +In Azure Developer CLI, there is a command to delete the deployed environment in bulk. Especially in cases like this time where resources are created by resource group, you can delete the resource group and all resources in it together. + +- **Execution of the delete command**: In the project directory (`postgres-agentic-shop`), execute the following command. + +```sh +azd down --purge +``` + +This will delete all resources created by `azd up`. By adding the `--purge` option, all resources, including "account" resources like Azure OpenAI, will be completely deleted. While a normal `azd down` only deletes within the resource group, adding `--purge` will undeploy the model from OpenAI resources and then delete the resources. It may take a few minutes to delete, but when it's done, a message like "Delete Successful" will be displayed. + +- **Confirmation of deletion results**: Just to be sure, check the list of resource groups on the Azure portal and confirm that the relevant resource group has disappeared. The `azd` environment is also recorded locally, but if you don't reuse it, you can delete the `.azure` directory. + +- **Alternative method**: If you do not use Azure Developer CLI, you can manually delete the resource group from the Azure portal. In that case, only the Azure OpenAI resource may exist outside the resource group, but this time it should be created in the same RG, so you can expect to delete it in bulk. When deleting a resource group from the portal, there is a confirmation of the resource group name, so please follow the instructions. + +> [!CAUTION] Warning +> Once you delete, all data in the database, connection information, logs, etc. will be lost. If you have any deliverables (e.g., exported feature data or analysis results) during the hands-on, please back them up in advance. However, there is no data to be retained in this hands-on, so you can delete it as it is. + +With this, the cleanup is complete. The habit of not leaving unnecessary resources on Azure is very important for cost management, so be sure to perform this procedure at the end of the hands-on. + +This concludes all the steps of the AI integrated application development hands-on on Azure using the GitHub repository postgres-agentic-shop. + +Good job! + +As you learned this time, by combining AI extensions, `pgvector`, and Apache AGE with Azure Database for PostgreSQL, you can build a simple yet powerful AI application platform. Please try to apply it in your actual projects. If you want to dig deeper into the technical elements touched on in each section, referring to official documents and related blog articles will deepen your understanding. + +[Back](08-GraphRAG.md) \ No newline at end of file diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index 43f6042..a766df4 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -4,12 +4,12 @@ ## ハンズオンに必要なもの - **Azureサブスクリプション**: Azureの有効なサブスクリプションが必要です(所有者またはリソース作成権限を持つこと)。Azure OpenAI サービスを利用するため、このサービスへのアクセス権がサブスクリプションに含まれていることを確認してください(場合によっては利用申請が必要です)。 -- **開発マシンとインターネット接続**: ハンズオンを実施するPC(Windows, macOS, Linuxいずれも可)と安定したインターネット接続が必要です。Azure CLI等を使用するため、ローカル環境にターミナル/コマンドプロンプトが利用可能であることを確認してください。 - **CloudShell**: 開発マシンの用意が難しい場合、AzureポータルのCloudShellでもハンズオンの実行が可能です。`az extension add --name rdbms-connect`を事前に実行しておいてください。 +- **開発マシンとインターネット接続**: ハンズオンを実施するPC(Windows, macOS, Linuxいずれも可)と安定したインターネット接続が必要です。Azure CLI等を使用するため、ローカル環境にターミナル/コマンドプロンプトが利用可能であることを確認してください。 ## 事前にインストールしておくべきソフトウェア -以下のソフトウェアを事前にインストールしてください。バージョンはできるだけ最新の安定版を推奨します。 +CloudShellではなく開発マシンを利用する場合、以下のソフトウェアを事前にインストールしてください。バージョンはできるだけ最新の安定版を推奨します。 - **Azure Developer CLI (`azd`)**: Azureの開発者向けCLIツールです。インストール手順は[Azure Developer CLI のインストール方法](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-linux)を参照してください。 @@ -17,8 +17,6 @@ - **Azure CLI拡張機能(rdbms-connect)**: Azure CLIにPostgreSQLサーバーへの一時的トンネリング接続等を追加する拡張です。インストールは`az extension add --name rdbms-connect`で行います。インストール後、`az extension list`で有効になっていることを確認してください。 -- **Python (3.8以上)**: デプロイ後のアプリケーションやスクリプトの動作に利用します。Pythonがインストールされていない場合は[公式サイト](https://www.python.org/downloads/windows/)から入手してください。Windowsの場合、インストール時に「Add Python to PATH」にチェックを入れておくと便利です。 - - **PowerShell Core (Windowsユーザーのみ)**: WindowsでLinux向けスクリプトを実行する場合に必要です。事前に[PowerShell 7.x (Core)](https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5)をインストールしておいてください。 インストール後、以下のコマンドを実行してbashが動作することを確認してください。 ```sh @@ -29,31 +27,35 @@ bash --version wsl --install ``` -- **psql もしくは pgAdmin**: PostgreSQLに接続し、テーブルやデータの確認・操作に利用します。psqlはPostgreSQLのパッケージに含まれているので、[PostgreSQLのダウンロードページ](https://www.postgresql.org/download/)から、pgAdminは[pgAdminのダウンロードページ](https://www.pgadmin.org/download/)から入手可能です。 - インストール後、それぞれのコマンドがパスに通っていることを確認しましょう。例えば`azd version`や`az --version`を実行してバージョン情報が表示されれば準備OKです。 ## Azureリージョンの選定条件 本ハンズオンでデプロイするリソースにはAzure Database for PostgreSQLやAzure OpenAIサービスなどが含まれます。**Azure OpenAI**サービスは利用可能なリージョンが限られているため、**「Azure OpenAIが利用可能なリージョン」を選択することが重要**です。また、**Azure Database for PostgreSQLにおける一部拡張機能(後述のApache AGEなど)**は新規作成したサーバでサポートされるプレビュー機能です。Azure Developer CLI (`azd`) 実行時に、インフラリソース用リージョンとAzure OpenAI用リージョンの2つを尋ねられます。原則として以下の条件を満たすリージョンを選びましょう。 -- **Azure OpenAI対応リージョン**: East USやSouth Central US、西ヨーロッパ、東日本など、Azure OpenAIリソースを作成できるリージョンを指定します。自分のサブスクリプションでOpenAIのサービス作成が許可されているリージョンか事前に確認してください。 +- **Azure OpenAI対応リージョン**: East USやSouth Central US、West Europe、Japan Eastなど、Azure OpenAIリソースを作成できるリージョンを指定します。自分のサブスクリプションでOpenAIのサービス作成が許可されているリージョンか事前に確認してください。 + +- **データベースと近接した場所**: パフォーマンスの観点から、Azure Database for PostgreSQLを配置するリージョンと物理的に近いリージョンをAzure OpenAIに選ぶのが望ましいです。例えば、Japan EastリージョンにDBを作成した場合、Azure OpenAIもJapan Eastまたは近隣のリージョンを選択します(ただしOpenAIサービスが限られるため妥協が必要な場合もあります)。 -- **データベースと近接した場所**: パフォーマンスの観点から、Azure Database for PostgreSQLを配置するリージョンと物理的に近いリージョンをAzure OpenAIに選ぶのが望ましいです。例えば、東日本リージョンにDBを作成した場合、Azure OpenAIも東日本または近隣のリージョンを選択します(ただしOpenAIサービスが限られるため妥協が必要な場合もあります)。 +- **CloudShellでの動作確認済みリージョン**: 以下のリージョンはCloudShellでデプロイして動作を確認済みです。 + - Japan East (took 30 mins) + - Australia East (took 15 mins) + - South India (took 16 mins) + - Korea Central (Failed due to lack of text-embedding-ada-002) -- **プレビュー機能の利用**: Apache AGE拡張は新規作成のPostgreSQLサーバでPG 13〜16の場合に利用可能で、既存サーバには有効化できません。`azd up`では自動的に新しいPostgreSQLサーバを作成しますが、念のためAzure Portalでプレビュー機能利用に関する記載がないか確認すると安心です。 +- **プレビュー機能の利用**: Apache AGE拡張は新規作成のPostgreSQLサーバで利用可能で、既存サーバには有効化できません。`azd up`では自動的に新しいPostgreSQLサーバを作成しますが、念のためAzure Portalでプレビュー機能利用に関する記載がないか確認すると安心です。 ## クォータの確認 -Azure OpenAIサービスを利用するには、モデルごとの使用上限(クォータ)がサブスクリプションに割り当てられている必要があります。特に本ハンズオンで用いる**GPT-4**モデルや**Embedding**モデルは要求クォータが比較的大きいです。デフォルト構成では以下の程度のスループットが必要となります。 +Azure OpenAIサービスを利用するには、モデルごとの使用上限(クォータ)がサブスクリプションに割り当てられている必要があります。特に本ハンズオンで用いる**GPT-4o**モデルや**Embedding**モデルは要求クォータが比較的大きいです。デフォルト構成では以下の程度のスループットが必要となります。 -- **GPT-4 モデル**: 1分あたり150kトークン程度の処理能力 (150K TPM)。Azure OpenAIの「モデル使用上限 (Requests per minute)」がこれを満たす必要があります。 +- **GPT-4o モデル**: 1分あたり150kトークン程度の処理能力 (150K TPM)。Azure OpenAIの「モデル使用上限 (Requests per minute)」がこれを満たす必要があります。 - **text-embedding-ada-002 (埋め込みモデル)**: 1分あたり120kトークン程度 (120K TPM)。 ご自身のAzureサブスクリプションのOpenAIクォータは、Azure Portalの「クォータ + 制限」ページや`az openai admin quota show`コマンドで確認できます。不足している場合は、Azure OpenAIリソースの作成時に上限引き上げのリクエストを申請してください。 > [!NOTE] 注意 -> `azd up`実行時のプロンプトでも、上記モデルのデプロイ容量 (デフォルト値) をパラメータとして設定するようになっています。必要に応じてリポジトリ内の `infra/main.parameters.json` を編集することで、GPT-4や埋め込みモデルのデプロイスケールを調整できます(クォータ節約のためにTPSを下げる等)。 +> `azd up`実行時のプロンプトでも、上記モデルのデプロイ容量 (デフォルト値) をパラメータとして設定するようになっています。必要に応じてリポジトリ内の `infra/main.parameters.json` を編集することで、GPT-4oや埋め込みモデルのデプロイスケールを調整できます(クォータ節約のためにTPSを下げる等)。 [前へ](01-Introduction.md) | [次へ](03-Integration.md) diff --git a/docs/workshop/ja/05-Provisioning.md b/docs/workshop/ja/05-Provisioning.md index 01cd834..5993f65 100644 --- a/docs/workshop/ja/05-Provisioning.md +++ b/docs/workshop/ja/05-Provisioning.md @@ -6,9 +6,19 @@ Azure Developer CLIを使うと、単一のコマンドでインフラの構築からデプロイまで実行できます。以下の手順で進めましょう。 -1. **Azure CLIへのログイン確認**: 事前準備でAzure CLIをインストール済みですが、念のためログイン状態を確認します。ターミナル上で `az account show` を実行し、サブスクリプション情報が表示されればログイン済みです。未ログインの場合は `az login` を実行してブラウザ認証を完了してください。加えて、`azd`自体もAzureへの認証が必要ですので、`azd auth login`を実行しておきます(ブラウザが自動起動しAzure認証画面が表示されます。うまくいかない場合 `azd auth login --use-device-code` を使用)。 +1. **Azure CLIへのログイン確認(CloudShellでは不要)**: 事前準備でAzure CLIをインストール済みですが、念のためログイン状態を確認します。ターミナル上で `az account show` を実行し、サブスクリプション情報が表示されればログイン済みです。未ログインの場合は `az login` を実行してブラウザ認証を完了してください。加えて、`azd`自体もAzureへの認証が必要ですので、`azd auth login`を実行しておきます(ブラウザが自動起動しAzure認証画面が表示されます。うまくいかない場合 `azd auth login --use-device-code` を使用)。 -2. **環境の初期化 (`azd init`)**: (※既定では必須ではありませんが推奨)`azd init` コマンドを実行し、このプロジェクト用の環境名を設定します。環境名は英数字の組み合わせで、他と被らないユニークな名前を付けます(例: `agshopenv1`)。環境名はAzureリソース名の一部としても使われます。ここで`azd init`を実行すると`azure.yaml`に基づきローカルに環境構成が作成されます。 +2. **シェルスクリプトへの実行権限の付与**: CloudShell、Linux及びmacOS環境でデプロイする際は、`azd-hooks/predeploy.sh`に実行権限を付与しておく必要があります。 + +```sh +chmod +x azd-hooks/predeploy.sh +``` + +また、Windowsでデプロイする際に、PowerShellでスクリプト実行ポリシーに関するエラーが出る場合があります。一時的に以下のコマンドで回避できます。 + +```sh +PowerShell -ExecutionPolicy Bypass -Scope Process +``` 3. **デプロイ実行**: 準備が整ったら、いよいよ `azd up` コマンドを実行します。このコマンドは以下の処理を自動で行います。 @@ -16,17 +26,10 @@ Azure Developer CLIを使うと、単一のコマンドでインフラの構築 - 作成したリソースに対して、ポストデプロイのスクリプト(`azd-hooks/predeploy.sh`)を実行して、データベース拡張の有効化やAzure OpenAI設定の書き込みを実施。 - アプリケーションのコード(バックエンド/APIやフロントエンド)をビルドし、それぞれ指定されたAzureサービス(例: App ServiceやStatic Web Appsなど)にデプロイ。 -> [!NOTE] 注意 -> Linux及びmacOS環境でデプロイする際は、`azd-hooks/predeploy.sh`に実行権限を付与しておく必要があります。 -> -> `chmod +x azd-hooks/predeploy.sh` -> -> を実行しておいてください(Windowsの場合は不要です)。PowerShellでスクリプト実行ポリシーに関するエラーが出た場合は、一時的に `PowerShell -ExecutionPolicy Bypass -Scope Process` を実行してから再度 `azd up` を試してください。 - 4. **プロンプトへの入力**: `azd up`を実行すると、最初にいくつか入力を求められます。具体的には「デプロイ先のAzureサブスクリプション」「デプロイするAzureリージョン(ロケーション)」「Azure OpenAIモデルのデプロイ先リージョン」などです。ここで、前セクションで検討したリージョンを選択してください。Azure OpenAIについては利用可能なリージョンのみ候補に出ます。また、今回新規に作成するリソースグループ名も求められる場合があります。適宜入力しましょう。 > [!CAUTION] 警告 -> OpenAIモデルのクォータに関する警告が表示されることがあります。「指定リージョンに十分なGPT-4のTPMがない」等のメッセージが出た場合は、前述のクォータ条件を満たすように設定を変更するかリージョンを変更する必要があります。 +> OpenAIモデルのクォータに関する警告が表示されることがあります。「指定リージョンに十分なGPT-4oのTPMがない」等のメッセージが出た場合は、前述のクォータ条件を満たすように設定を変更するかリージョンを変更する必要があります。 5. **デプロイ進行のモニタ**: コマンドを実行すると、ターミナル上に各ステップの進行状況が表示されます。内部ではTerraformのように状態を追跡しつつ、順次リソースが構築されます。特にAzure Database for PostgreSQLやAzure OpenAIリソースの構築には数分単位の時間がかかります。気長に待ちましょう。Azure Portalを開いてリソースグループを確認すると、リアルタイムでリソースが増えていくのが確認できます。 @@ -40,11 +43,11 @@ Azure Developer CLIを使うと、単一のコマンドでインフラの構築 - **Azure Database for PostgreSQL – Flexible Server**: PostgreSQLデータベースサーバが作成されます。本プロジェクトの中核データベースであり、製品情報やレビューなどのテーブルデータ、および拡張機能によるベクトル・グラフデータを格納します。BicepテンプレートでSKUやストレージサイズ、管理ユーザ名などが定義されています(デフォルトでは安価なプランが選ばれているはずです)。 -- **Azure OpenAI Service**: GPT-4やEmbeddingモデルを使用するためのAzure OpenAIリソースが作成されます。デプロイ時にモデル名やSKUも自動設定されます。例えばGPT-4(あるいはGPT-4-32k)と、text-embedding-ada-002のデプロイがセットアップされます。これにより、データベースからAzure OpenAIにリクエストを送信できるようになります。 +- **Azure OpenAI Service**: GPT-4やEmbeddingモデルを使用するためのAzure OpenAIリソースが作成されます。デプロイ時にモデル名やSKUも自動設定されます。例えばGPT-4oと、text-embedding-ada-002のデプロイがセットアップされます。これにより、データベースからAzure OpenAIにリクエストを送信できるようになります。 -- **アプリケーションホスティング(App Service / Function / Container Apps 等)**: バックエンドのアプリケーションコードをホストするためのコンピュートリソースが作られます。Pythonで書かれたAPI(エージェントのオーケストレーションロジック)と、TypeScript/JavaScriptで書かれたフロントエンドが存在します。Azure Developer CLIは`azure.yaml`に記載されたサービス定義に基づき、適切なホスティング先を構築・デプロイします。例えば、フロントエンドが静的WebアプリであればAzure Static Web Appsが、バックエンドがPythonならAzure FunctionsやApp Service(Web App)がデプロイされているかもしれません。 +- **Container Apps**: バックエンドのアプリケーションコードをホストするためのコンピュートリソースが作られます。Pythonで書かれたAPI(エージェントのオーケストレーションロジック)と、TypeScript/JavaScriptで書かれたフロントエンドが存在します。Azure Developer CLIは`azure.yaml`に記載されたサービス定義に基づき、適切なホスティング先を構築・デプロイします。 -- **接続設定・シークレット**: 構築された各リソース間の接続情報(例えばデータベースの接続文字列、OpenAIのAPIキーなど)は、Azure Developer CLIによりアプリの設定に組み込まれます。セキュリティ上、本来はAzure Key Vaultなどに格納すべき情報ですが、現時点のテンプレートでは環境変数等で直接App Serviceに設定している可能性があります(今後Key Vault統合予定)。 +- **接続設定・シークレット**: 構築された各リソース間の接続情報(例えばデータベースの接続文字列、OpenAIのAPIキーなど)は、Azure Developer CLIによりアプリの設定に組み込まれます。セキュリティをより強化する場合、Azure Key Vaultの利用も検討してください。 - **拡張機能のセットアップ**: PostgreSQLサーバが起動後、`azd-hooks/predeploy.sh` スクリプトが実行されます。このスクリプト内で、前セクションで述べた `CREATE EXTENSION` 文の実行や、Azure OpenAIエンドポイント・キーをデータベースに登録する `azure_ai.set_setting` 関数の呼び出しなどが行われます。Azure CLIの`rdbms-connect`拡張がここで活躍し、スクリプト内で `az postgres flexible-server connect` コマンドによりPostgreSQLに接続してSQL実行しています。 diff --git a/docs/workshop/ja/06-Post-provisioning.md b/docs/workshop/ja/06-Post-provisioning.md index 4b8bd3a..9603fc1 100644 --- a/docs/workshop/ja/06-Post-provisioning.md +++ b/docs/workshop/ja/06-Post-provisioning.md @@ -14,30 +14,28 @@ - **Azure OpenAI リソース**: 役割: GPT-4などのモデルをホストする認知サービスリソース。PostgreSQL側からの`azure_ai`経由の呼び出しや、バックエンドコードからのAPI呼び出しに応答します。 -確認内容: ポータルの「Azure OpenAI」から該当リソースを開き、モデルのデプロイ状況を確認します(「モデルのデプロイ」メニューで、例えば `gpt-4` や `text-embedding-ada-002` が Deploy 状態になっていればOKです)。また、キーとエンドポイントURIを確認し、PostgreSQL側に設定されたものと一致していることも後で検証できます。 +確認内容: ポータルの「Azure OpenAI」から該当リソースを開き、モデルのデプロイ状況を確認します(「モデルのデプロイ」メニューで、例えば `gpt-4o` や `text-embedding-ada-002` が Deploy 状態になっていればOKです)。また、キーとエンドポイントURIを確認し、PostgreSQL側に設定されたものと一致していることも後で検証できます。 -- **バックエンド アプリ (例: Azure App Service または Function)**: +- **バックエンド アプリ (Container Apps)**: 役割: Python製のアプリケーションロジックがデプロイされているホスティング環境です。ユーザーからの要求(例えば商品検索のクエリ)を受け取り、必要に応じてデータベースに問い合わせたりLLMエージェントを起動して応答を生成します。マルチエージェントのオーケストレーションが行われる中心的存在です。 -確認内容: ポータルの「App Service」や「Function App」を確認し、新しく作成されたリソースを探します。ネーミングは環境名などが含まれているはずです。App Serviceの場合、「デプロイセンター」や「構成 > アプリケーション設定」を見て、データベース接続文字列やOpenAIエンドポイントなどが環境変数として設定されていることを確認しましょう。また、「ログストリーム」を有効にし、後述のアプリテスト時にログが出力されるかを見るのも有益です。 +確認内容: ポータルのContainer Appsを確認し、新しく作成されたリソースを探します。「ログストリーム」を有効にし、後述のアプリテスト時にログが出力されるかを見るのも有益です。 -- **フロントエンド アプリ (例: Azure Static Web Apps あるいはStorageの静的サイト)**: -役割: ユーザーインターフェースをホストします。今回TypeScriptのコードが含まれていたため、おそらくReactやVue等のシングルページアプリ(SPA)がビルドされ、静的ファイルがデプロイされていると考えられます。ユーザーはこのフロントエンドを通じて検索クエリを入力したり結果を閲覧します。フロントエンドからバックエンドAPIへの呼び出しもここで行われます。 +- **フロントエンド アプリ (Container Apps)**: +役割: ユーザーインターフェースをホストします。ユーザーはこのフロントエンドを通じて検索クエリを入力したり結果を閲覧します。フロントエンドからバックエンドAPIへの呼び出しもここで行われます。 確認内容: Static Web Appsの場合、ポータルの対象リソースを開き、「URL」を確認します。このURLが後述の動作確認で使用するWebアプリのURLになります。Storageアカウントの静的サイトホスティングの場合、エンドポイント域名が表示されています。これらに実際にアクセスしてみて、ページが表示されるか確認します。 -- **その他リソース**: -Application Insights / Log Analytics: ログ収集用に設定されている可能性があります。バックエンドのログがここに送られていれば、障害調査等で参照できます。 - -Virtual Network / NSG: データベースをセキュアにするためにVNet内に配置されている場合、そのネットワーク関連リソースがあるかもしれません。ただ、Azure Database for PostgreSQL Flexibleはパブリックアクセスでも構築できるので、テンプレート次第です。必要に応じて確認してください。 - 以上のリソースが揃って初めてアプリケーションは動作します。それぞれの役割を理解することで、万一問題が発生した場合にどのコンポーネントを調査すべきか判断しやすくなります。 ## アプリケーションの動作確認 リソースのデプロイが完了したら、実際にアプリケーション(AgenticShop)が期待通り動いているかを確認しましょう。確認すべきポイントを順に説明します。 -1. **フロントエンドへのアクセス**: 前述のフロントエンドURL(例: `https://<ランダム名>.azurestaticapps.net` やカスタムドメイン)をブラウザで開きます。AgenticShopのトップページが表示されるはずです。画面には検索バーやカテゴリ選択、もしくはチャットボット風のUIがあるかもしれません。デザインは電子ガジェットのショッピングサイトを模しています。 +1. **フロントエンドへのアクセス**: 前述のフロントエンドURL(例: `https://rt-frontend.<ランダム名>.japaneast.azurecontainerapps.io` )をブラウザで開きます。AgenticShopのトップページが表示されるはずです。画面には検索バーやチャットボット風のUIが存在します。デザインは電子ガジェットのショッピングサイトを模しています。 + +> [!NOTE] 注意 +> フロントエンドにアクセスしても、接続中の表示のまま切り替わらない場合、バックエンドが正常に起動していないことがあります。この場合、バックエンドを一度停止し、再度起動すると正常に動作するはずです。 2. **製品情報の表示**: アプリ上で適当な操作をしてみます。例えば商品カテゴリーを選択すると、そのカテゴリの製品一覧やおすすめ商品が表示されるか確認します。AgenticShopではユーザプロファイルに基づくパーソナライズ機能があります。 diff --git a/docs/workshop/ja/07-WhyPostgreSQL.md b/docs/workshop/ja/07-WhyPostgreSQL.md index c382e85..484d638 100644 --- a/docs/workshop/ja/07-WhyPostgreSQL.md +++ b/docs/workshop/ja/07-WhyPostgreSQL.md @@ -126,4 +126,4 @@ $$) AS t(count bigint); などを試すと、グラフにデータが入っていることが確認できます。 -[前へ](06-Post-provisioning.md) | [次へ](08-Wrapup.md) +[前へ](06-Post-provisioning.md) | [次へ](08-GraphRAG.md) diff --git a/docs/workshop/ja/09-GraphRAG.md b/docs/workshop/ja/08-GraphRAG.md similarity index 99% rename from docs/workshop/ja/09-GraphRAG.md rename to docs/workshop/ja/08-GraphRAG.md index 635a88f..9715337 100644 --- a/docs/workshop/ja/09-GraphRAG.md +++ b/docs/workshop/ja/08-GraphRAG.md @@ -62,4 +62,4 @@ AgenticShopにおけるGraphRAGは、バックエンドの情報検索パイプ 総括すると、AgenticShopバックエンドのbackendディレクトリにはGraphRAGの概念が技術的に組み込まれており、Apache AGEによる知識グラフ構築とCypherクエリ活用、LLMエージェントへのグラフ情報供給を通じて、マルチエージェントAIの回答精度とユーザ体験を大幅に向上させています。GraphRAGは本ソリューションアーキテクチャのキーストーンとなっており、今後の拡張においてもグラフデータを活用した高度な検索・推論が容易に行える土台を提供しています。各種リテールドメイン特有の関係性(製品-特徴-レビュー-ユーザ嗜好)をモデル化したこのグラフ活用アプローチは、従来のRAG手法では得られなかった洞察を引き出し、最終的なエンドユーザへの回答品質を押し上げています。 -[前へ](08-Wrapup.md) +[前へ](07-WhyPostgreSQL.md) | [次へ](09-Wrapup.md) diff --git a/docs/workshop/ja/08-Wrapup.md b/docs/workshop/ja/09-Wrapup.md similarity index 96% rename from docs/workshop/ja/08-Wrapup.md rename to docs/workshop/ja/09-Wrapup.md index 37fee99..b710671 100644 --- a/docs/workshop/ja/08-Wrapup.md +++ b/docs/workshop/ja/09-Wrapup.md @@ -6,7 +6,7 @@ Azure Developer CLIでは、デプロイした環境を一括で削除するコマンドが用意されています。特に今回のようにリソースグループごと作成した場合、そのリソースグループと中の全リソースをまとめて消去できます。 -- **削除コマンドの実行**: プロジェクトのディレクトリ(postgres-agentic-shop)内で、以下のコマンドを実行します。 +- **削除コマンドの実行**: プロジェクトのディレクトリ(`postgres-agentic-shop`)内で、以下のコマンドを実行します。 ```sh azd down --purge @@ -30,4 +30,4 @@ azd down --purge 今回学んだように、Azure Database for PostgreSQLにAI拡張や`pgvector`、Apache AGEを組み合わせることで、シンプルながら強力なAIアプリケーション基盤を構築できます。ぜひ現場のプロジェクトでも応用してみてください。各セクションで触れた技術要素について更に深掘りしたい場合、公式ドキュメントや関連ブログ記事なども参照すると理解が深まるでしょう。 -[前へ](07-WhyPostgreSQL.md) | [次へ](09-GraphRAG.md) +[前へ](08-GraphRAG.md) From 529189fdfc4a6505f9772b408be757f558951bec Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Thu, 19 Jun 2025 13:10:28 +0900 Subject: [PATCH 14/23] Added confirmed regions --- docs/workshop/en/02-Prerequisites.md | 18 ++++++++++++++---- docs/workshop/ja/02-Prerequisites.md | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/workshop/en/02-Prerequisites.md b/docs/workshop/en/02-Prerequisites.md index cbaeb7d..fb99662 100644 --- a/docs/workshop/en/02-Prerequisites.md +++ b/docs/workshop/en/02-Prerequisites.md @@ -38,10 +38,20 @@ The resources to be deployed in this hands-on include Azure Database for Postgre - **Location close to the database**: From a performance perspective, it is desirable to choose a region for Azure OpenAI that is physically close to the region where Azure Database for PostgreSQL is located. For example, if you create a DB in the Japan East region, you will choose Azure OpenAI in the Japan East or a nearby region (however, compromises may be necessary because the OpenAI service is limited). - **Regions confirmed to work with CloudShell**: The following regions have been confirmed to work by deploying with CloudShell. - - Japan East (took 30 mins) - - Australia East (took 15 mins) - - South India (took 16 mins) - - Korea Central (Failed due to lack of text-embedding-ada-002) + - Australia East + - Brazil South + - France Central + - Japan East + - Norway East + - South Africa North + - South India + - Sweden Central + - Switzerland North + - UAE North + - UK South + - West Europe + - West US + - West US 3 - **Use of preview features**: The Apache AGE extension is available on newly created PostgreSQL servers and cannot be enabled on existing servers. `azd up` automatically creates a new PostgreSQL server, but for peace of mind, check the Azure Portal for any mention of using preview features. diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/02-Prerequisites.md index a766df4..b0a5e93 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/02-Prerequisites.md @@ -38,10 +38,20 @@ wsl --install - **データベースと近接した場所**: パフォーマンスの観点から、Azure Database for PostgreSQLを配置するリージョンと物理的に近いリージョンをAzure OpenAIに選ぶのが望ましいです。例えば、Japan EastリージョンにDBを作成した場合、Azure OpenAIもJapan Eastまたは近隣のリージョンを選択します(ただしOpenAIサービスが限られるため妥協が必要な場合もあります)。 - **CloudShellでの動作確認済みリージョン**: 以下のリージョンはCloudShellでデプロイして動作を確認済みです。 - - Japan East (took 30 mins) - - Australia East (took 15 mins) - - South India (took 16 mins) - - Korea Central (Failed due to lack of text-embedding-ada-002) + - Australia East + - Brazil South + - France Central + - Japan East + - Norway East + - South Africa North + - South India + - Sweden Central + - Switzerland North + - UAE North + - UK South + - West Europe + - West US + - West US 3 - **プレビュー機能の利用**: Apache AGE拡張は新規作成のPostgreSQLサーバで利用可能で、既存サーバには有効化できません。`azd up`では自動的に新しいPostgreSQLサーバを作成しますが、念のためAzure Portalでプレビュー機能利用に関する記載がないか確認すると安心です。 From 180868dd3b7d5246f2e80c34d5e3b870aab58aa4 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Sat, 21 Jun 2025 12:52:33 +0900 Subject: [PATCH 15/23] Fixed issues --- README.md | 5 - azd-hooks/predeploy.sh | 17 ++- azure.yaml | 120 +++++++++--------- ...2-Prerequisites.md => 00-Prerequisites.md} | 2 +- docs/workshop/en/01-Introduction.md | 2 +- .../{03-Integration.md => 02-Integration.md} | 2 +- .../en/{04-Repository.md => 03-Repository.md} | 2 +- ...{05-Provisioning.md => 04-Provisioning.md} | 10 +- ...rovisioning.md => 05-Post-provisioning.md} | 2 +- ...7-WhyPostgreSQL.md => 06-WhyPostgreSQL.md} | 2 +- .../en/{08-GraphRAG.md => 07-GraphRAG.md} | 2 +- .../en/{09-Wrapup.md => 08-Wrapup.md} | 2 +- ...2-Prerequisites.md => 00-Prerequisites.md} | 2 +- docs/workshop/ja/01-Introduction.md | 2 +- .../{03-Integration.md => 02-Integration.md} | 2 +- .../ja/{04-Repository.md => 03-Repository.md} | 2 +- ...{05-Provisioning.md => 04-Provisioning.md} | 13 +- ...rovisioning.md => 05-Post-provisioning.md} | 2 +- ...7-WhyPostgreSQL.md => 06-WhyPostgreSQL.md} | 2 +- .../ja/{08-GraphRAG.md => 07-GraphRAG.md} | 2 +- .../ja/{09-Wrapup.md => 08-Wrapup.md} | 2 +- 21 files changed, 95 insertions(+), 102 deletions(-) mode change 100644 => 100755 azd-hooks/predeploy.sh rename docs/workshop/en/{02-Prerequisites.md => 00-Prerequisites.md} (99%) rename docs/workshop/en/{03-Integration.md => 02-Integration.md} (99%) rename docs/workshop/en/{04-Repository.md => 03-Repository.md} (98%) rename docs/workshop/en/{05-Provisioning.md => 04-Provisioning.md} (94%) rename docs/workshop/en/{06-Post-provisioning.md => 05-Post-provisioning.md} (99%) rename docs/workshop/en/{07-WhyPostgreSQL.md => 06-WhyPostgreSQL.md} (99%) rename docs/workshop/en/{08-GraphRAG.md => 07-GraphRAG.md} (99%) rename docs/workshop/en/{09-Wrapup.md => 08-Wrapup.md} (99%) rename docs/workshop/ja/{02-Prerequisites.md => 00-Prerequisites.md} (99%) rename docs/workshop/ja/{03-Integration.md => 02-Integration.md} (99%) rename docs/workshop/ja/{04-Repository.md => 03-Repository.md} (98%) rename docs/workshop/ja/{05-Provisioning.md => 04-Provisioning.md} (93%) rename docs/workshop/ja/{06-Post-provisioning.md => 05-Post-provisioning.md} (99%) rename docs/workshop/ja/{07-WhyPostgreSQL.md => 06-WhyPostgreSQL.md} (99%) rename docs/workshop/ja/{08-GraphRAG.md => 07-GraphRAG.md} (99%) rename docs/workshop/ja/{09-Wrapup.md => 08-Wrapup.md} (99%) diff --git a/README.md b/README.md index ba8abf1..0a1ab83 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,6 @@ azd init ``` ### Grant permissions to azd hooks scripts -If you are deploying the solution on linux OS and macOS, grant the following permissions to `predeploy.sh` -```sh -cd azd-hooks -sudo chmod +x predeploy.sh -``` If you are deploying the solution on Windows OS, grant the following permissions to the current session to execute `pwsh` scripts ```sh Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass diff --git a/azd-hooks/predeploy.sh b/azd-hooks/predeploy.sh old mode 100644 new mode 100755 index 19627a6..c9771fa --- a/azd-hooks/predeploy.sh +++ b/azd-hooks/predeploy.sh @@ -1,13 +1,20 @@ #!/bin/sh +RESOURCE_GROUP=$(azd env get-value AZURE_RESOURCE_GROUP) POSTGRES_NAME=$(azd env get-value POSTGRES_NAME) POSTGRES_USERNAME=$(azd env get-value POSTGRES_USERNAME) POSTGRES_DATABASE=$(azd env get-value POSTGRES_DATABASE) POSTGRES_PASSWORD=$(azd env get-value POSTGRES_PASSWORD) +# wait until the server created +az postgres flexible-server wait \ + --name "$POSTGRES_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --created + az postgres flexible-server execute \ - --admin-user "$POSTGRES_USERNAME" \ - --admin-password "$POSTGRES_PASSWORD" \ - --name "$POSTGRES_NAME" \ - --database-name "$POSTGRES_DATABASE" \ - --file-path "../scripts/create-extension.sql" \ No newline at end of file + --admin-user "$POSTGRES_USERNAME" \ + --admin-password "$POSTGRES_PASSWORD" \ + --name "$POSTGRES_NAME" \ + --database-name "$POSTGRES_DATABASE" \ + --file-path "../scripts/create-extension.sql" diff --git a/azure.yaml b/azure.yaml index 3f16707..3ceac57 100644 --- a/azure.yaml +++ b/azure.yaml @@ -2,72 +2,72 @@ name: retail-solution-accelerator description: Agentic Shop metadata: - template: retail-solution-accelerator@0.0.1 + template: retail-solution-accelerator@0.0.1 requiredVersions: - azd: ">= 1.14.0" + azd: ">= 1.14.0" workflows: up: steps: - azd: provision - - azd: deploy + - azd: deploy -infra: - bicep: ./infra/main.bicep +infra: + bicep: ./infra/main.bicep services: - backend: - project: ./backend - language: py - module: backend - host: containerapp - docker: - remoteBuild: true - hooks: - predeploy: - posix: - # linux/macOS hook - shell: sh - interactive: true - continueOnError: false - run: ../azd-hooks/predeploy.sh - windows: - # Windows hook - shell: pwsh - interactive: true - continueOnError: false - run: ../azd-hooks/predeploy.ps1 - frontend: - project: ./frontend - language: ts - module: frontend - host: containerapp - docker: - context: ./ - registry: ${AZURE_CONTAINER_REGISTRY_ENDPOINT} - path: ./Dockerfile - remoteBuild: true - hooks: - prebuild: - posix: # run on linux/macOS - shell: sh - interactive: true - continueOnError: false - run: | - BE_APP_ENDPOINT=$(azd env get-value SERVICE_BACKEND_URI) - echo VITE_BE_APP_ENDPOINT=\"$BE_APP_ENDPOINT/\" >> ./.env.local - windows: - shell: pwsh # run on Windows - interactive: true - continueOnError: false - run: | - $beAppEndpoint = azd env get-value SERVICE_BACKEND_URI - $envLine = "VITE_BE_APP_ENDPOINT=`"$beAppEndpoint/`"" - Add-Content -Path ".\.env.local" -Value $envLine - arize: - project: ./arize-phoenix - language: py - module: arize - host: containerapp - docker: - remoteBuild: true + backend: + project: ./backend + language: py + module: backend + host: containerapp + docker: + remoteBuild: true + hooks: + predeploy: + posix: + # linux/macOS hook + shell: sh + interactive: true + continueOnError: false + run: chmod +x ../azd-hooks/predeploy.sh && ../azd-hooks/predeploy.sh + windows: + # Windows hook + shell: pwsh + interactive: true + continueOnError: false + run: ../azd-hooks/predeploy.ps1 + frontend: + project: ./frontend + language: ts + module: frontend + host: containerapp + docker: + context: ./ + registry: ${AZURE_CONTAINER_REGISTRY_ENDPOINT} + path: ./Dockerfile + remoteBuild: true + hooks: + prebuild: + posix: # run on linux/macOS + shell: sh + interactive: true + continueOnError: false + run: | + BE_APP_ENDPOINT=$(azd env get-value SERVICE_BACKEND_URI) + echo VITE_BE_APP_ENDPOINT=\"$BE_APP_ENDPOINT/\" >> ./.env.local + windows: + shell: pwsh # run on Windows + interactive: true + continueOnError: false + run: | + $beAppEndpoint = azd env get-value SERVICE_BACKEND_URI + $envLine = "VITE_BE_APP_ENDPOINT=`"$beAppEndpoint/`"" + Add-Content -Path ".\.env.local" -Value $envLine + arize: + project: ./arize-phoenix + language: py + module: arize + host: containerapp + docker: + remoteBuild: true diff --git a/docs/workshop/en/02-Prerequisites.md b/docs/workshop/en/00-Prerequisites.md similarity index 99% rename from docs/workshop/en/02-Prerequisites.md rename to docs/workshop/en/00-Prerequisites.md index fb99662..5bb1aa7 100644 --- a/docs/workshop/en/02-Prerequisites.md +++ b/docs/workshop/en/00-Prerequisites.md @@ -68,4 +68,4 @@ You can check the OpenAI quota of your Azure subscription on the "Quota + Limit" > [!NOTE] Note > The deploy capacity (default value) of the above models is also set as a parameter in the prompt at the time of `azd up` execution. If necessary, you can adjust the deploy scale of GPT-4o and the embedding model by editing `infra/main.parameters.json` in the repository (e.g., lowering TPS to save quota). -[Previous](01-Introduction.md) | [Next](03-Integration.md) +[Next](01-Introduction.md) diff --git a/docs/workshop/en/01-Introduction.md b/docs/workshop/en/01-Introduction.md index 9f4fb0d..6dab483 100644 --- a/docs/workshop/en/01-Introduction.md +++ b/docs/workshop/en/01-Introduction.md @@ -22,4 +22,4 @@ This solution accelerator has the following features: That's an overview. In this hands-on, we will deploy the AgenticShop application with the above features on Azure, and while experiencing its functions, we will learn about the internal processes. -[Next](02-Prerequisites.md) \ No newline at end of file +[Previous](00-Prerequisites.md) diff --git a/docs/workshop/en/03-Integration.md b/docs/workshop/en/02-Integration.md similarity index 99% rename from docs/workshop/en/03-Integration.md rename to docs/workshop/en/02-Integration.md index c26e3d0..bd0e3bf 100644 --- a/docs/workshop/en/03-Integration.md +++ b/docs/workshop/en/02-Integration.md @@ -80,4 +80,4 @@ You can create a graph named `shop_graph` by calling a function from SQL like th By following these steps, you will be able to use AI, vector, and graph functions on Azure Database for PostgreSQL. In this hands-on deployment script, the process to automatically execute these extension activation SQLs at deployment time (`azd-hooks` pre-deployment script) is built in, so participants do not need to manually execute SQL. However, to understand the mechanism, it would be good to check that the `azure.extensions` parameter of the target server is set in the Portal, etc., the extension is installed, and the `azure_ai` and `age` schemas are created in the database. -[Previous](02-Prerequisites.md) | [Next](04-Repository.md) \ No newline at end of file +[Previous](01-Introduction.md) | [Next](03-Repository.md) diff --git a/docs/workshop/en/04-Repository.md b/docs/workshop/en/03-Repository.md similarity index 98% rename from docs/workshop/en/04-Repository.md rename to docs/workshop/en/03-Repository.md index 5227372..f97f681 100644 --- a/docs/workshop/en/04-Repository.md +++ b/docs/workshop/en/03-Repository.md @@ -37,4 +37,4 @@ cd postgres-agentic-shop This completes the acquisition of the repository. It would be good to open the code in an editor or read the `README` to get an overall picture. In particular, in this project, we will deploy using the **Azure Developer CLI (azd)**, so it will deepen your understanding to check what resources are defined in the `azure.yaml` and the templates in the `infra` directory. -[Back](03-Integration.md) | [Next](05-Provisioning.md) +[Back](02-Integration.md) | [Next](04-Provisioning.md) diff --git a/docs/workshop/en/05-Provisioning.md b/docs/workshop/en/04-Provisioning.md similarity index 94% rename from docs/workshop/en/05-Provisioning.md rename to docs/workshop/en/04-Provisioning.md index 05be8f9..907a0c0 100644 --- a/docs/workshop/en/05-Provisioning.md +++ b/docs/workshop/en/04-Provisioning.md @@ -8,13 +8,7 @@ With the Azure Developer CLI, you can execute everything from infrastructure con 1. **Confirm login to Azure CLI (not necessary in CloudShell)**: You have already installed Azure CLI in the preliminary preparation, but check the login status just in case. Run `az account show` on the terminal, and if the subscription information is displayed, you are logged in. If you are not logged in, execute `az login` and complete browser authentication. In addition, `azd` itself also requires authentication to Azure, so execute `azd auth login` (the browser will automatically start and the Azure authentication screen will be displayed. If it does not work, use `azd auth login --use-device-code`). -2. **Grant execution rights to the shell script**: When deploying in a CloudShell, Linux, or macOS environment, you need to grant execution rights to `azd-hooks/predeploy.sh`. - -```sh -chmod +x azd-hooks/predeploy.sh -``` - -Also, when deploying on Windows, you may get an error about the script execution policy in PowerShell. You can temporarily bypass this with the following command. +2. **Grant execution rights to the shell script**: When deploying on Windows, you may get an error about the script execution policy in PowerShell. You can temporarily bypass this with the following command. ```sh PowerShell -ExecutionPolicy Bypass -Scope Process @@ -58,4 +52,4 @@ If an error occurs during provisioning, please check the error message. Common p > [!NOTE] Troubleshooting > For general troubleshooting related to Azure Developer CLI, please refer to the [official documentation](https://github.com/Azure-Samples/postgres-agentic-shop). -[Previous](04-Repository.md) | [Next](06-Post-provisioning.md) \ No newline at end of file +[Previous](03-Repository.md) | [Next](05-Post-provisioning.md) diff --git a/docs/workshop/en/06-Post-provisioning.md b/docs/workshop/en/05-Post-provisioning.md similarity index 99% rename from docs/workshop/en/06-Post-provisioning.md rename to docs/workshop/en/05-Post-provisioning.md index 327155c..076f9d4 100644 --- a/docs/workshop/en/06-Post-provisioning.md +++ b/docs/workshop/en/05-Post-provisioning.md @@ -47,4 +47,4 @@ Once the resource deployment is complete, let's check if the application (Agenti In this way, test each function while actually operating the application to see if it is working as expected. In particular, whether the role of the database is being properly fulfilled (whether the vector search results are reasonable, whether graph queries are being used) can be confirmed by looking at the "Query Performance Insight" on the Azure Portal or the `pg_stat_statements` extension. If you're interested, also take a look at the backend logs (for example, Log Stream or Application Insights logs if you're using App Service). The prompts and responses thrown to Azure OpenAI, any errors and their contents, etc., should be recorded. -[Back](05-Provisioning.md) | [Next](07-WhyPostgreSQL.md) \ No newline at end of file +[Back](04-Provisioning.md) | [Next](06-WhyPostgreSQL.md) diff --git a/docs/workshop/en/07-WhyPostgreSQL.md b/docs/workshop/en/06-WhyPostgreSQL.md similarity index 99% rename from docs/workshop/en/07-WhyPostgreSQL.md rename to docs/workshop/en/06-WhyPostgreSQL.md index 96dd477..0f651a7 100644 --- a/docs/workshop/en/07-WhyPostgreSQL.md +++ b/docs/workshop/en/06-WhyPostgreSQL.md @@ -114,4 +114,4 @@ $$) AS t(count bigint); By trying this, you can confirm that data is entered in the graph. -[Previous](06-Post-provisioning.md) | [Next](08-GraphRAG.md) \ No newline at end of file +[Previous](05-Post-provisioning.md) | [Next](07-GraphRAG.md) diff --git a/docs/workshop/en/08-GraphRAG.md b/docs/workshop/en/07-GraphRAG.md similarity index 99% rename from docs/workshop/en/08-GraphRAG.md rename to docs/workshop/en/07-GraphRAG.md index 512fe21..61f5375 100644 --- a/docs/workshop/en/08-GraphRAG.md +++ b/docs/workshop/en/07-GraphRAG.md @@ -61,4 +61,4 @@ In AgenticShop, GraphRAG functions as a core technology to sophisticate the back In summary, the concept of GraphRAG is technically incorporated into the backend directory of the AgenticShop backend, and by building a knowledge graph with Apache AGE and utilizing Cypher queries, and supplying graph information to the LLM agent, it significantly improves the accuracy of multi-agent AI responses and user experience. GraphRAG is the keystone of this solution architecture, providing a foundation for easy advanced search and inference using graph data in future expansions. This approach of utilizing graphs that model various retail domain-specific relationships (product-feature-review-user preference) brings out insights that could not be obtained with traditional RAG methods, and ultimately enhances the quality of responses to end users. -[Previous](07-WhyPostgreSQL.md) | [Next](09-Wrapup.md) \ No newline at end of file +[Previous](06-WhyPostgreSQL.md) | [Next](08-Wrapup.md) diff --git a/docs/workshop/en/09-Wrapup.md b/docs/workshop/en/08-Wrapup.md similarity index 99% rename from docs/workshop/en/09-Wrapup.md rename to docs/workshop/en/08-Wrapup.md index e57aa92..be8c234 100644 --- a/docs/workshop/en/09-Wrapup.md +++ b/docs/workshop/en/08-Wrapup.md @@ -29,4 +29,4 @@ Good job! As you learned this time, by combining AI extensions, `pgvector`, and Apache AGE with Azure Database for PostgreSQL, you can build a simple yet powerful AI application platform. Please try to apply it in your actual projects. If you want to dig deeper into the technical elements touched on in each section, referring to official documents and related blog articles will deepen your understanding. -[Back](08-GraphRAG.md) \ No newline at end of file +[Back](07-GraphRAG.md) diff --git a/docs/workshop/ja/02-Prerequisites.md b/docs/workshop/ja/00-Prerequisites.md similarity index 99% rename from docs/workshop/ja/02-Prerequisites.md rename to docs/workshop/ja/00-Prerequisites.md index b0a5e93..e351dba 100644 --- a/docs/workshop/ja/02-Prerequisites.md +++ b/docs/workshop/ja/00-Prerequisites.md @@ -68,4 +68,4 @@ Azure OpenAIサービスを利用するには、モデルごとの使用上限 > [!NOTE] 注意 > `azd up`実行時のプロンプトでも、上記モデルのデプロイ容量 (デフォルト値) をパラメータとして設定するようになっています。必要に応じてリポジトリ内の `infra/main.parameters.json` を編集することで、GPT-4oや埋め込みモデルのデプロイスケールを調整できます(クォータ節約のためにTPSを下げる等)。 -[前へ](01-Introduction.md) | [次へ](03-Integration.md) +[次へ](01-Introduction.md) diff --git a/docs/workshop/ja/01-Introduction.md b/docs/workshop/ja/01-Introduction.md index c144357..49b0861 100644 --- a/docs/workshop/ja/01-Introduction.md +++ b/docs/workshop/ja/01-Introduction.md @@ -22,4 +22,4 @@ 以上が全体の概要です。このハンズオンでは、以上の特徴を備えたAgenticShopアプリケーションをAzure上にデプロイし、実際にその機能を体験しながら内部でどのような処理が行われているか学んでいきます。 -[次へ](02-Prerequisites.md) +[前へ](00-Prerequisites.md) | [次へ](02-Integration.md) diff --git a/docs/workshop/ja/03-Integration.md b/docs/workshop/ja/02-Integration.md similarity index 99% rename from docs/workshop/ja/03-Integration.md rename to docs/workshop/ja/02-Integration.md index 208baab..fee4381 100644 --- a/docs/workshop/ja/03-Integration.md +++ b/docs/workshop/ja/02-Integration.md @@ -82,4 +82,4 @@ SELECT * FROM ag_catalog.create_graph('shop_graph'); 以上の手順によって、Azure Database for PostgreSQL上でAI・ベクトル・グラフの各機能が使えるようになります。本ハンズオンのデプロイスクリプトでは、デプロイ時に自動でこれら拡張の有効化SQLを実行する処理(`azd-hooks`のプリデプロイスクリプト)が組み込まれているため、参加者が手動でSQLを実行する必要はありません。ただし、仕組みを理解するために、Portal等で対象サーバの`azure.extensions`パラメータが設定され拡張がインストール済みになっていること、`azure_ai`や`age`スキーマがデータベース内に作成されていることなどを確認してみると良いでしょう。 -[前へ](02-Prerequisites.md) | [次へ](04-Repository.md) +[前へ](01-Introduction.md) | [次へ](03-Repository.md) diff --git a/docs/workshop/ja/04-Repository.md b/docs/workshop/ja/03-Repository.md similarity index 98% rename from docs/workshop/ja/04-Repository.md rename to docs/workshop/ja/03-Repository.md index d1abba2..33fee49 100644 --- a/docs/workshop/ja/04-Repository.md +++ b/docs/workshop/ja/03-Repository.md @@ -37,4 +37,4 @@ cd postgres-agentic-shop 以上でリポジトリの取得は完了です。エディタでコードを開いたり、`README`を一読して全体像を掴んでおくと良いでしょう。特に、本プロジェクトでは**Azure Developer CLI (azd)**を使ってデプロイしますので、`azure.yaml`や`infra`ディレクトリ内のテンプレートにどんなリソースが定義されているか目を通しておくと理解が深まります。 -[前へ](03-Integration.md) | [次へ](05-Provisioning.md) +[前へ](02-Integration.md) | [次へ](04-Provisioning.md) diff --git a/docs/workshop/ja/05-Provisioning.md b/docs/workshop/ja/04-Provisioning.md similarity index 93% rename from docs/workshop/ja/05-Provisioning.md rename to docs/workshop/ja/04-Provisioning.md index 5993f65..11a28ee 100644 --- a/docs/workshop/ja/05-Provisioning.md +++ b/docs/workshop/ja/04-Provisioning.md @@ -8,13 +8,7 @@ Azure Developer CLIを使うと、単一のコマンドでインフラの構築 1. **Azure CLIへのログイン確認(CloudShellでは不要)**: 事前準備でAzure CLIをインストール済みですが、念のためログイン状態を確認します。ターミナル上で `az account show` を実行し、サブスクリプション情報が表示されればログイン済みです。未ログインの場合は `az login` を実行してブラウザ認証を完了してください。加えて、`azd`自体もAzureへの認証が必要ですので、`azd auth login`を実行しておきます(ブラウザが自動起動しAzure認証画面が表示されます。うまくいかない場合 `azd auth login --use-device-code` を使用)。 -2. **シェルスクリプトへの実行権限の付与**: CloudShell、Linux及びmacOS環境でデプロイする際は、`azd-hooks/predeploy.sh`に実行権限を付与しておく必要があります。 - -```sh -chmod +x azd-hooks/predeploy.sh -``` - -また、Windowsでデプロイする際に、PowerShellでスクリプト実行ポリシーに関するエラーが出る場合があります。一時的に以下のコマンドで回避できます。 +2. **シェルスクリプトへの実行権限の付与**: Windowsでデプロイする際に、PowerShellでスクリプト実行ポリシーに関するエラーが出る場合があります。一時的に以下のコマンドで回避できます。 ```sh PowerShell -ExecutionPolicy Bypass -Scope Process @@ -28,6 +22,9 @@ PowerShell -ExecutionPolicy Bypass -Scope Process 4. **プロンプトへの入力**: `azd up`を実行すると、最初にいくつか入力を求められます。具体的には「デプロイ先のAzureサブスクリプション」「デプロイするAzureリージョン(ロケーション)」「Azure OpenAIモデルのデプロイ先リージョン」などです。ここで、前セクションで検討したリージョンを選択してください。Azure OpenAIについては利用可能なリージョンのみ候補に出ます。また、今回新規に作成するリソースグループ名も求められる場合があります。適宜入力しましょう。 +> [!CAUTION] 警告 +> 複数の参加者が同時にこのハンズオンを実行する場合、`azd`の環境名をユニークになるように指定してください。 + > [!CAUTION] 警告 > OpenAIモデルのクォータに関する警告が表示されることがあります。「指定リージョンに十分なGPT-4oのTPMがない」等のメッセージが出た場合は、前述のクォータ条件を満たすように設定を変更するかリージョンを変更する必要があります。 @@ -58,4 +55,4 @@ PowerShell -ExecutionPolicy Bypass -Scope Process > [!NOTE] トラブルシューティング > DescriptionAzure Developer CLIに関する一般的なトラブルシューティングは[公式ドキュメント](https://github.com/Azure-Samples/postgres-agentic-shop)を参照してください。 -[前へ](04-Repository.md) | [次へ](06-Post-provisioning.md) +[前へ](03-Repository.md) | [次へ](05-Post-provisioning.md) diff --git a/docs/workshop/ja/06-Post-provisioning.md b/docs/workshop/ja/05-Post-provisioning.md similarity index 99% rename from docs/workshop/ja/06-Post-provisioning.md rename to docs/workshop/ja/05-Post-provisioning.md index 9603fc1..b2f09c7 100644 --- a/docs/workshop/ja/06-Post-provisioning.md +++ b/docs/workshop/ja/05-Post-provisioning.md @@ -47,4 +47,4 @@ 以上のように、実際にアプリケーションを操作しながら各機能が期待通り動いているかテストします。特にデータベースの役割が正しく果たされているか(ベクトル検索結果が妥当か、グラフクエリが使われているか)は、Azure Portalの「クエリ パフォーマンス洞察」や`pg_stat_statements`拡張を見れば確認できるでしょう。興味があればバックエンドのログ(App Serviceの場合はLog StreamやApplication Insightsログ)も見てみましょう。Azure OpenAIに投げたプロンプトや応答内容、エラーがあればその内容などが記録されているはずです。 -[前へ](05-Provisioning.md) | [次へ](07-WhyPostgreSQL.md) +[前へ](04-Provisioning.md) | [次へ](06-WhyPostgreSQL.md) diff --git a/docs/workshop/ja/07-WhyPostgreSQL.md b/docs/workshop/ja/06-WhyPostgreSQL.md similarity index 99% rename from docs/workshop/ja/07-WhyPostgreSQL.md rename to docs/workshop/ja/06-WhyPostgreSQL.md index 484d638..245430e 100644 --- a/docs/workshop/ja/07-WhyPostgreSQL.md +++ b/docs/workshop/ja/06-WhyPostgreSQL.md @@ -126,4 +126,4 @@ $$) AS t(count bigint); などを試すと、グラフにデータが入っていることが確認できます。 -[前へ](06-Post-provisioning.md) | [次へ](08-GraphRAG.md) +[前へ](05-Post-provisioning.md) | [次へ](07-GraphRAG.md) diff --git a/docs/workshop/ja/08-GraphRAG.md b/docs/workshop/ja/07-GraphRAG.md similarity index 99% rename from docs/workshop/ja/08-GraphRAG.md rename to docs/workshop/ja/07-GraphRAG.md index 9715337..c87cf86 100644 --- a/docs/workshop/ja/08-GraphRAG.md +++ b/docs/workshop/ja/07-GraphRAG.md @@ -62,4 +62,4 @@ AgenticShopにおけるGraphRAGは、バックエンドの情報検索パイプ 総括すると、AgenticShopバックエンドのbackendディレクトリにはGraphRAGの概念が技術的に組み込まれており、Apache AGEによる知識グラフ構築とCypherクエリ活用、LLMエージェントへのグラフ情報供給を通じて、マルチエージェントAIの回答精度とユーザ体験を大幅に向上させています。GraphRAGは本ソリューションアーキテクチャのキーストーンとなっており、今後の拡張においてもグラフデータを活用した高度な検索・推論が容易に行える土台を提供しています。各種リテールドメイン特有の関係性(製品-特徴-レビュー-ユーザ嗜好)をモデル化したこのグラフ活用アプローチは、従来のRAG手法では得られなかった洞察を引き出し、最終的なエンドユーザへの回答品質を押し上げています。 -[前へ](07-WhyPostgreSQL.md) | [次へ](09-Wrapup.md) +[前へ](06-WhyPostgreSQL.md) | [次へ](08-Wrapup.md) diff --git a/docs/workshop/ja/09-Wrapup.md b/docs/workshop/ja/08-Wrapup.md similarity index 99% rename from docs/workshop/ja/09-Wrapup.md rename to docs/workshop/ja/08-Wrapup.md index b710671..fb110cc 100644 --- a/docs/workshop/ja/09-Wrapup.md +++ b/docs/workshop/ja/08-Wrapup.md @@ -30,4 +30,4 @@ azd down --purge 今回学んだように、Azure Database for PostgreSQLにAI拡張や`pgvector`、Apache AGEを組み合わせることで、シンプルながら強力なAIアプリケーション基盤を構築できます。ぜひ現場のプロジェクトでも応用してみてください。各セクションで触れた技術要素について更に深掘りしたい場合、公式ドキュメントや関連ブログ記事なども参照すると理解が深まるでしょう。 -[前へ](08-GraphRAG.md) +[前へ](07-GraphRAG.md) From 163242bb70a4b3adacac395138d2d2ced85be914 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Sat, 21 Jun 2025 12:54:41 +0900 Subject: [PATCH 16/23] Fixed lack of a link --- docs/workshop/en/01-Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workshop/en/01-Introduction.md b/docs/workshop/en/01-Introduction.md index 6dab483..3a0bcb8 100644 --- a/docs/workshop/en/01-Introduction.md +++ b/docs/workshop/en/01-Introduction.md @@ -22,4 +22,4 @@ This solution accelerator has the following features: That's an overview. In this hands-on, we will deploy the AgenticShop application with the above features on Azure, and while experiencing its functions, we will learn about the internal processes. -[Previous](00-Prerequisites.md) +[Previous](00-Prerequisites.md) | [Next](02-Integration.md) From b7e7a0041535510e6d1011a55474e24cf08fa3e1 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Sat, 21 Jun 2025 13:04:59 +0900 Subject: [PATCH 17/23] Fixed README and added README_ja --- README.md | 1 - README_ja.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 README_ja.md diff --git a/README.md b/README.md index 0a1ab83..b74d0ea 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ This repository showcases the power of agentic flow with llama index leveraging - [Architecture Diagram](#architecture-Diagram) - [Solution Accelerator Deployment](#solution-accelerator-deployment) - [Tear Down](#tear-down) -- [Development Methods](#development-methods) ## Key Features Retail solution accelerator provides the following features: diff --git a/README_ja.md b/README_ja.md new file mode 100644 index 0000000..759e8ca --- /dev/null +++ b/README_ja.md @@ -0,0 +1,102 @@ +# AgenticShop:AIエージェント時代の刷新されたショッピング体験 +このリポジトリは、マルチエージェントワークフローの機能を活用して、エージェントフローの力をLlamaindexで示しています。このソリューションは、電子ガジェットの購入に興味がある個々の強化されたショッピングを体験するために開発されました。これは、`azure cli` を使用した、テストとデプロイが簡単なワンクリックソリューションです。完全なレイアウトについては、アーキテクチャ図を参照してください: + +- [主要な機能](#key-features) +- [アーキテクチャ図](#architecture-Diagram) +- [ソリューションアクセラレータのデプロイメント](#solution-accelerator-deployment) +- [削除](#tear-down) + +## Key Features +小売ソリューションアクセラレータは、以下の機能を提供します: +- ユーザープロファイルに基づいたパーソナライズされた製品詳細 +- ユーザーエクスペリエンスの向上 +- マルチエージェントワークフローにより、複数のタスクをシームレスに処理 +- エージェントトリガーとトラッキングのためのArize Phoenixトレーシングを使用したデバッグパネル + +## Architecture Diagram +![AZURE ARCHITECTURE](https://github.com/user-attachments/assets/f3ca0e0d-0c93-4c0f-be5d-958eace3a138) + +## Solution Accelerator Deployment + +### CloudShell +このソリューションは、CloudShell経由でデプロイ可能に設計されています。ローカルの開発マシンからデプロイすることを希望する場合は、以下の手順に従ってください。 + +### 前提条件 +このソリューションのデプロイメントには、以下が前提条件となります: +1. [Azure Developer Cli](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux&pivots=os-linux) +2. [Azure Cli](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) +3. [Azure Cli extension](https://learn.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview) `rdbms-connect` +4. アクティブなサブスクリプションを持つAzureアカウント。 +5. [Python 3.8+](https://www.python.org/downloads/) +6. [Powershell Core](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5) (Windowsユーザーのみ) + +### デプロイメント手順 +注:このインフラストラクチャのバージョンは、動作するソリューションを正常にデプロイします。ただし、Azure Key Vault、Azure App Configなどの一部のモジュールやベストプラクティスはまだ進行中です。 +これらのモジュールとベストプラクティスは近日中に最終化され、実装される予定です。 + +### リポジトリのクローン +リポジトリをクローンします。完了したら、リポジトリに移動します +```sh +git clone https://github.com/EmumbaOrg/retail-solution-accelerator.git +cd retail-solution-accelerator +``` + +### Azureアカウントへのログイン +`azure cli`にログインするには、次のコマンドを使用します: +```sh +az login +``` + +`azure developer cli`にログインするには、このコマンドを使用します: +```sh +azd auth login +``` + +上記のコマンドが失敗した場合は、次のフラグを使用します: +```sh +azd auth login --use-device-code +``` + +### 新しいazure開発者環境の作成 +新しい`azd`環境を初期化し、作成します。`azd`環境に名前を付けてください +```sh +azd init +``` + +### azdフックスクリプトへの権限付与 +Windows OSでソリューションをデプロイする場合は、現在のセッションに`pwsh`スクリプトを実行するための次の権限を付与します +```sh +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + +Windows OS上のunix-like環境(例:cygwin、minGW)をデプロイする場合は、現在のセッションに`pwsh`スクリプトを実行するための次の権限を付与します +```sh +pwsh -NoProfile -Command "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass" +``` + +### ソリューションのデプロイメント +リソースをプロビジョニングするために、次のコマンドを実行します。 +```sh +azd up +``` + +このコマンドを実行すると、プロンプトがデプロイメントのサブスクリプション、ソリューションアクセラレータリソースの場所とAzure OpenAIモデルの場所、および作成するリソースグループを尋ねます。デプロイメントの地域で十分なAzure OpenAIモデルのクォータがあることを確認してください。このソリューションに必要なAzure OpenAIのクォータは以下に記載されています。この設定は、`infra`ディレクトリの`main.parameters.json`ファイルを使用して以下のパラメータで変更できます。デプロイメントには時間がかかる場合があり、進行状況はターミナルとAzure Portalの両方で提供されます。 +- **GPT-4o:** 150K TPM - `AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY` +- **text-embedding-ada-002:** 120K TPM - `AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY` + +### トラブルシューティング +1. `azd cli`のトラブルシューティングガイドは[こちら](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/troubleshoot?tabs=Browser)です。 +2. 初期化または新しいenvの作成時にサポートされていない文字が使用されると、検証エラーが発生します。 +3. ユーザーが`azd cli`を実行する際に適切な権限を持っていないと、スコープエラーが発生します。ユーザーの権限をサブスクリプションレベルに更新してください。 +4. `リソースエンティティのプロビジョニング状態が端末ではない`エラーが発生した場合、`azd up`コマンドを使用してデプロイメントを再起動します。 + +## Tear Down +上記のステップで作成されたすべてのリソースを破棄し、ソリューションアクセラレータによってデプロイされたアカウントを削除するには、以下のコマンドを使用します: +```sh +azd down --purge +``` +purgeフラグはすべてのアカウントを永久に削除します。 + +### パーソナライゼーションワークフロー +以下は、その可視化ツールを介して生成されたLlamaIndexワークフローです: +![スクリーンショット](./workflow.png) From 05f465bbcc21f0e141b19fb54b035bedd48d1af1 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Sat, 21 Jun 2025 13:28:20 +0900 Subject: [PATCH 18/23] Withdrew a fix --- azd-hooks/predeploy.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/azd-hooks/predeploy.sh b/azd-hooks/predeploy.sh index c9771fa..5811e55 100755 --- a/azd-hooks/predeploy.sh +++ b/azd-hooks/predeploy.sh @@ -6,12 +6,6 @@ POSTGRES_USERNAME=$(azd env get-value POSTGRES_USERNAME) POSTGRES_DATABASE=$(azd env get-value POSTGRES_DATABASE) POSTGRES_PASSWORD=$(azd env get-value POSTGRES_PASSWORD) -# wait until the server created -az postgres flexible-server wait \ - --name "$POSTGRES_NAME" \ - --resource-group "$RESOURCE_GROUP" \ - --created - az postgres flexible-server execute \ --admin-user "$POSTGRES_USERNAME" \ --admin-password "$POSTGRES_PASSWORD" \ From d42b6ade8541794db967b293506bbb5c1b80b52b Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Wed, 25 Jun 2025 17:24:26 +0900 Subject: [PATCH 19/23] Added a diagram --- README.md | 6 +++--- README_ja.md | 6 +++--- docs/images/arch-diagram.png | Bin 0 -> 293799 bytes 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 docs/images/arch-diagram.png diff --git a/README.md b/README.md index b74d0ea..e1eaa5c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Retail solution accelerator provides the following features: - Debug panel using Arize Phoenix tracing for agent triggers and tracking ## Architecture Diagram -![AZURE ARCHITECTURE](https://github.com/user-attachments/assets/f3ca0e0d-0c93-4c0f-be5d-958eace3a138) +![AZURE ARCHITECTURE](./docs/images/arch-diagram.png) ## Solution Accelerator Deployment @@ -37,8 +37,8 @@ These modules and best practices will be finalized and implemented soon. ### Clone Repository Clone the repository. Once done, navigate to the repository ```sh -git clone https://github.com/EmumbaOrg/retail-solution-accelerator.git -cd retail-solution-accelerator +git clone https://github.com/Azure-Samples/postgres-agentic-shop.git +cd postgres-agentic-shop ``` ### Login to your Azure account diff --git a/README_ja.md b/README_ja.md index 759e8ca..e30d921 100644 --- a/README_ja.md +++ b/README_ja.md @@ -14,7 +14,7 @@ - エージェントトリガーとトラッキングのためのArize Phoenixトレーシングを使用したデバッグパネル ## Architecture Diagram -![AZURE ARCHITECTURE](https://github.com/user-attachments/assets/f3ca0e0d-0c93-4c0f-be5d-958eace3a138) +![AZURE ARCHITECTURE](./docs/images/arch-diagram.png) ## Solution Accelerator Deployment @@ -37,8 +37,8 @@ ### リポジトリのクローン リポジトリをクローンします。完了したら、リポジトリに移動します ```sh -git clone https://github.com/EmumbaOrg/retail-solution-accelerator.git -cd retail-solution-accelerator +git clone https://github.com/Azure-Samples/postgres-agentic-shop.git +cd postgres-agentic-shop ``` ### Azureアカウントへのログイン diff --git a/docs/images/arch-diagram.png b/docs/images/arch-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a14d1f037e782f31b90e45fbf845ded7c827f5 GIT binary patch literal 293799 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�}KyF`_6J64NCl|o{0ZM7!2{%oC&p6;&QIj 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%0AYo$LtafVyw3^ZI)}2M@_W`jA6VBn8ZSm< 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 literal 0 HcmV?d00001 From 294c9393525c83e32cf52cda463b306f2998c1f8 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Wed, 25 Jun 2025 17:27:34 +0900 Subject: [PATCH 20/23] Reduced OpenAI quota --- infra/main.parameters.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index b0047f3..a3eed53 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -48,7 +48,7 @@ "value": "${AZURE_OPENAI_CHAT_DEPLOYMENT_SKU=GlobalStandard}" }, "chatDeploymentCapacity":{ - "value": "${AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY=150}" + "value": "${AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY=140}" }, "openAIEmbedHost": { "value": "${OPENAI_EMBED_HOST=azure}" @@ -66,10 +66,10 @@ "value": "${AZURE_OPENAI_EMBED_DEPLOYMENT_SKU=Standard}" }, "embedDeploymentCapacity":{ - "value": "${AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY=120}" + "value": "${AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY=60}" }, "containerRegistryName": { "value": "${AZURE_CONTAINER_REGISTRY_ENDPOINT}" } } -} \ No newline at end of file +} From bcd34c582c2beb15a4a72dbd144e26ba5f375c8e Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Wed, 25 Jun 2025 17:28:33 +0900 Subject: [PATCH 21/23] Added .DS_Store to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c863d4e..3dd98c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .azure next-steps.md .env +.DS_Store From d6c27c74c05df98b5f06014d51d6846e129f8030 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Wed, 25 Jun 2025 18:07:06 +0900 Subject: [PATCH 22/23] Changed exported port --- backend/.devcontainer/Dockerfile | 2 +- backend/.devcontainer/devcontainer.json | 2 +- backend/.vscode/launch.json | 2 +- backend/Dockerfile | 3 ++- backend/startup.sh | 4 ++-- infra/backend.bicep | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/.devcontainer/Dockerfile b/backend/.devcontainer/Dockerfile index bf4e522..aaf7433 100644 --- a/backend/.devcontainer/Dockerfile +++ b/backend/.devcontainer/Dockerfile @@ -22,4 +22,4 @@ COPY poetry.lock pyproject.toml ./ RUN poetry install --no-interaction --no-ansi ADD . ./ -EXPOSE 8000 +EXPOSE 80 diff --git a/backend/.devcontainer/devcontainer.json b/backend/.devcontainer/devcontainer.json index 8d3f70b..8446687 100644 --- a/backend/.devcontainer/devcontainer.json +++ b/backend/.devcontainer/devcontainer.json @@ -18,7 +18,7 @@ }, // "postCreateCommand": "./.devcontainer/setupPreCommit.sh", // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [8000], + "forwardPorts": [80], // Uncomment the next line to run commands after the container is created. // "postCreateCommand": "cat /etc/os-release", diff --git a/backend/.vscode/launch.json b/backend/.vscode/launch.json index fb4bdfc..c70f337 100644 --- a/backend/.vscode/launch.json +++ b/backend/.vscode/launch.json @@ -10,7 +10,7 @@ "args": [ "src.main:app", "--port", - "8000", + "80", "--log-config", "logging_config.yaml", "--reload" diff --git a/backend/Dockerfile b/backend/Dockerfile index 9dbd725..9dddfbb 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -22,7 +22,8 @@ COPY poetry.lock pyproject.toml ./ RUN poetry install --no-interaction --no-ansi ADD . ./ -EXPOSE 8000 +EXPOSE 80 RUN chmod +x ./startup.sh CMD ["./startup.sh"] + diff --git a/backend/startup.sh b/backend/startup.sh index 6cd9cbf..a93a22a 100644 --- a/backend/startup.sh +++ b/backend/startup.sh @@ -9,7 +9,7 @@ if [ "$ENV" = "prod" ]; then echo "Starting in production mode..." uvicorn src.main:app \ --host 0.0.0.0 \ - --port 8000 \ + --port 80 \ --access-log \ --log-config logging_config.yaml \ --workers 8 @@ -17,7 +17,7 @@ else echo "Starting in development mode..." uvicorn src.main:app \ --host 0.0.0.0 \ - --port 8000 \ + --port 80 \ --reload \ --access-log \ --log-config logging_config.yaml diff --git a/infra/backend.bicep b/infra/backend.bicep index 4728cd0..087aaa8 100644 --- a/infra/backend.bicep +++ b/infra/backend.bicep @@ -46,7 +46,7 @@ module backendapp 'core/host/container-app.bicep' = { ] ) secrets: secrets - targetPort: 8000 + targetPort: 80 } } From 1844fb69599ac23255532a0819a7dd0ea1161523 Mon Sep 17 00:00:00 2001 From: Rio Fujita Date: Wed, 25 Jun 2025 18:15:48 +0900 Subject: [PATCH 23/23] Changed quota in docs --- README.md | 4 ++-- README_ja.md | 4 ++-- docs/workshop/en/00-Prerequisites.md | 4 ++-- docs/workshop/ja/00-Prerequisites.md | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e1eaa5c..5bf96e4 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,8 @@ azd up Once this command is executed, the prompt asks for subscription for deployment, two locations i.e. one for location of solution accelerator resources and other for location of Azure OpenAI models and the resource group to create. Make sure that you have enough Azure OpenAI model quota in the region of deployment. The Azure OpenAI quota required for this solution is listed below. This configuration can be changed from `main.parameters.json` file in `infra` directory using following parameters. The deployment might take some time and will provide progress of deployment in terminal as well as on Azure Portal. -- **GPT-4o:** 150K TPM - `AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY` -- **text-embedding-ada-002:** 120K TPM - `AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY` +- **GPT-4o:** 140K TPM - `AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY` +- **text-embedding-ada-002:** 60K TPM - `AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY` ### Troubleshooting 1. Troubleshooting guide for `azd cli` is [here](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/troubleshoot?tabs=Browser). diff --git a/README_ja.md b/README_ja.md index e30d921..f0209f0 100644 --- a/README_ja.md +++ b/README_ja.md @@ -81,8 +81,8 @@ azd up ``` このコマンドを実行すると、プロンプトがデプロイメントのサブスクリプション、ソリューションアクセラレータリソースの場所とAzure OpenAIモデルの場所、および作成するリソースグループを尋ねます。デプロイメントの地域で十分なAzure OpenAIモデルのクォータがあることを確認してください。このソリューションに必要なAzure OpenAIのクォータは以下に記載されています。この設定は、`infra`ディレクトリの`main.parameters.json`ファイルを使用して以下のパラメータで変更できます。デプロイメントには時間がかかる場合があり、進行状況はターミナルとAzure Portalの両方で提供されます。 -- **GPT-4o:** 150K TPM - `AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY` -- **text-embedding-ada-002:** 120K TPM - `AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY` +- **GPT-4o:** 140K TPM - `AZURE_OPENAI_CHAT_DEPLOYMENT_CAPACITY` +- **text-embedding-ada-002:** 60K TPM - `AZURE_OPENAI_EMBED_DEPLOYMENT_CAPACITY` ### トラブルシューティング 1. `azd cli`のトラブルシューティングガイドは[こちら](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/troubleshoot?tabs=Browser)です。 diff --git a/docs/workshop/en/00-Prerequisites.md b/docs/workshop/en/00-Prerequisites.md index 5bb1aa7..2de0d53 100644 --- a/docs/workshop/en/00-Prerequisites.md +++ b/docs/workshop/en/00-Prerequisites.md @@ -59,9 +59,9 @@ The resources to be deployed in this hands-on include Azure Database for Postgre To use the Azure OpenAI service, a usage limit (quota) for each model must be allocated to your subscription. In particular, the **GPT-4o** model and **Embedding** model used in this hands-on require relatively large request quotas. The default configuration requires the following throughput. -- **GPT-4o model**: Processing capacity of about 150k tokens per minute (150K TPM). The "Model Usage Limit (Requests per minute)" of Azure OpenAI must meet this requirement. +- **GPT-4o model**: Processing capacity of about 140k tokens per minute (140K TPM). The "Model Usage Limit (Requests per minute)" of Azure OpenAI must meet this requirement. -- **text-embedding-ada-002 (embedding model)**: About 120k tokens per minute (120K TPM). +- **text-embedding-ada-002 (embedding model)**: About 60k tokens per minute (60K TPM). You can check the OpenAI quota of your Azure subscription on the "Quota + Limit" page of the Azure Portal or with the `az openai admin quota show` command. If you are short, please request a limit increase when creating the Azure OpenAI resource. diff --git a/docs/workshop/ja/00-Prerequisites.md b/docs/workshop/ja/00-Prerequisites.md index e351dba..2336e22 100644 --- a/docs/workshop/ja/00-Prerequisites.md +++ b/docs/workshop/ja/00-Prerequisites.md @@ -59,9 +59,9 @@ wsl --install Azure OpenAIサービスを利用するには、モデルごとの使用上限(クォータ)がサブスクリプションに割り当てられている必要があります。特に本ハンズオンで用いる**GPT-4o**モデルや**Embedding**モデルは要求クォータが比較的大きいです。デフォルト構成では以下の程度のスループットが必要となります。 -- **GPT-4o モデル**: 1分あたり150kトークン程度の処理能力 (150K TPM)。Azure OpenAIの「モデル使用上限 (Requests per minute)」がこれを満たす必要があります。 +- **GPT-4o モデル**: 1分あたり140kトークン程度の処理能力 (140K TPM)。Azure OpenAIの「モデル使用上限 (Requests per minute)」がこれを満たす必要があります。 -- **text-embedding-ada-002 (埋め込みモデル)**: 1分あたり120kトークン程度 (120K TPM)。 +- **text-embedding-ada-002 (埋め込みモデル)**: 1分あたり60kトークン程度 (60K TPM)。 ご自身のAzureサブスクリプションのOpenAIクォータは、Azure Portalの「クォータ + 制限」ページや`az openai admin quota show`コマンドで確認できます。不足している場合は、Azure OpenAIリソースの作成時に上限引き上げのリクエストを申請してください。