RAG: A simple introduction¶

Notebook content¶

This notebook contains the steps and code to demonstrate the retrieval-augmented generation pattern in IBM watsonx.ai on IBM Cloud. If you are using IBM watsonx.ai on-premises, then see the other versions of this notebook.

Some familiarity with Python is helpful. This notebook uses Python 3.10.

Learning goal¶

The goal of this notebook is to demonstrate how to apply the retrieval-augmented generation pattern to a question-answering use case in watsonx.ai.

Scenario¶

The website for an online seed catalog has many articles to help customers plan their garden and ultimately select which seeds to purchase. A new widget is being added to the website to answer customer questions based on the contents of the articles.

Contents¶

This notebook contains the following parts:

  • Overview of retrieval-augmented generation
  • Step 1: Set up prerequisites
  • Step 2: Create a knowledge base
  • Step 3: Build a simple search component
  • Step 4: Craft prompt text
  • Step 5: Generate output using the foundation models Python library
  • Step 6: Pull everything together to perform retrieval-augmented generation
  • Summary

Overview of retrieval-augmented generation¶

The retrieval-augmented generation pattern involves three basic steps:

  1. Search for relevant content in your knowledge base
  2. Pull the most relevant content into your prompt as context
  3. Send the combined prompt text to a foundation model to generate output

The term retrieval-augmented generation (RAG) was introduced in this paper: Retrieval-augmented generation for knowledge-intensive NLP tasks

"We build RAG models where the parametric memory is a pre-trained seq2seq transformer, and the

non-parametric memory is a dense vector index of Wikipedia, accessed with a pre-trained neural retriever."

In that paper, the term "RAG models" refers to a specific implementation of a retriever (a specific query encoder and vector-based document search index) and a generator (a specific pre-trained, generative language model.)

However, the basic search-and-generate approach can be generalized to use different retriever components and foundation models.

In this notebook:

  • The knowledge base is a list of two articles
  • The retrieval component consists of a simple search function
  • The generate component uses the foundation model Python library in watsonx.ai

Step 1: Set up prerequisites¶

Before you use the sample code in this notebook, you must perform setup tasks.

1.1 Associate an instance of the Watson Machine Learning service with the current project¶

The current project is the project in which you are running this notebook.

If an instance of Watson Machine Learning is not already associated with the current project, follow the instructions in this topic to do so: Adding associated services to a project

1.2 Create an IBM Cloud API key¶

Create an IBM Cloud API key by following these instructions: Creating an IBM Cloud API key

Then paste your new IBM Cloud API key in the code cell below.

In [1]:
cloud_apikey = ""

1.3 Define a credentials object with the IBM Cloud API key¶

In [2]:
credentials = { 
    "url"    : "https://us-south.ml.cloud.ibm.com", 
    "apikey" : cloud_apikey
}

Step 2: Create a knowledge base¶

In this notebook, the knowledge base is a collection of two articles.

(These articles were written as samples for watsonx.ai, they are not real articles published anywhere else. The authors and publication dates are fictional.)

In [3]:
article_01 = \
"Tomatoes are one of the most popular plants for vegetable gardens.  Tip for success: If you select " \
"varieties that are resistant to disease and pests, growing tomatoes can be quite easy.  For "        \
"experienced gardeners looking for a challenge, there are endless heirloom and specialty varieties "  \
"to cultivate.  Tomato plants come in a range of sizes.  There are varieties that stay very small, "  \
"less than 12 inches, and grow well in a pot or hanging basket on a balcony or patio.  Some grow "    \
"into bushes that are a few feet high and wide, and can be grown is larger containers.  Other "       \
"varieties grow into huge bushes that are several feet wide and high in a planter or garden bed.  "   \
"Still other varieties grow as long vines, six feet or more, and love to climb trellises.  Tomato "   \
"plants do best in full sun.  You need to water tomatoes deeply and often.  Using mulch prevents "    \
"soil-borne disease from splashing up onto the fruit when you water.  Pruning suckers and even "      \
"pinching the tips will encourage the plant to put all its energy into producing fruit."
In [4]:
article_02 = \
"Cucumbers are fun to grow for beginning gardeners and advanced gardeners alike.  There are two "     \
"types of cucumbers: slicing and pickling.  Pickling cucumbers are smaller than slicing cucumbers.  " \
"Cucumber plants come in two types: vining cucumbers, which are more common, and bush cucumbers.  "   \
"Vining cucumbers, which can grow to more than 5 feet tall, grow fast, yield lots of fruit, and you " \
"can train them up a trellis.  Growing cucumbers up a trellis or fence can maximize garden space, "   \
"keep fruit clean, and make it easier to harvest the fruit.  Tropical plants, cucumbers are very "    \
"sensitive to frost or cold weather. Cucumbers prefer full sun for 6 to 8 hours per day.  Cucumbers " \
"need constant watering.  Cucumbers can grow quickly and ripen in just 6 weeks.  Harvest cucumbers "  \
"every day or two because the more you harvest, the more the plant will produce.  If any cucumber "   \
"is left on the vine to fully mature, the plant will stop producing more cucumbers.  You can extend " \
"the harvest season by planting cucumbers in batches, 2 weeks apart."
In [5]:
knowledge_base = [ 
    { 
        "title"     : "Growing tomatoes", 
        "Author"    : "A. Rossi",
        "Published" : "2010",
        "txt"       : article_01 
    }, 
    {
        "title"     : "Cucumbers for beginners",
        "Author"    : "B. Melnyk",
        "Published" : "2018",
        "txt"       : article_02 
    }
]

Step 3: Build a simple search component¶

Many articles that discuss retrieval-augmented generation assume the retrieval component uses a vector database.

However, to perform the general retrieval-augmented generation pattern, any search-and-retrieve method that can reliably return relevant content from the knowledge base will do.

In this notebook, the search component is a trivial search function that returns the index of one or the other of the two articles in the knowledge base, based on a simple regular expression match.

In [6]:
import re

def search( query_in, knowledge_base_in ):
    if re.match( r".*tomato.*", query_in, re.IGNORECASE ):
        return 0
    elif re.match( r".*cucumber.*", query_in, re.IGNORECASE ):
        return 1
    return -1
In [ ]:
index = search( "How tall do tomatoes grow?", knowledge_base )

if index >= 0:
    print( "Index: " + str( index ) + "\nArticle: \"" + knowledge_base[index]["title"] + "\"" )
else:
    print( "No matching content was found" )

Step 4: Craft prompt text¶

In this notebook, the task to be performed is a question-answering task.

There is no one, best prompt for any given task. However, models that have been instruction-tuned, such as bigscience/mt0-xxl-13b, google/flan-t5-xxl-11b, or google/flan-ul2-20b, can generally perform this task with the sample prompt below. Conservative decoding methods tend towards succinct answers.

In the prompt below, notice two string placeholders (marked with %s) that will be replaced at generation time:

  • The first placeholder will be replaced with the text of the relevant article from the knowledge base
  • The second placeholder will be replaced with the question to be answered
In [8]:
prompt_template = """
Article:
###
%s
###

Answer the following question using only information from the article. 
Answer in a complete sentence, with proper capitalization and punctuation. 
If there is no good answer in the article, say "I don't know".

Question: %s
Answer: 
"""

def augment( template_in, context_in, query_in ):
    return template_in % ( context_in,  query_in )
In [ ]:
query = "How tall do cucumber plants grow?"

article_txt = knowledge_base[1]["txt"]

augmented_prompt = augment( prompt_template, article_txt, query )

print( augmented_prompt )

Step 5: Generate output using the foundation models Python library¶

You can prompt foundation models in watsonx.ai programmatically using the Python library.

See:

  • Introduction to the foundation models Python library
  • Foundation models Python library reference
In [10]:
import os
from ibm_watson_machine_learning.foundation_models import Model

model_id = "google/flan-t5-xxl"

gen_parms = { 
    "DECODING_METHOD" : "greedy", 
    "MIN_NEW_TOKENS" : 1, 
    "MAX_NEW_TOKENS" : 50 
}

project_id = os.environ["PROJECT_ID"]

model = Model( model_id, credentials, gen_parms, project_id )
In [11]:
import json

def generate( model_in, augmented_prompt_in ):
    
    generated_response = model_in.generate( augmented_prompt_in )

    if ( "results" in generated_response ) \
       and ( len( generated_response["results"] ) > 0 ) \
       and ( "generated_text" in generated_response["results"][0] ):
        return generated_response["results"][0]["generated_text"]
    else:
        print( "The model failed to generate an answer" )
        print( "\nDebug info:\n" + json.dumps( generated_response, indent=3 ) )
        return ""
In [ ]:
output = generate( model, augmented_prompt )
print( output )

Step 6: Pull everything together to perform retrieval-augmented generation¶

In [13]:
def searchAndAnswer( knowledge_base_in, model ):
    
    question = input( "Type your question:\n")
    if not re.match( r"\S+", question ):
        print( "No question")
        return
        
    # Retrieve the relevant content
    top_matching_index = search( question, knowledge_base_in )
    if top_matching_index < 0:
        print( "No good answer was found in the knowledge base" )
        return;
    asset = knowledge_base_in[top_matching_index]
    asset_txt = asset["txt"]
    
    # Augment a prompt with context
    augmented_prompt = augment( prompt_template, asset_txt, question )
    
    # Generate output
    output = generate( model, augmented_prompt )
    if not re.match( r"\S+", output ):
        print( "The model failed to generate an answer")
    print( "\nAnswer:\n" + output )
    print( "\nSource: \"" + asset["title"] + "\", " + asset["Author"] + " (" + asset["Published"] + ")"  )

Test the solution by running the following cell multiple times.

*You will be prompted to enter a question each time.

In [ ]:
searchAndAnswer( knowledge_base, model )

Summary and next steps¶

You successfully completed this notebook!.

You learned how to apply the general retrieval-augmented generation pattern with a simple search component and a small knowledge base using watonx.ai.

Check out our Documentation for more samples, tutorials, documentation, and how-tos.

Other versions of this notebook¶

If you are using an on-premises version of IBM watsonx.ai, then use one of the following notebooks:

  • watsonx.ai version 1.0 on-premises
  • watsonx.ai version 2.0 on-premises

Authors¶

Sarah Packowski, AI ContentOps - IBM Data and AI.

Copyright © 2023-2024 IBM. This notebook and its source code are released under the terms of the MIT License.