Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Pode usar os seus próprios dados SQL para fundamentar o contexto da sua aplicação inteligente. Este artigo explica a criação de uma aplicação de geração aumentada de recuperação (RAG) para .NET Blazor, configurando uma pesquisa vetorial híbrida numa base de dados Azure SQL que possui embeddings vetorizados. O suporte ao vetor Azure SQL fornece novas funções vetoriais que ajudam a gerir os dados vetoriais.
Pré-requisitos
- Uma base de dados Azure SQL com dados que pode vetorizar
- Um recurso Azure OpenAI
- Uma aplicação web .NET 8 ou 9 Blazor implementada no Azure App Service
Este tutorial utiliza um exemplo complementar do Clone e implementa uma aplicação de chat .NET 9 Blazor ligada ao Azure OpenAI.
1. Configurar a aplicação web Blazor
Cria uma caixa de chat básica para interagir.
Certifique-se de que os
Microsoft.SemanticKernelpacotes eMicrosoft.Data.SqlClientestão instalados no ambiente de desenvolvimento.Na árvore de projetos da aplicação web, expanda as Páginas de Componentes> e crie um novo ficheiro em Páginas chamado OpenAI.razor.
Adicione o seguinte código da caixa de chat ao ficheiro OpenAI.razor e guarde o ficheiro.
@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. Configurar o cliente Azure OpenAI
Depois de adicionar a interface de chat, configure o cliente Azure OpenAI usando o Semantic Kernel. O código seguinte cria o cliente que se liga ao recurso Azure OpenAI.
O código faz referência a DEPLOYMENT_NAME, ENDPOINT, API_KEY, e MODEL_ID. Certifique-se de guardar estes valores em appsettings.json ou como variáveis de ambiente. Para instruções sobre como obter e gerir a informação de chaves e endpoints do Azure OpenAI, consulte Usar referências do Key Vault como definições de aplicação no Azure App Service e Azure Functions.
Observação
Se possível, deve usar a identidade gerida para proteger o seu cliente sem ter de gerir chaves API. Consulte o Tutorial: Construa um chatbot com Azure App Service e Azure OpenAI (.NET) para instruções sobre como configurar um cliente Azure OpenAI para usar identidade gerida.
Adicione o seguinte código ao ficheiro 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;
Agora tem uma aplicação de chat funcional ligada ao OpenAI. De seguida, configura a tua base de dados Azure SQL para trabalhar com a tua aplicação de chat.
3. Implantar modelos do Azure OpenAI
Para preparar uma base de dados Azure SQL para pesquisa vetorial híbrida, utiliza-se um modelo de embedding para gerar embeddings a usar na pesquisa. Depois de as incorporações apropriadas estarem na base de dados, pode usá-las juntamente com o seu modelo de linguagem inicial para realizar uma pesquisa vetorial híbrida na base de dados.
Este exemplo utiliza o text-embedding-ada-002 modelo para gerar embeddings e gpt-4o-mini para o modelo de linguagem. Antes de continuar, utilize o portal Microsoft Foundry para implementar os dois modelos no seu recurso OpenAI. Para mais informações, consulte Implementar Modelos Microsoft Foundry no portal Foundry.
4. Vetorizar a sua base de dados Azure SQL
Para realizar uma pesquisa vetorial híbrida numa base de dados Azure SQL, os embeddings apropriados devem estar na base de dados. Vetorize a sua base de dados antes de continuar. Existem muitas formas de vetorizar uma base de dados. Uma opção é usar o vetorizador Azure SQLDB.
5. Criar um procedimento armazenado que gere embeddings
Pode usar suporte vetorial Azure SQL para criar um procedimento armazenado que utiliza um VECTOR tipo de dado para armazenar embeddings gerados para consultas de pesquisa. O procedimento armazenado invoca um endpoint externo da API REST para obter as incorporações.
A consulta SQL seguinte cria o procedimento armazenado. Substitua o <resourcename> marcador de posição no @url parâmetro pelo nome do seu recurso Azure OpenAI, e substitua o <openAIkey> marcador de posição pela chave API do seu modelo de embedding de texto. O nome do modelo faz parte do @url, que é preenchido com o termo de pesquisa.
Pode usar o Query Editor no portal Azure ou o Visual Studio Code para se ligar à sua base de dados e executar a consulta. Para mais informações sobre a utilização do Visual Studio Code, consulte a documentação da extensão 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
Após a criação, este procedimento armazenado aparece em Procedimentos Armazenados na pasta Programmability da base de dados Azure SQL. Pode usar o nome do seu modelo de incorporação de texto para executar uma pesquisa de similaridade de teste no editor de consultas SQL. Este teste utiliza o procedimento armazenado para gerar embeddings, calcular a distância vetorial usando uma função de distância vetorial e devolver resultados com base na consulta de texto.
6. Conecte-se e pesquise seu banco de dados
Agora que a sua base de dados está configurada para criar embeddings, pode ligar-se à base de dados na sua aplicação e configurar a consulta de pesquisa vetorial híbrida. Adicione o seguinte código ao ficheiro OpenAI.razor , certificando-se de que a cadeia de ligação utiliza a cadeia de ligação da base de dados Azure SQL implementada.
O código utiliza um parâmetro SQL que transmite de forma segura a entrada do utilizador da aplicação de chat para a consulta. O exemplo utiliza o conjunto de dados 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");
A própria consulta SQL utiliza uma pesquisa híbrida para executar o procedimento armazenado que cria embeddings e filtra os resultados usando SQL. Este exemplo atribui pontuações aos resultados, ordena os resultados para selecionar os melhores e usa-os como contexto base para gerar uma resposta.
Proteja os seus dados com identidade gerida
O Azure SQL pode usar identidade gerida com o Microsoft Entra para proteger o recurso SQL configurando autenticação sem palavra-passe. Use os seguintes passos para configurar uma cadeia de ligação sem palavra-passe para usar na sua aplicação.
- No recurso Azure SQL Server no portal Azure, selecione Definições>Microsoft Entra ID no menu de navegação à esquerda.
- Seleccionar Definir administrador, pesquisar e seleccionar o teu nome, e depois seleccionar Guardar. O Entra ID está agora configurado no seu servidor SQL e aceita autenticação Entra ID.
- Vai ao recurso da tua base de dados, seleciona Definições>cadeia de ligação no menu de navegação da esquerda, e copia a cadeia de ligação ADO.NET (autenticação sem palavra-passe Microsoft Entra).
- Atualize a cadeia de ligação na appsettings.jsonda sua aplicação.
Agora pode testar a sua aplicação localmente com a sua cadeia de ligação sem palavra-passe.
Conceder acesso à base de dados ao Serviço de Aplicações
Antes de poder usar a sua aplicação web para chamar a base de dados Azure SQL usando identidade gerida, deve conceder à aplicação o acesso necessário à base de dados.
Na sua aplicação web no portal Azure, selecione Definições>Identidade no menu de navegação à esquerda.
No separador Sistema Atribuído , defina Estado para Ligado se ainda não estiver definido, e depois selecione Guardar.
Vá ao seu recurso de base de dados Azure SQL e selecione Editor de Consultas no menu de navegação da esquerda. Inicie sessão na sua base de dados se necessário.
No editor de Consultas, execute os seguintes comandos SQL que criam a aplicação web como utilizador e lhe atribuem as permissões necessárias, substituindo
<your-app-name>pelo nome da sua aplicação 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>"; GODe seguida, conceda à aplicação web acesso ao uso do procedimento armazenado e do endpoint 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
A sua base de dados Azure SQL está agora segura.
7. Implementar a aplicação
Agora pode implementar a sua aplicação no Azure App Service. Se quiser implementar a aplicação usando um azd modelo, veja Deployar com Azure Developer CLI.
Exemplo completo
O código seguinte mostra a página completa adicionada do 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;
}
}