Day 60 of 80

Project: Autonomous Research Assistant

Phase 6: Frameworks & Agents

What You'll Build Today

Welcome to Day 60! You have reached a massive milestone. Today marks the end of Phase 6: Frameworks & Agents. To celebrate, we are going to build something that feels like science fiction: an Autonomous Research Assistant.

Up until now, we've mostly had "conversations" with AI. You ask a question, and it answers based on its training data. Today, we change the dynamic. You will give the AI a goal, and it will go out into the world (the internet), gather information, synthesize it, and write a report for you.

Here is what you will master today:

* End-to-End Agent Design: You will move beyond simple scripts to creating a system that can "think" about which steps to take next without you hard-coding them.

* Tool Integration: You will learn why—and how—to give an LLM "hands" (tools) to interact with the outside world, specifically a web search engine.

* Context Management: You will see how an agent holds onto gathered information to synthesize a final answer, rather than forgetting it immediately.

* Output Formatting: You will learn how to force an agent to structure unstructured web data into a clean, readable Markdown report.

This is the culmination of everything you have learned about Python, APIs, and logic. Let's build an employee that never sleeps.

The Problem

Let's say you want to know the very latest developments in "Solid State Batteries" for a report you are writing.

If you use a standard LLM script (like we did in Phase 4), you run into a major wall: the Knowledge Cutoff. The AI only knows what it was trained on. It cannot browse the web by default.

Here is code that represents the "Old Way." It is frustrating because it is confident, but often wrong or outdated.

import os

from openai import OpenAI

# Assume API key is set in environment variables

client = OpenAI()

def simple_researcher(topic):

print(f"Researching: {topic}...")

# We ask the model directly

response = client.chat.completions.create(

model="gpt-4o", # Or gpt-3.5-turbo

messages=[

{"role": "system", "content": "You are a helpful assistant."},

{"role": "user", "content": f"Write a report on the latest news about {topic} from yesterday."}

]

)

return response.choices[0].message.content

# THE PAIN: # If you ask about something that happened today, the AI will either: # 1. Hallucinate (make up facts). # 2. Apologize and say "I don't have real-time access to the internet."

print(simple_researcher("The stock price of NVIDIA right this second"))

The Pain Points:
  • It's blind: The AI is locked in a box. It cannot see the outside world.
  • It's manual: To fix this, you currently have to Google the topic yourself, copy the text from three different websites, paste it into the prompt, and then ask for a summary.
  • It doesn't scale: If you need to research 50 topics, you have to do that copy-paste dance 50 times.
  • There has to be a way to give the AI a web browser and let it do the clicking and reading for us.

    Let's Build It

    We will use LangChain to build this. While you can write raw Python to call APIs, LangChain provides a standard interface for "Agents"—systems that use an LLM as a reasoning engine to decide which tools to use.

    We will use DuckDuckGo as our search engine because it is free and requires no API key setup for this tutorial.

    Step 1: Install Dependencies

    We need the LangChain ecosystem and the search tool.

    Note: You may need to run this in your terminal first. pip install langchain langchain-openai langchain-community duckduckgo-search

    Step 2: Initialize the LLM and the Tools

    First, let's set up our "Brain" (OpenAI) and our "Hands" (DuckDuckGo Search). We need to verify the search tool works before giving it to the agent.

    import os
    

    from langchain_openai import ChatOpenAI

    from langchain_community.tools import DuckDuckGoSearchRun

    # 1. Setup the LLM (The Brain) # We use temperature=0 because we want factual research, not creative fiction.

    llm = ChatOpenAI(model="gpt-4o", temperature=0)

    # 2. Setup the Tool (The Hands)

    search_tool = DuckDuckGoSearchRun()

    # TEST: Let's verify the tool works on its own

    print("--- Testing Search Tool ---")

    test_result = search_tool.invoke("Current CEO of OpenAI")

    print(test_result)

    Why this matters: Never build a complex agent without testing the individual parts. If the search tool fails here, the whole agent will fail later.

    Step 3: Define the Prompt Template

    An agent needs a specific type of prompt. It needs to know:

  • Who it is (Persona).
  • What tools it has access to.
  • The placeholder for the "Agent Scratchpad"—this is a memory area where the agent writes down its intermediate thoughts (e.g., "I searched for X, but didn't find it, so now I will search for Y").
  • from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
    
    # Define the prompt
    

    prompt = ChatPromptTemplate.from_messages([

    ("system", "You are a world-class technical researcher. "

    "Your goal is to research a topic thoroughly using the search tool provided. "

    "Summarize your findings into a professional markdown report. "

    "Always cite your sources."),

    ("user", "{input}"),

    # This placeholder is CRITICAL. It is where the agent's internal monologue # and tool outputs are injected back into the chat history.

    MessagesPlaceholder(variable_name="agent_scratchpad"),

    ])

    Why this matters: The agent_scratchpad is the magic. It creates a loop. The AI outputs a "function call," the system runs the function, puts the result in the scratchpad, and sends it back to the AI.

    Step 4: Construct the Agent

    Now we bind the LLM, the tools, and the prompt together. We use a specific LangChain constructor for OpenAI tools, as OpenAI models are fine-tuned to detect when to call functions.

    from langchain.agents import create_tool_calling_agent, AgentExecutor
    
    # List of tools (we only have one right now, but we could add Wikipedia, Calculator, etc.)
    

    tools = [search_tool]

    # Create the agent structure

    agent = create_tool_calling_agent(llm, tools, prompt)

    # Create the Executor # The Agent is the "brain", the Executor is the "runtime" that actually loops # and executes the tools.

    agent_executor = AgentExecutor(

    agent=agent,

    tools=tools,

    verbose=True # This lets us see the "thinking" process in the console

    )

    Why this matters: The AgentExecutor handles the while loop for us. It keeps asking the LLM "Are you done?" If the LLM says "No, I need to search for X," the Executor runs the search and feeds the result back. If the LLM says "Yes, here is the answer," the loop ends.

    Step 5: Run the Autonomous Researcher

    Now, let's run it. We will ask a question that requires current knowledge.

    print("--- Starting Research Agent ---")
    
    

    topic = "What are the latest features released in Python 3.12 and 3.13?"

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

    print("\n\n=== FINAL REPORT ===\n")

    print(result['output'])

    What to expect in the output:

    Because we set verbose=True, you will see green text in your console showing the agent's thought process:

  • Thought: I need to search for Python 3.12 features.
  • Action: DuckDuckGo Search("Python 3.12 features").
  • Observation: (Search results text).
  • Thought: Now I need to search for Python 3.13 features.
  • Action: DuckDuckGo Search("Python 3.13 features").
  • Observation: (Search results text).
  • Final Answer: A summarized report.
  • Step 6: Complete Runnable Code

    Here is the entire script combined into one file.

    import os
    

    from langchain_openai import ChatOpenAI

    from langchain_community.tools import DuckDuckGoSearchRun

    from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

    from langchain.agents import create_tool_calling_agent, AgentExecutor

    # --- CONFIGURATION --- # Ensure your OPENAI_API_KEY is in your environment variables

    def main():

    # 1. Tools

    search = DuckDuckGoSearchRun()

    tools = [search]

    # 2. LLM

    llm = ChatOpenAI(model="gpt-4o", temperature=0)

    # 3. Prompt

    prompt = ChatPromptTemplate.from_messages([

    ("system", "You are a Senior Research Assistant. "

    "When given a topic, search for the most recent and relevant information. "

    "Compile a report in Markdown format. "

    "Include a 'Key Takeaways' section and a 'Sources' section with URLs."),

    ("user", "{input}"),

    MessagesPlaceholder(variable_name="agent_scratchpad"),

    ])

    # 4. Agent Construction

    agent = create_tool_calling_agent(llm, tools, prompt)

    agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

    # 5. Execution

    topic = input("Enter a topic to research: ")

    print(f"\nResearching '{topic}'... please wait.\n")

    try:

    response = agent_executor.invoke({"input": topic})

    print("\n" + "="*40)

    print("RESEARCH REPORT")

    print("="*40 + "\n")

    print(response["output"])

    except Exception as e:

    print(f"An error occurred: {e}")

    if __name__ == "__main__":

    main()

    Now You Try

    You have a working agent. Now, let's make it smarter and more robust. Try these three extensions:

  • Add a Second Tool:
  • LangChain has a built-in Wikipedia tool.

    * Install: pip install wikipedia

    * Import: from langchain_community.tools import WikipediaQueryRun and from langchain_community.utilities import WikipediaAPIWrapper

    * Setup: wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())

    Task: Add it to your tools list (tools = [search, wikipedia]). Watch how the agent decides which* tool to use based on your question!
  • Change the Persona:
  • Modify the System Message in the prompt. Change it from "Senior Research Assistant" to "Explain Like I'm 5."

    * Task: Run the same query about complex tech (like "Quantum Entanglement") and see how the output style changes completely while the research data remains accurate.

  • Save to File:
  • Right now, the report just prints to the console.

    * Task: Add standard Python file I/O at the end of the script to save response["output"] to a file named research_report.md.

    Challenge Project: The "Publish-Ready" Reporter

    Your main project prints to the console, but that is hard to read for long reports. Your challenge is to build a full CLI (Command Line Interface) wrapper around your agent.

    Requirements:
  • Input Loop: The program should keep asking for topics until the user types "exit".
  • Filename Generation: Automatically generate a filename based on the topic (e.g., topic "AI News" -> ai_news_report.md).
  • Sanitization: Ensure the filename doesn't contain illegal characters (spaces should be underscores).
  • Feedback: Print "Report saved to [filename]" after generation.
  • Example Interaction:
    > Enter topic: Apple Vision Pro reviews
    

    Researching...

    [Agent thinking...]

    Report saved to apple_vision_pro_reviews.md

    > Enter topic: exit

    Goodbye!

    Hint:

    * Use Python's string method .replace(" ", "_") for the filename.

    * Use a while True: loop for the main program interaction.

    What You Learned

    Today you graduated from "User" to "Architect."

    * Agents vs. Chains: You learned that a Chain follows a fixed path (A -> B -> C), but an Agent uses the LLM to decide the path (A -> ? -> ?).

    * Tool Calling: You saw that LLMs aren't just text generators; they can be "routers" that pick the right function to call to solve a problem.

    * The Loop: You observed the Thought -> Action -> Observation loop that allows AI to correct itself and gather more data.

    Why This Matters:

    In the real world, data isn't static. It lives in databases, APIs, and websites. The ability to build Agents that can reach out and grab that data is what separates a toy project from enterprise-grade software.

    Phase 6 Complete! You have conquered frameworks and agents. Tomorrow: We enter Phase 7. We will stop using generic models and start looking at Fine-Tuning—training the model on your data to think exactly how you want it to. See you there!