インテリジェントなアプリを作成する場合は、独自の SQL データを使用してアプリのコンテキストを作成できます。 Azure SQL ベクトルのサポート (プレビュー) の最近の発表により、ベクトル データの管理に役立つ新しいベクトル関数で既にお持ちの Azure SQL データを使用してコンテキストを作成できるようになりました。
このチュートリアルでは、.NET 8 Blazor アプリを使用して Azure SQL データベースに対してハイブリッド ベクトル検索を設定することで、RAG サンプル アプリケーションを作成します。 この例は、OpenAI を使用して .NET Blazor アプリをデプロイするために、前のドキュメントからビルドされています。 azd テンプレートを使用してアプリをデプロイする場合は、デプロイ手順を使用して Azure Samples リポジトリ にアクセスできます。
前提条件
- モデルがデプロイされた Azure OpenAI リソース
- App Service にデプロイされた .NET 8 または 9 Blazor Web アプリ
- ベクトル埋め込みを使用する Azure SQL データベース リソース。
1.Blazor Web アプリを設定する
この例では、操作するシンプルなチャット ボックスを作成します。 前の記事の前提条件の .NET Blazor アプリを使用している場合、コンテンツは同じであるため、OpenAI.razor ファイルへの変更をスキップできます。 ただし、次のパッケージがインストールされていることを確認する必要があります。
Azure OpenAI と Azure SQL を操作するには、次のパッケージをインストールします。
Microsoft.SemanticKernel
Microsoft.Data.SqlClient
- Components フォルダーの下にある Pages フォルダーを右クリックして、OpenAI.razor という名前の新しい項目を追加します。
- 次のコードを 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;
}
API キーとエンドポイント
Azure OpenAI リソースを使うには、API キーとエンドポイントの値を使用する必要があります。 Azure OpenAI でシークレットを管理および処理するには、 Azure App Service と Azure Functions のアプリ設定として Key Vault 参照を使用 する方法に関するページを参照してください。 必須ではありませんが、API キーを管理することなく、マネージド ID を使用してクライアントをセキュリティで保護することをお勧めします。 前のドキュメントを参照し、Azure OpenAI でマネージド ID を使用するように次の手順で Azure OpenAI クライアントを設定します。
2.Azure OpenAI クライアントを追加する
チャット インターフェイスを追加した後、セマンティック カーネルを使用して Azure OpenAI クライアントを設定できます。 次のコードを追加して、Azure OpenAI リソースに接続するクライアントを作成します。 前の手順で設定して処理した Azure OpenAI API キーとエンドポイント情報を使用する必要があります。
@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-3.5-turbo
は言語モデルに使用されます
次の手順に進む前に、これら 2 つのモデルをデプロイする必要があります。 Azure AI Foundry を使用した Azure OpenAI でのモデルのデプロイに関するドキュメントを参照してください。
4.SQL データベースをベクトル化する
Azure SQL データベースでハイブリッド ベクトル検索を実行するには、まずデータベースに適切な埋め込みを行う必要があります。 データベースをベクトル化するさまざまな方法があります。 1 つのオプションは、次の Azure SQL データベース ベクターライザーを使用して SQL データベースの埋め込みを生成することです。 続行する前に、Azure SQL データベースをベクトル化します。
5.埋め込みを生成するプロシージャを作成する
Azure SQL ベクトルのサポート (プレビュー) では、検索クエリ用に生成された埋め込みを格納するためにベクトル データ型を使用するストアド プロシージャを作成できます。 ストアド プロシージャでは、外部 REST API エンドポイントを呼び出して埋め込みを取得します。 クエリを実行する前に Azure Data Studio を使用してデータベースに接続するには、こちらのドキュメントを参照してください。
- 任意の SQL クエリ エディターを使用してストアド プロシージャを作成するには、以下を使用します。 @url パラメーターに Azure OpenAI リソース名を設定し、テキスト埋め込みモデルの API キーを残りのエンドポイントに設定する必要があります。 検索クエリが入力されると、@url の一部にモデル名が表示されます。
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
ストアド プロシージャを作成した後、SQL データベースの [プログラミング] フォルダーにある [ストアド プロシージャ] フォルダーの下に表示できるはずです。 作成したら、テキスト埋め込みモデル名を使用して、SQL クエリ エディター内でテストの類似性検索を実行できます。 これにより、ストアド プロシージャを使って埋め込みが生成され、ベクトル距離関数を使用してベクトル距離が計算され、テキスト クエリに基づいて結果が返されます。
6.データベースに接続して検索する
データベースが埋め込みを作成するように設定されたので、アプリケーションで接続し、ハイブリッド ベクトル検索クエリを設定できます。
次のコードを OpenAI.razor
ファイルに追加し、デプロイされた Azure SQL データベース接続文字列を使用するように接続文字列が更新されていることを確認します。 このコードでは、チャット アプリからクエリにユーザー入力を安全に渡す SQL パラメーターを使用しています。
// 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 リソースをセキュリティで保護できます。 アプリケーションで使用するパスワードレス接続文字列を構成するには、次の手順に従います。
- Azure SQL Server リソースに移動し、[設定] の下にある [Microsoft Entra ID] をクリックします。
- 次に、[+ 管理者の設定] をクリックし、Entra ID を設定するために自分自身を検索して選び、[保存] をクリックします。 これで、SQL Server に Entra ID が設定され、Entra ID 認証が受け入れられます。
- 次に、データベース リソースに移動し、ADO.NET (Microsoft Entra パスワードレス認証) 接続文字列をコピーし、接続文字列を保持するコードに追加します。
この時点で、パスワードレス接続文字列を使用してアプリケーションをローカルでテストできます。
App Service へのアクセス権を付与する
Azure SQL でマネージド ID を使用するときにデータベースの呼び出しを行うには、まず、App Service へのアクセス権をデータベースに付与する必要があります。 この時点で作成していない場合は、次の手順を完了する前に、まず Web アプリを作成する必要があります。
Web アプリへのアクセス権を付与するには、次の手順に従います。
- Web アプリに移動し、[設定] の下にある [ID] ブレードをクリックします。
- まだ有効にしていない場合は、システム割り当てマネージド ID を有効にします。
- データベース リソースに移動し、左側のメニューにあるクエリ エディターを開きます。 エディターを使用するには、サインインが必要な場合があります。
- 次のコマンドを実行してユーザーを作成し、ロールを変更して 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
- 次に、ストアド プロシージャと Azure OpenAI エンドポイントを使用するためのアクセス権を付与します
-- 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 データベースがセキュリティで保護され、アプリケーションを App Service にデプロイできるようになりました。
追加された 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;
}
}