Engenharia de prompts com DSPy

Joshua Noble

Data Scientist

O DSPy é um framework do Python de código aberto para criar aplicações de grandes modelos de linguagem (LLMs) e ajustar o desempenho por meio de código, em vez de técnicas únicas para otimização de prompts. Um programa DSPy fornece uma maneira modular de configurar e realizar o ajuste fino de aplicações otimizando prompts para obter saídas precisas. A principal vantagem do DSPy é que ele permite que você faça engenharia de prompts e rastreamento por meio de código Python, em vez de precisar rastrear o desempenho do modelo por conta própria.

O poder do DSPy é que ele usa IA generativa para gerar linguagem natural e, em seguida, testar os resultados para criar os prompts mais eficazes. Isso permite que você crie um sistema de IA de autoaprimoramento. Ele é compatível com uma ampla variedade de interfaces para modelos de recuperação e modelos de linguagem. Você pode executar os modelos localmente por meio de sistemas como o Ollama ou HuggingFace ou pode executá-los usando uma API, se estiver usando o ChatGPT ou o GPT-4 da OpenAI. O DSPy é compatível com uma ampla variedade de casos de uso, como cadeia de pensamento (CoT), geração aumentada de recuperação (RAG), bem como sumarização. 

Neste tutorial, você avançará no fluxo de trabalho para criar uma aplicação de resposta às perguntas da RAG com o DSPy no IBM watsonx. Você usará o Llama 3 como modelo de linguagem e o ColBERT como modelo de recuperação. Você terá o DSPy para realizar o ajuste fino dos prompts e ajudar a estruturar várias abordagens diferentes para responder às perguntas para ver como gerar melhores respostas, mesmo com perguntas altamente complexas.

Configure seu ambiente

Embora você possa escolher entre várias ferramentas, este tutorial explica como configurar uma conta da IBM para usar um Jupyter Notebook.

Faça login no watsonx.ai com sua conta do IBM Cloud.

Crie um projeto do watsonx.ai.

Você pode obter a ID do projeto a partir de seu projeto.

Em seguida, clique na guia "Gerenciar" e copie a ID do projeto da seção "Detalhes" da página "Geral". Você precisa dessa ID para este tutorial.

Em seguida, crie um Jupyter Notebook no ambiente de sua escolha. Você vai copiar o código deste tutorial para o novo Notebook. Uma outra opção é baixar esse Notebook do GitHub para o seu sistema local e carregá-lo em seu projeto do watsonx.ai como um ativo.

Configure uma instância de serviço e chave de API para o Watson Machine Learning (WML)

Crie uma instância do serviço do watsonx.ai Runtime (selecione a região apropriada e escolha o plano Lite, que é uma instância gratuita).

Gere uma chave de API no watsonx.ai Runtime.

Associe o serviço watsonx.ai Runtime ao projeto que você criou no watsonx.ai

Instale a biblioteca do DSPy e configure suas credenciais

Para usar o DSPy, execute uma instalação de pip simples. Você também instalará o dotenv para gerenciar suas variáveis de ambiente:

!pip install dspy-ai python-dotenv;

Em seguida, você importará as bibliotecas necessárias para o restante deste tutorial:

import dspy
from dspy import LM
from dspy.datasets import HotPotQA
from dspy.teleprompt import BootstrapFewShot
import json
import os

from dotenv import load_dotenv
load_dotenv(os.getcwd()+’/.env’, override=True)

Para definir suas credenciais, você precisa do WATSONX_APIKEY e do PROJECT_ID gerados na Etapa 1. Você pode armazená-los em um arquivo .env em seu diretório ou substituir o texto do espaço reservado. Você também define o serviço de URL como endpoint da API.

os.environ[‘WX_URL’] = “https://us-south.ml.cloud.ibm.com”
os.environ[‘WX_APIKEY’] = os.getenv(“WATSONX_APIKEY”, “”)

WATSONX_APIKEY= os.getenv(“WATSONX_APIKEY”, “”)
PROJECT_ID = os.getenv(“PROJECT_ID”,””)

Uso do watsonx com o DSPy

Agora, você configurará o DSPy para trabalhar com modelos do watsonx com a classe LM do DSPy. Essa classe permite que você chame as APIs do watsonx para gerar novos prompts e gerar respostas para os prompts que você pode testar. O DSPy usa outra biblioteca chamada LiteLLM para acessar os serviços do watsonx. O LiteLLM fornece um wrapper simples para chamar uma variedade muito ampla de APIs de LLMs usando o formato OpenAI, incluindo Hugging Face, Azure e watsonx.

Antes de poder acessar sua conta do watsonx, você precisa armazenar um token do serviço do watsonx com a chave de API que você gerou na primeira etapa. Chame a biblioteca os para acessar “https://iam.cloud.ibm.com/identity/token”, recuperar seu token e armazená-lo para uso posterior.

token = os.popen(‘curl -k -X POST \
    --header “Content-Type: application/x-www-form-urlencoded” \
    --header “Accept: application/json” \
    --data-urlencode “grant_type=urn:ibm:params:oauth:grant-type:apikey” \
    --data-urlencode “apikey=’ + WATSONX_APIKEY + ‘” \
    “https://iam.cloud.ibm.com/identity/token”’).read()

Agora, você pode criar uma instância do LanguageModel que usa o watsonx . Use o token que você recuperou anteriormente como a chave de API, e usaremos o modelo "llama-3-8b-instruct" do Meta como seu modelo de linguagem. Você passa o caminho para esse modelo para o DSPy para usar como seu modelo de linguagem junto com a temperatura que deseja que o modelo de linguagem use. Mais informações sobre a configuração do LiteLLM para usar o watsonx estão disponíveis nos documentos do GitHub. Nesse caso, o valor de 0,7 dá a você um pouco de criatividade sem excesso de alucinações.

lm = dspy.LM(‘watsonx/meta-llama/llama-3-8b-instruct’, api_key=WATSONX_APIKEY, api_base=”https://us-south.ml.cloud.ibm.com”)

dspy.configure(lm=lm, trace=[], temperature=0.7, experimental=True)

Adição de um modelo de recuperação

Agora, você carrega o modelo de recuperação para o R de sua RAG. Use ColBERTv2 para carregar as extrações do conjunto de dados da Wikipedia 2017. O ColBERT é um modelo de recuperação rápido e preciso, permitindo uma pesquisa escalável baseada em BERT em grandes coleções de texto em dezenas de milissegundos. O ColBERT é simplesmente uma das muitas opções que podem ser usadas para recuperar informações de um banco de dados de vetores. É comparável a outros bancos de dados de vetores, como o Qdrant, Milvus, Pinecone, Chroma ou Weaviate.

Os bancos de dados de vetores terão um conjunto específico de informações que o modelo de linguagem pode acessar rapidamente. Nesse caso, você utilizará um conjunto de resumos da Wikipedia 2017 para fornecer uma ampla gama de fatos para seu modelo de linguagem usar na geração. Essa combinação do ColBERT e do conjunto de dados da Wikipedia 17 também é especialmente útil, pois uma versão dele é hospedada gratuitamente pela equipe do DSPy para uso por qualquer pessoa. Isso fornece acesso a uma ampla gama de informações sem exigir que você faça ingestão de dados ou configure seu próprio sistema de banco de dados de vetores. Uma desvantagem desse conjunto de dados é que ele não contém nada sobre eventos pós-2017, mas, para fins de demonstração, é muito útil.

Se você estiver interessado em executar sua própria versão do ColBERT com seus próprios dados ou um conjunto de dados atualizado, os tutoriais aqui são úteis.

Depois disso, carregue o conjunto de dados HotPotQA e divida-o em conjuntos de treinamento e testes que você pode usar para testar sua cadeia de recuperação. O HotpotQA é um conjunto de dados de resposta a perguntas que contém perguntas naturais e com vários saltos, com forte supervisão de fatos corroboradores para permitir sistemas de resposta a perguntas mais explicáveis. 

colbertv2_wiki17_abstracts = dspy.ColBERTv2(url=’http://20.102.90.50:2017/wiki17_abstracts’)
dspy.configure(rm=colbertv2_wiki17_abstracts)

Testes de QA básico

Agora, você criará uma assinatura que será usada em seu exemplo inicial. Uma assinatura é uma classe que define os tipos de entrada e saída de um módulo, o que garante a compatibilidade entre diferentes módulos em um programa DSPy. Uma assinatura combina várias tarefas, como a ingestão de uma pergunta e a saída de uma resposta e o raciocínio dos modelos. A assinatura que você usará aqui aceita apenas uma pergunta e fornece uma resposta:

class BasicQA(dspy.Signature):
    “””Answer questions with short factoid answers.”””

    question = dspy.InputField()
    answer = dspy.OutputField(desc=”often between 1 and 5 words”)

Agora você tem um preditor que pode testar simplesmente chamando o método Predict do DSPy. Esse método pega a classe newBasicQA que você definiu anteriormente e usa essa classe quando você passa uma pergunta para o DSPy.

# Define the predictor.
generate_answer = dspy.Predict(BasicQA)

Agora, você criará uma pergunta que requer várias informações para ser respondida corretamente e a testará com uma arquitetura que usa apenas um modelo de linguagem. Você usará a função thegenerate_answer que acabou de criar para responder à pergunta.

# Call the predictor on a particular input.
test_question = “What country was the winner of the Nobel Prize in Literature in 2006 from and what was their name?”

pred = generate_answer(question=test_question)

if pred == None:
    print(“ no answer “)
else:
    # Print the input and the prediction.
    print(f”Answer: Turkey, Orhan Pamuk”)
    print(f”Predicted Answer: {pred.answer}”)

O código retorna o seguinte (sua resposta pode ser diferente):

Answer: Turkey, Orhan Pamuk
Predicted Answer: The winner was France and the author was Orhan Pamuk.

Orhan Pamuk foi o vencedor do Prêmio Nobel de Literatura de 2006, mas ele não é da França, e o enquadramento da resposta não está correto. Agora, você aumentará o modelo com a recuperação usando a geração aumentada de recuperação e fará com que o DSPy crie prompts melhores para melhorar o desempenho.

Geração aumentada de recuperação (RAG)

A geração aumentada de recuperação (RAG) é uma arquitetura que otimiza a saída de um grande modelo de linguagem usando referências de uma base de conhecimento confiável. Isso aumenta os dados de treinamento com fontes verificadas antes que o modelo de linguagem gere uma resposta. Os LLMs são treinados em grandes corporações e usam bilhões de parâmetros para gerar a saída, mas podem não conseguir acessar informações atualizadas ou precisas de seus corpus de treinamento. A RAG estende os recursos já poderosos dos LLMs para um domínio específico sem exigir que o modelo seja retreinado. É uma maneira poderosa e potencialmente econômica de melhorar as saídas dos LLMs para que permaneçam relevantes, precisos e úteis em vários contextos.

No DSPy, você utiliza uma arquitetura de RAG adicionando uma etapa de contexto na assinatura. Essa etapa reúne o contexto do modelo de recuperação e o adiciona à solicitação do modelo de linguagem para obter uma resposta melhor.

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”)

Essa assinatura do newGenerateAnswer pode ser usada com seu modelo de RAG. Você passa o theGenerateAnswer para o módulo "ChainOfThought" para que o contexto recuperado e a pergunta e a resposta usem uma abordagem da cadeia de pensamento.

Você também atualiza o método theforward para gerar passagens de contexto a partir da RAG e usa essas passagens contextuais para gerar respostas. O DSPy chamará esse método "forward" cada vez que gera uma nova resposta em resposta a uma pergunta, reunindo o contexto do conjunto de dados de resumos ColBERT Wikipedia 17 e, em seguida, passando esse contexto para o modelo de linguagem, neste caso, o Llama 3.1. À medida que cada resposta é gerada, o DSPy compara a saída com o resultado desejado para garantir que os prompts estejam auxiliando o modelo a gerar as respostas corretas.

class RAG(dspy.Module):
    def __init__(self, num_passages=3):
        super().__init__()

        self.retrieve = dspy.Retrieve(k=num_passages)
        self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
   
    def forward(self, question):
        context = self.retrieve(question).passages
        prediction = self.generate_answer(context=context, question=question)
        return dspy.Prediction(context=context, answer=prediction.answer)

Para ajudar o DSPy a criar os melhores prompts para nós, você precisa de um conjunto de dados que ele possa usar para testar os prompts e, depois, avaliá-los.

Para fornecer perguntas de teste ao DSPy, você carrega o conjunto de dados HotPotQA. O HotpotQA é um conjunto de dados de respostas a perguntas que apresenta perguntas naturais com vários saltos que exigem várias recuperações e inferências para chegar à resposta correta. É uma ótima ferramenta para testar o quão bem os modelos geram fatos corroboradores para treinar e testar sistemas de respostas a perguntas mais explicáveis. 

Por exemplo, uma pergunta do conjunto de dados é: "Quem o presidente Franklin Roosevelt nomeou como responsável pela transmissão dos votos do Colegiado Eleitoral para o Congresso?" Você pode ver que essa pergunta requer várias informações para ser respondida corretamente.

The answer is: “Robert Digges Wimberly Connor”.

O contexto corroborador vem de páginas da Wikipedia sobre Robert Digges Wimberly Connor e sobre a National Archives and Records Administration.

O HotPotQA é coletado e publicado por uma equipe de pesquisadores de NLP da Carnegie Mellon University, Stanford University e Universite de Montreal. Mais informações sobre o HotPotQA estão disponíveis no site do GitHub.

Depois de carregar o conjunto de dados, divida-o em conjuntos de treino e teste. Isso permite testar a cadeia de recuperação e ajudar o DSPy a localizar os melhores prompts para o modelo de linguagem.

# 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]

Em seguida, você fará bootstrapping de mais exemplos para dar ao DSPy mais oportunidades de gerar prompts e avaliá-los. O Callingcompile é o que usa toda a arquitetura que você configurou, bem como o conjunto de dados do HotPotQA para gerar e testar prompts e obter o melhor desempenho de seu modelo de linguagem.

  from dspy.teleprompt import BootstrapFewShot

# Validation logic: check that the predicted answer is correct.
# Also check that the retrieved context does actually 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 DSPy optimizer, which will compile your RAG program.
bfs_optimizer = BootstrapFewShot(metric=validate_context_and_answer)

# Compile!
compiled_rag = bfs_optimizer.compile(RAG(), trainset=trainset)

Agora que o DSPy fez a engenharia de prompts para você, você a testará com a pergunta personalizada sobre o Prêmio Nobel de 2006 que usou antes. Como o modelo de recuperação está usando extratos da Wikipedia de 2017, ele terá o melhor desempenho com o conhecimento que possa estar presente nesse corpus:

# Get the prediction. This contains `pred.context` and `pred.answer`.
pred = compiled_rag(test_question)

# Print the contexts and the answer.
print(f”Question: {test_question}”)
print(f”Predicted Answer: {pred.answer}”)

Agora você recebe a resposta correta. 

    Pergunta: De que país foi o vencedor do prêmio Nobel de literatura em 2006 e qual era seu nome?
    Resposta prevista: Turquia, Orhan Pamuk

Orhan Pamuk é da Turquia e, portanto, essa resposta está correta. A versão compilada do DSPy não só obteve a resposta correta, como também a enquadrou corretamente, respondendo com uma resposta curta e clara. Vamos ver o contexto dessa resposta prevista para ver como o modelo chegou à resposta correta:

pred.context

Isso retorna:

    ["Orhan Pamuk | Ferit Orhan Pamuk (conhecido em geral simplesmente como Orhan Pamuk; nascido em 7 de junho de 1952) é um romancista, roteirista e acadêmico turco e ganhador do Prêmio Nobel de Literatura de 2006. Um dos romancistas mais proeminentes da Turquia, sua obra vendeu mais de 13 milhões de livros em 63 idiomas, tornando-o o escritor mais vendido do país.",
     "2006 Palanca Awards | Os vencedores do Carlos Palanca Memorial Awards for Literature no ano de 2006 (classificação, título do trabalho vencedor, nome do autor).",
     "Miguel Donoso Pareja | Miguel Donoso Pareja (13 de julho de 1931 - 16 de março de 2015) foi um escritor equatoriano e vencedor do Prêmio Eugenio Espejo de 2006 (Prêmio Nacional do Equador em literatura, concedido pelo Presidente do Equador)."]

A resposta está na primeira parte do contexto retornado. Você pode ver como o DSPy criou prompts ideais examinando o histórico do modelo de linguagem usando o método inspect_history() do modelo de linguagem.

lm.inspect_history()

Esse histórico é muito longo, pois inclui todos os exemplos do processo de compilação em que o DSPy testou seus prompts gerados. A última parte do histórico mostra como o modelo chegou à resposta certa e no formato correto:

    [[ ## context ## ]][1] "Orhan Pamuk | Ferit Orhan Pamuk (conhecido em geral simplesmente como Orhan Pamuk; nascido em 7 de junho de 1952) é um romancista, roteirista e acadêmico turco e ganhador do Prêmio Nobel de Literatura de 2006. Um dos romancistas mais proeminentes da Turquia, sua obra vendeu mais de 13 milhões de livros em 63 idiomas, tornando-o o escritor mais vendido do país."
    [2] "2006 Palanca Awards | Os vencedores do Carlos Palanca Memorial Awards for Literature no ano de 2006 (classificação, título do trabalho vencedor, nome do autor)."
    [3] Miguel Donoso Pareja | Miguel Donoso Pareja (13 de julho de 1931 - 16 de março de 2015) foi um escritor equatoriano e vencedor do Prêmio Eugenio Espejo de 2006 (Prêmio Nacional do Equador em literatura, concedido pelo Presidente do Equador)."
    
    [[ ## question ## ]] De que país foi o vencedor do prêmio Nobel de literatura em 2006 e qual era seu nome?
    
    Responda com os campos de saída correspondentes, começando com o campo "[[ ## thinking ## ]]", depois "[[ ## answer ## ]]" e terminando com o marcador para "[[ ## completed ## ]]`.
    
    
    [31mResponse:[0m [32m[[ ## thinking ## ]] O texto menciona o Prêmio Nobel de Literatura de 2006 e afirma que Orhan Pamuk, um romancista turco, foi o vencedor.
    
    [[ ## answer ## ]] Turquia, Orhan Pamuk [[ ## completed ## ]][0m
    

Você pode ver que o DSPy usou o modelo para gerar o prompt:

Responda com os campos de saída correspondentes, começando pelo campo [[ ## reasoning ## ]] , então [[ ## answer ## ]] e terminando com o marcador para [[ ## completed ## ]] .

Isso leva à resposta e ao enquadramento corretos.

Resumo

Neste tutorial, você usou o DSPy para fazer ajustes finos em um agente de RAG com a plataforma watsonx. Seu agente de RAG consistia em um modelo de linguagem, Llama 3, e um modelo de recuperação, ColBERT. Em seguida, você usou o DSPy para fazer engenharia de prompts para uma tarefa de resposta a perguntas, compilando seu modelo e gerando um prompt otimizado.

Você pode aprender mais sobre o DSPy em seu repositório do GitHub, onde eles hospedam tutoriais, demonstrações e seus documentos.

Soluções relacionadas
IBM® watsonx.ai

Treine, valide, ajuste e implemente recursos de IA generativa, modelos de base e recursos de aprendizado de máquina com o IBM watsonx.ai, um estúdio empresarial de última geração para construtores de IA. Crie aplicações de IA em menos tempo com menos dados.

Explore o watsonx.ai
Soluções de inteligência artificial

Coloque a IA em ação na sua empresa com a experiência em IA líder do setor e com o portfólio de soluções da IBM.

Explore as soluções de IA
Consultoria e serviços em inteligência artificial (IA)

Os serviços de IA da IBM Consulting ajudam a reinventar a forma como as empresas trabalham com IA para gerar transformação.

Explore os serviços de IA
Dê o próximo passo

Ao utilizar a IA, o IBM Concert revela insights cruciais sobre suas operações e fornece recomendações específicas para cada aplicação com foco em melhorias. Descubra como o Concert pode impulsionar sua empresa.

Explorar Concert Explore as soluções de automação de processos de negócios