チュートリアル: Azure App Service で Azure SQL と Azure OpenAI に接続された .NET Blazor アプリをデプロイする

独自の SQL データを使用して、インテリジェント アプリのコンテキストを作成できます。 この記事では、ベクター化された埋め込みを持つ Azure SQL データベースに対してハイブリッド ベクター検索を設定することで、取得拡張生成 (RAG) .NET Blazor アプリケーションを作成する手順について説明します。 Azure SQL ベクターのサポート では、ベクター データの管理に役立つ新しいベクター 関数 が提供されます。

前提条件

  • ベクター化できるデータを含む Azure SQL データベース
  • Azure OpenAI リソース
  • Azure App Service にデプロイされた .NET 8 または 9 Blazor Web アプリ

このチュートリアルでは、Clone のコンパニオン サンプル を使用して、Azure OpenAI に接続された .NET 9 Blazor チャット アプリをデプロイします。

1. Blazor Web アプリを設定する

操作する基本的なチャット ボックスを作成します。

  1. Microsoft.SemanticKernelパッケージとMicrosoft.Data.SqlClient パッケージが開発環境にインストールされていることを確認します。

  2. Web アプリ プロジェクト ツリーで、[コンポーネント]>Pages を展開し、OpenAI.razor という名前のページに新しいファイルを作成します。

  3. OpenAI.razor ファイルに次のチャット ボックス コードを追加し、ファイルを保存します。

    @page "/openai"
    @rendermode InteractiveServer
    @inject Microsoft.Extensions.Configuration.IConfiguration _config
    
    <PageTitle>OpenAI</PageTitle>
    
    <h3>OpenAI input query: </h3>
    <input class="col-sm-4" @bind="userMessage" />
    <button class="btn btn-primary" @onclick="SemanticKernelClient">Send Request</button>
    
    <br />
    <br />
    
    <h4>Server response:</h4> <p>@serverResponse</p>
    
    @code {
    
        @using Microsoft.SemanticKernel;
        @using Microsoft.SemanticKernel.ChatCompletion;
    
        }
    

2. Azure OpenAI クライアントを設定する

チャット インターフェイスを追加した後、セマンティック カーネルを使用して Azure OpenAI クライアントを設定します。 次のコードでは、Azure OpenAI リソースに接続するクライアントを作成します。

コードは、 DEPLOYMENT_NAMEENDPOINTAPI_KEY、および MODEL_IDを参照します。 これらの値は、 必ずappsettings.json または環境変数として格納してください。 Azure OpenAI キーとエンドポイント情報の取得と管理の手順については、「Azure App Service と Azure Functions で Key Vault 参照をアプリ設定として使用する」を参照してください。

可能であれば、API キーを管理することなく、マネージド ID を使用してクライアントをセキュリティで保護する必要があります。 マネージド ID を使用するように Azure OpenAI クライアントを設定する手順については、「 チュートリアル: Azure App Service と Azure OpenAI (.NET) を使用してチャットボットを構築する」を参照してください。

OpenAI.razor ファイルに次のコードを追加します。

@inject Microsoft.Extensions.Configuration.IConfiguration _config

@code {

    @using Microsoft.SemanticKernel;
    @using Microsoft.SemanticKernel.ChatCompletion;

    private string? userMessage;
    private string? serverResponse;

    private async Task SemanticKernelClient()
    {
    
        // App settings
        string deploymentName = _config["DEPLOYMENT_NAME"];
        string endpoint = _config["ENDPOINT"];
        string apiKey = _config["API_KEY"];
        string modelId = _config["MODEL_ID"];

        var builder = Kernel.CreateBuilder();

        // Chat completion service
        builder.Services.AddAzureOpenAIChatCompletion(
            deploymentName: deploymentName,
            endpoint: endpoint,
            apiKey: apiKey,
            modelId: modelId
        );

        var kernel = builder.Build();

        // Create prompt template
        var chat = kernel.CreateFunctionFromPrompt(
            @"{{$history}}
            User: {{$request}}
            Assistant: ");

        ChatHistory chatHistory = new("""You are a helpful assistant that answers questions""");

        var chatResult = kernel.InvokeStreamingAsync<StreamingChatMessageContent>(
                chat,
                new()
                    {
                        { "request", userMessage },
                        { "history", string.Join("\n", chatHistory.Select(x => x.Role + ": " + x.Content)) }
                    }
            );

        string message = "";
        await foreach (var chunk in chatResult)
        {
            message += chunk;
        }

        // Add messages to chat history
        chatHistory.AddUserMessage(userMessage!);
        chatHistory.AddAssistantMessage(message);

        serverResponse = message;

これで、作業用チャット アプリケーションが OpenAI に接続されました。 次に、チャット アプリケーションと連携するように Azure SQL データベースを設定します。

3.Azure OpenAI モデルのデプロイ

ハイブリッド ベクター検索用に Azure SQL データベースを準備するには、埋め込みモデルを使用して、検索に使用する埋め込みを生成します。 適切な埋め込みをデータベース内に配置した後、それらを初期言語モデルと共に使用して、データベースでハイブリッド ベクター検索を実行できます。

この例では、 text-embedding-ada-002 モデルを使用して、言語モデルの埋め込みと gpt-4o-mini を生成します。 続行する前に、Microsoft Foundry ポータルを使用して、OpenAI リソースに 2 つのモデルをデプロイします。 詳細については、「 Foundry ポータルでの Microsoft Foundry モデルのデプロイ」を参照してください。

4. Azure SQL データベースをベクター化する

Azure SQL データベースでハイブリッド ベクター検索を実行するには、適切な埋め込みをデータベースに含める必要があります。 続行する前に、データベースをベクター化します。 データベースをベクター化する方法は多数あります。 1 つのオプションは、 Azure SQL DB ベクターライザーを使用することです。

5. 埋め込みを生成するストアド プロシージャを作成する

Azure SQL ベクター サポートを使用して、VECTOR データ型を使用して検索クエリ用に生成された埋め込みを格納するストアド プロシージャを作成できます。 ストアド プロシージャでは、外部 REST API エンドポイントを呼び出して埋め込みを取得します。

次の SQL クエリでは、ストアド プロシージャが作成されます。 <resourcename> パラメーターの@url プレースホルダーを Azure OpenAI リソース名に置き換え、<openAIkey>プレースホルダーをテキスト埋め込みモデルの API キーに置き換えます。 検索クエリに応じて入力される@urlの一部はモデル名です。

Azure portal または Visual Studio Code でクエリ エディターを使用してデータベースに接続し、クエリを実行できます。 Visual Studio Code の使用の詳細については、 MSSQL 拡張機能のドキュメントを参照してください

CREATE PROCEDURE [dbo].[GET_EMBEDDINGS]
(
    @model VARCHAR(MAX),
    @text NVARCHAR(MAX),
    @embedding VECTOR(1536) OUTPUT
)
AS
BEGIN
    DECLARE @retval INT, @response NVARCHAR(MAX);
    DECLARE @url VARCHAR(MAX);
    DECLARE @payload NVARCHAR(MAX) = JSON_OBJECT('input': @text);

    -- Set the @url variable with proper concatenation before the EXEC statement
    SET @url = 'https://<resourcename>.openai.azure.com/openai/deployments/' + @model + '/embeddings?api-version=2023-03-15-preview';

    EXEC dbo.sp_invoke_external_rest_endpoint 
        @url = @url,
        @method = 'POST',   
        @payload = @payload,   
        @headers = '{"Content-Type":"application/json", "api-key":"<openAIkey>"}', 
        @response = @response OUTPUT;

    -- Use JSON_QUERY to extract the embedding array directly
    DECLARE @jsonArray NVARCHAR(MAX) = JSON_QUERY(@response, '$.result.data[0].embedding');

    
    SET @embedding = CAST(@jsonArray as VECTOR(1536));
END
GO

作成後、このストアド プロシージャは、Azure SQL データベースの [プログラミング] フォルダーの [ストアド プロシージャ] の下に表示されます。 テキスト埋め込みモデル名を使用して、SQL クエリ エディターでテスト 類似性検索 を実行できます。 このテストでは、ストアド プロシージャを使用して埋め込みを生成し、ベクター距離関数を使用してベクター距離を計算し、テキスト クエリに基づいて結果を返します。

6.データベースに接続して検索する

データベースが埋め込みを作成するように設定されたので、アプリケーション内のデータベースに接続し、ハイブリッド ベクター検索クエリを設定できます。 OpenAI.razor ファイルに次のコードを追加し、接続文字列でデプロイされた Azure SQL データベース接続文字列が使用されていることを確認します。

このコードでは、チャット アプリからクエリにユーザー入力を安全に渡す SQL パラメーターを使用します。 この例では、 Amazon Fine Food Reviews データセットを 使用します。

// Database connection string
var connectionString = _config["AZURE_SQL_CONNSTRING"];

try
{
    await using var connection = new SqlConnection(connectionString);
    Console.WriteLine("\nQuery results:");

    await connection.OpenAsync();

    // Hybrid search query
    var sql =
        @"DECLARE @e VECTOR(1536);
        EXEC dbo.GET_EMBEDDINGS @model = 'text-embedding-ada-002', @text = '@userMessage', @embedding = @e OUTPUT;

             -- Comprehensive query with multiple filters.
        SELECT TOP(5)
            f.Score,
            f.Summary,
            f.Text,
            VECTOR_DISTANCE('cosine', @e, VectorBinary) AS Distance,
            CASE
                WHEN LEN(f.Text) > 100 THEN 'Detailed Review'
                ELSE 'Short Review'
            END AS ReviewLength,
            CASE
                WHEN f.Score >= 4 THEN 'High Score'
                WHEN f.Score BETWEEN 2 AND 3 THEN 'Medium Score'
                ELSE 'Low Score'
            END AS ScoreCategory
        FROM finefoodembeddings10k$ f
        WHERE
            f.UserId NOT LIKE 'Anonymous%' -- User-based filter to exclude anonymous users
            AND f.Score >= 4 -- Score threshold filter
            AND LEN(f.Text) > 50 -- Text length filter for detailed reviews
            AND (f.Text LIKE '%juice%') -- Inclusion of specific words
        ORDER BY
            Distance,  -- Order by distance
            f.Score DESC, -- Secondary order by review score
            ReviewLength DESC; -- Tertiary order by review length
    ";

    // Set SQL Parameter to pass in user message
    SqlParameter param = new SqlParameter();
    param.ParameterName = "@userMessage";
    param.Value = userMessage;
    
    await using var command = new SqlCommand(sql, connection);

    // add parameter to SqlCommand
    command.Parameters.Add(param);

    await using var reader = await command.ExecuteReaderAsync();

    while (await reader.ReadAsync())
    {
        // write results to console logs
        Console.WriteLine("{0} {1} {2} {3}", "Score: " + reader.GetDouble(0), "Text: " + reader.GetString(1), "Summary: " + reader.GetString(2), "Distance: " + reader.GetDouble(3));
        Console.WriteLine();

        // add results to chat history
        chatHistory.AddSystemMessage(reader.GetString(1) + ", " + reader.GetString(2));
    }
}
catch (SqlException e)
{
    Console.WriteLine($"SQL Error: {e.Message}");
}
catch (Exception e)
{
    Console.WriteLine(e.ToString());
}

Console.WriteLine("Done");

SQL クエリ自体は、ハイブリッド検索を使用して、埋め込みを作成し、SQL を使用して結果をフィルター処理するストアド プロシージャを実行します。 次の使用例は、結果にスコアを割り当て、最適な結果を得るために出力を並べ替え、それらをグラウンド コンテキストとして使用して応答を生成します。

マネージド ID を使用してデータをセキュリティで保護する

Azure SQL では、Microsoft Entra でマネージド ID を使用して、パスワードレス認証を構成することで SQL リソースをセキュリティで保護できます。 アプリケーションで使用するパスワードなしの接続文字列を構成するには、次の手順に従います。

  1. Azure portal の Azure SQL Server リソースで、左側のナビゲーション メニューから >Microsoft Entra ID] を選択します。
  2. [ 管理者の設定] を選択し、自分を検索して選択し、[保存] を選択 します。 これで、SQL サーバーに Entra ID が設定され、Entra ID 認証が受け入れられます。
  3. データベース リソースに移動し、左側のナビゲーション メニューから >Connection 文字列] を選択し、ADO.NET (Microsoft Entra パスワードレス認証) 接続文字列をコピーします。
  4. アプリの appsettings.jsonで接続文字列を更新します。

パスワードなしの接続文字列を使用して、アプリケーションをローカルでテストできるようになりました。

App Service へのデータベース アクセスを許可する

Web アプリを使用してマネージド ID を使用して Azure SQL データベースを呼び出す前に、アプリにデータベースへの必要なアクセス権を付与する必要があります。

  1. Azure portal の Web アプリで、左側のナビゲーション メニューから [Settings>Identity ] を選択します。

  2. [ システム割り当て済み ] タブで、まだ設定されていない場合は [状態][オン] に 設定し、[保存] を選択 します

  3. Azure SQL データベース リソースに移動し、左側のナビゲーション メニューから [クエリ エディター ] を選択します。 必要に応じて、データベースにサインインします。

  4. クエリ エディターで、Web アプリをユーザーとして作成し、必要なロール メンバーシップを割り当てる次の SQL コマンドを実行し、<your-app-name>を Web アプリの名前に置き換えます。

    -- Create member, alter roles to your database
    CREATE USER "<your-app-name>" FROM EXTERNAL PROVIDER;
    ALTER ROLE db_datareader ADD MEMBER "<your-app-name>";
    ALTER ROLE db_datawriter ADD MEMBER "<your-app-name>";
    ALTER ROLE db_ddladmin ADD MEMBER "<your-app-name>";
    GO
    
  5. 次に、ストアド プロシージャと Azure OpenAI エンドポイントを使用するためのアクセス権を Web アプリに付与します。

    -- Grant access to use stored procedure
    GRANT EXECUTE ON OBJECT::[dbo].[GET_EMBEDDINGS]  
      TO "<your-app-name>"  
    GO
    
    -- Grant access to use Azure OpenAI endpoint in stored procedure
    GRANT EXECUTE ANY EXTERNAL ENDPOINT TO "<your-app-name>";
    GO
    

これで、Azure SQL データベースがセキュリティで保護されました。

7.アプリをデプロイする

これで、アプリケーションを Azure App Service にデプロイできるようになりました。 azd テンプレートを使用してアプリをデプロイする場合は、「Azure Developer CLI を使用したデプロイ」を参照してください。

完全なコード例

次のコードは、完全に追加された OpenAI.razor ページを示しています。

@page "/openai"
@rendermode InteractiveServer
@inject Microsoft.Extensions.Configuration.IConfiguration _config

<PageTitle>OpenAI</PageTitle>

<h3>OpenAI input query: </h3>
<input class="col-sm-4" @bind="userMessage" />
<button class="btn btn-primary" @onclick="SemanticKernelClient">Send Request</button>

<br />
<br />

<h4>Server response:</h4> <p>@serverResponse</p>

@code {

    @using Microsoft.SemanticKernel;
    @using Microsoft.SemanticKernel.ChatCompletion;
    @using Microsoft.Data.SqlClient;

    private string? userMessage;
    private string? serverResponse;

    private async Task SemanticKernelClient()
    {
        // App settings
        string deploymentName = _config["DEPLOYMENT_NAME"];
        string endpoint = _config["ENDPOINT"];
        string apiKey = _config["API_KEY"];
        string modelId = _config["MODEL_ID"];

        // Semantic Kernel builder
        var builder = Kernel.CreateBuilder();

        // Chat completion service
        builder.Services.AddAzureOpenAIChatCompletion(
            deploymentName: deploymentName,
            endpoint: endpoint,
            apiKey: apiKey,
            modelId: modelId
        );

        var kernel = builder.Build();

        // Create prompt template
        var chat = kernel.CreateFunctionFromPrompt(
            @"{{$history}}
            User: {{$request}}
            Assistant: ");

        ChatHistory chatHistory = new("""You are a helpful assistant that answers questions about my data""");

        #region Azure SQL
        // Database connection string
        var connectionString = _config["AZURE_SQL_CONNECTIONSTRING"];

        try
        {
            await using var connection = new SqlConnection(connectionString);
            Console.WriteLine("\nQuery results:");
    
            await connection.OpenAsync();

            // Hybrid search query
            var sql =
                    @"DECLARE @e VECTOR(1536);
                    EXEC dbo.GET_EMBEDDINGS @model = 'text-embedding-ada-002', @text = '@userMessage', @embedding = @e OUTPUT;

                         -- Comprehensive query with multiple filters.
                    SELECT TOP(5)
                        f.Score,
                        f.Summary,
                        f.Text,
                        VECTOR_DISTANCE('cosine', @e, VectorBinary) AS Distance,
                        CASE
                            WHEN LEN(f.Text) > 100 THEN 'Detailed Review'
                            ELSE 'Short Review'
                        END AS ReviewLength,
                        CASE
                            WHEN f.Score >= 4 THEN 'High Score'
                            WHEN f.Score BETWEEN 2 AND 3 THEN 'Medium Score'
                            ELSE 'Low Score'
                        END AS ScoreCategory
                    FROM finefoodembeddings10k$ f
                    WHERE
                        f.UserId NOT LIKE 'Anonymous%' -- User-based filter to exclude anonymous users
                        AND f.Score >= 4 -- Score threshold filter
                        AND LEN(f.Text) > 50 -- Text length filter for detailed reviews
                        AND (f.Text LIKE '%juice%') -- Inclusion of specific words
                    ORDER BY
                        Distance,  -- Order by distance
                        f.Score DESC, -- Secondary order by review score
                        ReviewLength DESC; -- Tertiary order by review length
                ";

            // Set SQL Parameter to pass in user message
            SqlParameter param = new SqlParameter();
            param.ParameterName = "@userMessage";
            param.Value = userMessage;

            await using var command = new SqlCommand(sql, connection);

            // add parameter to SqlCommand
            command.Parameters.Add(param);

            await using var reader = await command.ExecuteReaderAsync();

            while (await reader.ReadAsync())
            {
                // write results to console logs
                Console.WriteLine("{0} {1} {2} {3}", "Score: " + reader.GetDouble(0), "Text: " + reader.GetString(1), "Summary: " + reader.GetString(2), "Distance: " + reader.GetDouble(3));
                Console.WriteLine();

                // add results to chat history
                chatHistory.AddSystemMessage(reader.GetString(1) + ", " + reader.GetString(2));

            }
        }
        catch (SqlException e)
        {
            Console.WriteLine($"SQL Error: {e.Message}");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }

        Console.WriteLine("Done");
        #endregion

        var chatResult = kernel.InvokeStreamingAsync<StreamingChatMessageContent>(
                chat,
                new()
                    {
                        { "request", userMessage },
                        { "history", string.Join("\n", chatHistory.Select(x => x.Role + ": " + x.Content)) }
                    }
            );

        string message = "";
        await foreach (var chunk in chatResult)
        {
            message += chunk;
        }

        // Append messages to chat history
        chatHistory.AddUserMessage(userMessage!);
        chatHistory.AddAssistantMessage(message);

        serverResponse = message;

    }
}