Probabilmente è persino superfluo spiegare i vantaggi e le peculiarità dei Large Language Model (LLM) nell'ambito delle applicazioni moderne. Grazie a OpenAI - o ad Azure OpenAI - possiamo aggiungere funzionalità alle nostre applicazioni che erano assolutamente impensabili fino a un paio di anni fa.
Nel corso di questo script, e dei successivi, vedremo come possiamo integrare questi servizi in un'applicazione ASP.NET Core.
Il primo passo è quello di determinare quale libreria vogliamo usare per interfacciarci con i modelli. Esistono diverse opzioni, dalle librerie native di OpenAI, all'Azure OpenAI SDK. Tuttavia, uno degli approcci più utilizzati è quello di sfruttare Semantic Kernel, una libreria open source di Microsoft che costituisce un livello di astrazione intermedio tra il nostro codice e le API native.
Quali sono i vantaggi?
- Innanzi tutto il fatto che abbiamo un'unica codebase, che possiamo usare per interagire con diversi modelli, spesso cambiando solo alcune righe di codice nella configurazione.
- Inoltre, Semantic Kernel ha una serie di primitive, di oggetti e componenti di alto livello, che implementano già molte delle logiche che altrimenti dovremmo realizzare autonomamente.
- Infine, la libreria tende a essere un po' più stabile in termini di breaking change tra una versione e l'altra, rispetto alle controparti native.
Iniziamo a vedere un esempio base, che poi renderemo via via più complesso nei prossimi script.
Come al solito, il primo passo è quello di aggiungere il pacchetto NuGet al progetto ASP.NET Core:
dotnet add package Microsoft.SemanticKernel
Il modo più semplice per sfruttare il Chat Completion endpoint di OpenAI è tramite un servizio chiamato IChatCompletionService, che possiamo registrare nella dependency injection di ASP.NET Core:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Altro codice qui... builder.Services.Configure<AzureConfig>(builder.Configuration.GetSection("AzureConfig")); builder.Services.AddSingleton<IChatCompletionService>(sp => { AzureConfig options = sp.GetRequiredService<IOptions<AzureConfig>>().Value; return new AzureOpenAIChatCompletionService( options.OpenAi.DeploymentName, options.OpenAi.OpenAiEndpoint, options.OpenAi.OpenAiKey); }); // .. altro codice qui .. }
Nel codice in alto abbiamo registrato IChatCompletionService come singleton, in particolare restituendo un'istanza di AzureOpenAIChatCompletionService, ossia la versione di questo servizio fatta per interfacciarsi con Azure OpenAI. Se invece stessimo utilizzando OpenAI, avremmo dovuto restituire un'istanza di OpenAIChatCompletionService.
Come possiamo notare, in fase di inizializzazione, abbiamo recuperato gli estremi del servizio dalla configurazione, e poi passato DeploymentName, Endpoint e Key al costruttore.
A questo punto siamo pronti per sfruttare questo oggetto in un ChatController:
[Route("api/[controller]")] [ApiController] public class ChatController : ControllerBase { private IChatCompletionService _chatCompletionService; public static ChatHistory ChatHistory { get; } = new ChatHistory( "You are a useful AI who answers questions using rhymes."); public ChatController(IChatCompletionService chatCompletionService) { _chatCompletionService = chatCompletionService; } }
Il controller in alto, oltre che iniettare un'istanza di IChatCompletionService, mantiene anche una ChatHistory (qui per semplicità, in un field static), che conterrà l'elenco dei messaggi scambiati con la AI: i modelli GPT sono infatti stateless, e pertanto l'unico modo con cui possono avere cognizione dei messaggi passati è quello di mandare l'intera history a ogni richiesta.
Quando creiamo questo oggetto, dobbiamo anche specificare il cosidetto System Prompt, ossia le istruzioni di base per il modello GPT su come comportarsi durante la chat.
Ora non ci resta che creare un'action tramite cui inviare e ricevere messaggi:
[HttpPost] public async Task<IActionResult> PostMessage([FromBody] string message) { ChatHistory.AddUserMessage(message); var result = await _chatCompletionService.GetChatMessageContentAsync(ChatHistory); string responseMessage = result.ToString(); ChatHistory.AddAssistantMessage(responseMessage); return Ok(responseMessage); }
Questa action, invocabile in POST, accetta un messaggio da parte dell'utente che, come prima cosa, viene aggiunto alla History. Successivamente invoca GetChatMessageContentAsync passando l'intera history, così che - come detto - il modello abbia conoscenza non solo dell'ultimo messaggio dell'utente, ma anche di tutto il resto della conversazione eseguita fino a quel momento.
Una volta ottenuta la risposta, non dobbiamo far altro che restituirla al client.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Configurare lo startup di applicazioni server e client con .NET Aspire
Gestire i dati con Azure Cosmos DB Data Explorer
Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API
Paginare i risultati con QuickGrid in Blazor
Utilizzare Container Queries nominali
Migliorare la scalabilità delle Azure Function con il Flex Consumption
Autenticarsi in modo sicuro su Azure tramite GitHub Actions
Creazione di plugin per Tailwind CSS: espandere le Funzionalità del Framework
C# 12: Cosa c'è di nuovo e interessante
Utilizzare gRPC su App Service di Azure
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Creare una libreria CSS universale - Rotazione degli elementi
I più letti di oggi
- Simulare Azure Cosmos DB in locale con Docker
- Utilizzare il metodo Index di LINQ per scorrere una lista sapendo anche l'indice dell'elemento
- .NET Conference Italia 2023 - Milano e Online
- .NET Conference Italia 2024 - Milano
- Configurare lo startup di applicazioni server e client con .NET Aspire
- MS03-45: risolti i problemi della patch 824141
- Utilizzare Container Queries nominali