Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 

Repository files navigation

AI Multi-Agent Research System

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.

Project Structure

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

Prerequisites

  • Node.js 18+ and npm
  • Python 3.10 or higher
  • Git

API Keys Required

You need two API keys to run this project:

1. OpenAI API Key

Required for AI agent functionality

  1. Go to https://platform.openai.com/signup
  2. Sign up or log in to your account
  3. Navigate to https://platform.openai.com/api-keys
  4. Click "Create new secret key"
  5. Copy the key (starts with sk-)
  6. Note: You'll need to add a payment method. Cost is approximately $0.01-0.05 per request.

2. Serper API Key

Optional - for web search functionality (can be omitted for testing)

  1. Go to https://serper.dev/
  2. Sign up with Google, GitHub, or email
  3. You'll automatically receive 2,500 free searches
  4. Copy your API key from the dashboard
  5. No credit card required

Installation

Step 1: Clone or Download the Project

mkdir multi-agent-research
cd multi-agent-research

Step 2: Setup Backend

# 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 editor

Step 3: Create Backend Files

backend/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.3

backend/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 8000

backend/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

Step 4: Setup Frontend

# 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
EOF

Step 5: Create Frontend Files

frontend/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 dev

Make script executable:

chmod +x run-frontend.sh

frontend/.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

Running the Application

Start Backend (Terminal 1)

cd backend
./run-backend.sh

Backend will be available at http://localhost:8000

Start Frontend (Terminal 2)

cd frontend
./run-frontend.sh

Frontend will be available at http://localhost:3000

Testing the Application

  1. Open http://localhost:3000 in your browser
  2. Enter a research topic (e.g., "Quantum Computing")
  3. Click "Start Research"
  4. Watch the agents collaborate and generate results

Deploying to Production

Deploy Backend to Railway

cd backend
./deploy-backend.sh

After deployment:

  1. Get your deployment URL: railway status
  2. Set environment variables: railway variables set OPENAI_API_KEY=your_key

Deploy Frontend to Vercel

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 --prod

Environment Variables Reference

Backend (.env)

OPENAI_API_KEY=sk-...
SERPER_API_KEY=...

Frontend (.env.local)

PYTHON_BACKEND_URL=http://localhost:8000

For production, set PYTHON_BACKEND_URL to your deployed backend URL.

Troubleshooting

Backend won't start

Check Python version:

python3 --version  # Should be 3.10 or higher

Reinstall dependencies:

cd backend
source venv/bin/activate
pip install -r requirements.txt

Frontend can't connect to backend

  1. Verify backend is running: curl http://localhost:8000/health
  2. Check .env.local has correct PYTHON_BACKEND_URL
  3. Restart both servers

API key errors

  1. Verify keys are in .env file
  2. Check keys are valid at respective platforms
  3. Ensure .env is in backend directory (not frontend)

Timeout errors

If requests timeout, the topic may be too complex. Try:

  1. Shorter, more specific topics
  2. Check your OpenAI API quota/limits

Cost Estimates

  • 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

Tech Stack

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)

License

MIT

Support

For issues or questions, please refer to:

About

Collaborative AI agents powered by OpenAI and CrewAI

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors