Activity 1.5: Prompting Techniques

Work in progress

This section is under construction. This information hasn’t been reviewed or edited yet!


Practical Activity Overview

Building on our previous chat application, we can expand it to create a prompt engineering lab that allows students to experiment with different prompting techniques and parameters.

Prerequisites

  • Python 3.8 or higher installed on your system
  • Basic familiarity with command line/terminal
  • A text editor or IDE of your choice

Activities

Step 1: Set Up Your Development Environment

1.1 Make sure you are using the virtual environment we created in the previous activity:

  • On Windows:
.\venv\Scripts\activate
  • On macOS/Linux:
source venv/bin/activate

Step 2: Add Parameter Controls

2.1 Add temperature and top-P controls:

# Add parameter controls to sidebar
st.sidebar.subheader("Generation Parameters")
temperature = st.sidebar.slider("Temperature", 0.0, 1.0, 0.7, 0.1, 
                              help="Higher values make output more random, lower more deterministic")
top_p = st.sidebar.slider("Top-P", 0.0, 1.0, 0.9, 0.1,
                        help="Controls diversity via nucleus sampling")

2.2 Add prompt technique selection:

# Add prompt technique selector
prompt_technique = st.sidebar.selectbox(
    "Prompt Technique",
    ["Basic", "Few-Shot Learning", "Chain-of-Thought", "Self-Consistency"]
)

# Function to apply prompt techniques
def apply_prompt_technique(user_input, technique):
    if technique == "Basic":
        return user_input
    
    elif technique == "Few-Shot Learning":
        return f"""Here are some examples of how to respond:

Question: What is the capital of France?
Answer: The capital of France is Paris.

Question: What is the boiling point of water?
Answer: Water boils at 100 degrees Celsius at standard pressure.

Now answer this question: {user_input}"""
    
    elif technique == "Chain-of-Thought":
        return f"""Think through this step-by-step to solve the problem:
{user_input}

Let's break this down into steps:
1. """
    
    elif technique == "Self-Consistency":
        return f"""Generate three different approaches to answer this question, then select the best one:
{user_input}

Approach 1:"""
    
    return user_input

Step 3: Add Comparative View

3.1 Add a comparative view for different techniques:

# Add a compare button
if st.sidebar.button("Compare All Techniques"):
    if "user_input" in st.session_state and st.session_state.user_input:
        st.subheader("Technique Comparison")
        
        techniques = ["Basic", "Few-Shot Learning", "Chain-of-Thought", "Self-Consistency"]
        
        # Create columns for comparison
        cols = st.columns(len(techniques))
        
        for i, technique in enumerate(techniques):
            with cols[i]:
                st.markdown(f"**{technique}**")
                modified_prompt = apply_prompt_technique(st.session_state.user_input, technique)
                
                with st.spinner(f"Generating {technique} response..."):
                    if model_type == "Ollama":
                        response = generate_ollama_response([{"role": "user", "content": modified_prompt}], 
                                                           temperature=temperature, top_p=top_p)
                    else:
                        response = generate_gemini_response([{"role": "user", "content": modified_prompt}],
                                                          temperature=temperature, top_p=top_p)
                
                st.text_area("Response", response, height=300)

Step 4: Update Model Response Functions

4.1 Update the model response functions to include parameters:

def generate_ollama_response(messages, temperature=0.7, top_p=0.9):
    # Convert our message format to Ollama's expected format
    ollama_messages = [{"role": m["role"], "content": m["content"]} for m in messages]
    
    response = requests.post("http://localhost:11434/api/chat", 
        json={
            "model": "llama3.2:1b",
            "messages": ollama_messages,
            "stream": False,
            "options": {
                "temperature": temperature,
                "top_p": top_p
            }
        }
    )
    if response.status_code == 200:
        return response.json()["message"]["content"]
    else:
        raise Exception(f"Ollama error: {response.text}")

def generate_gemini_response(messages, temperature=0.7, top_p=0.9):
    # Convert our messages to Gemini format
    gemini_messages = []
    for message in messages:
        role = "user" if message["role"] == "user" else "model"
        gemini_messages.append(types.Content(role=role, parts=[types.Part.from_text(text=message["content"])]))
    
    response = client.models.generate_content(
        model="gemini-2.0-flash",
        contents=gemini_messages,
        generation_config={
            "temperature": temperature,
            "top_p": top_p
        }
    )
    return response.text

Step 5: Add Prompt Template Library

5.1 Add a prompt template library:

# Add prompt template library
with st.sidebar.expander("Prompt Template Library"):
    templates = {
        "Summarize Text": "Summarize the following text in 3-5 bullet points: {{input}}",
        "Explain Concept": "Explain {{input}} in simple terms a high school student would understand.",
        "Compare and Contrast": "Compare and contrast {{input}}. Highlight key similarities and differences.",
        "Generate Ideas": "Generate 5 creative ideas related to {{input}}.",
        "Analyze Argument": "Analyze the following argument and identify any logical fallacies: {{input}}"
    }
    
    selected_template = st.selectbox("Select Template", list(templates.keys()))
    
    if st.button("Apply Template"):
        template = templates[selected_template]
        if "user_input" in st.session_state and st.session_state.user_input:
            new_prompt = template.replace("{{input}}", st.session_state.user_input)
            st.session_state.user_input = new_prompt
            st.experimental_rerun()

Step 6: Experiment and Observe

6.1 Now that you’ve implemented the prompt engineering lab, experiment with:

  1. Different prompting techniques (Basic, Few-Shot, Chain-of-Thought, Self-Consistency)
  2. Adjusting temperature and top-P parameters to see how they affect outputs
  3. Comparing responses across different techniques side-by-side
  4. Using pre-built prompt templates for common tasks
  5. Maintaining conversation context while experimenting

Key Learning Points

  • Experiment with different prompting techniques (Basic, Few-Shot, Chain-of-Thought, Self-Consistency)
  • Adjust temperature and top-P parameters to see how they affect outputs
  • Compare responses across different techniques side-by-side
  • Use pre-built prompt templates for common tasks
  • Maintain conversation context while experimenting
Security Note

Never commit your .env file to version control. Add it to your .gitignore file if you’re using Git.

Complete Script

Here’s the full app.py script with all the prompt engineering lab additions:

import os
import streamlit as st
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.llms import Ollama
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage, HumanMessage
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Check for API key
if not os.getenv('GOOGLE_API_KEY'):
    st.error("Please set your Google API Key in the .env file!")
    st.stop()

st.title("Prompt Engineering Lab")
model_type = st.sidebar.selectbox("Model", ["Gemini", "Ollama"])

# Initialize session state
if "messages" not in st.session_state:
    st.session_state.messages = []
if "user_input" not in st.session_state:
    st.session_state.user_input = ""

# Add parameter controls to sidebar
st.sidebar.subheader("Generation Parameters")
temperature = st.sidebar.slider("Temperature", 0.0, 1.0, 0.7, 0.1)
top_p = st.sidebar.slider("Top-P", 0.0, 1.0, 0.9, 0.1)
context_window = st.sidebar.slider("Context Window Size", 1, 10, 5)

# Prompt technique library
PROMPT_TECHNIQUES = {
    "Basic": lambda query: query,
    "Few-Shot Learning": lambda query: f"""Here are some examples of how to respond:
Question: What is the capital of France?
Answer: The capital of France is Paris.
Question: What is the boiling point of water?
Answer: Water boils at 100 degrees Celsius at standard pressure.
Now answer this question: {query}""",
    "Chain-of-Thought": lambda query: f"""Think through this step-by-step to solve the problem:
{query}
Let's break this down into steps:
1. """,
    "Self-Consistency": lambda query: f"""Generate three different approaches to answer this question, then select the best one:
{query}
Approach 1:"""
}

# Prompt template library
PROMPT_TEMPLATES = {
    "Summarize Text": "Summarize the following text in 3-5 bullet points: {input}",
    "Explain Concept": "Explain {input} in simple terms a high school student would understand.",
    "Compare and Contrast": "Compare and contrast {input}. Highlight key similarities and differences.",
    "Generate Ideas": "Generate 5 creative ideas related to {input}.",
    "Analyze Argument": "Analyze the following argument and identify any logical fallacies: {input}"
}

# Add prompt technique selector
prompt_technique = st.sidebar.selectbox("Prompt Technique", list(PROMPT_TECHNIQUES.keys()))

# Add prompt template library
with st.sidebar.expander("Prompt Template Library"):
    selected_template = st.selectbox("Select Template", list(PROMPT_TEMPLATES.keys()))
    if st.button("Apply Template"):
        if st.session_state.user_input:
            new_prompt = PROMPT_TEMPLATES[selected_template].replace("{input}", st.session_state.user_input)
            st.session_state.user_input = new_prompt
            st.experimental_rerun()

# Display chat history
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.write(message["content"])

# Create a function to get the appropriate model with parameters
@st.cache_resource
def get_conversation_chain():
    if model_type == "Gemini":
        llm = ChatGoogleGenerativeAI(
            model="gemini-2.0-flash",
            temperature=temperature,
            top_p=top_p
        )
    else:
        llm = Ollama(
            model="llama3.2:1b", 
            base_url="http://localhost:11434",
            temperature=temperature,
            top_p=top_p
        )
    
    # Create a system message template
    system_template = "You are a helpful AI assistant."
    
    # Create a chat prompt template with memory
    prompt = ChatPromptTemplate.from_messages([
        SystemMessage(content=system_template),
        MessagesPlaceholder(variable_name="history"),
        HumanMessage(content="{input}")
    ])
    
    # Initialize memory
    memory = ConversationBufferMemory(k=context_window, return_messages=True, memory_key="history")
    
    # Load existing conversation into memory
    for msg in st.session_state.messages[-context_window:]:
        if msg["role"] == "user":
            memory.chat_memory.add_user_message(msg["content"])
        else:
            memory.chat_memory.add_ai_message(msg["content"])
    
    # Create the conversation chain
    chain = ConversationChain(llm=llm, prompt=prompt, memory=memory, verbose=False)
    
    return chain, max_tokens

# Add token counting display
if st.session_state.messages:
    chain, max_tokens = get_conversation_chain()
    total_tokens = sum(len(msg["content"].split()) * 1.3 for msg in st.session_state.messages)
    st.sidebar.metric("Estimated Tokens Used", int(total_tokens))
    st.sidebar.progress(min(1.0, total_tokens / max_tokens))
    st.sidebar.text(f"{model_type} context: ~{max_tokens} tokens")

# Add a compare button
if st.sidebar.button("Compare All Techniques"):
    if st.session_state.user_input:
        st.subheader("Technique Comparison")
        cols = st.columns(len(PROMPT_TECHNIQUES))
        
        for i, (technique_name, technique_func) in enumerate(PROMPT_TECHNIQUES.items()):
            with cols[i]:
                st.markdown(f"**{technique_name}**")
                modified_prompt = technique_func(st.session_state.user_input)
                
                with st.spinner(f"Generating {technique_name} response..."):
                    chain, _ = get_conversation_chain()
                    response = chain.invoke({"input": modified_prompt})
                    
                st.text_area("Response", response["response"], height=300)

user_input = st.chat_input("Type your message here...")
if user_input:
    # Store the user input for template application
    st.session_state.user_input = user_input
    
    # Apply selected prompt technique
    modified_input = PROMPT_TECHNIQUES[prompt_technique](user_input)
    
    # Add user message to history
    st.session_state.messages.append({"role": "user", "content": modified_input})
    
    # Display user message
    with st.chat_message("user"):
        st.write(modified_input)
    
    try:
        # Get the conversation chain
        chain, _ = get_conversation_chain()
        
        # Generate response
        response = chain.invoke({"input": modified_input})
        assistant_response = response["response"]
        
        # Add assistant response to history
        st.session_state.messages.append({"role": "assistant", "content": assistant_response})
        
        # Display assistant response
        with st.chat_message("assistant"):
            st.write(assistant_response)
    except Exception as e:
        st.error(str(e))

# Add button to clear conversation history
if st.sidebar.button("Clear Conversation"):
    st.session_state.messages = []
    st.session_state.user_input = ""
    st.experimental_rerun()