Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Al crear aplicaciones inteligentes, es posible que desee establecer el contexto de la aplicación con sus propios datos SQL. Con el anuncio reciente de compatibilidad con vectores de Azure SQL (versión preliminar), puede establecer el contexto mediante los datos de Azure SQL que ya tiene con nuevas funciones vectoriales que ayudan a administrar los datos vectoriales.
En este tutorial, creará una aplicación de ejemplo RAG mediante la configuración de una búsqueda de vectores híbridos en la base de datos de Azure SQL mediante una aplicación Blazor de .NET 8. Este ejemplo tiene como base la documentación anterior para implementar una aplicación Blazor de .NET con OpenAI. Si quiere implementar la aplicación mediante una plantilla azd, puede visitar el repositorio de ejemplos de Azure con instrucciones de implementación.
Requisitos previos
- Un recurso de Azure OpenAI con modelos implementados
- Una aplicación web Blazor de .NET 8 o 9 implementada en App Service
- Un recurso de base de datos de Azure SQL con incrustaciones de vectores.
1. Configuración de la aplicación web Blazor
En este ejemplo, vamos a crear un cuadro de chat sencillo con el que interactuar. Si usa la aplicación .NET Blazor de requisitos previos del artículo anterior, puede omitir los cambios en el archivo OpenAI.razor, ya que el contenido es el mismo. Sin embargo, debe asegurarse de que se instalan los siguientes paquetes:
Instale los siguientes paquetes para interactuar con Azure OpenAI y Azure SQL.
Microsoft.SemanticKernelMicrosoft.Data.SqlClient
- Haga clic con el botón derecho en la carpeta Páginas que se encuentra en la carpeta Componentes y agregue un nuevo elemento denominado OpenAI.razor
- Agregue el código siguiente al archivo OpenAI.razor y haga clic en Guardar
@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;
}
Claves de API y puntos de conexión
El uso del recurso de Azure OpenAI requiere el uso de claves de API y valores de punto de conexión. Consulte Uso de referencias de Key Vault como configuración de la aplicación en Azure App Service y Azure Functions para administrar y controlar los secretos con Azure OpenAI. Aunque no es necesario, se recomienda usar la identidad administrada para proteger el cliente sin necesidad de administrar las claves de API. Consulte la documentación anterior para configurar el cliente de Azure OpenAI en el paso siguiente para usar la identidad administrada con Azure OpenAI.
2. Adición de un cliente de Azure OpenAI
Después de agregar la interfaz de chat, podemos configurar el cliente de Azure OpenAI mediante el Kernel semántico. Agregue el código siguiente para crear el cliente que se conecta al recurso de Azure OpenAI. Debe usar las claves de API de Azure OpenAI y la información del punto de conexión que se configuraron y gestionaron en el paso anterior.
@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;
Desde aquí, debe tener una aplicación de chat en funcionamiento que esté conectada a OpenAI. A continuación, configuraremos nuestra base de datos de Azure SQL para trabajar con nuestra aplicación de chat.
3. Implementación de modelos de Azure OpenAI
Para preparar la base de datos de Azure SQL para la búsqueda de vectores, debe usar un modelo de inserción para generar incrustaciones usadas para buscar además del modelo de lenguaje implementado inicial. En este ejemplo, se usan los siguientes modelos:
-
text-embedding-ada-002se usa para generar las incrustaciones -
gpt-3.5-turbose usa para el modelo de lenguaje
Estos dos modelos deben implementarse antes de continuar con el paso siguiente. Visite la documentación para implementar modelos con Azure OpenAI mediante Microsoft Foundry.
4. Vectorización de la base de datos SQL
Para realizar una búsqueda de vectores híbridos en la base de datos de Azure SQL, primero debe tener las incrustaciones adecuadas en la base de datos. Hay muchas maneras de vectorizar la base de datos. Una opción es usar el siguiente vectorizador de base de datos de Azure SQL para generar incrustaciones para la base de datos SQL. Vectorice la base de datos de Azure SQL antes de continuar.
5. Creación de un procedimiento para generar incrustaciones
Con la compatibilidad con vectores de Azure SQL (versión preliminar), puede crear un procedimiento almacenado que usará un tipo de datos Vector para almacenar incrustaciones generadas para consultas de búsqueda. El procedimiento almacenado invoca un punto de conexión de API de REST externo para obtener las inserciones. Consulte la documentación para usar Azure Data Studio para conectarse a la base de datos antes de ejecutar la consulta.
Use lo siguiente para crear un procedimiento almacenado con el editor de consultas SQL preferido. Debe rellenar el parámetro @url con el nombre del recurso de Azure OpenAI y rellenar el punto de conexión de REST con la clave de API del modelo de inserción de texto. Observará el nombre del modelo como parte de @url, que se rellenará con la consulta de búsqueda.
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
Después de crear el procedimiento almacenado, debería poder verlo en la carpeta Stored Procedures que se encuentra en la carpeta Programmability de la base de datos SQL. Una vez creado, puede ejecutar una búsqueda de similitud de prueba en el editor de consultas SQL mediante el nombre del modelo de inserción de texto. Esto usa el procedimiento almacenado para generar incrustaciones y usar una función de distancia vectorial para calcular la distancia vectorial y devolver los resultados en función de la consulta de texto.
6. Conexión y búsqueda de la base de datos
Ahora que la base de datos está configurada para crear incrustaciones, podemos conectarnos a ella en nuestra aplicación y configurar la consulta de búsqueda de vectores híbridos.
Agregue el código siguiente al archivo OpenAI.razor y asegúrese de que la cadena de conexión se actualiza para usar la cadena de conexión de Azure SQL Database implementada. El código usa un parámetro SQL que pasará de forma segura la entrada del usuario desde la aplicación de chat a la consulta.
// 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");
La propia consulta SQL usa una búsqueda híbrida que ejecuta el procedimiento almacenado configurado anteriormente para crear incrustaciones y usa SQL para filtrar los resultados deseados. En este ejemplo, puntuamos los resultados y ordenamos la salida para obtener los mejores resultados antes de usarlos como contexto fundamentado para generar una respuesta.
Protección de los datos con identidad administrada
Azure SQL puede usar la identidad administrada con Microsoft Entra para proteger el recurso de SQL mediante la configuración de la autenticación sin contraseña. Siga los pasos siguientes para configurar una cadena de conexión sin contraseña que se usará en la aplicación.
- Vaya al recurso de Azure SQL Server y haga clic en Microsoft Entra ID en Configuración.
- A continuación, haga clic en +Establecer administrador y busque y asígnese usted mismo para configurar Entra ID y haga clic en Guardar. Ahora Entra ID está configurado en el servidor SQL Server y acepta la autenticación de Entra ID.
- A continuación, vaya al recurso de base de datos y copie la cadena de conexión ADO.NET (autenticación sin contraseña de Microsoft Entra) y agréguela al código donde mantenga la cadena de conexión.
En este momento, puede probar la aplicación localmente con la cadena de conexión sin contraseña.
Concesión de acceso a App Service
Para poder realizar una llamada a la base de datos al usar la identidad administrada con Azure SQL, primero deberá conceder acceso a la base de datos a App Service. Si no lo ha hecho en este momento, primero deberá crear una aplicación web antes de completar los pasos siguientes.
Siga estos pasos para conceder acceso a la aplicación web:
- Vaya a la aplicación web y haga clic en la hoja Identidad que se encuentra en Configuración.
- Active la identidad administrada asignada por el sistema si aún no lo ha hecho.
- Vaya al recurso de base de datos y abra el editor de consultas que se encuentra en el menú del lado izquierdo. Es posible que tenga que iniciar sesión para usar el editor.
- Ejecute los comandos siguientes para crear un usuario y modificar los roles, agregue la aplicación web como miembro
-- 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
- A continuación, conceda el acceso para usar el procedimiento almacenado y el punto de conexión de 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
Desde aquí, la base de datos de Azure SQL ahora es segura y puede implementar la aplicación en App Service.
Este es el ejemplo completo de la página OpenAI.razor agregada:
@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;
}
}