Una delle sfide più attuali del momento consiste nell’integrare sistemi di Intelligenza Artificiale (Generativa, possibilmente) all’interno delle proprie applicazioni e servizi. Il difficile non è tanto creare un singolo servizio che ne faccia uso (abbiamo già visto con le API di OpenAI che possono bastare poche righe di codice) quanto introdurli all’interno di un proprio flusso di produzione che porti dalle sorgenti di dati direttamente all’implementazione della soluzione da condividere con l’utenza, il tutto con una serie di passaggi strutturati e sotto il totale controllo del progettista.

La sfida è soprattutto quella di applicare questi potenti sistemi di analisi ai propri dati, preservandone la privacy e mettendo al lavoro modelli ad hoc per il nostro business.

Il framework Haystack può rappresentare un’ottima soluzione a questo tipo di necessità.

Conosciamo un Haystack

Haystack è un framework open-source, realizzato da deepset, che permette di costruire facilmente delle soluzioni end-to-end che integrino dei LLM (Large Language Model) o di implementare sistemi di ricerca che spazino su collezioni di documenti.

La sua struttura modulare e la sua mentalità aperta lo rendono pronto a integrarsi con moltissimi sistemi diversi e ad adattarsi a quelli via via prodotti. Questo gli ha permesso, per esempio, di adattarsi rapidamente ai nuovi modelli di IA generativa, che nell’ultimo anno sono stati rilasciati a ritmo elevatissimo.

La seguente figura accoglie gli utenti nella documentazione ufficiale e mette in luce chiaramente come Haystack riesca ad essere un hub tra importanti community di AI, piattaforme e servizi in Cloud e possa convogliare il tutto in nuove applicazioni da poter realizzare in linguaggi estremamente produttivi come Python.

202315-Haystack_img_01

 

Possiamo immaginare un’applicazione costruita con Haystack come una sequenza di passaggi che mette in collegamento direttamente la fonte delle informazioni fino alla soluzione che viene offerta all’utente finale. Ognuno di questi passaggi svolge un compito specifico ed è basato su una sorta di modello comportamentale preimpostato dagli sviluppatori di Haystack. Proprio questo tipo di approccio rappresenta un elemento fortemente distintivo di questa tecnologia.

Gli elementi fondamentali di tale struttura sono essenzialmente due:

  • i nodi: su di essi si basano quelli che sinora abbiamo chiamato genericamente “passaggi”. Rappresentano degli elementi dotati di una specifica funzionalità (recupero dati, analisi, gestione di un prompt o altro ancora) e sono basati su un template di fondo che può essere configurato. È proprio il template che determina la natura del nodo;
  • le pipeline: sono catene di nodi, specializzate nel creare con essi un flusso di lavoro. L’output di un nodo diventerà l’input del successivo. Una volta completata la pipeline potrà essere messa in esecuzione con un metodo run e ciò la renderà operativa.

Il vantaggio di lavorare con tali elementi è essenzialmente un connubio di completezza e flessibilità. Si hanno infatti a disposizione molti tipi di nodi e ciò permette di venire incontro a tutto ciò che può servire in una tipica applicazione basata sull’analisi di documenti. In questo modo, gli sviluppatori sono sempre pronti a mettere in campo la soluzione. Dall’altro, si ha la possibilità di integrare tra loro questi nodi in modo molto veloce, avendo a disposizione tutti i servizi collaterali di cui si ha bisogno. Per esempio, un sistema di logging già pronto.

Le pipeline risolvono da subito il problema della comunicazione tra le componenti. In sistemi di questo genere fortemente basati sulle informazioni ci troviamo spesso a realizzare processi suddivisi in vari step, in ciascuno dei quali viene svolto un task. È fondamentale che i task vengano sequenziati e coordinati con la pipeline, che svolge il ruolo dell’orchestratore. Tutta la gestione del flusso e l’organizzazione del lavoro ricadranno sotto le competenze della pipeline stessa, che trasformerà un insieme di nodi in un vero e proprio processo di produzione che è base del nostro servizio.

Come è fatta una pipeline di Haystack?

Proprio in virtù della sua grande flessibilità, una pipeline può avere le forme più disparate ma qui siamo interessati a comprenderne la struttura generale e a come avviarci velocemente all’implementazione di soluzioni.

Un caso cui potremmo sin da subito essere interessati consiste nel creare dei nodi in grado di:

  • immagazzinare un insieme di nostri documenti (possiamo usare vari formati, dal Word a PDF, a JSON, etc.);
  • renderli disponibili in Haystack;
  • offrire una modalità di interazione che ci permetta di porre domande – un prompt essenzialmente – in cui API di Intelligenza Artificiale Generativa possano offrire risposte alimentandosi con le informazioni contenute nei nostri documenti.

Ebbene, tutto ciò può essere realizzato in davvero poco tempo con Haystask soprattutto perchè i blocchi necessari sono già pronti.

Per il primo utilizzeremo un DocumentStore. Si tratta di un componente specializzato nel rappresentare un archivio di documenti su cui lavorare. Possiamo vederlo, nell’ambito di Haystack, come la cosa più vicina a un database. Esistono molte diverse implementazioni di un DocumentStore. L’InMemoryDocumentStore è il più usato nei test e negli esempi iniziali perchè funziona in memoria e non richiede alcun servizio running. Esistono però anche SQLDocumentStore basato su database relazionali o ElasticsearchDocumentStore basato su ElasticSearch.

L’inserimento dei nostri documenti in un DocumentStore rappresenterà spesso il primo passaggio nel nostro lavoro con Haystack.

Il DocumentStore verrà utilizzato per alimentare la vera a propria componente che ci permetterà di accedere ai dati, il Retriever, e questo rappresenterà il secondo step della catena.

Al Retriever potremo agganciare un componente che permette di svolgere interrogazioni e cercare risposte nei nostri documenti sfruttando API come quelle di OpenAI. Il PromptNode, l’ultimo tassello, dovrà ricevere come parametri di inizializzazione il nome del LLM che vogliamo utilizzare ed una secret key per autorizzare le interrogazioni (a pagamento o ancora in fascia gratuita).

Quale sarà il collegamento tra il Retriever ed il PromptNode? O meglio, quale struttura li riceverà come componenti per fare in modo che ogni attività del prompt venga svolta sui documenti incamerati nel DocumentStore ma resi accessibili dal Retriever? La Pipeline ovviamente!

Esempi di codice per Haystack

Non svilupperemo qui l’esempio per intero anche perchè il portale di Haystack offre dei tutorial molto rapidi, eseguibili subito su Colab e ben connessi con la documentazione, ma per chiarirci le idee e facilitare il nostro approccio a questa tecnologia vedremo i principali passaggi in una sorta di codice Python sintetizzato:


# Haystack offre tutte le classi di cui abbiamo bisogno
from haystack.document_stores import InMemoryDocumentStore
from haystack.nodes import BM25Retriever
from haystack.nodes import PromptNode
from haystack.pipelines import Pipeline

# Creazione di un DocumentStore
document_store = InMemoryDocumentStore(use_bm25=True)

# Importazione dei documenti nel DocumentStore
document_store.write_documents(....)

# Inizializzazione del Retriever con aggancio al DocumentStore
retriever = BM25Retriever(document_store=document_store, ...)

# Inizializzazione del PromptNode (saltiamo alcuni passaggi intermedi)
prompt_node = PromptNode(
    model_name_or_path="text-davinci-003", api_key="sk-....", ...
)

# Creazione della Pipeline e connessione dei componenti

pipe = Pipeline()
pipe.add_node(component=retriever, name="retriever", inputs=["Query"])
pipe.add_node(component=prompt_node, name="prompt_node", inputs=["retriever"])

Per la struttura di questo esempio di ci siamo ispirati ad un tutorial molto interessante presente sulla documentazione ufficiale ma con l’esposizione di questo codice sintetizzato abbiamo voluto evidenziare come il lavoro con Haystack possa essere facilmente impostato con la creazione dei singoli blocchi e l’aggiunta di tali componenti alla Pipeline con il metodo add_component.

Interessante notare come non ci sia in effetti un collegamento diretto tra Retriever e PromptNode ma questo venga composto dalla Pipeline che mostra alle componenti la strada per trovarsi.

Come iniziare subito con Haystack

Con Haystack possiamo quindi, sin da subito e molto velocemente, creare i nostri motori di ricerca e prompt. Per iniziare è sufficiente seguire la documentazione ed eseguire passo passo i tutorial mostrati. Per ognuno di essi verrà subito evidenziato quali componenti sono coinvolte, le finalità ed il tempo di esecuzione (molto spesso di soli 15 minuti!) ed infine si potrà arrivare ad una veloce sperimentazione su Colab con un solo click sull’apposito pulsante.

Haystack in definitiva appare come un framework rapidissimo e semplice da utilizzare con il quale si potranno creare subito i propri motori di ricerca.