Creare un'app GenAI end-to-end con Ruby e Milvus
L'introduzione di framework GenAI specializzati come LangChain ci ha permesso di creare applicazioni IA sofisticate in modo rapido e semplice sfruttando potenti modelli linguistici di grandi dimensioni (LLMs) come ChatGPT e LLaMA. LangChain, ad esempio, ci consente di creare una potente applicazione di Retrieval Augmented Generation (RAG) in poche righe di codice senza richiedere una profonda conoscenza teorica dell'IA.
Questa tendenza significa che, al giorno d'oggi, i data scientist e gli ingegneri di machine learning non sono più gli unici in grado di creare applicazioni GenAI. Gli ingegneri full-stack o gli sviluppatori software possono ora creare app GenAI usando LangChain.
Tuttavia, questi framework GenAI sono in genere scritti in Python, e sappiamo che alcuni ingegneri full-stack e sviluppatori software usano raramente Python nei loro progetti. Pertanto, c'è bisogno di estensioni di questi framework GenAI in altri linguaggi di programmazione, affinché questi ingegneri full-stack possano sfruttare potenti LLM per creare applicazioni GenAI nei loro progetti software.
In un recente intervento, Andrei Bondarev, Solution Architect presso Source Labs LLC, ha presentato un'estensione Ruby di LangChain chiamata LangChain.rb per rendere più semplice agli ingegneri full-stack creare applicazioni GenAI nei loro progetti software.
Guarda questo intervento su Youtube
Ma prima di discutere come creare un'applicazione GenAI con Ruby, esploriamo brevemente il funzionamento interno della Retrieval Augmented Generation (RAG), un caso d'uso popolare della GenAI.
Come funziona la RAG
Non è un segreto che i dati siano la miniera d'oro di qualsiasi app GenAI. Servono come fonte di informazioni usata dalla GenAI per generare risposte fattuali e accurate. Di tutti i dati attualmente disponibili, l'80% può essere classificato come dati non strutturati.
I dati non strutturati si riferiscono a dati che non sono conformi a un formato di dati predefinito. Questo tipo di dati include immagini, testo, suoni e video. Affinché le macchine possano dare senso a questi tipi di dati non strutturati, dobbiamo trasformarli in un formato numerico chiamato embedding vettoriali.
Concetti fondamentali degli embedding vettoriali
Un embedding consiste in un vettore n-dimensionale, dove n si riferisce alla dimensionalità dell'embedding. La dimensionalità dipende dal modello di deep learning che trasforma i dati in un embedding. Un embedding porta con sé il significato semantico dei dati che rappresenta.
Possiamo usare modelli di deep learning per trasformare varie modalità di dati in embedding. Ad esempio, se abbiamo dati testuali, possiamo usare modelli OpenAI o Sentence Transformer per trasformare questi dati testuali in un embedding. Se abbiamo dati immagine, possiamo usare modelli pre-addestrati specializzati capaci di estrarre caratteristiche dalle immagini, come Vision Transformer, come modello di embedding.
Poiché un embedding porta con sé il significato semantico dei dati che rappresenta, possiamo calcolare la similarità di quell'embedding con altri embedding nel cosiddetto spazio vettoriale. Gli embedding con significati semantici simili saranno collocati vicini tra loro nello spazio vettoriale, come puoi vedere nella visualizzazione qui sotto:
Vector embeddings in a vector space.png
Embedding di parole correlate nello spazio vettoriale
Come mostrato nell'immagine sopra, gli embedding di "queen" e "king" sono posizionati vicini tra loro, e lo stesso vale per "woman" e "man". Anche la Distanza euclidea tra "queen-king" e "woman-man" sarebbe approssimativamente la stessa, poiché hanno significati simili.
Questo concetto è alla base di un'operazione di ricerca vettoriale, in cui calcoliamo la similarità tra un embedding e diversi embedding.
Il ruolo del database vettoriale nella ricerca vettoriale e nelle applicazioni RAG
Implementare una ricerca vettoriale è semplice se abbiamo a che fare solo con una piccola quantità di embedding. Tuttavia, nei casi reali abbiamo comunemente a che fare con migliaia, milioni o persino miliardi di embedding Pertanto, abbiamo bisogno di una soluzione per archiviare gli embedding in modo efficiente ed eseguire ricerche vettoriali rapide su di essi.
È qui che entra in gioco un database vettoriale come Milvus. Milvus è un database vettoriale open-source in cui puoi archiviare enormi quantità di embedding ed eseguire ricerche vettoriali su questi embedding in una frazione di secondo.
Il workflow di trasformazione dei dati non strutturati in embedding e della loro archiviazione in Milvus
Il workflow di trasformazione dei dati non strutturati in embedding e della loro archiviazione in Milvus
I database vettoriali svolgono anche un ruolo cruciale nelle app GenAI popolari come RAG. Come forse già sai, l'obiettivo principale di RAG è migliorare l'accuratezza delle risposte generate da LLM come ChatGPT e LLaMA fornendo loro un contesto che possa essere utile per rispondere alla query dell'utente.
In un'applicazione RAG, una volta ricevuta la query dell'utente, questa viene trasformata in un embedding utilizzando un modello di embedding. Successivamente, viene eseguita una ricerca vettoriale, in cui l'embedding della query viene confrontato con gli embedding di contesto archiviati all'interno del database vettoriale come Milvus. I dati di contesto più simili vengono quindi recuperati e passati insieme alla query all'LLM. L'LLM può quindi utilizzare le informazioni del contesto per generare una risposta contestualizzata alla query dell'utente.
RAG
Workflow RAG
LangChain come popolare framework GenAI
LangChain è un framework che semplifica la creazione e lo sviluppo di app GenAI utilizzando modelli LLM all'avanguardia. Si integra facilmente con provider LLM popolari come OpenAI, Anthropic e Google, così come con provider di database vettoriali come Zilliz.
LangChain offre anche astrazioni flessibili per lo sviluppo di applicazioni IA basate su LLM, rendendo semplice per data scientist e sviluppatori software costruire sistemi sofisticati come RAG con poche righe di codice.
Ad esempio, supponiamo di voler riassumere il contenuto di questo post del blog usando GPT-4. Possiamo completare questa attività con il seguente codice:
import os
from langchain.chains.summarize import load_summarize_chain
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "True"
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
chain = load_summarize_chain(llm, chain_type="stuff")
result = chain.invoke(docs)
"""
Output: L'articolo discute il concetto di agenti autonomi potenziati dagli LLM, con un focus sui componenti di pianificazione, memoria e uso degli strumenti. Include casi di studio ed esempi proof-of-concept, oltre a sfide e riferimenti a ricerche correlate. L'autore sottolinea il potenziale degli LLM nella creazione di potenti agenti per la risoluzione dei problemi, evidenziando al contempo limitazioni come la lunghezza finita del contesto e l'affidabilità delle interfacce in linguaggio naturale.
"""
Come puoi vedere, con all'incirca solo 10 righe di codice, possiamo sfruttare il modello GPT-4 per riassumere accuratamente un lungo post di blog.
Con LangChain, puoi anche eseguire attività più complesse. Ad esempio, puoi suddividere un lungo testo da un documento PDF in chunk, trasformare ogni chunk in un embedding utilizzando un modello di embedding a tua scelta, archiviare l'embedding di quei chunk all'interno di un database vettoriale ed eseguire RAG in seguito.
import getpass
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.milvus import Milvus
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain.embeddings import SentenceTransformerEmbeddings
from langchain_openai import ChatOpenAI
# Set the API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
llm = ChatOpenAI(model="gpt-4")
# Text to be processed
texts = "This is a very long text.....:"
# Split text into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) # Example chunk size and overlap
chunk_texts = text_splitter.split_text(texts)
# Instantiate embedding model
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
# Store embeddings of chunks inside Milvus DB
vector_db = Milvus.from_texts(texts=chunk_texts, embedding=embeddings, collection_name="rag_milvus")
retriever = vector_db.as_retriever()
# Define function to format documents
def format_docs(docs):
return "\\n\\n".join(doc.page_content for doc in docs)
# Perform RAG (Retrieval Augmented Generation)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| llm
| StrOutputParser()
)
# Example question
question = "What is the main idea of the text?"
# Execute RAG chain and print the result
for chunk in rag_chain.stream(question):
print(chunk, end="", flush=True)
Oltre alle demo fornite sopra, LangChain offre un'ampia gamma di funzionalità. Ad esempio, integra LLM e API da fonti esterne come app meteo, calcolatrici o Google Search. Questo approccio consente agli LLM di utilizzare informazioni provenienti da queste fonti per generare risposte più accurate e contestuali. Vedremo l'implementazione dettagliata di questo approccio nelle prossime sezioni.
Puoi anche esplorare tutte le funzionalità di LangChain sulla loro pagina di documentazione.
Sviluppo di app GenAI con Ruby e Milvus
Python è diventato il linguaggio di programmazione de facto per i framework di ricerca e sviluppo nell'AI, incluso LangChain. Nel frattempo, Ruby rimane popolare per lo sviluppo rapido di software e applicazioni web.
Tuttavia, come hai visto nella sezione precedente, l'introduzione di LangChain apre possibilità per gli sviluppatori software di integrare la potenza degli LLM nelle loro app web senza conoscere le teorie dettagliate degli LLM e dell'AI in generale.
Questa capacità ha creato una crescente domanda di estendere questi framework di sviluppo GenAI ad altri linguaggi più familiari agli sviluppatori full-stack, come Ruby. Per soddisfare questa domanda, Andrei Bondarev ha introdotto LangChain.rb, che è l'estensione Ruby del framework LangChain originale.
LangChain.rb consente agli sviluppatori full-stack Ruby di creare app web basate su LLM senza il fastidio di incorporare più linguaggi di programmazione nei loro progetti. Con esso, puoi integrare facilmente database vettoriali popolari, LLM e risorse esterne nelle tue app web LLM.
LangChain.rb ha le stesse funzionalità generali del LangChain originale, come:
Gestione dei prompt: crea, carica e salva modelli di prompt per gli LLM di tua scelta
Validazione della lunghezza del contesto: valida la lunghezza del contesto degli input in base alla lunghezza del contesto degli LLM e dei modelli di embedding di tua scelta
Suddivisione dei dati in blocchi: suddivide i dati in blocchi con regole predefinite prima di inserirli nei database vettoriali di tua scelta
Memoria della conversazione: persiste una chat con un LLM in una memoria
Nelle sezioni seguenti, dimostreremo lo sviluppo di semplici app basate su LLM con l’aiuto di LangChain.rb.
App RAG generali con LangChain.rb
In questo primo esempio, creeremo un’applicazione RAG semplice e rapida utilizzando LangChain.rb. Prima di poter usare LangChain.rb per il tuo progetto Ruby, assicurati di installare la gem eseguendo il seguente comando:
gem install langchainrb
In questo progetto, useremo Milvus come database vettoriale e i modelli di OpenAI sia come LLM sia come modelli di embedding. Per avviare Milvus, dobbiamo installare Milvus in Docker e avviare il container con il seguente comando:
# Scarica lo script di installazione
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# Avvia il container Docker
bash standalone_embed.sh start
Ora che abbiamo avviato il container Docker, istanziamo Milvus e i modelli che useremo per la nostra applicazione RAG.
require 'langchain'
milvus = Langchain::Vectorsearch::Milvus.new(
url: ENV["MILVUS_URL"],
index_name: "Documents",
llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
)
La prima cosa che dobbiamo fare è creare uno schema all’interno del database vettoriale Milvus e il metodo di indicizzazione corrispondente. Poi, dobbiamo caricare quello schema prima di poter eseguire una ricerca vettoriale con esso.
# Crea lo schema predefinito
milvus.create_default_schema
# Crea l'indice predefinito
milvus.create_default_index
# Carica lo schema predefinito
milvus.load_default_schema
Ora siamo in grado di inserire alcuni dati nel nostro schema. Supponiamo di avere un PDF nella nostra directory locale contenente informazioni sui benefit dei dipendenti. Se vogliamo archiviare tutti i testi di questo PDF all’interno del database Milvus, possiamo farlo eseguendo i seguenti comandi:
pdf = Langchain.root.join("path/to/my.pdf")
# Aggiungi PDF all'interno di Milvus
milvus.add_data(path: pdf)
Una volta eseguiti i comandi sopra, LangChain farà tutto il lavoro di preprocessing dietro le quinte. Analizzerà il testo all’interno del file PDF, lo suddividerà in diversi blocchi, trasformerà ogni blocco in embedding e poi archivierà gli embedding all’interno del database vettoriale Milvus.
Dopo aver archiviato i nostri dati all’interno del database vettoriale Milvus, possiamo iniziare a fare domande relative al nostro documento PDF. Supponiamo di voler chiedere: “Qual è la politica sulle ferie dell’azienda? Quanto tempo posso prendermi?” allora possiamo chiedere ai nostri LLM in un sistema RAG semplicemente eseguendo questa singola riga di codice:
response = milvus.ask(question: "What’s the company’s vacation policy? How much can I take off?")
puts response
"""
Response:
=> The company's vacation policy allows employees to take any reasonable amount of time off with pay,
as long as they consult with their manager in advance and get their work done.
"""
E questo è tutto! Oltre a creare un’applicazione RAG generale, possiamo anche creare un’applicazione RAG agentica con LangChain.rb, di cui discuteremo nella prossima sezione.
Utilizzare agenti per interagire con strumenti di terze parti
La principale limitazione di molti LLM è la loro data di cutoff della conoscenza. GPT-4, ad esempio, ha una data di cutoff di aprile 2023. Ciò significa che, se vogliamo porre domande su eventi generali o fattuali successivi ad aprile 2023, non otterremo una risposta accurata dall'LLM.
Per risolvere questo problema, LangChain.rb ci consente di creare un'applicazione RAG agentica. Questo tipo di applicazione RAG aggiunge un ulteriore livello di intelligenza, contenente un "agent" che agisce come decisore. L'agent analizza la query dell'utente e poi decide quali strumenti di terze parti siano più efficaci e possano fornire il contesto più adatto per rispondere alla query.
Supponiamo di voler chiedere al nostro LLM informazioni sul meteo attuale a New York. Con un sistema RAG generale, l'LLM non può conoscere il meteo in tempo reale a New York. Molto probabilmente inizierà ad avere allucinazioni e ci fornirà previsioni meteo casuali.
Il workflow RAG agentico (1).png
Workflow di un RAG agentico
Il RAG agentico risolve questo problema consentendoci di utilizzare strumenti o API, come l'API OpenWeather, nel sistema per ottenere il meteo in tempo reale a New York. L'agent elaborerà prima la query dell'utente e poi deciderà quali strumenti possono fornire un contesto rilevante per rispondere alla query, prima di sintetizzare il contesto in una risposta accurata.
La seguente demo utilizzerà strumenti di terze parti all'interno del nostro sistema RAG, come una calcolatrice, l'app OpenWeather e Google Search.
weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
google_search = Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
calculator = Langchain::Tool::Calculator.new
Successivamente, dobbiamo aggiungere questi tre strumenti al nostro sistema RAG con il seguente comando:
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
agent = Langchain::Agent::ReActAgent.new(
llm: openai,
tools: [weather, google_search, calculator]
)
Ora possiamo iniziare a porre domande al nostro LLM. Supponiamo di voler porre la seguente domanda: “Trova il meteo attuale a Boston, MA, e Washington, D.C., e calcola una media.”
response = agent.run(question: "Find current weather in Boston, MA and Washington, D.C. and take an average")
Output del RAG con integrazione di strumenti
Output del RAG con integrazione di strumenti
Come puoi vedere nello screenshot sopra, il nostro sistema RAG agentico è riuscito a rispondere alla query in modo accurato. Analizziamo il workflow di questo sistema RAG:
La query è stata prima inviata all'LLM di OpenAI.
L'agent ha riconosciuto la necessità di utilizzare l'API OpenWeather per ottenere il meteo attuale a Boston e Washington D.C.
Dopo aver recuperato i dati meteo, l'agent ha visto che la query richiedeva di calcolare la media del meteo delle due città.
L'agent ha quindi invocato uno strumento calcolatrice per calcolare la media del meteo.
Infine, l'LLM ha sintetizzato i risultati in un'unica risposta coerente e l'ha restituita all'utente.
Questo esempio dimostra la potenza dell'approccio RAG agentico. Incorporando strumenti e API esterni, il sistema supera le limitazioni della data di cutoff della conoscenza dell'LLM e fornisce una risposta accurata e aggiornata alla query dell'utente.
Utilizzare gli agent per interagire con un database interno
Possiamo anche usare il RAG agentico per interagire con i nostri database interni. Questo è molto utile perché possiamo chiedere insight sui nostri dati usando un linguaggio simile a quello umano, invece di affidarci alle tradizionali query SQL.
Supponiamo di avere un negozio online e dati utente archiviati in un database. Normalmente, dovremmo scrivere query SQL per estrarre insight da quei dati. Con il RAG agentico, tutto ciò che dobbiamo fare è chiedere all'LLM l'insight che desideriamo, e la risposta verrà restituita immediatamente.
Ad esempio, supponiamo di voler sapere quanti record utente sono archiviati nel database. Possiamo semplicemente chiedere: "Quanti utenti ci sono?" ed eseguire i seguenti comandi:
require 'langchain'
# Istanzia la connessione al database
database = Langchain::Tool::Database.new(connection_string: "postgres://localhost:5432/my_database")
# Crea un'istanza LLM OpenAI
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
# Crea SQLAgent con l'LLM e la connessione al database
agent = Langchain::Agent::SQLAgent.new(
llm: openai,
db: database
)
# Fai una domanda all'agente
response = agent.run("How many users are there?")
Di seguito è riportato l'output di esempio del comando:
Output di RAG con integrazione SQL
Output di RAG con integrazione SQL
Come puoi vedere, il nostro sistema RAG agentico è stato in grado di rispondere accuratamente a una domanda specifica relativa ai dati del nostro database. Il workflow dell'agente è simile all'esempio precedente:
La query è stata inviata all'LLM OpenAI.
L'agente ha analizzato la query e ha determinato che è necessaria una ricerca nel database per contare il numero di utenti.
L'LLM ha generato una query SQL appropriata in base allo schema della tabella del database.
La query SQL è stata eseguita sul database e ha restituito il risultato.
L'output del database è stato rinviato all'LLM.
L'LLM ha sintetizzato il risultato del database in una risposta coerente e leggibile dall'essere umano e l'ha fornita come risposta finale.
Conclusione
L'introduzione di LangChain rende gli LLM accessibili ai professionisti che potrebbero non avere una conoscenza approfondita delle teorie dell'IA e della data science. Possiamo creare una potente applicazione RAG utilizzando LangChain con poche righe di codice.
Questa accessibilità è il motivo per cui Andrei Bondarev ha introdotto LangChain.rb, un'estensione di LangChain per Ruby. Questo framework consente agli sviluppatori full-stack di incorporare le potenti prestazioni degli LLM nelle loro applicazioni web senza la necessità di una vasta competenza in IA. Inoltre, LangChain.rb elimina il problema per gli sviluppatori full-stack di passare a un altro linguaggio di programmazione quando vogliono sfruttare gli LLM nelle loro applicazioni web.
Continua a leggere

Notion's Vector Search Is Excellent. Their Next Problem Is Harder.
Notion solved vector search scaling in two years. The next bottleneck — offline context engineering, unified data, and the real-time/offline gap — is harder.

Why Not All VectorDBs Are Agent-Ready
Explore why choosing the right vector database is critical for scaling AI agents, and why traditional solutions fall short in production.

Democratizing AI: Making Vector Search Powerful and Affordable
Zilliz democratizes AI vector search with Milvus 2.6 and Zilliz Cloud for powerful, affordable scalability, cutting costs in infrastructure, operations, and development.


