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"),
}),
}
);