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
Utilizzare Containers in .NET Aspire
Modificare lo stile in una QuickGrid Blazor
Integrare OpenAI tramite Aspire
Dallo sviluppo locale ad Azure con .NET Aspire
Self-healing degli unit test con Copilot in GitHub
Supportare la sessione affinity di Azure App Service con Application Gateway
Esporre tool MCP con Azure Functions
Mischiare codice server side e client side in una query LINQ con Entity Framework
Scrivere selettori CSS più semplici ed efficienti con :is()
Gestire pubblicazione Kubernetes tramite .NET Aspire
Raggruppare risorse in .NET Aspire
Ridurre il reflow cambiando il CSS
I più letti di oggi
- Effettuare il multi-checkout in linea nelle pipeline di Azure DevOps
- Sfruttare una CDN con i bundle di ASP.NET
- Alleggerire le applicazioni WPF sfruttando gli oggetti Freezable
- Le DirectInk API nella Universal Windows Platform
- Gli oggetti CallOut di Expression Blend 4.0
- Effetto turnstile su tutte le pagine con il Windows Phone Toolkit
- Esaminare documenti XML con namespace utilizzando LINQ to XML
- Inserire le news di Punto Informatico nel proprio sito


