Press ESC to exit fullscreen
📖 Lesson ⏱️ 150 minutes

Building Your First Agent with LangChain

Step-by-step: build a web research agent that writes a report

What You’ll Build

By the end of this lesson, you’ll have a working web research agent that can:

  • Search the web for current information
  • Look up Wikipedia articles for background context
  • Combine information from multiple sources into a coherent summary
  • Run queries like “Summarize the latest research on transformer efficiency improvements”

This is a real, runnable agent — not a toy demo. The same architecture scales to production use cases.

Setup: Installing Dependencies

Create a virtual environment and install the required packages:

python -m venv agent-env
source agent-env/bin/activate  # On Windows: agent-env\Scripts\activate

pip install langchain langchain-community langchain-anthropic
pip install duckduckgo-search wikipedia

You’ll also need an Anthropic API key:

export ANTHROPIC_API_KEY="your-api-key-here"

Step 1: Understanding LangChain’s Agent Architecture

LangChain structures agents around three components:

  1. LLM — the model that does the reasoning (we’ll use Claude)
  2. Tools — the functions the agent can call
  3. Agent executor — the loop that runs the observe-think-act cycle

LangChain provides pre-built tools for common tasks and a standardized interface for adding your own. It also handles the message formatting, tool call parsing, and iteration loop — so you don’t have to write that boilerplate yourself.

Step 2: Setting Up the LLM

from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(
    model="claude-opus-4-5",
    temperature=0,  # Use 0 for more deterministic reasoning
    max_tokens=4096
)

Using temperature=0 makes the agent’s reasoning more consistent and reproducible — important for debugging. You can increase it for more creative tasks.

Step 3: Configuring the Tools

LangChain has built-in wrappers for DuckDuckGo search and Wikipedia:

from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

# Web search tool
search_tool = DuckDuckGoSearchRun(
    name="web_search",
    description="""Search the web for current information, recent events, and news.
    Use for: recent developments, current statistics, news articles.
    Input: a search query string."""
)

# Wikipedia tool  
wiki_tool = WikipediaQueryRun(
    api_wrapper=WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=3000),
    name="wikipedia",
    description="""Look up background information on a topic from Wikipedia.
    Use for: technical concepts, historical context, definitions, established facts.
    Input: the topic to search for."""
)

tools = [search_tool, wiki_tool]

The descriptions matter enormously. The agent reads them to decide which tool to use for a given sub-question. “Use for: recent developments” vs “Use for: technical concepts” gives the agent a clear mental model of when to use each tool.

Step 4: Creating the ReAct Agent

from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub

# Pull the ReAct prompt template from LangChain Hub
# This is the standard Thought/Action/Observation format
prompt = hub.pull("hwchase17/react")

# Create the agent (LLM + tools + prompt)
agent = create_react_agent(
    llm=llm,
    tools=tools,
    prompt=prompt
)

# Wrap in an executor that handles the loop
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,       # Print the full Thought/Action/Observation trace
    max_iterations=10,  # Prevent infinite loops
    handle_parsing_errors=True  # Gracefully handle malformed outputs
)

The AgentExecutor is where the magic happens. It:

  • Sends the current state to the agent
  • Parses the agent’s output for tool calls
  • Executes the tools
  • Appends results back to context
  • Repeats until the agent produces a final answer

Setting verbose=True during development is essential — you’ll see exactly what the agent is thinking and doing.

Step 5: Running Your First Query

result = agent_executor.invoke({
    "input": "Summarize the latest research on transformer efficiency improvements"
})

print("\n=== FINAL ANSWER ===")
print(result["output"])

With verbose=True, you’ll see the full trace:

> Entering new AgentExecutor chain...

Thought: The user wants a summary of recent research on transformer efficiency improvements.
I should search for the latest research on this topic, then look up background 
on what transformer efficiency means in this context.

Action: web_search
Action Input: latest research transformer efficiency improvements 2025 2026

Observation: Recent papers focus on attention mechanism optimization, sparse 
transformers, and mixture-of-experts (MoE) architectures...

Thought: I have some recent results. Let me get background on the key techniques 
mentioned to give better context.

Action: wikipedia
Action Input: Sparse Transformer attention mechanism

Observation: Sparse Transformers reduce computational complexity from O(n²) to 
O(n√n) by limiting attention to a subset of tokens...

Thought: I now have both current research trends and background context. 
I can write a comprehensive summary.

Final Answer: Recent research on transformer efficiency has focused on three 
main directions...

This trace is your best debugging tool. If the agent makes wrong tool choices or misinterprets results, you’ll see exactly where it went wrong.

Step 6: Adding a Custom Tool

Built-in tools cover common cases, but most real-world agents need custom tools. Here’s how to add one — an arXiv paper search tool:

from langchain.tools import tool
import requests

@tool
def search_arxiv_papers(query: str) -> str:
    """Search arXiv for academic papers on a specific topic.
    Use when the user needs academic research, papers, or peer-reviewed findings.
    Input: a specific search query (e.g. 'flash attention memory efficient transformers').
    Returns: list of recent papers with title, authors, and abstract summary."""
    
    # ArXiv API endpoint
    url = "http://export.arxiv.org/api/query"
    params = {
        "search_query": f"all:{query}",
        "max_results": 5,
        "sortBy": "submittedDate",
        "sortOrder": "descending"
    }
    
    try:
        response = requests.get(url, params=params, timeout=10)
        
        # Simple XML parsing (in production, use feedparser)
        results = []
        entries = response.text.split("<entry>")[1:]
        
        for entry in entries[:3]:
            title = entry.split("<title>")[1].split("</title>")[0].strip()
            summary_start = entry.find("<summary>") + 9
            summary_end = entry.find("</summary>")
            summary = entry[summary_start:summary_end].strip()[:300]
            
            results.append(f"Title: {title}\nSummary: {summary}...")
        
        return "\n\n".join(results) if results else "No papers found for this query."
    
    except requests.RequestException as e:
        return f"Failed to search arXiv: {str(e)}"


# Add to the tools list
tools_with_arxiv = [search_tool, wiki_tool, search_arxiv_papers]

# Recreate the agent with the new tool
agent_with_arxiv = create_react_agent(llm=llm, tools=tools_with_arxiv, prompt=prompt)
agent_executor_v2 = AgentExecutor(
    agent=agent_with_arxiv,
    tools=tools_with_arxiv,
    verbose=True,
    max_iterations=10,
    handle_parsing_errors=True
)

The @tool decorator automatically uses the function’s docstring as the tool description. Keep docstrings clear and include “Use when…” guidance.

Step 7: A More Complex Query

Let’s test with something that requires multiple coordinated searches:

result = agent_executor_v2.invoke({
    "input": """Provide a technical overview of mixture-of-experts (MoE) architectures 
    in large language models. Include: what they are, why they're more efficient than 
    dense transformers, and what recent models use this approach."""
})

The agent should:

  1. Search Wikipedia for background on MoE
  2. Search the web for recent MoE models (Mistral, Gemini, etc.)
  3. Search arXiv for recent technical papers
  4. Synthesize into a structured answer

This multi-source synthesis is exactly where agents shine over a simple RAG pipeline — they adaptively query different sources based on what each sub-question needs.

Controlling Agent Behavior

Limiting iterations: The max_iterations=10 setting prevents the agent from getting stuck in a loop. For research tasks, 10-15 is usually enough. For complex coding tasks, you might need 20-30.

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    max_iterations=15,
    max_execution_time=120,  # Also add a timeout in seconds
    early_stopping_method="generate",  # Generate a final answer when limit hit
)

Handling parsing errors: Sometimes the agent produces output that doesn’t match the expected Thought/Action format. handle_parsing_errors=True tells the agent to try again with a reminder about the format, rather than crashing.

Controlling verbosity in production:

import logging

# In production, capture the trace to a log file instead of printing
logging.basicConfig(filename="agent_traces.log", level=logging.INFO)

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=False,  # Don't print to stdout
    return_intermediate_steps=True  # But capture steps in the return value
)

result = agent_executor.invoke({"input": query})

# Access the trace programmatically
for step in result["intermediate_steps"]:
    action, observation = step
    logging.info(f"Action: {action.tool}({action.tool_input})")
    logging.info(f"Observation: {observation[:200]}")

Complete Working Example

Here’s the full script, ready to run:

import os
from langchain_anthropic import ChatAnthropic
from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub

def create_research_agent():
    llm = ChatAnthropic(
        model="claude-opus-4-5",
        temperature=0,
        max_tokens=4096
    )
    
    tools = [
        DuckDuckGoSearchRun(
            name="web_search",
            description="Search the web for current information and recent news."
        ),
        WikipediaQueryRun(
            api_wrapper=WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=2000),
            name="wikipedia",
            description="Look up background information and definitions on Wikipedia."
        )
    ]
    
    prompt = hub.pull("hwchase17/react")
    agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
    
    return AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=True,
        max_iterations=10,
        handle_parsing_errors=True,
        return_intermediate_steps=True
    )


if __name__ == "__main__":
    agent = create_research_agent()
    
    queries = [
        "Summarize the latest research on transformer efficiency improvements",
        "What is Flash Attention and why is it important for LLM training?",
        "Compare the efficiency of GPT-4 vs Mistral 7B for production deployment"
    ]
    
    for query in queries:
        print(f"\n{'='*60}")
        print(f"Query: {query}")
        print('='*60)
        result = agent.invoke({"input": query})
        print(f"\nAnswer: {result['output']}")
        print(f"Steps taken: {len(result['intermediate_steps'])}")

What to Watch For in the Trace

As you run this, watch for these patterns in the verbose output:

  • Redundant searches: The agent searches for the same thing twice. Fix: improve the system prompt to say “don’t repeat searches you’ve already done.”
  • Wrong tool selection: The agent uses web search for something Wikipedia would answer better. Fix: sharpen the “Use for:” section of your tool descriptions.
  • Getting stuck: The agent keeps searching without reaching a conclusion. Fix: lower max_iterations and set early_stopping_method="generate" so it synthesizes what it has.

Summary

  • Install: langchain, langchain-anthropic, langchain-community, duckduckgo-search, wikipedia
  • Use ChatAnthropic with temperature=0 for consistent reasoning
  • Tool descriptions should specify “Use when…” to guide tool selection
  • AgentExecutor handles the full ReAct loop — set verbose=True during development
  • Add custom tools with the @tool decorator; use docstrings as descriptions
  • Set max_iterations and max_execution_time to prevent runaway agents
  • Use return_intermediate_steps=True in production to capture traces for debugging

Next up: Agent Evaluation — how to measure whether your agent is actually doing its job correctly.