The era of “talking to AI” is ending. The era of AI doing work has begun.
For the last two years, we’ve been obsessed with RAG (Retrieval Augmented Generation)—essentially teaching a chatbot to read a PDF and answer questions. But in late 2025, the industry standard has shifted. We are no longer building chatbots that wait for a prompt; we are building Agents that receive a goal, plan a workflow, execute tasks, and correct their own errors.
Today, we are going to build your first “AI Intern”: an autonomous research agent that can take a simple topic (e.g., “The future of solid-state batteries”), browse the live web, analyze multiple sources, and generate a comprehensive briefing document—all without you typing a second prompt.
The Tech Stack
To build this, we need a framework that handles state (memory of what has been done) and cycles (looping back to fix errors or search again).
- Language: Python
- Orchestrator: LangGraph (The industry standard for stateful, multi-actor applications).
- LLM: OpenAI GPT-4o (or Anthropic Claude 3.5 Sonnet).
- Tools: Tavily API (A search engine built specifically for AI agents).
The Core Concept: Chains vs. Graphs
Traditional AI chains (like LangChain) are linear: Step A -> Step B -> Step C. Agents, however, are cyclical. They need to loop.
- The Agent Node: Decides what to do next.
- The Tool Node: Executes an action (e.g., searches Google).
- The Edge: Connects them. If the Agent says “I need more info,” it loops to the Tool Node. If it says “I’m done,” it ends.
Step 1: Setting the State
In LangGraph, the “State” is the brain of the operation. It tracks the conversation history and the results of any tool calls.
Python
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
# The "Brain" of our agent
class AgentState(TypedDict):
# 'messages' tracks the conversation history.
# 'add_messages' ensures new messages are appended, not overwritten.
messages: Annotated[list, add_messages]
Step 2: Giving the Agent Tools
An agent without tools is just a philosopher. We need to give it eyes (Search). We’ll use Tavily, which returns clean text results optimized for LLMs, avoiding the mess of HTML parsing.
Python
from langchain_community.tools.tavily_search import TavilySearchResults
# Initialize the search tool (requires TAVILY_API_KEY)
tool = TavilySearchResults(max_results=3)
tools = [tool]
Step 3: Building the Nodes
We need two primary nodes:
- The Reasoner (LLM): Looks at the state and decides “Call a tool” or “Give final answer.”
- The Doer (Tool Node): Actually runs the search when requested.
Python
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode
# Bind the tools to the LLM so it knows they exist
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools)
# Node 1: The Agent (Decision Maker)
def agent_node(state: AgentState):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# Node 2: The Tool Executor
tool_node = ToolNode(tools)
Step 4: Wiring the Graph
This is where the magic happens. We define the logic flow.
- Start at the Agent.
- Check: Did the Agent ask to use a tool?
- Yes: Go to Tool Node, then loop back to Agent.
- No: End the process.
Python
from langgraph.graph import END, START
workflow = StateGraph(AgentState)
# Add the nodes
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)
# Define the entry point
workflow.add_edge(START, "agent")
# Define the conditional logic (The "Manager")
def should_continue(state: AgentState):
last_message = state["messages"][-1]
# If the LLM made a tool call, go to tools
if last_message.tool_calls:
return "tools"
# Otherwise, stop
return END
workflow.add_conditional_edges("agent", should_continue)
workflow.add_edge("tools", "agent") # Always loop back to agent after using a tool
# Compile the graph into a runnable app
app = workflow.compile()
Step 5: Running Your AI Intern
Now we simply invoke the graph with a user prompt.
Python
initial_state = {"messages": [("user", "Research the current state of Quantum Computing in 2025 and write a brief summary.")]}
# Stream the output to watch the agent "think"
for event in app.stream(initial_state):
for value in event.values():
print("Agent Step:", value["messages"][-1].content)
What happens when you run this?
- Agent: “I need to search for ‘State of Quantum Computing 2025’.” (Decides to call tool)
- Tool: Returns search results from the web.
- Agent: “I have the info. Now I will summarize…” (Writes the final report)
- End.
Why This Goes Viral
This tutorial works because it breaks the illusion of “magic.” It shows that an “autonomous agent” isn’t sentient—it’s just a loop. A loop that checks: Do I have the answer? No? Search. Do I have it now? Yes? Answer.
The Future: Multi-Agent Systems
Once you master this single agent, the next step is Multi-Agent Orchestration, where a “Manager Agent” supervises a “Researcher Agent” and a “Writer Agent.” But that is a topic for another day.
Clone the repo, get your API keys, and start building employees, not chatbots.