Explorando o DSPy e sua Integração com o Milvus para Criar Pipelines RAG Altamente Eficientes
Introdução
Grandes modelos de linguagem (LLMs) possuem capacidades generativas transformadoras e são ampliados com ferramentas como bases de conhecimento e recuperadores, impulsionando aplicações avançadas de GenAI, como chatbots e agents. No centro da interação com LLMs estão os prompts, que são instruções que orientam esses modelos a realizar tarefas específicas. No entanto, criar prompts eficazes é um processo sutil e intricado, muitas vezes exigindo técnicas sofisticadas como Chain-of-Thought e ReAct. Com isso adicionado, os prompts estão se tornando cada vez mais complexos. Além disso, até mesmo prompts idênticos podem produzir resultados divergentes em diferentes LLMs, como GPT-4 e Gemini, devido a variações em seus métodos e conjuntos de dados de pré-treinamento. Esse desafio provocou um aumento de interesse em Prompt Engineering—uma tarefa trabalhosa focada em ajustar e otimizar prompts para resultados melhores e personalizados.
Embora a criação manual de prompts funcione bem para aplicações simples de LLM, ela se torna frustrante e demorada para pipelines complexos baseados em LLM que envolvem múltiplos componentes. DSPy representa uma mudança de paradigma na forma como os desenvolvedores interagem com modelos de linguagem ao introduzir uma interface programável que permite a otimização algorítmica de prompts e pesos do modelo, levando a um desenvolvimento de modelos de linguagem mais eficiente. DSPy integrou perfeitamente o banco de dados vetorial Milvus, automatizando a otimização de aplicações de Retrieval Augmented Generation (RAG) usando uma abordagem programática.
Nas seções a seguir, exploraremos a essência do DSPy e sua mecânica operacional e forneceremos um exemplo prático que demonstra como construir e otimizar uma aplicação RAG usando DSPy e o banco de dados vetorial Milvus.
O que é DSPy?
DSPy, apresentado pelo Stanford NLP Group, é um framework programático projetado para otimizar prompts e pesos em modelos de linguagem, o que é particularmente útil quando LLMs são integrados em múltiplas etapas de pipeline. Ele fornece vários módulos componíveis e declarativos para instruir LLMs em sintaxe Pythonic.
Ao contrário das técnicas tradicionais de engenharia de prompts que dependem da criação e do ajuste manual de prompts, DSPy aprende exemplos de perguntas e respostas e imita esse aprendizado para gerar prompts otimizados para resultados mais personalizados. Essa abordagem permite a remontagem dinâmica de todo o pipeline, explicitamente adaptada às nuances da sua tarefa, eliminando assim a necessidade de ajustes manuais contínuos de prompts.
Conceitos-chave e Componentes Fundamentais
No DSPy, três elementos fundamentais—Signatures, Modules e Optimizers (anteriormente chamados de Teleprompters)—formam os principais blocos de construção para otimização automatizada de prompts e ajuste fino de modelos.
Signatures
Signatures são especificações declarativas que definem o comportamento de entrada/saída de um módulo DSPy. Elas dizem ao modelo de linguagem quais tarefas ele deve executar, em vez de como devemos solicitar ao modelo de linguagem.
- Uma assinatura compreende três elementos essenciais:
- Uma descrição concisa da subtarefa que o modelo de linguagem visa resolver.
- Uma descrição de um ou mais campos de entrada (por exemplo, perguntas de entrada) que fornecemos ao modelo de linguagem.
- Uma descrição de um ou mais campos de saída (por exemplo, respostas a perguntas) que esperamos do modelo de linguagem.
Aqui estão exemplos de assinaturas para tarefas populares de LLM:
- Resposta a Perguntas:
"question -> answer" - Classificação de Sentimento:
"sentence -> sentiment" - Resposta a Perguntas Aumentada por Recuperação:
"context, question -> answer" - Resposta a Perguntas de Múltipla Escolha com Raciocínio:
"question, choices -> reasoning, selection"
Essas assinaturas orientam o DSPy a orquestrar com eficiência as operações de LLM em vários módulos, promovendo uma execução de tarefas simplificada e precisa.
Módulos
Os módulos DSPy abstraem técnicas convencionais de prompting dentro de um pipeline de LLM. Eles têm três características principais:
- Cada módulo integrado abstrai uma técnica específica de prompting (como Chain of Thoughts ou ReAct) e lida com Assinaturas DSPy.
- Os módulos DSPy têm parâmetros aprendíveis, incluindo componentes de prompt e pesos de LLM, permitindo que processem entradas e gerem saídas.
- Os módulos DSPy podem ser combinados para criar módulos maiores e mais complexos.
- O DSPy fornece sete módulos integrados para diversos propósitos, incluindo
dspy.ReAct,dspy.ChainofThought,dspy.Predict,dspy.ProgramOfThought,dspy.ReAct,dspy.MultiChainComparisonedspy.Retrieve.
Otimizadores
Os Otimizadores DSPy, anteriormente Teleprompters, são algoritmos projetados para ajustar finamente os parâmetros de um programa DSPy—como prompts e pesos de LLM—para maximizar métricas especificadas, como precisão. Um otimizador DSPy típico requer três entradas:
Seu programa DSPy: pode ser um único módulo (por exemplo,
dspy.Predict) ou um programa complexo com vários módulos.Sua métrica escolhida: uma função que avalia a saída do programa e atribui a ela uma pontuação (pontuações mais altas denotam melhores resultados).
Um pequeno conjunto de entradas de treinamento: geralmente apenas 5 a 10 exemplos.
Depois de definir seus dados de treinamento, módulos e métricas, o otimizador otimiza os pesos de LLM, as instruções de prompt e as demonstrações few-shot para melhorar a eficiência do programa. Por exemplo, o otimizador BootstrapFewShot gera respostas que se alinham com a métrica especificada, enquanto módulos como COT (Chain of Thought) geram raciocínio estruturado para chegar a respostas precisas. O DSPy registra essas instâncias bem-sucedidas e justificativas como demonstrações few-shot para lidar com futuras consultas de teste.
Além dos blocos de construção centrais mencionados acima, o DSPy incorpora dados, métricas e asserções como componentes suplementares, enriquecendo sua funcionalidade e adaptabilidade. Consulte a documentação do DSPy para mais detalhes.
Fluxos de Trabalho DSPy: Construindo Pipelines de LLM Eficientes
Como exatamente o DSPy funciona na construção de pipelines de LLM? Para maior clareza, podemos dividir o processo em várias etapas principais.
- Primeiro, você deve definir sua tarefa e preparar alguns exemplos de entrada — muitas vezes sem rótulos (ou apenas com rótulos para as saídas finais, se sua métrica exigir).
- Segundo, construa seu pipeline escolhendo entre os módulos integrados, atribuindo a cada módulo uma assinatura (especificação de entrada/saída) e incorporando perfeitamente esses módulos ao seu código Python.
- Terceiro, defina a lógica de validação para seu pipeline, incluindo quais métricas e exemplos de entrada usar para avaliar a qualidade dos prompts e os resultados finais.
- Quarto, compile seu código usando um otimizador DSPy, que gera instruções de alta qualidade, exemplos few-shot automáticos ou pesos de LLM atualizados.
- Por fim, envolva-se em um processo iterativo de refinamento do seu conjunto de dados, programa ou lógica de validação para alcançar o nível de desempenho desejado para seu pipeline. Avalie e melhore continuamente para atender a requisitos em evolução e otimizar os resultados.
DSPy vs. LlamaIndex/LangChain/AutoGPT
O DSPy se destaca por sua abordagem em comparação com muitos outros frameworks populares de IA, como LangChain, LlamaIndex e AutoGPT. Veja mais de perto como eles diferem e se alinham:
LangChain é um toolkit para criar aplicações personalizadas. Ele aproveita vários modelos de linguagem e pacotes utilitários, permitindo que os desenvolvedores adaptem aplicações a necessidades específicas.
LlamaIndex é um framework de orquestração projetado para simplificar a integração de várias fontes de dados privadas com modelos de linguagem. Ele simplifica tarefas de manipulação e processamento de dados.
AutoGPT é um agente de IA avançado impulsionado por GPT-4 e GPT3.5. Ele é programado para tomar decisões e executar ações com base em regras e objetivos predefinidos. Ele enfatiza autonomia e capacidades de tomada de decisão.
Características distintivas do DSPy:
- O DSPy otimiza e automatiza a construção de prompts para uma interação aprimorada com modelos de linguagem.
- Ao contrário do LangChain e do LlamaIndex, que atendem ao desenvolvimento de aplicações de alto nível com módulos pré-construídos, o DSPy fornece módulos poderosos e de propósito geral capazes de aprender a criar prompts ou ajustar LLMs dentro de pipelines personalizados. A força do DSPy está em sua capacidade de adaptar dinamicamente prompts e ajustar LLMs com base em dados em mudança, ajustes no fluxo de controle do programa ou variações do idioma-alvo. Esse processo de otimização automatizado pode levar a saídas de qualidade superior com esforço mínimo, especialmente quando os desenvolvedores estão abertos a escalar seus programas simples ou de prototipagem para versões mais sofisticadas para fins de produção.
- O DSPy é ideal para casos de uso que exigem um modelo de programação leve, mas com otimização automática, em vez de depender de prompts e integrações predefinidos oferecidos por bibliotecas como LangChain e LlamaIndex.
Integração do DSPy com o Banco de Dados Vetorial Milvus
Milvus é um banco de dados vetorial nativo da nuvem, open-source, altamente flexível, confiável e extremamente rápido. Ele impulsiona a busca por similaridade vetorial e é particularmente benéfico na construção de várias aplicações de GenAI e Retrieval Augmented Generation (RAG).
O Milvus foi integrado ao fluxo de trabalho do DSPy como um módulo de recuperação na forma do cliente MilvusRM, facilitando a implementação de um pipeline RAG rápido e eficiente.
Criando uma Aplicação RAG com DSPy e Milvus
Retrieval Augmented Generation (RAG) é um método que capacita LLMs a acessar repositórios externos de conhecimento, pesquisar nesses repositórios informações contextuais relevantes para as consultas dos usuários e gerar respostas refinadas.
Nesta demonstração, criaremos uma aplicação RAG simples usando GPT-3.5 (gpt-3.5-turbo) para geração de respostas. Usamos Milvus como armazenamento vetorial por meio do MilvusRM e DSPy para configurar e otimizar o pipeline RAG.
Pré-requisitos
Antes de criar o app RAG, instale o Cliente MilvusRM e o Milvus.
- Para instalar o MilvusRM, execute o código a seguir.
pip install dspy-ai[milvus]
- Instale o Milvus. Consulte a documentação do Milvus para instruções detalhadas.
Carregando o conjunto de dados
Neste exemplo, usamos o HotPotQA, uma coleção de pares complexos de perguntas e respostas, como nosso conjunto de dados de treinamento. Podemos carregá-los por meio da 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]
Importar dados para o banco de dados vetorial Milvus
Faça a ingestão das informações de contexto na coleção Milvus para recuperação vetorial. Esta coleção deve ter um campo embedding e um campo text. Usamos o modelo text-embedding-3-small da OpenAI como a função padrão de embedding de consulta neste caso.
import requests
MILVUS_URI = "http://localhost:19530"
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}])
Definir MilvusRM.
Agora, você precisa definir o MilvusRM.
from dspy.retrieve.milvus_rm import MilvusRM
import os
import dspy
os.environ["OPENAI_API_KEY"] = "<YOUR_OPENAI_API_KEY>"
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)
Criando assinaturas
Agora que carregamos os dados, vamos começar definindo as assinaturas para as subtarefas do nosso pipeline. Podemos identificar nossa entrada simples question e saída answer, mas, como estamos criando um pipeline RAG, recuperaremos informações contextuais do Milvus. Então vamos definir nossa assinatura como 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")
Incluímos descrições curtas para os campos context e answer para definir diretrizes mais claras sobre o que o modelo receberá e deverá gerar.
Criando o pipeline
Agora, vamos definir o 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)
Executando o pipeline e obtendo os resultados
Agora, construímos este pipeline RAG. Vamos testá-lo e obter resultados.
rag = RAG(retriever_model)
print(rag("who write At My Window").answer)
# The result:
# 'Townes Van Zandt'
Podemos avaliar os resultados quantitativos no dataset.
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)
# The result:
# rag: 50.0
Otimizando o pipeline
Depois de definir este programa, o próximo passo é a compilação. Esse processo atualiza os parâmetros dentro de cada módulo para melhorar o desempenho. O processo de compilação depende de três fatores críticos:
- Conjunto de treinamento: Utilizaremos os 20 exemplos de pergunta-resposta do nosso dataset de treinamento para esta demonstração.
- Métrica de validação: Estabeleceremos uma métrica simples
validate_context_and_answer. Essa métrica verifica a precisão da resposta prevista e garante que o contexto recuperado inclua a resposta. - Otimizador específico (Teleprompter): O compilador do DSPy incorpora vários teleprompters projetados para otimizar seus programas de forma eficaz.
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!
Agora, vamos avaliar o programa RAG compilado.
score = evaluate_on_hotpotqa(compiled_rag, metric=metric)
print(score)
print('compile_rag:', score)
# The result:
# compile_rag: 52.0
A pontuação de avaliação aumentou de seu valor anterior de 50.0 para 52.0, indicando uma melhoria na qualidade das respostas.
Resumo
DSPy marca um avanço nas interações com modelos de linguagem por meio de sua interface programável, que facilita a otimização algorítmica e automatizada de prompts e pesos de modelos. Ao aproveitar o DSPy para a implementação de RAG, a adaptabilidade a diferentes modelos de linguagem ou datasets se torna muito simples, reduzindo drasticamente a necessidade de intervenções manuais tediosas.
Além disso, o DSPy introduz um módulo de recuperação MilvusRM em seu fluxo de trabalho ao integrar o banco de dados vetorial Milvus. Com essa nova integração, os desenvolvedores podem automatizar a otimização de prompts e os ajustes de parâmetros do modelo em aplicações RAG para melhorar a qualidade das respostas. Se quiser saber mais, confira o guia detalhado de MilvusRM na documentação do DSPy!
Referências
Página do DSPy no GitHub: https://github.com/stanfordnlp/dspy
Artigo do DSPy: https://arxiv.org/pdf/2310.03714
Documentação do DSPy: https://dspy-docs.vercel.app/quick-start/installation/
Guia do MilvusRM: https://dspy-docs.vercel.app/docs/deep-dive/retrieval_models_clients/MilvusRM
Documentação do Milvus: https://milvus.io/docs
Continue lendo

Zilliz Cloud On-Demand Compute: Pay Only for What You Use
The customer case behind Zilliz Cloud On-Demand: how a $10K vector search bill came down to under $500, and the engineering changes that made it possible.

Balancing Precision and Performance: How Zilliz Cloud's New Parameters Help You Optimize Vector Search
Optimize vector search with Zilliz Cloud’s level and recall features to tune accuracy, balance performance, and power AI applications.

Cosmos World Foundation Model Platform for Physical AI
NVIDIA's Cosmos platform enables safe, digital twin training of GenAI models for physical applications, overcoming data scarcity and safety challenges.



