Activity 1.7: Building an Autonomous Agent with Web Search

Work in progress

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


Practical Activity Overview

In this activity, we’ll extend our RAG system to create a simple autonomous agent that can search the web when needed. Our agent will:

  1. Detect when a query requires information beyond our document knowledge base
  2. Autonomously decide whether to use local RAG or web search
  3. Perform web searches for current or specialized information
  4. Combine information from documents and the web as needed

By the end of this activity, you’ll have a hybrid system that leverages both your local document knowledge base and real-time web information.

Libraries to Install

pip install requests

New Code Sections to Add

import requests
from urllib.parse import quote_plus

2. Add Search Intent Detection

def detect_search_intent(query, vector_store, model_type, temperature=0.1):
    """Determine if the query requires web search based on RAG results quality"""
    # First check if we have relevant documents
    if vector_store:
        results = rag_query(vector_store, query, n_results=2)
        # Get similarity score of top result
        if results and len(results["distances"]) > 0 and results["distances"][0]:
            top_similarity = results["distances"][0][0]
            # If we have a good match in our documents, use RAG
            if top_similarity  0:
        rag_context = "\n\n".join(rag_results["documents"][0])
    
    # Prepare context from search results
    search_context = ""
    if search_results:
        search_context = "\n\n".join([
            f"Title: {r['title']}\nLink: {r['link']}\nSnippet: {r['snippet']}" 
            for r in search_results
        ])
    
    # Create combined prompt
    prompt = f"""Answer the following question using the provided information sources.

Question: {query}

"""
    
    if rag_context:
        prompt += f"""Document Knowledge:
{rag_context}

"""
    
    if search_context:
        prompt += f"""Web Search Results:
{search_context}

"""
    
    prompt += "Answer:"
    
    # Generate response
    if model_type == "Ollama":
        response = generate_ollama_response([{"role": "user", "content": prompt}], 
                                          temperature=temperature, top_p=top_p)
    else:
        response = generate_gemini_response([{"role": "user", "content": prompt}],
                                          temperature=temperature, top_p=top_p)
    
    return response

5. Update the Chat Input Handler

# Update the user input handler section
user_input = st.chat_input("Type your message here...")
if user_input:
    # Store the user input
    st.session_state.user_input = user_input
    
    # Add user message to history
    st.session_state.messages.append({"role": "user", "content": user_input})
    
    # Display user message
    with st.chat_message("user"):
        st.write(user_input)
    
    try:
        # Decide whether to use web search
        use_search = detect_search_intent(user_input, 
                                        st.session_state.vector_store if use_rag else None, 
                                        model_type)
        
        # Get RAG results if available and enabled
        rag_results = None
        if use_rag and st.session_state.vector_store:
            rag_results = rag_query(st.session_state.vector_store, user_input, n_results)
        
        # Get web search results if needed
        search_results = None
        if use_search:
            with st.spinner("Searching the web..."):
                search_results = search_web(user_input, num_results=3)
        
        # Generate response using available information
        if rag_results or search_results:
            # If we have either RAG or search results, use hybrid approach
            response = generate_hybrid_response(user_input, rag_results, search_results, 
                                              model_type, temperature, top_p)
        else:
            # Fallback to regular chat
            recent_messages = st.session_state.messages[-context_window:]
            if model_type == "Ollama":
                response = generate_ollama_response(recent_messages, 
                                                  temperature=temperature, top_p=top_p)
            else:
                response = generate_gemini_response(recent_messages,
                                                  temperature=temperature, top_p=top_p)
        
        # Add assistant response to history
        st.session_state.messages.append({"role": "assistant", "content": response})
        
        # Display assistant response
        with st.chat_message("assistant"):
            st.write(response)
            
            # Show source information if we used search
            if search_results:
                with st.expander("Web Sources"):
                    for i, result in enumerate(search_results):
                        st.markdown(f"**{i+1}. [{result['title']}]({result['link']})**")
                        st.markdown(f"{result['snippet']}")
                        st.markdown("---")
            
    except Exception as e:
        st.error(str(e))

6. Add Web Search UI Controls

# Add to the sidebar section
st.sidebar.subheader("Agent Settings")
use_websearch = st.sidebar.checkbox("Enable Web Search", value=True, 
                                  help="Allow the agent to search the web for information")

# Add a compare button for RAG, Web Search, and Hybrid
if st.sidebar.button("Compare Information Sources"):
    if "user_input" in st.session_state and st.session_state.user_input:
        query = st.session_state.user_input
        
        st.subheader("Information Source Comparison")
        
        # Create columns for the comparison
        col1, col2, col3 = st.columns(3)
        
        # RAG Only
        with col1:
            st.markdown("**RAG Only**")
            if st.session_state.vector_store:
                results = rag_query(st.session_state.vector_store, query, n_results)
                if results["documents"]:
                    context = "\n\n".join(results["documents"][0])
                    with st.spinner("Generating RAG response..."):
                        rag_response = generate_with_context(query, context, model_type, temperature, top_p)
                    st.text_area("Response", rag_response, height=250)
                else:
                    st.info("No relevant documents found")
            else:
                st.info("No document store available")
        
        # Web Search Only
        with col2:
            st.markdown("**Web Search Only**")
            with st.spinner("Searching the web..."):
                search_results = search_web(query, num_results=3)
            
            if search_results:
                search_context = "\n\n".join([
                    f"Title: {r['title']}\nLink: {r['link']}\nSnippet: {r['snippet']}" 
                    for r in search_results
                ])
                with st.spinner("Generating search response..."):
                    search_prompt = f"""Use these search results to answer: {query}
                    
Search Results:
{search_context}

Answer:"""
                    if model_type == "Ollama":
                        search_response = generate_ollama_response([{"role": "user", "content": search_prompt}], 
                                                                temperature=temperature, top_p=top_p)
                    else:
                        search_response = generate_gemini_response([{"role": "user", "content": search_prompt}],
                                                                temperature=temperature, top_p=top_p)
                st.text_area("Response", search_response, height=250)
            else:
                st.info("No search results found")
        
        # Hybrid Approach
        with col3:
            st.markdown("**Hybrid Approach**")
            
            rag_results = None
            if st.session_state.vector_store:
                rag_results = rag_query(st.session_state.vector_store, query, n_results)
            
            with st.spinner("Searching the web..."):
                search_results = search_web(query, num_results=3)
            
            if rag_results or search_results:
                with st.spinner("Generating hybrid response..."):
                    hybrid_response = generate_hybrid_response(query, rag_results, search_results, 
                                                            model_type, temperature, top_p)
                st.text_area("Response", hybrid_response, height=250)
            else:
                st.info("No information sources available")

What to Test

  1. Autonomous Decision Making:

    • Ask questions that clearly need web search (e.g., “What is the current population of Tokyo?”)
    • Ask questions that should use your documents (if you’ve uploaded some)
    • Ask ambiguous questions that might use both
  2. Information Integration:

    • See how the system combines information from both sources
    • Test queries where RAG and web search might provide complementary information
  3. Comparison Tool:

    • Use the “Compare Information Sources” button to see the differences in responses
    • Notice how each source might provide different perspectives or details
  4. Source Citation:

    • When web search is used, examine the sources provided in the expandable section
    • Verify that the information in the response matches the cited sources
  5. Different Types of Queries:

    • Current events (should trigger web search)
    • Historical information (might use RAG if in your documents)
    • Conceptual questions (might use base model knowledge)

Key Learning Outcomes

After completing this activity, you should understand:

  1. Hybrid Knowledge Systems: How to combine local knowledge bases with real-time web information

  2. Intent Detection: How to create systems that can autonomously decide which information source to use

  3. Information Synthesis: How to prompt models to integrate information from multiple sources

  4. Source Attribution: How to implement proper citation of information sources in AI systems

  5. Agent Architecture: The fundamental components of AI agents that can make decisions and use tools

Complete Script

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 langchain_community.tools import DuckDuckGoSearchRun
from langchain.agents import Tool, AgentExecutor, create_react_agent
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("Agentic Chat App with Web Search")
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_llm():
    if model_type == "Gemini":
        return ChatGoogleGenerativeAI(
            model="gemini-2.0-flash",
            temperature=temperature,
            top_p=top_p
        )
    else:
        return Ollama(
            model="llama3.2:1b", 
            base_url="http://localhost:11434",
            temperature=temperature,
            top_p=top_p
        )

# Set up the DuckDuckGo search tool
search_tool = DuckDuckGoSearchRun()
tools = [
    Tool(
        name="DuckDuckGo Search",
        func=search_tool.run,
        description="Useful for searching the internet for current information."
    )
]

# Create the agent
llm = get_llm()
prompt = ChatPromptTemplate.from_messages([
    SystemMessage(content="You are a helpful AI assistant with access to internet search."),
    MessagesPlaceholder(variable_name="chat_history"),
    HumanMessage(content="{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])

agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Initialize conversation memory
memory = ConversationBufferMemory(return_messages=True, memory_key="chat_history")

user_input = st.chat_input("Type your message here...")
if user_input:
    st.session_state.messages.append({"role": "user", "content": user_input})
    with st.chat_message("user"):
        st.write(user_input)

    with st.chat_message("assistant"):
        with st.spinner("Thinking..."):
            response = agent_executor.invoke({
                "input": user_input,
                "chat_history": memory.chat_memory.messages
            })
            st.write(response["output"])
            memory.chat_memory.add_user_message(user_input)
            memory.chat_memory.add_ai_message(response["output"])

    st.session_state.messages.append({"role": "assistant", "content": response["output"]})

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