Module 8.6 — Project: Research Agent

An agent that searches the web, synthesizes information, and writes structured research reports


What This Project Does

Input:  Any research topic
Output: Structured report with:
        → Key findings from multiple searches
        → Summary per subtopic
        → Final synthesized report
        → Sources used

Real use cases:
→ Market research
→ Competitor analysis
→ Technical topic deep-dives
→ News summarization
→ Academic topic overviews

Project Setup

mkdir research-agent
cd research-agent
npm init -y

Update package.json:


  {
    "name": "research-agent",
    "version": "1.0.0",
    "type": "module",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
      "@langchain/core": "^1.2.5",
      "@langchain/langgraph": "^1.4.9",
      "@langchain/openai": "^1.5.6",
      "@langchain/tavily": "^1.2.0",
      "dotenv": "^16.6.1",
      "langchain": "^1.5.5",
      "zod": "^4.4.3"
    }
  }

Get a free Tavily API key at https://tavily.com — sign up takes 30 seconds, gives 1000 free searches per month.

Create .env:


    OPENAI_API_KEY=sk-proj-your-key-here
    TAVILY_API_KEY=tvly-your-key-here


Project Structure

research-agent/
├── .env
├── package.json
└── src/
    ├── tools.js     ← search + analysis tools
    ├── agent.js     ← research agent setup
    └── index.js     ← entry point + interactive CLI

Step 1 — Tools

Create src/tools.js:


    import * as dotenv from "dotenv";
    dotenv.config();
    // MUST be at the very top — before TavilySearch instantiation
    // TavilySearch reads TAVILY_API_KEY at import time, not at call time
    // so dotenv must load .env BEFORE the new TavilySearch() line runs

    import { tool } from "@langchain/core/tools";
    // tool from "@langchain/core/tools" — 2025 canonical import
    import { TavilySearch } from "@langchain/tavily";
    // TavilySearch = ready-made web search tool from LangChain community
    // uses Tavily API internally — reads TAVILY_API_KEY from process.env automatically
    import { z } from "zod";


    // ─────────────────────────────────────────
    // TOOL 1 — Web Search via Tavily
    // Searches the real web and returns results
    // ─────────────────────────────────────────

    const tavilySearch = new TavilySearch({
        maxResults: 5,
        // return top 5 search results per query
        // each result has: title, url, content (snippet)
    });
    // TavilySearch is already a LangChain tool — no need to wrap with tool()
    // it has .name, .description, .schema built in
    // name:        "tavily_search_results_json"
    // description: "A search engine..."

    export const webSearchTool = tavilySearch;
    // export directly — agent uses this for live web searches


    // ─────────────────────────────────────────
    // TOOL 2 — Save Research Finding
    // Stores important findings during research
    // Agent uses this to accumulate notes
    // ─────────────────────────────────────────

    const researchNotes = [];
    // in-memory storage for research findings
    // accumulates as agent searches multiple topics
    // example after 3 searches:
    // [
    //   { subtopic: "market size", finding: "AI market worth $200B in 2025", source: "techcrunch.com" },
    //   { subtopic: "key players", finding: "OpenAI, Google, Anthropic lead the space", source: "forbes.com" },
    //   { subtopic: "trends",      finding: "Agentic AI is the dominant trend", source: "mit.edu" },
    // ]

    export const saveFindingTool = tool(
        async ({ subtopic, finding, source }) => {
            // subtopic = which aspect of the topic this covers
            //            example: "market size" or "key challenges" or "recent developments"
            // finding  = the key information discovered
            //            example: "AI agent market projected at $28B by 2028"
            // source   = where this information came from
            //            example: "techcrunch.com" or "research.google.com"

            researchNotes.push({ subtopic, finding, source });
            // add this finding to our in-memory notes array

            return `Finding saved: [${subtopic}] ${finding} (from: ${source})`;
            // confirmation back to agent
            // agent knows the save succeeded and can continue researching
        },
        {
            name: "save_finding",
            description: `Saves an important research finding to memory.
    Use this after each web search to record key information before moving to the next search.
    This helps build up a comprehensive set of notes across multiple searches.`,
            schema: z.object({
                subtopic: z.string()
                    .describe("which aspect of the topic this finding covers, e.g. 'market size', 'key players', 'challenges'"),
                finding: z.string()
                    .describe("the key information or insight discovered"),
                source: z.string()
                    .describe("the website or source this came from"),
            }),
        }
    );


    // ─────────────────────────────────────────
    // TOOL 3 — Get All Saved Findings
    // Retrieves accumulated research notes
    // Agent uses this before writing the final report
    // ─────────────────────────────────────────

    export const getResearchNotesTool = tool(
        async () => {
            // no parameters — just returns everything saved so far

            if (researchNotes.length === 0) {
                return "No research notes saved yet. Please search and save findings first.";
            }

            const formatted = researchNotes
                .map((note, i) =>
                    `${i + 1}. [${note.subtopic}]\n   Finding: ${note.finding}\n   Source: ${note.source}`
                )
                .join("\n\n");
            // format each note with its subtopic, finding, and source
            // example single formatted note:
            // "1. [market size]
            //    Finding: AI agent market worth $28B by 2028
            //    Source: techcrunch.com"

            return `ALL RESEARCH NOTES (${researchNotes.length} findings):\n\n${formatted}`;
            // returns all accumulated notes as one formatted string
            // agent reads this and synthesizes into final report
        },
        {
            name: "get_research_notes",
            description: `Retrieves all saved research findings collected so far.
    Use this when you are ready to write the final report.
    Call this AFTER you have done all your searches and saved findings.`,
            schema: z.object({}),
            // no inputs needed — just returns everything stored
        }
    );


    // ─────────────────────────────────────────
    // TOOL 4 — Write Final Report
    // Structures research notes into a polished report
    // ─────────────────────────────────────────

    export const writeReportTool = tool(
        async ({ topic, executiveSummary, sections, conclusion }) => {
            // topic            = the research topic
            // executiveSummary = 2-3 sentence overview
            // sections         = array of { heading, content } objects
            // conclusion       = final takeaways

            const date = new Date().toLocaleDateString("en-IN", {
                year: "numeric", month: "long", day: "numeric"
            });
            // example: "4 August 2026"

            const report = `
    ╔══════════════════════════════════════════════════════╗
    ║           RESEARCH REPORT                           ║
    ╚══════════════════════════════════════════════════════╝

    Topic:     ${topic}
    Date:      ${date}
    Sources:   ${researchNotes.length} web sources consulted

    ══════════════════════════════════════════════════════

    EXECUTIVE SUMMARY
    ─────────────────
    ${executiveSummary}

    ══════════════════════════════════════════════════════
    ${sections.map((section, i) => `
    SECTION ${i + 1}: ${section.heading.toUpperCase()}
    ${"".repeat(section.heading.length + 10)}
    ${section.content}
    `).join("\n")}

    ══════════════════════════════════════════════════════

    CONCLUSION
    ──────────
    ${conclusion}

    ══════════════════════════════════════════════════════
    SOURCES CONSULTED
    ─────────────────
    ${researchNotes.map((note, i) => `${i + 1}. ${note.source}${note.subtopic}`).join("\n")}
    ══════════════════════════════════════════════════════
    `.trim();

            return report;
            // returns the complete formatted report as a string
            // agent returns this as its final answer to the user
        },
        {
            name: "write_report",
            description: `Writes the final structured research report using all gathered information.
    Use this as the LAST step after getting all research notes.
    Produces a complete, professional report ready to share.`,
            schema: z.object({
                topic: z.string()
                    .describe("the main research topic"),

                executiveSummary: z.string()
                    .describe("2-3 sentence high-level summary of key findings"),

                sections: z.array(z.object({
                    heading: z.string().describe("section title"),
                    content: z.string().describe("detailed section content"),
                }))
                    .min(3)
                    .max(6)
                    .describe("3 to 6 main sections of the report"),
                // minimum 3 sections = always covers multiple angles
                // maximum 6 sections = keeps report focused

                conclusion: z.string()
                    .describe("final takeaways and recommendations"),
            }),
        }
    );


Step 2 — Agent Setup

Create src/agent.js:


    import { createReactAgent } from "@langchain/langgraph/prebuilt";
    // createReactAgent from langgraph/prebuilt — 2025 correct import
    // creates a ReAct agent with: think → call tool → observe → repeat

    import { ChatOpenAI } from "@langchain/openai";
    import { MemorySaver } from "@langchain/langgraph";
    import {
        webSearchTool,
        saveFindingTool,
        getResearchNotesTool,
        writeReportTool,
    } from "./tools.js";


    // ─────────────────────────────────────────
    // CREATE THE RESEARCH AGENT
    // ─────────────────────────────────────────

    export function createResearchAgent() {

        const llm = new ChatOpenAI({
            model: "gpt-4o",
            temperature: 0.1,
            // slightly above 0 to allow natural language variation in the report
            // but still mostly deterministic for consistent research quality
        });

        const checkpointer = new MemorySaver();
        // enables conversation memory
        // user can ask follow-up questions after the initial report

        const agent = createReactAgent({
            llm,

            tools: [webSearchTool, saveFindingTool, getResearchNotesTool, writeReportTool],
            // four tools in specific order of use:
            // 1. webSearchTool       → search the real web
            // 2. saveFindingTool     → save each key finding
            // 3. getResearchNotesTool → retrieve all notes before writing
            // 4. writeReportTool     → write the final report

            checkpointer,

            prompt: `You are a professional research analyst with access to web search.
    Today's date: ${new Date().toLocaleDateString("en-IN")}

    MANDATORY WORKFLOW — you MUST follow all 4 phases:

    PHASE 1 — PLAN:
    Identify 4 subtopics to research for the given topic.

    PHASE 2 — SEARCH AND SAVE (repeat 4 times):
    For each subtopic:
    a) Call tavily_search_results_json with a specific query
    b) Read results carefully
    c) Call save_finding immediately with the key finding

    PHASE 3 — COMPILE:
    Call get_research_notes to get all saved findings.

    PHASE 4 — WRITE REPORT (MANDATORY):
    Call write_report tool with all sections filled in.
    You MUST call write_report — do not write the report yourself.
    The write_report tool produces the final formatted output.

    RULES:
    - Never skip any phase
    - Always call write_report as the last step
    - Use specific search queries, not generic ones
    - Save at least 4 findings before writing the report`,
        });

        return agent;
    }


Step 3 — Entry Point

Create src/index.js:


    import { createResearchAgent } from "./agent.js";
    import { createInterface } from "readline";
    import * as dotenv from "dotenv";
    dotenv.config();


    // ─────────────────────────────────────────
    // STREAMING RESEARCH OUTPUT
    // Shows agent's progress as it researches
    // ─────────────────────────────────────────

    async function runResearchWithStreaming(agent, topic, threadId) {
        console.log(`\n🔬 Researching: "${topic}"`);
        console.log("".repeat(55));
        console.log("(Agent is searching the web — this takes 30-60 seconds)\n");

        let toolCallCount = 0;
        // tracks how many tools have been called
        // printed to show research progress

        // Use streaming to show progress as agent works
        for await (const event of await agent.streamEvents(
            { messages: [{ role: "user", content: `Research this topic and write a comprehensive report: ${topic}` }] },
            {
                configurable: { thread_id: threadId },
                version: "v2",
                // version: "v2" = required for streamEvents in 2025
            }
        )) {
            // streamEvents yields events as the agent runs
            // each event has an event type and data

            if (event.event === "on_tool_start") {
                // agent is calling a tool — show which one
                toolCallCount++;
                const toolName = event.name;
                // event.name = the name of the tool being called

                const friendlyNames = {
                    "tavily_search_results_json": "🔍 Searching web",
                    "save_finding": "💾 Saving finding",
                    "get_research_notes": "📋 Retrieving all notes",
                    "write_report": "📝 Writing final report",
                };

                const displayName = friendlyNames[toolName] || `⚙️  Running ${toolName}`;
                process.stdout.write(`${displayName}... `);
            }

            if (event.event === "on_tool_end") {
                // tool finished — print completion indicator
                console.log("");
            }
        }

        // Get the final result after streaming
        const result = await agent.invoke(
            { messages: [{ role: "user", content: `Research this topic and write a comprehensive report: ${topic}` }] },
            { configurable: { thread_id: threadId } }
        );
        // note: this calls the agent again — in production use the streamed result
        // for this demo it's cleaner to invoke separately for the final output

        return result.messages[result.messages.length - 1].content;
    }


    // ─────────────────────────────────────────
    // SIMPLER VERSION — No streaming
    // Easier to debug if streaming has issues
    // ─────────────────────────────────────────

    async function runResearch(agent, topic, threadId) {
        console.log(`\n🔬 Researching: "${topic}"`);
        console.log("".repeat(55));
        console.log("Agent is working... (30-60 seconds)\n");

        const interval = setInterval(() => process.stdout.write("."), 2000);

        try {
            const result = await agent.invoke(
                {
                    messages: [{
                        role: "user",
                        content: `Research this topic thoroughly and produce a complete report: ${topic}

    IMPORTANT: You MUST follow these steps in order:
    1. Use tavily_search_results_json to search at least 4 different subtopics
    2. Use save_finding after EACH search to record key findings
    3. Use get_research_notes to retrieve all saved findings
    4. Use write_report to produce the final formatted report

    Do NOT summarize in your own words — always use the write_report tool for the final output.`
                    }]
                },
                { configurable: { thread_id: threadId } }
            );

            clearInterval(interval);
            console.log("\n");

            // Find the write_report tool result in messages
            // Agent's tool results are stored as ToolMessages
            const messages = result.messages;

            // Look for write_report tool output first
            for (let i = messages.length - 1; i >= 0; i--) {
                const msg = messages[i];

                // ToolMessage from write_report tool
                if (msg.constructor.name === "ToolMessage" &&
                    msg.content &&
                    msg.content.includes("RESEARCH REPORT")) {
                    return msg.content;
                    // found the actual report from write_report tool
                }
            }

            // If write_report wasn't called, return last AI message
            return messages[messages.length - 1].content;

        } catch (err) {
            clearInterval(interval);
            throw err;
        }
    }


    // ─────────────────────────────────────────
    // MAIN
    // ─────────────────────────────────────────

    async function main() {
        console.log("\n" + "=".repeat(55));
        console.log("🔬 RESEARCH AGENT");
        console.log("   Powered by GPT-4o + Tavily Web Search");
        console.log("=".repeat(55));

        const agent = createResearchAgent();
        // create the research agent once — reuse for all topics

        const rl = createInterface({ input: process.stdin, output: process.stdout });
        const question = (q) => new Promise(resolve => rl.question(q, resolve));

        console.log('\nEnter a research topic or "exit" to quit.');
        console.log('Examples:');
        console.log('  → "AI agents market 2025"');
        console.log('  → "React vs Next.js for production apps"');
        console.log('  → "Pinecone vs Chroma vector database comparison"');
        console.log('  → "Best practices for RAG systems"\n');

        while (true) {
            const topic = await question("Research topic: ");

            if (topic.toLowerCase() === "exit") {
                console.log("\n👋 Goodbye!\n");
                rl.close();
                break;
            }

            if (!topic.trim()) continue;
            // skip empty input

            const threadId = `research_${Date.now()}`;
            // unique thread for each research session
            // allows follow-up questions about the same research

            try {
                const report = await runResearch(agent, topic.trim(), threadId);

                console.log("\n" + "=".repeat(55));
                console.log("📊 RESEARCH REPORT");
                console.log("=".repeat(55));
                console.log(report);

                // Allow follow-up questions
                console.log("\n" + "".repeat(55));
                console.log('Ask a follow-up question or press Enter for a new topic.');
                console.log(''.repeat(55));

                while (true) {
                    const followUp = await question("Follow-up (or Enter to skip): ");

                    if (!followUp.trim()) break;
                    // empty input = go back to main loop

                    console.log("\nAgent: thinking...\n");

                    const followUpResult = await agent.invoke(
                        { messages: [{ role: "user", content: followUp }] },
                        { configurable: { thread_id: threadId } }
                        // SAME threadId = agent remembers the full research
                        // can answer "what were the key challenges?" without re-researching
                    );

                    const followUpMsg = followUpResult.messages[followUpResult.messages.length - 1];
                    console.log("\n" + "".repeat(55));
                    console.log(followUpMsg.content);
                    console.log("".repeat(55) + "\n");
                }

            } catch (err) {
                console.error("\n❌ Error:", err.message);
                if (err.message.includes("TAVILY_API_KEY")) {
                    console.log("→ Get a free key at https://tavily.com and add to .env");
                }
                if (err.message.includes("OPENAI_API_KEY")) {
                    console.log("→ Check your OpenAI API key in .env");
                }
            }

            console.log("\n" + "=".repeat(55) + "\n");
        }
    }

    main().catch(console.error);


Run the Project

node src/index.js

Expected Output

=======================================================
🔬 RESEARCH AGENT
   Powered by GPT-4o + Tavily Web Search
=======================================================

Enter a research topic or "exit" to quit.
Examples:
  → "AI agents market 2025"
  → "React vs Next.js for production apps"

Research topic: AI agents market 2025

🔬 Researching: "AI agents market 2025"
───────────────────────────────────────────────────────
Agent is working... (30-60 seconds)

..............................

=======================================================
📊 RESEARCH REPORT
=======================================================

╔══════════════════════════════════════════════════════╗
║           RESEARCH REPORT                           ║
╚══════════════════════════════════════════════════════╝

Topic:     AI agents market 2025
Date:      4 August 2026
Sources:   5 web sources consulted

══════════════════════════════════════════════════════

EXECUTIVE SUMMARY
─────────────────
The AI agent market is experiencing explosive growth in 2025,
valued at approximately $5.1 billion with projections to reach
$28.5 billion by 2028. Key enterprise adoption is being driven
by automation gains across software development, customer service,
and data analysis workflows.

══════════════════════════════════════════════════════

SECTION 1: MARKET SIZE AND GROWTH
──────────────────────────────────────────────
The global AI agent market reached $5.1B in 2025...
[real data from Tavily search results]

SECTION 2: KEY PLAYERS
───────────────────────────────────
OpenAI, Anthropic, Google DeepMind, and Microsoft...

SECTION 3: MAIN USE CASES
──────────────────────────────────────
Enterprise automation, coding assistants, customer service...

SECTION 4: CHALLENGES AND RISKS
────────────────────────────────────────────
Hallucination, security concerns, regulatory uncertainty...

SECTION 5: FUTURE OUTLOOK
──────────────────────────────────────
Agentic AI expected to dominate by 2026...

══════════════════════════════════════════════════════

CONCLUSION
──────────
AI agents represent the most significant shift in enterprise
software since cloud computing. Organizations that invest in
agent infrastructure now will hold substantial competitive advantages.

══════════════════════════════════════════════════════
SOURCES CONSULTED
─────────────────
1. techcrunch.com — market size
2. mckinsey.com — enterprise adoption
3. openai.com — key players
4. venturebeat.com — challenges
5. mit.edu — future outlook
══════════════════════════════════════════════════════

──────────────────────────────────────────────────────
Ask a follow-up question or press Enter for a new topic.
──────────────────────────────────────────────────────
Follow-up (or Enter to skip): What were the main challenges mentioned?

Agent: thinking...

──────────────────────────────────────────────────────
Based on the research, the main challenges identified were:
1. Hallucination and reliability — agents sometimes make up facts
2. Security concerns — agents with tool access create new attack surfaces
3. Cost — running GPT-4o agents at scale is expensive
4. Regulatory uncertainty — no clear framework for autonomous AI actions

These came from sources including venturebeat.com and mit.edu.
──────────────────────────────────────────────────────

How the Agent Researches — Step by Step

User: "Research AI agents market 2025"
          ↓
PHASE 1 — Planning (no tool calls):
Agent thinks: "I'll research: market size, key players,
               use cases, challenges, future outlook"

PHASE 2 — Research loop (real web searches):
Search 1: "AI agents market size 2025 billion"
  → Tavily returns 5 real URLs with snippets
  → Agent reads results
  → Saves finding: "market worth $5.1B in 2025"

Search 2: "top AI agent companies OpenAI Anthropic 2025"
  → Agent reads results
  → Saves finding: "OpenAI leads with GPT-4o agents"

Search 3: "AI agent enterprise use cases 2025"
Search 4: "AI agent challenges risks hallucination"
Search 5: "AI agent future predictions 2026 2027"

PHASE 3 — Report writing:
get_research_notes → retrieves all 5 saved findings
write_report → structures into sections
          ↓
Final polished report

3-Line Summary

  1. The Research Agent uses a four-tool workflow — TavilySearchResults for real web searches, save_finding to accumulate notes across multiple searches, get_research_notes to retrieve all notes before writing, and write_report to structure everything into a polished final report.
  2. The agent follows three phases defined in the system prompt — Planning (identify subtopics), Research (search + save for each subtopic), and Writing (retrieve notes + write report) — this planning-before-acting pattern dramatically improves report quality vs a single search.
  3. The same thread_id for follow-up questions means the agent remembers the complete research session — users can ask "what were the main challenges?" without re-running the research because the full conversation including all tool results is stored in the checkpointer.

Module 8.6 — Complete ✅

Phase 8 — Complete 🎉

✅ Module 8.1 — What is an Agent + ReAct Pattern
✅ Module 8.2 — Tool Calling Deep Dive
✅ Module 8.3 — Agent Memory + Planning
✅ Module 8.4 — Multi-Agent Systems with LangGraph
✅ Module 8.5 — Project: Resume Analyzer Agent
✅ Module 8.6 — Project: Research Agent

Full Course — Complete 🎓

✅ Phase 1 — AI Foundations
✅ Phase 2 — LLM Internals
✅ Phase 3 — Embeddings
✅ Phase 4 — Vector Databases
✅ Phase 5 — RAG + PDF Chatbot
✅ Phase 6 — LangChain Core
✅ Phase 7 — Production System
✅ Phase 8 — AI Agents

"AI Engineering Fundamentals to Production" — Done. 🚀

You went from zero AI knowledge to building:

  • PDF chatbots with streaming
  • Production RAG with Pinecone
  • Multi-agent research systems
  • Resume analyzers
  • Full Next.js + Express AI apps

This is real, production-grade AI engineering. 🎉

No comments:

Post a Comment

Module 8.6 — Project: Research Agent

An agent that searches the web, synthesizes information, and writes structured research reports What This Project Does Input: Any research ...