DhruvSoni commited on
Commit
eb7faf3
·
0 Parent(s):

Initial commit: Multi-Agent research pipeline using LangGraph and Gemini

Browse files
Files changed (5) hide show
  1. .gitignore +4 -0
  2. agents.py +79 -0
  3. pipeline.py +80 -0
  4. requirement.txt +23 -0
  5. tools.py +40 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ .env
4
+ *.pyc
agents.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This is the heart of the project. We will build 4 things here. First the Search Agent using create_react_agent + AgentExecutor which will use the web_search tool. Second the Reader Agent using the same pattern but with the scrape_url tool. Third the Write Chain using the modern LCEL pipe syntax -prompt | 11m | StrOutputParser() which takes the research and writes a full report. Fourth the Critic CHain again using LCEL pipeline which reads the report and gives a score and feedback
2
+ from langchain.agents import create_agent
3
+ from langchain_google_genai import ChatGoogleGenerativeAI
4
+ from langchain_core.prompts import ChatPromptTemplate
5
+ from langchain_core.output_parsers import StrOutputParser
6
+ from tools import web_search, scrape_url
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv() # Load environment variables from .env file
10
+
11
+ #Model Setup
12
+ llm= ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
13
+
14
+
15
+ #1st Agent
16
+ def build_search_agent():
17
+ return create_agent(
18
+ model = llm,
19
+ tools= [web_search],
20
+ system_prompt="You are a search agent. You must search the web to find recent and reliable information. Always use the web_search tool to find URLs and actual sources. Return the search results."
21
+ )
22
+
23
+
24
+ #2nd Agent
25
+ def build_reader_agent():
26
+ return create_agent(
27
+ model= llm,
28
+ tools= [scrape_url],
29
+ system_prompt="You are a reader agent. You must select the most relevant URL from the search results and use the scrape_url tool to extract its content. Always perform scraping using the tool."
30
+ )
31
+
32
+ #Writer chain
33
+
34
+ writer_prompt= ChatPromptTemplate.from_messages([
35
+ ('system', "You are an expert research writer. Write clear, structured and insightful reports. "),
36
+ ('human', """Write a detailed research report on the topic below.
37
+ Topic: {topic}
38
+
39
+ Research Gathered:
40
+ {research}
41
+
42
+ Structure the report as:
43
+ -Introduction
44
+ -Key Findings (minimum 3 well-explained points)
45
+ -Conclusion
46
+ -Sources (list all URLs found in the research)
47
+
48
+ Be detsailed, factual and professional."""),
49
+ ])
50
+
51
+ writer_chain= writer_prompt | llm | StrOutputParser()
52
+
53
+ #Critic Chain
54
+
55
+ critic_prompt= ChatPromptTemplate.from_messages([
56
+ ('system', "You are an expert research writer. Write clear, structured and insightful reports. "),
57
+ ('human', """Write a detailed research report on the topic below.
58
+
59
+ Report:
60
+ {report}
61
+
62
+ Respond in this exact format:
63
+
64
+ Score : X/10
65
+
66
+ Strengths:
67
+ - ...
68
+ - ...
69
+
70
+ Areas to Improve:
71
+ - ...
72
+ - ...
73
+
74
+ One liner verdict:
75
+ ..."""),
76
+
77
+ ])
78
+
79
+ critic_chain= critic_prompt | llm | StrOutputParser()
pipeline.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agents import build_reader_agent, build_search_agent, writer_chain, critic_chain
2
+
3
+ def extract_text(content) -> str:
4
+ if isinstance(content, str):
5
+ return content
6
+ elif isinstance(content, list):
7
+ return "".join(item.get("text", "") for item in content if isinstance(item, dict) and "text" in item)
8
+ return str(content)
9
+
10
+ def run_research_pipeline(topic: str) -> dict:
11
+
12
+ state={}
13
+
14
+ #Search Agent working
15
+
16
+ print("\n" + "="*50 )
17
+ print("step 1 - search agent is wokring ...")
18
+ print("=" *50)
19
+
20
+ search_agent= build_search_agent()
21
+ search_result= search_agent.invoke({
22
+ "messages": [("user", f"Find recent, reliable and detailed information about: {topic}")]
23
+ })
24
+
25
+ state["search_results"]= extract_text(search_result['messages'][-1].content)
26
+ print("\n search result", state['search_results'])
27
+
28
+ #Step 2 - reader agent
29
+ print("\n" + "="*50)
30
+ print("step 2- reader agent is scrapping top respurces ...")
31
+ print("="*50)
32
+
33
+ reader_agent= build_reader_agent()
34
+ reader_result = reader_agent.invoke({
35
+ "messages": [("user",
36
+ f"Based on the following search results about '{topic}',"
37
+ f"pick the most relevant URL and scrape it for deeper content.\n\n"
38
+ f"Search Results: \n{state['search_results'][:800]}"
39
+ )]
40
+ })
41
+
42
+ state['scraped_content']= extract_text(reader_result['messages'][-1].content)
43
+ print("\nScraped content\n", state['scraped_content'])
44
+
45
+ #Step 3- writer chain
46
+
47
+ print("\n" + "="*50)
48
+ print("step 3- Writer is drafting the report ...")
49
+ print("="*50)
50
+
51
+ research_combined= (
52
+ f"Search Results: \n {state['search_results']}\n\n"
53
+ f"Detailed Scraped Content: \n {state['scraped_content']}"
54
+
55
+ )
56
+
57
+ state['report']= writer_chain.invoke({
58
+ "topic":topic,
59
+ "research": research_combined
60
+ })
61
+
62
+ print("\n final report\n", state['report'])
63
+
64
+ #Critic Report
65
+ print("\n" + "="*50)
66
+ print("step 3- Critic is reviewing the report ...")
67
+ print("="*50)
68
+
69
+ state['feedback']=critic_chain.invoke({
70
+ "report": state['report']
71
+ })
72
+
73
+ print("\n critic report \n", state['feedback'])
74
+
75
+ return state
76
+
77
+ if __name__ == "__main__":
78
+ topic= input("\n Enter a research topic: " )
79
+ run_research_pipeline(topic)
80
+
requirement.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ langchain
2
+ langchain-core
3
+ langchain-community
4
+ langgraph
5
+ langchain-google-genai>=2.0.0
6
+
7
+ tavily-python
8
+ beautifulsoup4
9
+ requests
10
+ lxml
11
+
12
+ python-dotenv
13
+ pydantic
14
+ httpx
15
+ aiohttp
16
+ tiktoken
17
+ tenacity
18
+
19
+ fastapi
20
+ uvicorn
21
+
22
+ langsmith
23
+ pytest
tools.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # We build 2 custom tools using the @tool decorator. Firest the web_search tool which talks to the Tavily API and fetches live search results from the internet. Second the scrape_url tool which takes a URL, visits that page and extracts clean and readable text from it using BeautifulSoup.
2
+ from langchain.tools import tool
3
+ import requests
4
+ from bs4 import BeautifulSoup
5
+ from tavily import TavilyClient
6
+ import os
7
+ from dotenv import load_dotenv
8
+ from rich import print
9
+
10
+ load_dotenv()
11
+
12
+ tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
13
+ @tool
14
+ def web_search(query : str ) -> str:
15
+ """Search the web for recent and reliable information on a given topic. Returns Titles, URLs and snippets """
16
+ results = tavily.search(query= query, max_result=5)
17
+ out=[]
18
+
19
+ for r in results['results']:
20
+ out.append(
21
+ f'Title: {r["title"]}\nURL: {r["url"]}\nSnippet: {r["content"][:300]}\n'
22
+ )
23
+ return "\n----\n".join(out)
24
+
25
+ # print(web_search.invoke("What is the recent news of war"))
26
+
27
+
28
+ @tool
29
+ def scrape_url(url: str) -> str:
30
+ """Scrape and return clean text content from a given URL."""
31
+ try:
32
+ resp= requests.get(url, timeout=8, headers= {"User-Agent": "Mozilla/5.0"})
33
+ soup= BeautifulSoup(resp.text, "html.parser")
34
+ for tag in soup(["script","style", "nav", "footer"]):
35
+ tag.decompose()
36
+ return soup.get_text(separator="\n", strip=True)[:3000]
37
+ except Exception as e:
38
+ return f"Could not scrape URL: {str(e)}"
39
+
40
+ # print(scrape_url.invoke("https://www.bbc.com/news/world-europe-66707497"))