Exercício - Implementar o serviço Azure Cosmos DB para NoSQL

Concluído

O serviço Azure Cosmos DB (CosmosDbService) gerencia consultas, criações, exclusões e atualizações de sessões e mensagens em seu aplicativo assistente de IA. Para gerenciar todas essas operações, o serviço é necessário para implementar vários métodos para cada operação potencial usando vários recursos do SDK do .NET.

Há vários requisitos fundamentais a abordar neste exercício:

  • Implementar operações para criar uma sessão ou mensagem
  • Implementar consultas para recuperar várias sessões ou mensagens
  • Implementar uma operação para atualizar uma única sessão ou atualização em lote de várias mensagens
  • Implementar uma operação para consultar e excluir várias sessões e mensagens relacionadas

Criar uma sessão ou mensagem

O Azure Cosmos DB para NoSQL armazena dados no formato JSON, o que nos permite armazenar vários tipos de dados em um único contêiner. Esta aplicação armazena tanto uma "sessão" de chat com o assistente de IA como as "mensagens" individuais dentro de cada sessão. Com a API para NoSQL, o aplicativo pode armazenar os dois tipos de dados no mesmo contêiner e, em seguida, diferenciar entre esses tipos usando um campo simples type .

  1. Abra o arquivo Serviços/CosmosDbService.cs .

  2. Dentro do InsertSessionAsync método, remova qualquer código de espaço reservado existente.

    public async Task<Session> InsertSessionAsync(Session session)
    {
    }
    
  3. Crie uma nova variável chamada partitionKey de tipo PartitionKey usando a propriedade da SessionId sessão atual como parâmetro.

    PartitionKey partitionKey = new(session.SessionId);
    
  4. Invoque o CreateItemAsync método do contêiner passando no parâmetro e partitionKey na session variável. Retorne a resposta como resultado do InsertSessionAsync método.

    return await _container.CreateItemAsync<Session>(
        item: session,
        partitionKey: partitionKey
    ); 
    
  5. Dentro do InsertMessageAsync método, remova qualquer código de espaço reservado existente.

    public async Task<Message> InsertMessageAsync(Message message)
    {
    }
    
  6. Crie uma PartitionKey variável usando session.SessionId como o valor da chave de partição.

    PartitionKey partitionKey = new(message.SessionId);
    
  7. Crie uma nova variável de mensagem nomeada newMessage com a Timestamp propriedade atualizada para o carimbo de data/hora UTC atual.

    Message newMessage = message with { TimeStamp = DateTime.UtcNow };
    
  8. Invoque CreateItemAsync a passagem das novas variáveis de chave de mensagem e de partição. Retorne a resposta como resultado de InsertMessageAsync.

    return await _container.CreateItemAsync<Message>(
        item: newMessage,
        partitionKey: partitionKey
    );
    
  9. Salve o arquivo Serviços/CosmosDbService.cs .

Recuperar várias sessões ou mensagens

Há dois casos de uso principais em que o aplicativo precisa recuperar vários itens do nosso contêiner. Primeiro, o aplicativo recupera todas as sessões para o usuário atual filtrando os itens para aqueles em que type = Session. Em segundo lugar, o aplicativo recupera todas as mensagens de uma sessão executando um filtro semelhante onde type = Session & sessionId = <current-session-id>. Implemente ambas as consultas aqui usando o SDK do .NET e um iterador de feed.

  1. Dentro do GetSessionsAsync método, remova qualquer código de espaço reservado existente.

    public async Task<List<Session>> GetSessionsAsync()
    {
    }
    
  2. Crie uma nova variável chamada query do tipo QueryDefinition com a consulta SELECT DISTINCT * FROM c WHERE c.type = @typeSQL . Use o método fluent WithParameter para atribuir o Session nome da classe como o valor para o parâmetro.

    QueryDefinition query = new QueryDefinition("SELECT DISTINCT * FROM c WHERE c.type = @type")
        .WithParameter("@type", nameof(Session));
    
  3. Invoque o método genérico GetItemQueryIterator<> na _container variável passando o tipo Session genérico e a query variável como um parâmetro. Armazene o resultado em uma variável do tipo FeedIterator<Session> chamada response.

    FeedIterator<Session> response = _container.GetItemQueryIterator<Session>(query);
    
  4. Crie uma nova variável de lista genérica chamada output.

    List<Session> output = new();
    
  5. Crie um loop while que é executado até response.HasMoreResults que não seja mais verdadeiro.

    while (response.HasMoreResults)
    {
    }
    

    Nota

    Usar um loop while aqui irá efetivamente percorrer todas as páginas da sua resposta até que não restem páginas.

  6. Dentro do loop while, obtenha de forma assíncrona a próxima página de resultados invocando ReadNextAsync a response variável e, em seguida, adicione esses resultados à variável de lista chamada output.

    FeedResponse<Session> results = await response.ReadNextAsync();
    output.AddRange(results);
    
  7. Fora do loop while, retorne a output variável com uma lista de sessões como resultado do GetSessionsAsync método.

    return output;
    
  8. Dentro do GetSessionMessagesAsync método, remova qualquer código de espaço reservado existente.

    public async Task<List<Message>> GetSessionMessagesAsync(string sessionId)
    {
    }
    
  9. Crie uma query variável do tipo QueryDefinition. Use a consulta SELECT * FROM c WHERE c.sessionId = @sessionId AND c.type = @typeSQL . Use o método fluent WithParameter para atribuir o @sessionId parâmetro ao identificador de sessão passado como um parâmetro e o @type parâmetro ao nome da Message classe.

    QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.sessionId = @sessionId AND c.type = @type")
        .WithParameter("@sessionId", sessionId)
        .WithParameter("@type", nameof(Message));
    
  10. Crie um FeedIterator<Message> usando a query variável e o GetItemQueryIterator<> método.

    FeedIterator<Message> response = _container.GetItemQueryIterator<Message>(query);
    
  11. Use um loop while para iterar todas as páginas de resultados e armazenar os resultados em uma única List<Message> variável chamada output.

    List<Message> output = new();
    while (response.HasMoreResults)
    {
        FeedResponse<Message> results = await response.ReadNextAsync();
        output.AddRange(results);
    }
    
  12. Retornar a output variável como resultado do GetSessionMessagesAsync método.

    return output;
    
  13. Salve o arquivo Serviços/CosmosDbService.cs .

Atualizar uma ou mais sessões ou mensagens

Há cenários em que uma única sessão requer uma atualização ou mais de uma mensagem requer uma atualização. Para o primeiro cenário, use o ReplaceItemAsync método do SDK para substituir um item existente por uma versão modificada. Para o segundo cenário, use o recurso de lote transacional do SDK para modificar várias mensagens em um único lote.

  1. Dentro do UpdateSessionAsync método, remova qualquer código de espaço reservado existente.

    public async Task<Session> UpdateSessionAsync(Session session)
    {
    }
    
  2. Crie uma PartitionKey variável usando session.SessionId como o valor da chave de partição.

    PartitionKey partitionKey = new(session.SessionId);
    
  3. Invoque ReplaceItemAsync a passagem do identificador exclusivo e da chave de partição da nova mensagem. Retorne a resposta como resultado de UpdateSessionAsync.

    return await _container.ReplaceItemAsync(
        item: session,
        id: session.Id,
        partitionKey: partitionKey
    );
    
  4. Dentro do UpsertSessionBatchAsync método, remova qualquer código de espaço reservado existente.

    public async Task UpsertSessionBatchAsync(params dynamic[] messages)
    {
    }
    
  5. validar se todas as mensagens contêm um único identificador de sessão (SessionId) usando a consulta integrada ao idioma (LINQ). Se alguma das mensagens contiver um valor diferente, lance um ArgumentExceptionarquivo .

    if (messages.Select(m => m.SessionId).Distinct().Count() > 1)
    {
        throw new ArgumentException("All items must have the same partition key.");
    }
    
  6. Crie uma nova PartitionKey variável usando a SessionId propriedade da primeira mensagem.

    PartitionKey partitionKey = new(messages.First().SessionId);
    

    Nota

    Lembre-se, você pode assumir com segurança que todas as mensagens têm o mesmo identificador de sessão se o aplicativo tiver sido movido para este ponto no código do método.

  7. Crie uma nova variável chamada batch de tipo TransactionalBatch invocando o CreateTransactionalBatch método da _container variável. Use a variável de chave de partição atual para as operações em lote.

    TransactionalBatch batch = _container.CreateTransactionalBatch(partitionKey);
    

    Importante

    Lembre-se, todas as transações dentro deste lote devem estar na mesma partição lógica.

  8. Itere messages sobre cada mensagem na matriz usando um loop foreach.

    foreach (var message in messages)
    {
    }
    
  9. Dentro do loop foreach, adicione cada mensagem como uma operação de upsert ao lote.

    batch.UpsertItem(
        item: message
    );
    

    Nota

    Upsert diz ao Azure Cosmos DB para determinar, do lado do servidor, se um item deve ser substituído ou atualizado. O Azure Cosmos DB fará essa determinação com a id chave de partição e de cada item.

  10. Fora do loop foreach, invoque de forma assíncrona o ExecuteAsync método do lote para executar todas as operações dentro do lote.

    await batch.ExecuteAsync();
    
  11. Salve o arquivo Serviços/CosmosDbService.cs .

Por fim, combine a funcionalidade de consulta e lote transacional para remover vários itens. Neste exemplo, obtenha o item de sessão e todas as mensagens relacionadas consultando todos os itens com um identificador de sessão específico, independentemente do tipo. Em seguida, crie um lote transacional para excluir todos os itens correspondentes como uma única transação.

  1. Dentro do DeleteSessionAndMessagesAsync método, remova qualquer código de espaço reservado existente.

    public async Task DeleteSessionAndMessagesAsync(string sessionId)
    {
    }
    
  2. Crie uma variável chamada partitionKey de tipo PartitionKey usando o sesionId valor da cadeia de caracteres passado como um parâmetro para esse método.

    PartitionKey partitionKey = new(sessionId);
    
  3. Usando o mesmo sessionId parâmetro de método, crie um QueryDefinition objeto que localize todos os itens que correspondem ao identificador de sessão. Use um parâmetro de consulta para o sessionId e certifique-se de não filtrar a consulta no tipo de item.

    QueryDefinition query = new QueryDefinition("SELECT VALUE c.id FROM c WHERE c.sessionId = @sessionId")
        .WithParameter("@sessionId", sessionId);
    

    Nota

    Se aplicar um type filtro nesta consulta, poderá perder inadvertidamente mensagens ou sessões relacionadas que devem ser removidas em massa como parte desta operação.

  4. Crie um novo FeedIterator<string> uso GetItemQueryIterator e a consulta que você criou.

    FeedIterator<string> response = _container.GetItemQueryIterator<string>(query);
    
  5. Crie um TransactionalBatch nome batch usando CreateTransactionalBatch e a variável de chave de partição.

    TransactionalBatch batch = _container.CreateTransactionalBatch(partitionKey);
    
  6. Crie um loop while para iterar em todas as páginas de resultados. Dentro do loop while, obtenha a próxima página de resultados e use um loop foreach para iterar todos os identificadores de item por página. Dentro do loop foreach, adicione uma operação em lote para excluir o item usando batch.DeleteItem.

    while (response.HasMoreResults)
    {
        FeedResponse<string> results = await response.ReadNextAsync();
        foreach (var itemId in results)
        {
            batch.DeleteItem(
                id: itemId
            );
        }
    }
    
  7. Após o loop while, execute o lote usando batch.ExecuteAsync.

    await batch.ExecuteAsync();
    
  8. Salve o arquivo Serviços/CosmosDbService.cs .

Verifique o seu trabalho

Agora seu aplicativo tem uma implementação completa do Azure OpenAI e do Azure Cosmos DB. Você pode testar o aplicativo de ponta a ponta depurando a solução.

  1. Abra um novo terminal.

  2. Inicie o aplicativo com recarregamentos a quente habilitados usando dotnet watcho .

    dotnet watch run --non-interactive
    
  3. O Visual Studio Code inicia o navegador simples na ferramenta novamente com o aplicativo Web em execução. Na aplicação web, crie uma nova sessão de chat e faça uma pergunta ao assistente de IA. Em seguida, feche o aplicativo Web em execução.

  4. Feche o terminal. Agora, abra um novo terminal.

  5. Inicie o aplicativo mais uma vez com recarregamentos quentes habilitados usando dotnet watcho .

    dotnet watch run --non-interactive
    
  6. O Visual Studio Code inicia o navegador simples na ferramenta mais uma vez com o aplicativo Web em execução. Para essa iteração, observe que suas sessões de bate-papo persistem entre as sessões de depuração.

    Screenshot of the application with both Azure Cosmos DB and Azure OpenAI services implemented.

  7. Feche o terminal.