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:
- Detect when a query requires information beyond our document knowledge base
- Autonomously decide whether to use local RAG or web search
- Perform web searches for current or specialized information
- 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 requestsNew Code Sections to Add
1. Add Imports for Web Search
import requests
from urllib.parse import quote_plus2. 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 response5. 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
-
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
-
Information Integration:
- See how the system combines information from both sources
- Test queries where RAG and web search might provide complementary information
-
Comparison Tool:
- Use the “Compare Information Sources” button to see the differences in responses
- Notice how each source might provide different perspectives or details
-
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
-
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:
-
Hybrid Knowledge Systems: How to combine local knowledge bases with real-time web information
-
Intent Detection: How to create systems that can autonomously decide which information source to use
-
Information Synthesis: How to prompt models to integrate information from multiple sources
-
Source Attribution: How to implement proper citation of information sources in AI systems
-
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()