Persistere la ChatHistory di Semantic Kernel in ASP.NET Core Web API per GPT

di Marco De Sanctis, in ASP.NET Core,

Negli scorsi esempi abbiamo introdotto il ruolo della classe ChatHistory di Semantic Kernel per rappresentare una sessione di chat con un modello GPT. Tuttavia, per semplificare gli esempi, ci siamo limitati a utilizzarne una istanza static.

Ovviamente, in un'applicazione reale, la history deve essere persistita su uno storage durevole, sia esso SQL Server, Cosmos DB, file system, e via discorrendo. Nel nostro caso useremo Entity Framework e Azure SQL Database.

Innanzi tutto dobbiamo costruire una classe wrapper, che chiameremo ChatSession, così che possiamo decorare la history con delle informazioni ulteriori, quali per esempio l'ID, il nome dell'utente "proprietario" della chat, il titolo, e quant'altro.

public class ChatSession
{
    public int Id { get; set; }

    public string User { get; set; }

    public string Title { get; set; }

    public ChatHistory History { get; set; }
}

ChatHistory non è ovviamente un tipo nativo di .NET, e pertanto non può essere salvato direttamente. Tuttavia si tratta di un oggetto serializzabile, quindi possiamo semplicemente configurare un value converter nel mapping per poterne gestire il salvataggio su database:

public class ChatDbContext : DbContext
{
    public ChatDbContext (DbContextOptions<ChatDbContext> options)
        : base(options)
    {
    }

    public DbSet<ChatSession> ChatSession { get; set; } = default!;

    override protected void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ChatSession>().ToTable("ChatSession")
            .Property(x => x.History)
            .HasColumnType("varchar(max)")
            .HasConversion(
                h => JsonSerializer.Serialize(h, new JsonSerializerOptions() { }),
                s => JsonSerializer.Deserialize<ChatHistory>(s, new JsonSerializerOptions() { }));
    }
}

Nell'esempio in alto, abbiamo specificato che la proprietà History deve essere salvata su una colonna di tipo varchar(max), effettuandone la serializzazione in JSON in fase di scrittura. Per contro, durante la lettura, sarà sufficiente deserializzare il JSON letto.

A questo punto, possiamo modificare il nostro ChatController, aggiungendo un endpoint per creare una nuova chat:

[HttpPost]
public async Task<IActionResult> CreateChat()
{
    var session = new ChatSession()
    {
        User = "some user", // dovrebbe essere letto dallo User corrente
        Title = "Placeholder Title", // sarebbe da generare tramite GPT una dopo la prima risposta
        History = new ChatHistory("You are a useful AI who answers questions using rhymes.")
    };

    _dbContext.ChatSession.Add(session);

    await _dbContext.SaveChangesAsync();

    return Ok(session.Id);
}

Il codice è davvero elementare, e si limita a creare una nuova istanza di ChatSession, salvarla tramite DbContext e restituirne l'ID al chiamante.

Questo ID dovrà poi essere passato come parametro all'endpoint PostMessage, che abbiamo visto nello scorso script, e che quindi dovremo modificare come nell'esempio in basso:

[HttpPost("{sessionId}/messages")]
public async IAsyncEnumerable<string> PostMessage(int sessionId, [FromBody] string message)
{
    var session = await _dbContext.ChatSession.FindAsync(sessionId);

    // session null, return 404
    if (session == null)
    {
        Response.StatusCode = StatusCodes.Status404NotFound;
        yield break;
    }

    // set the entity as modified
    session.History.AddUserMessage(message);

    var result = _chatCompletionService.GetStreamingChatMessageContentsAsync(session.History);

    string responseMessage = string.Empty;

    await foreach (var messageContent in result)
    {
        responseMessage += messageContent.Content;
        yield return messageContent.Content;
    }

    session.History.AddAssistantMessage(responseMessage);

    _dbContext.Entry(session).State = EntityState.Modified;
    await _dbContext.SaveChangesAsync();
}

Abbiamo quindi aggiunto il parametro sessionId al path e, come primo passo, recuperiamo la ChatSession corrispondente dal database, restituendo 404 in caso non esista. Ovviamente in un caso reale, dovremo anche verificare che l'utente corretto sia il "proprietario" di questa sessione di chat.

Poi gestiamo la richiesta a GPT come abbiamo visto in precedenza, e infine, dopo aver aggiunto il messaggio di risposta alla history, andiamo ad aggiornare il dato su database.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi