An autonomous AI research system where three specialized agents collaborate to research any topic, generate content, and ensure quality. Powered by CrewAI, OpenAI, and Next.js.
multi-agent-research/
├── frontend/ # Next.js application
│ ├── app/
│ │ ├── page.tsx
│ │ ├── layout.tsx
│ │ ├── globals.css
│ │ └── api/
│ │ └── research/
│ │ └── route.ts
│ ├── components/
│ │ ├── agent-card.tsx
│ │ ├── research-form.tsx
│ │ └── results-display.tsx
│ ├── package.json
│ ├── .env.local
│ └── run-frontend.sh
│
└── backend/ # Python FastAPI application
├── main.py
├── requirements.txt
├── .env
├── run-backend.sh
└── deploy-backend.sh
- Node.js 18+ and npm
- Python 3.10 or higher
- Git
You need two API keys to run this project:
Required for AI agent functionality
- Go to https://platform.openai.com/signup
- Sign up or log in to your account
- Navigate to https://platform.openai.com/api-keys
- Click "Create new secret key"
- Copy the key (starts with
sk-) - Note: You'll need to add a payment method. Cost is approximately $0.01-0.05 per request.
Optional - for web search functionality (can be omitted for testing)
- Go to https://serper.dev/
- Sign up with Google, GitHub, or email
- You'll automatically receive 2,500 free searches
- Copy your API key from the dashboard
- No credit card required
mkdir multi-agent-research
cd multi-agent-research# Create backend directory
mkdir backend
cd backend
# Create Python virtual environment
python3 -m venv venv
# Activate virtual environment
# On macOS/Linux:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate
# Install dependencies
pip install --upgrade pip
pip install fastapi uvicorn[standard] crewai crewai[tools] langchain-openai python-dotenv
# Create .env file
cat > .env << EOF
OPENAI_API_KEY=your_openai_key_here
SERPER_API_KEY=your_serper_key_here
EOF
# Edit .env and add your actual API keys
nano .env # or use your preferred editorbackend/main.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from crewai import Agent, Task, Crew, Process
from langchain_openai import ChatOpenAI
import os
from dotenv import load_dotenv
load_dotenv()
app = FastAPI(title="Multi-Agent Research API")
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "http://localhost:3001"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class ResearchRequest(BaseModel):
topic: str
class ResearchResponse(BaseModel):
research: str
content: str
review: str
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.3,
api_key=os.getenv("OPENAI_API_KEY"),
max_tokens=500,
timeout=10
)
def create_crew(topic: str):
researcher = Agent(
role='Research Analyst',
goal=f'Find 3 key facts about {topic}',
backstory="""Quick research specialist.""",
verbose=False,
allow_delegation=False,
tools=[],
llm=llm
)
writer = Agent(
role='Writer',
goal=f'Write brief summary of {topic}',
backstory="""Concise content writer.""",
verbose=False,
allow_delegation=False,
llm=llm
)
research_task = Task(
description=f"""Provide 3 bullet points about {topic}. Keep it under 100 words total.""",
expected_output='3 bullet points (100 words max)',
agent=researcher
)
writing_task = Task(
description=f"""Write a 2-3 sentence summary about {topic}. Be concise.""",
expected_output='2-3 sentences',
agent=writer
)
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, writing_task],
process=Process.sequential,
verbose=False
)
return crew
@app.get("/")
async def root():
return {"message": "Multi-Agent Research API", "status": "running"}
@app.post("/research", response_model=ResearchResponse)
async def research(request: ResearchRequest):
try:
crew = create_crew(request.topic)
result = crew.kickoff(inputs={'topic': request.topic})
tasks_output = result.tasks_output if hasattr(result, 'tasks_output') else []
return ResearchResponse(
research=str(tasks_output[0]) if len(tasks_output) > 0 else str(result),
content=str(tasks_output[1]) if len(tasks_output) > 1 else "Content generated",
review="Quick demo - full review available in production"
)
except Exception as e:
print(f"ERROR: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"api_key_set": bool(os.getenv("OPENAI_API_KEY"))
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)backend/requirements.txt
fastapi==0.109.0
uvicorn[standard]==0.27.0
crewai==0.28.0
crewai[tools]==0.28.0
langchain-openai==0.0.5
python-dotenv==1.0.0
pydantic==2.5.3backend/run-backend.sh
#!/bin/bash
echo "Starting Multi-Agent Research Backend..."
echo "========================================="
# Check if virtual environment exists
if [ ! -d "venv" ]; then
echo "Error: Virtual environment not found."
echo "Please run: python3 -m venv venv"
exit 1
fi
# Activate virtual environment
source venv/bin/activate
# Check if .env exists
if [ ! -f ".env" ]; then
echo "Error: .env file not found."
echo "Please create .env with your API keys."
exit 1
fi
# Start the server
echo "Starting FastAPI server on http://localhost:8000"
uvicorn main:app --reload --port 8000backend/deploy-backend.sh
#!/bin/bash
echo "Deploying Backend to Railway..."
echo "================================"
# Check if Railway CLI is installed
if ! command -v railway &> /dev/null; then
echo "Railway CLI not found. Installing..."
npm install -g @railway/cli
fi
# Login to Railway
echo "Logging into Railway..."
railway login
# Initialize project if needed
if [ ! -f "railway.json" ]; then
echo "Initializing Railway project..."
railway init
fi
# Deploy
echo "Deploying to Railway..."
railway up
echo ""
echo "Deployment complete!"
echo "Get your deployment URL with: railway status"
echo "Set environment variables with: railway variables"Make scripts executable:
chmod +x run-backend.sh
chmod +x deploy-backend.sh# Navigate back to project root
cd ..
# Create Next.js app
npx create-next-app@latest frontend --typescript --tailwind --app --no-src-dir
# Navigate to frontend
cd frontend
# Install additional dependencies
npm install lucide-react
# Create .env.local
cat > .env.local << EOF
PYTHON_BACKEND_URL=http://localhost:8000
EOFfrontend/app/page.tsx
'use client';
import { useState } from 'react';
import { Loader2, Users } from 'lucide-react';
import AgentCard from '../components/agent-card';
import ResearchForm from '../components/research-form';
import ResultsDisplay from '../components/results-display';
export default function Home() {
const [topic, setTopic] = useState('');
const [loading, setLoading] = useState(false);
const [results, setResults] = useState<any>(null);
const [activeAgent, setActiveAgent] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const handleResearch = async (researchTopic: string) => {
setLoading(true);
setResults(null);
setError(null);
setTopic(researchTopic);
try {
const response = await fetch('/api/research', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic: researchTopic }),
});
if (!response.ok) {
throw new Error('Research failed');
}
const data = await response.json();
setResults(data.data);
} catch (err) {
setError('Failed to complete research. Please try again.');
console.error(err);
} finally {
setLoading(false);
setActiveAgent(null);
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 text-white p-8">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-12">
<div className="flex items-center justify-center gap-3 mb-4">
<Users className="w-12 h-12 text-purple-400" />
<h1 className="text-4xl font-bold">Multi-Agent Research System</h1>
</div>
<p className="text-slate-300">
Powered by CrewAI collaborative agents
</p>
</div>
<div className="grid md:grid-cols-3 gap-6 mb-8">
<AgentCard
name="Researcher"
role="Gathers and analyzes information"
color="bg-blue-500"
icon="search"
isActive={activeAgent === 'researcher'}
/>
<AgentCard
name="Writer"
role="Creates structured content"
color="bg-green-500"
icon="file-text"
isActive={activeAgent === 'writer'}
/>
<AgentCard
name="Reviewer"
role="Reviews and provides feedback"
color="bg-purple-500"
icon="check-circle"
isActive={activeAgent === 'reviewer'}
/>
</div>
<ResearchForm onSubmit={handleResearch} loading={loading} error={error} />
{results && <ResultsDisplay results={results} />}
</div>
</div>
);
}frontend/app/api/research/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const { topic } = await request.json();
if (!topic) {
return NextResponse.json(
{ error: 'Topic is required' },
{ status: 400 }
);
}
const backendUrl = process.env.PYTHON_BACKEND_URL || 'http://localhost:8000';
const response = await fetch(`${backendUrl}/research`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ topic }),
});
if (!response.ok) {
throw new Error('Research API failed');
}
const data = await response.json();
return NextResponse.json({
success: true,
data: data
});
} catch (error) {
console.error('Research error:', error);
return NextResponse.json(
{ error: 'Failed to complete research' },
{ status: 500 }
);
}
}
export async function GET() {
return NextResponse.json({
message: 'Multi-Agent Research API',
status: 'ready'
});
}frontend/components/agent-card.tsx
import { Search, FileText, CheckCircle, Loader2 } from 'lucide-react';
interface AgentCardProps {
name: string;
role: string;
color: string;
icon: 'search' | 'file-text' | 'check-circle';
isActive: boolean;
}
const iconMap = {
'search': Search,
'file-text': FileText,
'check-circle': CheckCircle,
};
export default function AgentCard({ name, role, color, icon, isActive }: AgentCardProps) {
const Icon = iconMap[icon];
return (
<div
className={`bg-white rounded-xl p-6 border-2 transition-all ${
isActive
? 'border-purple-400 shadow-lg shadow-purple-500/50 scale-105'
: 'border-gray-200'
}`}
>
<div className={`${color} w-12 h-12 rounded-lg flex items-center justify-center mb-4`}>
<Icon className={`w-6 h-6 ${name === 'Writer' ? 'text-green-900' : 'text-white'}`} />
</div>
<h3 className="text-xl font-semibold mb-2 text-black">{name}</h3>
<p className="text-black text-sm">{role}</p>
{isActive && (
<div className="mt-4 flex items-center gap-2 text-black">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="text-sm">Working...</span>
</div>
)}
</div>
);
}frontend/components/research-form.tsx
'use client';
import { useState } from 'react';
import { Loader2 } from 'lucide-react';
interface ResearchFormProps {
onSubmit: (topic: string) => void;
loading: boolean;
error: string | null;
}
export default function ResearchForm({ onSubmit, loading, error }: ResearchFormProps) {
const [topic, setTopic] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (topic.trim()) {
onSubmit(topic);
}
};
return (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700 mb-8">
<label className="block text-sm font-medium mb-2">Research Topic</label>
<form onSubmit={handleSubmit} className="flex gap-4">
<input
type="text"
value={topic}
onChange={(e) => setTopic(e.target.value)}
placeholder="Enter a topic to research (e.g., 'Artificial Intelligence in Healthcare')"
className="flex-1 bg-slate-900 border border-slate-600 rounded-lg px-4 py-3 focus:outline-none focus:border-purple-500 transition-colors text-white"
disabled={loading}
/>
<button
type="submit"
disabled={loading || !topic.trim()}
className="bg-purple-600 hover:bg-purple-700 disabled:bg-slate-700 disabled:cursor-not-allowed px-8 py-3 rounded-lg font-medium transition-colors flex items-center gap-2 whitespace-nowrap"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Processing
</>
) : (
'Start Research'
)}
</button>
</form>
{error && <p className="mt-2 text-red-400 text-sm">{error}</p>}
</div>
);
}frontend/components/results-display.tsx
import { Search, FileText, CheckCircle } from 'lucide-react';
interface ResultsDisplayProps {
results: {
research: string;
content: string;
review: string;
};
}
export default function ResultsDisplay({ results }: ResultsDisplayProps) {
return (
<div className="space-y-6">
<div className="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
<div className="flex items-center gap-3 mb-4">
<div className="bg-blue-500 w-10 h-10 rounded-lg flex items-center justify-center">
<Search className="w-5 h-5 text-white" />
</div>
<h2 className="text-2xl font-semibold">Research Findings</h2>
</div>
<div className="text-slate-300 whitespace-pre-wrap">{results.research}</div>
</div>
<div className="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
<div className="flex items-center gap-3 mb-4">
<div className="bg-green-500 w-10 h-10 rounded-lg flex items-center justify-center">
<FileText className="w-5 h-5 text-white" />
</div>
<h2 className="text-2xl font-semibold">Generated Content</h2>
</div>
<div className="text-slate-300 whitespace-pre-wrap">{results.content}</div>
</div>
<div className="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
<div className="flex items-center gap-3 mb-4">
<div className="bg-purple-500 w-10 h-10 rounded-lg flex items-center justify-center">
<CheckCircle className="w-5 h-5 text-white" />
</div>
<h2 className="text-2xl font-semibold">Review & Feedback</h2>
</div>
<div className="text-slate-300 whitespace-pre-wrap">{results.review}</div>
</div>
</div>
);
}frontend/run-frontend.sh
#!/bin/bash
echo "Starting Multi-Agent Research Frontend..."
echo "=========================================="
# Check if node_modules exists
if [ ! -d "node_modules" ]; then
echo "Installing dependencies..."
npm install
fi
# Check if .env.local exists
if [ ! -f ".env.local" ]; then
echo "Warning: .env.local file not found."
echo "Creating default .env.local..."
echo "PYTHON_BACKEND_URL=http://localhost:8000" > .env.local
fi
# Start the development server
echo "Starting Next.js development server on http://localhost:3000"
npm run devMake script executable:
chmod +x run-frontend.shfrontend/.gitignore
# Dependencies
node_modules
/.pnp
.pnp.js
# Testing
/coverage
# Next.js
/.next/
/out/
# Production
/build
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem
.vercel
backend/.gitignore
# Python
venv/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
# Environment
.env
.env.local
# Distribution
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
*.swp
# OS
.DS_Store
cd backend
./run-backend.shBackend will be available at http://localhost:8000
cd frontend
./run-frontend.shFrontend will be available at http://localhost:3000
- Open http://localhost:3000 in your browser
- Enter a research topic (e.g., "Quantum Computing")
- Click "Start Research"
- Watch the agents collaborate and generate results
cd backend
./deploy-backend.shAfter deployment:
- Get your deployment URL:
railway status - Set environment variables:
railway variables set OPENAI_API_KEY=your_key
cd frontend
npm install -g vercel
vercel
# Set environment variable
vercel env add PYTHON_BACKEND_URL production
# Enter your Railway URL (e.g., https://your-app.up.railway.app)
# Deploy
vercel --prodOPENAI_API_KEY=sk-...
SERPER_API_KEY=...
PYTHON_BACKEND_URL=http://localhost:8000
For production, set PYTHON_BACKEND_URL to your deployed backend URL.
Check Python version:
python3 --version # Should be 3.10 or higherReinstall dependencies:
cd backend
source venv/bin/activate
pip install -r requirements.txt- Verify backend is running:
curl http://localhost:8000/health - Check .env.local has correct PYTHON_BACKEND_URL
- Restart both servers
- Verify keys are in .env file
- Check keys are valid at respective platforms
- Ensure .env is in backend directory (not frontend)
If requests timeout, the topic may be too complex. Try:
- Shorter, more specific topics
- Check your OpenAI API quota/limits
- OpenAI GPT-4o-mini: ~$0.01-0.05 per request
- Serper API: Free for 2,500 searches/month
- Railway: Free tier available (500 hours/month)
- Vercel: Free tier available
Frontend:
- Next.js 14 (React framework)
- TypeScript (type safety)
- Tailwind CSS (styling)
- Lucide React (icons)
Backend:
- Python 3.10+
- FastAPI (web framework)
- CrewAI (multi-agent orchestration)
- LangChain (LLM framework)
- OpenAI GPT-4o-mini (language model)
MIT
For issues or questions, please refer to:
- CrewAI Documentation: https://docs.crewai.com/
- Next.js Documentation: https://nextjs.org/docs
- FastAPI Documentation: https://fastapi.tiangolo.com/