Creazione di un'esperienza di chat personalizzata in tempo reale con la piattaforma GenAI di
Introduzione
Immagina di creare un'esperienza simile a ChatGPT che puoi personalizzare completamente in base alle tue esigenze. Con la nuova piattaforma GenAI di DigitalOcean, che è stata appena lanciata in anteprima pubblica alla conferenza Deploy di quest'anno, puoi fare esattamente questo.
Ciò che è particolarmente interessante della piattaforma GenAI è la sua flessibilità. Per molti casi d'uso, non è necessario scrivere alcun codice: puoi semplicemente prendere uno snippet JavaScript dal pannello di controllo di DigitalOcean e incorporare un chatbot direttamente nella tua applicazione. Ma come sviluppatore, ciò che mi entusiasma davvero è la possibilità di utilizzare le API per creare qualsiasi tipo di esperienza io voglia.
In questo post, illustrerò la creazione di un'applicazione di chat personalizzata che imita l'esperienza di ChatGPT. Questo esempio illustra tre componenti chiave per la creazione di un'applicazione di chat affidabile:
- Autenticazione API - Connessione sicura alla piattaforma GenAI
- Comunicazione in tempo reale con WebSockets - Impostazione della comunicazione bidirezionale
- Servizio di chat ed elaborazione dei messaggi - Gestione delle conversazioni e delle risposte in streaming
La parte migliore è che una volta compresi tutti i pezzi, puoi personalizzarlo per farlo funzionare nel modo desiderato.
Comprensione della piattaforma GenAI
La piattaforma DigitalOcean GenAI è una soluzione all-in-one per la creazione e la scalabilità rapida di agenti di intelligenza artificiale. Fornisce l'accesso a modelli di intelligenza artificiale generativa all'avanguardia di organizzazioni come Anthropic, Meta e Mistral AI, con l'obiettivo di rendere lo sviluppo dell'intelligenza artificiale accessibile a tutti.
Ecco cosa lo rende speciale:
- Distribuzione immediata: crea e distribuisci agenti di intelligenza artificiale in pochi clic
- Modelli di base: accedi ai principali modelli di Anthropic, Meta e Mistral AI
- Integrazione flessibile: scegli tra l'incorporamento del chatbot senza codice o l'accesso completo all'API
- Sicurezza integrata: guardrail personalizzabili per filtrare i contenuti dannosi
- Conveniente: prezzi trasparenti per una scalabilità sicura
Funzionalità avanzate:
- Generazione aumentata da recupero (RAG) per l'integrazione della knowledge base
- Routing delle funzioni per funzionalità personalizzate
- Personalizzazione degli agenti e guardrail
La piattaforma si occupa del lavoro dell'infrastruttura, permettendoti di concentrarti sulla creazione della tua applicazione indipendentemente dal tuo livello di esperienza con l'intelligenza artificiale.
Compatibilità API OpenAI
Uno degli aspetti più potenti della piattaforma GenAI è la sua compatibilità API con OpenAI. Ciò significa che puoi utilizzare qualsiasi SDK o libreria compatibile con OpenAI per interagire con la piattaforma. Se hai già lavorato con OpenAI, hai già tutta l'esperienza di cui avrai bisogno per utilizzare la piattaforma GenAI e, se hai appena iniziato, puoi sfruttare l'ampio ecosistema di strumenti e librerie creati per OpenAI.
Ecco cosa significa questa compatibilità per gli sviluppatori:
- Usa il tuo SDK OpenAI preferito (Python, Node.js, ecc.)
- Sfrutta il codice e gli esempi esistenti
- Accedi a un ricco ecosistema di strumenti e librerie
- Curva di apprendimento minima se hai familiarità con OpenAI
- Percorso di migrazione semplificato per le applicazioni esistenti
Ad esempio, nella nostra applicazione di chat, utilizzeremo l'SDK di Node.js OpenAI:
const { OpenAI } = require('openai');
const client = new OpenAI({
baseURL: agent_endpoint,
apiKey: access_token,
});
const response = await client.chat.completions.create({
model: "n/a", // Model is handled by the GenAI Platform
messages: conversationHistory,
stream: true,
});
Questa compatibilità semplifica notevolmente lo sviluppo e l'adozione. Invece di imparare una nuova API o riscrivere il codice esistente, puoi concentrarti sulla creazione di funzionalità importanti per i tuoi utenti.
Creazione dell'applicazione di chat
Prima di addentrarci nei dettagli tecnici, vale la pena notare che tutto ciò che stiamo per esplorare è disponibile nell'applicazione di esempio su GitHub. È possibile clonare il repository, eseguirlo localmente e vedere questi componenti in azione. In questo modo è più facile seguire e sperimentare il codice da soli.
Permettetemi di immergermi nei tre componenti chiave che fanno funzionare questa applicazione di chat.
Autenticazione API
Quando si lavora con le API, l'autenticazione sicura è essenziale. La piattaforma GenAI utilizza le chiavi di accesso per autenticare le tue richieste, fornendo un modo semplice e sicuro per interagire con l'API senza inviare credenziali sensibili a ogni chiamata.
Dovrai:
- Crea una chiave di accesso nel pannello di controllo DigitalOcean
- Archivialo in modo sicuro nella tua applicazione
- Usalo per autenticare le tue richieste API
Ecco come possiamo implementarlo nella nostra applicazione di chat:
const { OpenAI } = require('openai');
class TokenService {
constructor() {
this.AGENT_ENDPOINT = process.env.AGENT_ENDPOINT + "/api/v1/";
this.AGENT_KEY = process.env.AGENT_KEY;
if (!this.AGENT_ENDPOINT || !this.AGENT_KEY) {
throw new Error('Missing required configuration');
}
}
getClient() {
return new OpenAI({
baseURL: this.AGENT_ENDPOINT,
apiKey: this.AGENT_KEY,
});
}
}
Questo approccio semplificato:
- Utilizza un'unica chiave di accesso per l'autenticazione
- Richiede una configurazione e una manutenzione minime
- Segue le best practice di sicurezza
- Funziona perfettamente con l'SDK OpenAI
Quando effettuiamo chiamate API alla piattaforma GenAI, possiamo semplicemente utilizzare il client per interagire con il nostro agente:
const client = tokenService.getClient();
const response = await client.chat.completions.create({
model: "n/a", // Model is handled by the GenAI Platform
messages: conversationHistory,
stream: true,
});
Comunicazione in tempo reale con WebSocket
WebSocket è un protocollo di comunicazione che fornisce canali di comunicazione full-duplex su una singola connessione TCP. A differenza delle tradizionali richieste HTTP, i WebSocket mantengono una connessione persistente tra il client e il server, consentendo:
- Comunicazione bidirezionale in tempo reale
- Latenza inferiore (non sono necessari nuovi handshake dopo la connessione)
- Streaming efficiente dei dati
- Migliore utilizzo delle risorse
Per questa app di chat, i WebSocket sono l'ideale perché ci permettono di:
- Trasmetti in streaming le risposte dell'intelligenza artificiale in tempo reale man mano che vengono generate
- Mantieni lo stato di connessione per ogni sessione di chat
- Gestire gli scenari di riconnessione in modo corretto
- Fornisci un feedback immediato sullo stato della connessione
Implementazione lato server
Qui abbiamo utilizzato il pacchetto ws
, un'implementazione client e server WebSocket popolare e leggera per Node.js.
Ecco come possiamo configurare il server WebSocket:
const { WebSocketServer } = require('ws');
const WS_PING_INTERVAL = 30000; // 30 seconds
// Create WebSocket server with no direct HTTP server attachment
const wss = new WebSocketServer({ noServer: true });
// Handle WebSocket connections
wss.on('connection', async (ws, req) => {
// Extract chat session ID from URL parameters
const chatId = new URL(req.url, 'ws://localhost').searchParams.get('chatId');
if (!chatId) {
ws.close(1008, 'Chat ID is required');
return;
}
try {
// Store connection in chat service
chatService.addConnection(chatId, ws);
// Implement connection health checks
const pingInterval = setInterval(() => {
if (ws.readyState === ws.OPEN) ws.ping();
}, WS_PING_INTERVAL);
// Clean up on connection close
ws.on('close', () => {
clearInterval(pingInterval);
chatService.removeConnection(chatId);
});
} catch (error) {
ws.close(1011, 'Failed to initialize connection');
}
});
// Set up WebSocket upgrade handling
server.on('upgrade', (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
});
Aspetti chiave dell'implementazione del server:
- Gestisce manualmente gli aggiornamenti WebSocket con
noServer: true,
dandoci un maggiore controllo sul processo di connessione e permettendoci di convalidare i dati della sessione prima di accettare le connessioni - Implementa il ping/pong per il monitoraggio dello stato della connessione
- Gestisce le connessioni per sessione di chat
- Gestisce la pulizia alla chiusura della connessione
Gestione delle connessioni
Gestire correttamente le connessioni WebSocket è fondamentale per un'applicazione di chat affidabile. Dobbiamo tenere traccia delle connessioni attive, gestire le disconnessioni in modo corretto e ripulire le risorse quando non sono più necessarie. Ecco come possiamo mantenere attive le connessioni nel servizio di chat:
class ChatService {
constructor() {
this.activeConnections = new Map();
this.connectionTimeouts = new Map();
this.CLEANUP_TIMEOUT = 5 * 60 * 1000; // 5 minutes
}
addConnection(id, ws) {
// Remove any existing connection
if (this.activeConnections.has(id)) {
this.removeConnection(id);
}
// Store new connection
this.activeConnections.set(id, ws);
// Set up cleanup timeout
ws.on('close', () => {
this.connectionTimeouts.set(id, setTimeout(() => {
this.conversations.delete(id);
this.connectionTimeouts.delete(id);
}, this.CLEANUP_TIMEOUT));
});
}
removeConnection(id) {
const connection = this.activeConnections.get(id);
if (connection?.readyState === 1) {
connection.send(JSON.stringify({ content: 'Connection closed' }));
}
this.activeConnections.delete(id);
}
}
Analizziamo cosa sta succedendo qui:
Archiviazione delle connessioni:
- Utilizziamo una
mappa
per memorizzare le connessioni WebSocket attive, con chiave in base all'ID di sessione
- Utilizziamo una
- Un'altra
mappa
tiene traccia dei timeout di pulizia per le sessioni disconnesse - Il
CLEANUP_TIMEOUT
di 5 minuti offre agli utenti il tempo di riconnettersi senza perdere il contesto Aggiunta di connessioni:
- Prima di aggiungere una nuova connessione, ripuliamo quella esistente per quella sessione
- In questo modo si evitano perdite di risorse e si garantisce una connessione attiva per sessione
- Ogni connessione è associata a un ID sessione di chat univoco
Gestione della pulizia:
- Quando una connessione si chiude, viene avviato un timer di pulizia
- Se l'utente non si riconnette entro 5 minuti, rimuoviamo la cronologia delle conversazioni
- In questo modo si bilancia il mantenimento del contesto per la riconnessione degli utenti con la liberazione delle risorse
Rimozione della connessione:
- Avvisiamo il cliente prima di chiudere la connessione se è ancora attiva
- Tutte le risorse di connessione vengono pulite correttamente per evitare perdite di memoria
Questa attenta gestione delle connessioni aiuta a garantire che la nostra applicazione di chat rimanga stabile ed efficiente, anche con molti utenti simultanei.
Implementazione lato client
L'implementazione di WebSocket lato client deve gestire diversi aspetti importanti della comunicazione in tempo reale:
- Stabilire e mantenere la connessione
- Gestione delle disconnessioni senza problemi
- Elaborazione dei messaggi in arrivo
- Fornire un feedback visivo agli utenti
Ecco come possiamo implementare queste funzionalità:
const MAX_RECONNECT_ATTEMPTS = 5;
const RECONNECT_DELAY = 1000; // Start with 1 second
let reconnectAttempts = 0;
let ws = null;
function initializeWebSocket() {
if (ws) {
ws.close();
ws = null;
}
// Determine WebSocket protocol based on page protocol
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
ws = new WebSocket(`${protocol}//${window.location.host}?chatId=${window.chatId}`);
// Update UI connection status
updateConnectionStatus('connecting', 'Connecting...');
// Handle incoming messages
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.content === 'Connected to chat stream') {
// Initial connection successful
updateConnectionStatus('connected', 'Connected');
reconnectAttempts = 0;
} else if (data.content) {
// Process chat message
addMessage(data.content, false);
} else if (data.error) {
// Handle error messages
console.error('WebSocket Error:', data.error);
addMessage(`Error: ${data.error}`, false);
}
};
// Implement reconnection with exponential backoff
ws.onclose = (event) => {
updateConnectionStatus('disconnected', 'Connection lost');
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
// Calculate delay with exponential backoff
const delay = Math.min(RECONNECT_DELAY * Math.pow(2, reconnectAttempts), 30000);
updateConnectionStatus('reconnecting',
`Reconnecting in ${delay/1000} seconds...`);
setTimeout(initializeWebSocket, delay);
reconnectAttempts++;
} else {
// Max retries reached
updateConnectionStatus('disconnected',
'Connection failed. Please refresh the page.');
}
};
// Handle connection errors
ws.onerror = (error) => {
console.error('WebSocket error:', error);
updateConnectionStatus('error', 'Connection error');
};
}
Analizziamo le caratteristiche principali:
Gestione delle connessioni:
- Gestisce sia le connessioni sicure (
wss:
) che quelle non sicure (ws:
)
- Gestisce sia le connessioni sicure (
- Chiude le connessioni esistenti prima di crearne di nuove
- Include l'ID della sessione di chat nell'URL di connessione
- Mantiene lo stato di connessione per l'interfaccia utente
Elaborazione dei messaggi:
- Analizza i messaggi JSON in arrivo
- Gestisce diversi tipi di messaggi (stato della connessione, contenuto della chat, errori)
- Aggiorna l'interfaccia utente con nuovi messaggi
- Registra gli errori per il debug
Riconnessione intelligente:
- Implementa il backoff esponenziale per prevenire il sovraccarico del server
- Inizia con un ritardo di 1 secondo, raddoppiando ad ogni tentativo
- Limita il ritardo massimo a 30 secondi
- Limita il totale dei tentativi di riconnessione a 5
- Fornisce un feedback chiaro durante i tentativi di riconnessione
Gestione degli errori:
- Rileva e registra gli errori WebSocket
- Aggiorna l'interfaccia utente per riflettere lo stato della connessione
- Fornisce messaggi di errore intuitivi
- Suggerisce azioni in caso di interruzione permanente della connessione
L'implementazione del client è caratterizzata da:
- Rilevamento del protocollo per connessioni sicure/non sicure
- Aggiornamenti visivi dello stato della connessione
- Riconnessione automatica con backoff esponenziale
- Numero massimo di tentativi per evitare cicli infiniti
- Gestione degli errori e feedback degli utenti
Gestione dello stato della connessione
Costruire la fiducia degli utenti in un'applicazione in tempo reale è fondamentale e un modo semplice per raggiungere questo obiettivo è mostrare lo stato della connessione. Poiché teniamo già traccia dello stato della connessione nell'implementazione di WebSocket, possiamo facilmente mostrarlo agli utenti:
function updateConnectionStatus(status, message) {
const statusDot = document.querySelector('.status-dot');
const statusText = document.querySelector('.status-text');
// Update visual indicators
statusDot.className = `status-dot ${status}`;
statusText.textContent = message;
// Disable/enable chat input based on connection status
const chatInput = document.getElementById('message-input');
const sendButton = document.querySelector('button[type="submit"]');
const isConnected = status === 'connected';
chatInput.disabled = !isConnected;
sendButton.disabled = !isConnected;
}
Questa semplice aggiunta fornisce agli utenti un feedback immediato sullo stato della connessione e disabilita automaticamente l'input quando la connessione viene persa. È un piccolo tocco che rende l'applicazione più raffinata e affidabile.
Servizio di chat ed elaborazione dei messaggi
Quando si crea un'interfaccia di chat con la piattaforma GenAI, una delle sfide interessanti è la gestione delle risposte in streaming. Il modello di intelligenza artificiale genera testo token per token, il che solleva alcune domande interessanti:
- Come posso trasformare questi piccoli pezzi in un'esperienza di lettura fluida?
- Come posso mantenere il contesto di conversazione per interazioni significative?
- Come posso gestire gli errori che potrebbero verificarsi durante lo streaming?
- Qual è il modo migliore per mantenere un'interfaccia utente reattiva?
Ecco un approccio che possiamo adottare per affrontare queste sfide. In questo caso verrà implementata una strategia di buffering che raccoglie i token in blocchi significativi prima di inviarli al client, mantenendo al contempo la cronologia delle conversazioni per le risposte sensibili al contesto.
Approccio all'elaborazione dei messaggi
Invece di inviare ogni token non appena arriva, possiamo bufferizzare il contenuto e inviarlo nei punti di interruzione naturali. Ecco l'implementazione:
async processStream(stream, ws, sessionId) {
const conversationHistory = this.conversations.get(sessionId);
let currentChunk = [];
let fullResponse = [];
for await (const chunk of stream) {
if (chunk.choices[0]?.delta?.content) {
const content = chunk.choices[0].delta.content;
currentChunk.push(content);
// Send chunk at natural breaks (punctuation or word count)
if (this.shouldSendChunk(content, currentChunk)) {
const message = currentChunk.join('');
fullResponse.push(message);
ws.send(JSON.stringify({ content: message }));
currentChunk = [];
}
}
}
// Store the complete response in conversation history
if (fullResponse.length > 0) {
conversationHistory.push({
role: 'assistant',
content: fullResponse.join('')
});
}
}
Diamo un'occhiata alle caratteristiche principali:
Chunking intelligente:
- Raccoglie i token in blocchi significativi
- Invia aggiornamenti nei punti di interruzione naturali (punteggiatura o conteggio delle parole)
- Crea un'esperienza di lettura più fluida per gli utenti
Cronologia delle conversazioni:
- Mantiene in memoria il contesto per ogni sessione di chat
- Memorizza sia i messaggi dell'utente che le risposte dell'intelligenza artificiale durante la sessione
- Consente interazioni più coerenti e contestuali durante la connessione
- Ogni sessione ha una propria cronologia indipendente, che viene cancellata dopo 5 minuti di inattività
Gestione degli errori:
- Monitora lo stato della connessione durante lo streaming
- Gestisce con grazia le disconnessioni
- Fornisce messaggi di errore chiari agli utenti
- Previene le perdite di risorse
Gestione delle risorse:
- Elabora in modo efficiente le risposte in streaming
- Pulisce le risorse quando le connessioni si chiudono
- Mantiene contesti separati per ogni sessione
- Bilancia l'utilizzo della memoria con l'esperienza utente
Questa implementazione crea un'esperienza di chat affidabile che:
- Sembra reattivo e naturale per gli utenti
- Mantiene il contesto di conversazione in modo efficace
- Gestisce gli errori con grazia
- Scalabilità ottimale con più utenti
Naturalmente, questo è solo un modo per affrontare il problema. È possibile trovare altre strategie che si adattano meglio alle proprie esigenze specifiche, ad esempio diverse strategie di suddivisione in blocchi, approcci alternativi alla gestione degli errori o altri modi per gestire lo stato della conversazione.
Esecuzione dell'applicazione
Leggere sulla creazione di un'applicazione di chat è una cosa, ma non c'è niente di meglio che sporcarsi le mani e provarla da soli. Ho semplificato l'inizio con pochi semplici passaggi:
Clonare il repository e installare le dipendenze:
git clone https://github.com/wadewegner/do-genai-customchatbot.git cd do-genai-customchatbot npm install
Crea e configura il tuo agente:
- Segui la guida introduttiva della piattaforma GenAI per creare il tuo agente
- Rendi pubblico l'endpoint dell'agente seguendo queste istruzioni
- Copia l'ID, la chiave e l'URL dell'endpoint dell'agente per il passaggio successivo
Configura le variabili di ambiente in
.env
:API_BASE=https://cluster-api.do-ai.run/v1 AGENT_ID=your-agent-id AGENT_KEY=your-agent-key AGENT_ENDPOINT=your-agent-endpoint SESSION_SECRET=your-session-secret
Avviare il server:
npm start
Ora puoi aprire il browser per http://localhost:3000
e iniziare a chattare! Questa è la tua occasione per sperimentare l'applicazione e personalizzarla in base alle tue esigenze. Prova a modificare l'interfaccia della chat, a regolare l'elaborazione dei messaggi o ad aggiungere nuove funzionalità: le possibilità sono infinite con la flessibilità della piattaforma GenAI.
Quando sei pronto a condividere la tua creazione con il mondo, l'App Platform di DigitalOcean è un ottimo posto per distribuire ed eseguire pubblicamente la tua applicazione.
Oltre il chat bot
Sebbene questo esempio dimostri un'interfaccia di chat, le funzionalità della piattaforma GenAI si estendono ben oltre questo caso d'uso. Potresti:
- Crea assistenti di knowledge base personalizzati utilizzando le pipeline RAG
- Crea strumenti per la generazione di contenuti con modelli personalizzati
- Implementare sistemi di analisi e generazione del codice
- Sviluppare servizi di traduzione linguistica
- Aggiungi funzionalità di intelligenza artificiale alle applicazioni esistenti
Ciò che mi entusiasma di più della creazione con la piattaforma GenAI è il modo in cui ti consente di concentrarti sugli aspetti creativi dello sviluppo dell'intelligenza artificiale. L'applicazione di chat che abbiamo creato qui è solo l'ultimo grattacapo: è una base su cui puoi costruire, sperimentare e creare completamente tua. Spero che questa procedura dettagliata abbia suscitato alcune idee per i tuoi progetti. Ora tocca a te prendere questi modelli e creare qualcosa di straordinario!