Multi-Agent_Research_Assist / multi_agent_research.py
Solo448's picture
Upload 2 files
a1da592 verified
import os
from typing import TypedDict
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END
from langchain_groq import ChatGroq
from langchain_community.tools.tavily_search import TavilySearchResults
# Load environment variables from .env file
load_dotenv()
# ------------------------------------------------------------------------------
# 1. State Definition
# ------------------------------------------------------------------------------
class ResearchState(TypedDict):
"""
Represents the state of our multi-agent research workflow.
"""
topic: str
raw_data: str
draft_summary: str
final_report: str
# ------------------------------------------------------------------------------
# 2. Node Functions
# ------------------------------------------------------------------------------
def researcher_node(state: ResearchState) -> ResearchState:
"""
Uses the Tavily search tool to browse the web for recent information
based on the topic, and saves the findings to `raw_data`.
"""
print("-> Running [Researcher Node]...")
topic = state.get("topic", "")
try:
tagline = f"Latest findings on {topic}"
# Initialize the Tavily tool
search_tool = TavilySearchResults(max_results=4)
# Invoke search
results = search_tool.invoke({"query": topic})
# Format the results into a string block
if isinstance(results, list):
formatted_data = "\n\n".join(
[f"Source: {res.get('url', 'N/A')}\nContent: {res.get('content', 'N/A')}"
for res in results]
)
else:
# Fallback if the results aren't a list
formatted_data = str(results)
return {"raw_data": f"--- Search Results ---\n{formatted_data}"}
except Exception as e:
error_msg = f"Error during web search: {str(e)}"
print(f" [Error] {error_msg}")
return {"raw_data": error_msg}
def analyst_node(state: ResearchState) -> ResearchState:
"""
Takes the `raw_data` and uses the Groq LLM to synthesize the
information into a structured, well-formatted draft summary.
"""
print("-> Running [Analyst Node]...")
topic = state.get("topic", "")
raw_data = state.get("raw_data", "")
try:
# Initialize Groq LLM
llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.3)
prompt = f"""
You are an expert analyst. Your task is to write a well-formatted, comprehensive
draft summary based on the provided raw research data.
Topic: {topic}
Raw Data:
{raw_data}
Please synthesize this information into a structured draft.
"""
# Invoke LLM
response = llm.invoke(prompt)
# The content of the response represents our draft
return {"draft_summary": response.content}
except Exception as e:
error_msg = f"Error generating draft: {str(e)}"
print(f" [Error] {error_msg}")
return {"draft_summary": error_msg}
def reviewer_node(state: ResearchState) -> ResearchState:
"""
Takes the `draft_summary` and `raw_data`, uses the Groq LLM to fact-check
the draft against the raw data for hallucinations, and outputs the final report.
"""
print("-> Running [Reviewer Node]...")
topic = state.get("topic", "")
raw_data = state.get("raw_data", "")
draft_summary = state.get("draft_summary", "")
try:
# Initialize Groq LLM
llm = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.1)
prompt = f"""
You are a meticulous reviewer and fact-checker. Your task is to review
the draft summary below against the provided raw data.
Goal:
1. Ensure the draft does not contain any hallucinations or claims NOT supported by the raw data.
2. Correct any inaccuracies.
3. Output the final, polished report in a clear, highly-readable format.
Topic: {topic}
--- RAW DATA ---
{raw_data}
--- DRAFT SUMMARY ---
{draft_summary}
Output ONLY the final, refined report. Do not include any extra conversational filler.
"""
# Invoke LLM
response = llm.invoke(prompt)
return {"final_report": response.content}
except Exception as e:
error_msg = f"Error during review: {str(e)}"
print(f" [Error] {error_msg}")
return {"final_report": error_msg}
# ------------------------------------------------------------------------------
# 3. Graph Construction
# ------------------------------------------------------------------------------
def build_research_graph():
"""
Builds and compiles the StateGraph workflow.
"""
# Create the graph builder with our TypedDict state
builder = StateGraph(ResearchState)
# Add all three nodes
builder.add_node("researcher", researcher_node)
builder.add_node("analyst", analyst_node)
builder.add_node("reviewer", reviewer_node)
# Define the sequential flow
builder.add_edge(START, "researcher")
builder.add_edge("researcher", "analyst")
builder.add_edge("analyst", "reviewer")
builder.add_edge("reviewer", END)
# Compile the graph into a runnable workflow
return builder.compile()
# ------------------------------------------------------------------------------
# 4. Main Execution
# ------------------------------------------------------------------------------
if __name__ == "__main__":
# Check if necessary API keys are present in the environment
if not os.getenv("GROQ_API_KEY") or not os.getenv("TAVILY_API_KEY"):
print("⚠️ Missing API Keys!")
print("Please ensure both GROQ_API_KEY and TAVILY_API_KEY are set in your .env file or environment.")
exit(1)
# Build the computational graph
app = build_research_graph()
# Define our initial state with the requested topic
initial_topic = "Latest advancements in Agentic AI workflows"
initial_state = {
"topic": initial_topic,
"raw_data": "",
"draft_summary": "",
"final_report": ""
}
print("=" * 60)
print(f"STARTING RESEARCH WORKFLOW")
print(f"Topic: {initial_topic}")
print("=" * 60)
try:
# Run the workflow
final_state = app.invoke(initial_state)
# Output the Final Report
print("\n\n" + "=" * 60)
print(" " * 20 + "FINAL REPORT")
print("=" * 60 + "\n")
print(final_state.get("final_report", "No report generated."))
print("\n" + "=" * 60)
except Exception as e:
print(f"\n[Fatal Error] Workflow execution failed: {e}")