Integrare Milvus con DSPy
Cos'è DSPy
DSPy, introdotto dallo Stanford NLP Group, è un framework programmatico innovativo progettato per ottimizzare i prompt e i pesi all'interno dei modelli linguistici, particolarmente utile in scenari in cui i modelli linguistici di grandi dimensioni (LLM) sono integrati in più fasi di una pipeline. A differenza delle tecniche convenzionali di ingegneria dei prompt, che si affidano alla creazione e alla modifica manuale, DSPy adotta un approccio basato sull'apprendimento. Assimilando esempi di domande e risposte, DSPy genera dinamicamente prompt ottimizzati, adatti a compiti specifici. Questa metodologia innovativa consente di riassemblare senza problemi intere pipeline, eliminando la necessità di continui aggiustamenti manuali dei prompt. La sintassi pitonica di DSPy offre vari moduli componibili e dichiarativi, semplificando l'istruzione degli LLM.
Vantaggi dell'uso di DSPy
- Approccio alla programmazione: DSPy fornisce un approccio di programmazione sistematico per lo sviluppo di pipeline LM, astraendo le pipeline come grafici di trasformazione del testo, invece di limitarsi a richiedere le LLM. I suoi moduli dichiarativi consentono una progettazione e un'ottimizzazione strutturate, sostituendo il metodo "trial-and-error" dei modelli di prompt tradizionali.
- Miglioramento delle prestazioni: DSPy dimostra un significativo aumento delle prestazioni rispetto ai metodi esistenti. Attraverso casi di studio, supera il prompting standard e le dimostrazioni create da esperti, dimostrando la sua versatilità ed efficacia anche quando viene compilato in modelli LM più piccoli.
- Astrazione modularizzata: DSPy astrae efficacemente gli aspetti più complessi dello sviluppo di pipeline LM, come la decomposizione, la messa a punto e la selezione dei modelli. Con DSPy, un programma conciso può essere tradotto senza problemi in istruzioni per vari modelli, come GPT-4, Llama2-13b o T5-base, semplificando lo sviluppo e migliorando le prestazioni.
I moduli
Sono numerosi i componenti che contribuiscono alla costruzione di una pipeline LLM. Qui descriveremo alcuni componenti chiave per fornire una comprensione di alto livello del funzionamento di DSPy.
Moduli DSPy
Firma: Le firme in DSPy servono come specifiche dichiarative, delineando il comportamento di input/output dei moduli, guidando il modello linguistico nell'esecuzione dei task. Modulo: I moduli di DSPy sono componenti fondamentali per i programmi che sfruttano i modelli linguistici (LM). Essi astraggono da varie tecniche di prompting, come la catena del pensiero o ReAct, e sono adattabili per gestire qualsiasi DSPy Signature. Grazie ai parametri apprendibili e alla capacità di elaborare gli input e produrre output, questi moduli possono essere combinati per formare programmi più ampi, ispirandosi ai moduli NN di PyTorch ma adattandoli alle applicazioni LM. Ottimizzatore: Gli ottimizzatori in DSPy mettono a punto i parametri dei programmi DSPy, come i prompt e i pesi LLM, per massimizzare metriche specifiche come l'accuratezza, migliorando l'efficienza del programma.
Perché Milvus in DSPy
DSPy è un potente framework di programmazione che potenzia le applicazioni RAG. Tali applicazioni devono recuperare informazioni utili per migliorare la qualità delle risposte, il che richiede un database vettoriale. Milvus è un noto database vettoriale open-source per migliorare le prestazioni e la scalabilità. Con MilvusRM, un modulo di recupero in DSPy, l'integrazione di Milvus diventa semplice. Ora gli sviluppatori possono facilmente definire e ottimizzare i programmi RAG utilizzando DSPy, sfruttando le forti capacità di ricerca vettoriale di Milvus. Questa collaborazione rende le applicazioni RAG più efficienti e scalabili, combinando le capacità di programmazione di DSPy con le funzioni di ricerca di Milvus.
Esempi
Vediamo ora un rapido esempio per dimostrare come sfruttare Milvus in DSPy per ottimizzare un'applicazione RAG.
Prerequisiti
Prima di creare l'applicazione RAG, installare DSPy e PyMilvus.
$ pip install "dspy-ai[milvus]"
$ pip install -U pymilvus
Caricamento del set di dati
In questo esempio, utilizziamo HotPotQA, una raccolta di coppie complesse domanda-risposta, come dataset di allenamento. Possiamo caricarli attraverso la classe HotPotQA.
from dspy.datasets import HotPotQA
# Load the dataset.
dataset = HotPotQA(
train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0
)
# Tell DSPy that the 'question' field is the input. Any other fields are labels and/or metadata.
trainset = [x.with_inputs("question") for x in dataset.train]
devset = [x.with_inputs("question") for x in dataset.dev]
Inserire i dati nel database vettoriale di Milvus
Inseriamo le informazioni sul contesto nella raccolta Milvus per il recupero dei vettori. Questa collezione dovrebbe avere un campo embedding
e un campo text
. In questo caso, utilizziamo il modello text-embedding-3-small
di OpenAI come funzione predefinita di incorporazione della query.
import requests
import os
os.environ["OPENAI_API_KEY"] = "<YOUR_OPENAI_API_KEY>"
MILVUS_URI = "example.db"
MILVUS_TOKEN = ""
from pymilvus import MilvusClient, DataType, Collection
from dspy.retrieve.milvus_rm import openai_embedding_function
client = MilvusClient(uri=MILVUS_URI, token=MILVUS_TOKEN)
if "dspy_example" not in client.list_collections():
client.create_collection(
collection_name="dspy_example",
overwrite=True,
dimension=1536,
primary_field_name="id",
vector_field_name="embedding",
id_type="int",
metric_type="IP",
max_length=65535,
enable_dynamic=True,
)
text = requests.get(
"https://raw.githubusercontent.com/wxywb/dspy_dataset_sample/master/sample_data.txt"
).text
for idx, passage in enumerate(text.split("\n")):
if len(passage) == 0:
continue
client.insert(
collection_name="dspy_example",
data=[
{
"id": idx,
"embedding": openai_embedding_function(passage)[0],
"text": passage,
}
],
)
Definire MilvusRM.
Ora è necessario definire MilvusRM.
from dspy.retrieve.milvus_rm import MilvusRM
import dspy
retriever_model = MilvusRM(
collection_name="dspy_example",
uri=MILVUS_URI,
token=MILVUS_TOKEN, # ignore this if no token is required for Milvus connection
embedding_function=openai_embedding_function,
)
turbo = dspy.OpenAI(model="gpt-3.5-turbo")
dspy.settings.configure(lm=turbo)
Creare le firme
Ora che abbiamo caricato i dati, iniziamo a definire le firme per le sottoattività della nostra pipeline. Possiamo identificare i nostri semplici input question
e output answer
, ma poiché stiamo costruendo una pipeline RAG, recupereremo informazioni contestuali da Milvus. Definiamo quindi la nostra firma come context, question --> answer
.
class GenerateAnswer(dspy.Signature):
"""Answer questions with short factoid answers."""
context = dspy.InputField(desc="may contain relevant facts")
question = dspy.InputField()
answer = dspy.OutputField(desc="often between 1 and 5 words")
Includiamo brevi descrizioni per i campi context
e answer
per definire linee guida più chiare su ciò che il modello riceverà e dovrà generare.
Costruire la pipeline
Ora definiamo la pipeline RAG.
class RAG(dspy.Module):
def __init__(self, rm):
super().__init__()
self.retrieve = rm
# This signature indicates the task imposed on the COT module.
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
# Use milvus_rm to retrieve context for the question.
context = self.retrieve(question).passages
# COT module takes "context, query" and output "answer".
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(
context=[item.long_text for item in context], answer=prediction.answer
)
Esecuzione della pipeline e ottenimento dei risultati
Ora abbiamo costruito questa pipeline RAG. Proviamo e otteniamo i risultati.
rag = RAG(retriever_model)
print(rag("who write At My Window").answer)
Townes Van Zandt
Possiamo valutare i risultati quantitativi sul set di dati.
from dspy.evaluate.evaluate import Evaluate
from dspy.datasets import HotPotQA
evaluate_on_hotpotqa = Evaluate(
devset=devset, num_threads=1, display_progress=False, display_table=5
)
metric = dspy.evaluate.answer_exact_match
score = evaluate_on_hotpotqa(rag, metric=metric)
print("rag:", score)
Ottimizzazione della pipeline
Dopo aver definito il programma, il passo successivo è la compilazione. Questo processo aggiorna i parametri di ciascun modulo per migliorare le prestazioni. Il processo di compilazione dipende da tre fattori critici:
- Set di allenamento: Per questa dimostrazione utilizzeremo i 20 esempi di domande e risposte del nostro set di dati di addestramento.
- Metrica di convalida: Stabiliamo una semplice metrica
validate_context_and_answer
. Questa metrica verifica l'accuratezza della risposta prevista e assicura che il contesto recuperato includa la risposta. - Ottimizzatore specifico (Teleprompter): Il compilatore di DSPy incorpora più telepromptori progettati per ottimizzare efficacemente i programmi.
from dspy.teleprompt import BootstrapFewShot
# Validation logic: check that the predicted answer is correct.# Also check that the retrieved context does contain that answer.
def validate_context_and_answer(example, pred, trace=None):
answer_EM = dspy.evaluate.answer_exact_match(example, pred)
answer_PM = dspy.evaluate.answer_passage_match(example, pred)
return answer_EM and answer_PM
# Set up a basic teleprompter, which will compile our RAG program.
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)
# Compile!
compiled_rag = teleprompter.compile(rag, trainset=trainset)
# Now compiled_rag is optimized and ready to answer your new question!
# Now, let’s evaluate the compiled RAG program.
score = evaluate_on_hotpotqa(compiled_rag, metric=metric)
print(score)
print("compile_rag:", score)
Il punteggio Ragas è aumentato dal precedente valore di 50,0 a 52,0, indicando un miglioramento della qualità delle risposte.
Sintesi
DSPy segna un salto di qualità nelle interazioni con i modelli linguistici grazie alla sua interfaccia programmabile, che facilita l'ottimizzazione algoritmica e automatica dei prompt e dei pesi dei modelli. Sfruttando DSPy per l'implementazione delle RAG, l'adattabilità a modelli linguistici o insiemi di dati diversi diventa un gioco da ragazzi, riducendo drasticamente la necessità di noiosi interventi manuali.