A research agent is the best first agent to build. It is complex enough to demonstrate real agentic behaviour — planning, tool use, iterating on results — but simple enough to understand end to end. Here is how to build one.
What the Agent Will Do
Given a question like "What are the main risks of deploying LLMs in production?", the agent will:
- ▸Search the web for relevant results
- ▸Look up key terms on Wikipedia if needed
- ▸Read and synthesise the information
- ▸Return a researched, sourced answer
This is the ReAct (Reasoning + Acting) loop: think about what to do, call a tool, observe the result, think about what to do next.
Setup
pip install langchain langchain-community langchain-openai \ wikipedia duckduckgo-search
Set your API key:
export OPENAI_API_KEY=sk-...
Step 1: Define the Tools
Tools are functions the agent can call. LangChain wraps several common ones out of the box.
from langchain_community.tools import WikipediaQueryRun, DuckDuckGoSearchRun from langchain_community.utilities import WikipediaAPIWrapper # Web search search = DuckDuckGoSearchRun() # Wikipedia lookup wiki = WikipediaQueryRun( api_wrapper=WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=2000) ) tools = [search, wiki]
Step 2: Create the Agent
LangChain's create_react_agent builds the ReAct prompting framework automatically. You provide the LLM and the tools.
from langchain import hub from langchain.agents import create_react_agent, AgentExecutor from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # Pull the standard ReAct prompt from LangChain Hub prompt = hub.pull("hwchase17/react") agent = create_react_agent(llm, tools, prompt) executor = AgentExecutor( agent=agent, tools=tools, verbose=True, # prints the agent's reasoning steps max_iterations=5, # prevent infinite loops handle_parsing_errors=True )
Step 3: Run the Agent
result = executor.invoke({ "input": "What are the main risks of deploying LLMs in production? " "Include recent examples where possible." }) print(result["output"])
With verbose=True you will see the agent's inner monologue:
Thought: I need to find current information about LLM production risks.
Action: duckduckgo_search
Action Input: "LLM production deployment risks 2024"
Observation: [search results...]
Thought: I have some results. Let me also check Wikipedia for foundational concepts.
Action: wikipedia
Action Input: "Large language model hallucination"
Observation: [Wikipedia content...]
Thought: I now have enough information to synthesise a comprehensive answer.
Final Answer: [researched response]
That is the full ReAct loop. The agent decides which tool to use, in what order, based on what it finds.
Step 4: Add Memory
By default the agent has no memory between calls. Add conversation history so users can ask follow-up questions.
from langchain.memory import ConversationBufferWindowMemory memory = ConversationBufferWindowMemory( k=5, # remember last 5 exchanges memory_key="chat_history", return_messages=True ) executor_with_memory = AgentExecutor( agent=agent, tools=tools, memory=memory, verbose=True, max_iterations=5 ) # First question executor_with_memory.invoke({"input": "What are the risks of deploying LLMs?"}) # Follow-up — agent remembers the context executor_with_memory.invoke({"input": "Which of those risks is hardest to mitigate?"})
Step 5: Add a Custom Tool
The real power of agents is custom tools. Here is a tool that reads a URL and returns its text content:
from langchain.tools import tool import httpx from bs4 import BeautifulSoup @tool def read_webpage(url: str) -> str: """Reads and returns the text content of a webpage. Use this when you have a specific URL to read.""" try: response = httpx.get(url, timeout=10, follow_redirects=True) soup = BeautifulSoup(response.text, "html.parser") # Remove script and style elements for tag in soup(["script", "style", "nav", "footer"]): tag.decompose() text = soup.get_text(separator="\n", strip=True) return text[:3000] # limit to 3000 chars except Exception as e: return f"Could not read URL: {str(e)}" # Add to tools list tools = [search, wiki, read_webpage]
The docstring is critical — the agent reads it to decide when to use the tool. Write it as a clear description of what the tool does and when to use it.
Full Working Code (Under 50 Lines)
import os from langchain import hub from langchain.agents import create_react_agent, AgentExecutor from langchain.memory import ConversationBufferWindowMemory from langchain_community.tools import WikipediaQueryRun, DuckDuckGoSearchRun from langchain_community.utilities import WikipediaAPIWrapper from langchain_openai import ChatOpenAI os.environ["OPENAI_API_KEY"] = "your-key-here" # Tools tools = [ DuckDuckGoSearchRun(), WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper(top_k_results=2)) ] # LLM + Agent llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = hub.pull("hwchase17/react") agent = create_react_agent(llm, tools, prompt) # Memory memory = ConversationBufferWindowMemory( k=5, memory_key="chat_history", return_messages=True ) # Executor executor = AgentExecutor( agent=agent, tools=tools, memory=memory, verbose=True, max_iterations=6, handle_parsing_errors=True ) # Run while True: question = input("\nYou: ") if question.lower() in ["quit", "exit"]: break result = executor.invoke({"input": question}) print(f"\nAgent: {result['output']}")
What to Build Next
- ▸Replace DuckDuckGo with a custom tool that searches your internal documentation
- ▸Add a tool that queries a database or runs Python code
- ▸Replace
ConversationBufferWindowMemorywith a vector-store backed memory for longer conversations - ▸Swap GPT-4o-mini for a local Ollama model for a fully offline agent
The pattern stays the same. The power comes from the tools you give it and the quality of their docstrings.