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
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Configurare il nome della run di un workflow di GitHub in base al contesto di esecuzione
Utilizzare DeepSeek R1 con Azure AI
Utilizzare Container Queries nominali
Creare una libreria CSS universale: Immagini
Utilizzare gRPC su App Service di Azure
Aprire una finestra di dialogo per selezionare una directory in WPF e .NET 8
Introduzione alle Container Queries
Usare le navigation property in QuickGrid di Blazor
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Effettuare il refresh dei dati di una QuickGrid di Blazor
Creare alias per tipi generici e tuple in C#
Utilizzare l nesting nativo dei CSS