An AI-powered multi-agent pipeline that automates the sales engineering workflow: ingest RFP/RFQ documents, extract structured intelligence using Google Gemini, match the best-fit employees from your talent pool, and send outreach notifications — all orchestrated through a modern web interface.
- Architecture
- Tech Stack
- Prerequisites & Setup
- Environment Variables
- Running the Application
- API Documentation
- Core Modules
- Data Models
- CLI Usage
- Project Structure
- Dependencies Reference
flowchart TB
subgraph Frontend ["Next.js Frontend (:3000)"]
UI[Web UI - React 19]
API_ROUTES[Next.js API Routes]
STORAGE[Local JSON Storage]
end
subgraph Backend ["FastAPI Backend (:8000)"]
ANALYZE["/api/analyze"]
ANALYZE_MULTI["/api/analyze-multi"]
MATCH["/api/match"]
EXTRACT_CV["/api/extract-cv"]
end
subgraph AI ["Google Gemini 2.5 Flash"]
RFP_AGENT[RFP Analysis Agent]
MATCHMAKER[Matchmaking Agent]
CV_PARSER[CV Extraction]
end
subgraph External ["External Services"]
NTFY[ntfy.sh Notifications]
GDRIVE[Google Drive]
GSHEETS[Google Sheets CRM]
end
UI -->|Upload RFP/RFQ| API_ROUTES
API_ROUTES -->|Forward files| ANALYZE_MULTI
API_ROUTES -->|Employee matching| MATCH
API_ROUTES -->|CV upload| EXTRACT_CV
API_ROUTES -->|Send notifications| NTFY
API_ROUTES <-->|Read/Write| STORAGE
ANALYZE --> RFP_AGENT
ANALYZE_MULTI --> RFP_AGENT
MATCH --> MATCHMAKER
EXTRACT_CV --> CV_PARSER
GDRIVE -->|CV ingestion| CV_PARSER
CV_PARSER -->|Structured data| GSHEETS
stateDiagram-v2
[*] --> INGESTION: Upload RFP/RFQ documents
INGESTION --> ANALYSIS: Gemini extracts requirements, skills, risks
ANALYSIS --> PLANNING: Human clarification + employee matching
PLANNING --> OUTREACH: PM selected, team assembled
OUTREACH --> [*]: Notifications sent via ntfy.sh
- Ingestion — User uploads one or more RFP/RFQ documents (PDF, DOCX, TXT)
- Analysis — Gemini 2.5 Flash extracts structured intelligence: requirements, skills matrix, deadlines, risks, compliance norms
- Planning — System matches employees against extracted requirements, human provides clarification if needed, PM is auto-selected
- Outreach — Notifications are sent to the PM and matched team members via ntfy.sh
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | Next.js 16, React 19, TypeScript | App Router-based web interface |
| Styling | Tailwind CSS v4, Framer Motion | Responsive design and animations |
| Backend API | FastAPI, Uvicorn | Python AI/ML service layer |
| Backend Orchestration | Next.js API Routes | Request orchestration, state management |
| AI/ML | Google Gemini 2.5 Flash | Structured extraction, matchmaking, CV parsing |
| Document Parsing | pdfplumber, python-docx, Gemini Files API | PDF/DOCX/TXT text extraction (with multimodal fallback) |
| Data Storage | Local JSON files | Employee and project persistence |
| Notifications | ntfy.sh | Push notifications to team members |
| Onboarding | Google Drive API, Google Sheets API | CV ingestion pipeline to CRM |
| Validation | Pydantic v2 | Structured output validation and schemas |
- Python 3.10+
- Node.js 18+
- Gemini API Key — get one here
git clone <repo-url>
cd web
# Create your .env file
cp .env.example .env
# Set your Gemini API key:
# GEMINI_API_KEY=your_key_here# Python dependencies (FastAPI + RFP agent + AI libraries)
pip3 install -r requirements.txt
# Node.js dependencies (Next.js frontend)
npm installTerminal 1 — Python API server (port 8000):
python3 -m uvicorn server:app --port 8000Terminal 2 — Next.js frontend (port 3000):
npm run devOpen http://localhost:3000 in your browser.
Note: Both servers must be running simultaneously. The frontend proxies AI requests to the Python backend.
- Use
python3/pip3— macOS ships with Python 2 by default - If
uvicornnot found:python3 -m uvicorn server:app --port 8000 - Port conflict: try
--port 8001and setNEXT_PUBLIC_AGENT_API_URL=http://localhost:8001in.env
| Variable | Required | Default | Description |
|---|---|---|---|
GEMINI_API_KEY |
Yes | — | Google Gemini API key for all AI operations |
NEXT_PUBLIC_AGENT_API_URL |
No | http://localhost:8000 |
URL of the Python FastAPI server |
CRM_SPREADSHEET_ID |
For onboarding | — | Google Sheets spreadsheet ID for CRM |
DROPZONE_FOLDER_NAME |
For onboarding | 01_CV_Dropzone |
Google Drive folder name for incoming CVs |
PROCESSED_FOLDER_NAME |
For onboarding | 02_Processed_CVs |
Google Drive folder for processed CVs |
SKILLS_DB_SHEET_NAME |
For onboarding | Skills_DB |
Sheet tab name in the CRM spreadsheet |
GEMINI_MODEL |
No | gemini-1.5-flash |
Model override for onboarding agent |
- Start both servers (see Setup above)
- Navigate to
http://localhost:3000 - Upload an RFP/RFQ document
- Review extracted requirements, skills, and risks
- Provide clarification when prompted
- View matched employees and selected PM
- Trigger outreach notifications
- Upload — Drag and drop any RFP PDF into the upload zone
- Analysis — The system displays extracted requirements, skills matrix, deadlines, and risks within seconds
- Matchmaking — Answer the clarification question, then view ranked employee matches with fit scores
- Outreach — Click to notify the selected PM and team via push notifications
Analyze a single uploaded RFP document.
| Field | Value |
|---|---|
| Content-Type | multipart/form-data |
| Body | file — PDF, DOCX, or TXT |
Response:
{
"requirements": ["REQ-001 [HIGH*] SCADA integration: ..."],
"ambiguity": "Scope boundary between Phase 1 and Phase 2 is unclear...",
"suggestedSkills": ["Siemens TIA Portal — senior × 2"],
"rfp_title": "...",
"client_name": "...",
"rfp_summary": "...",
"submission_deadline": "2025-06-30",
"project_duration": "18 months",
"confidence_score": 0.87,
"deadlines": [...],
"risks": [...],
"dependencies": [...],
"compliance_norms": [...],
"key_evaluation_criteria": [...],
"pitfalls": [...],
"planning_payload": { ... }
}Analyze multiple uploaded files as one combined RFP.
| Field | Value |
|---|---|
| Content-Type | multipart/form-data |
| Body | files — multiple PDF/DOCX/TXT files |
Response: Same schema as /api/analyze.
Extract structured employee profile from a CV PDF.
| Field | Value |
|---|---|
| Content-Type | multipart/form-data |
| Body | file — PDF CV |
Response:
{
"status": "success",
"data": {
"firstName": "Anna",
"lastName": "Schmidt",
"email": "anna@example.com",
"location": "Munich",
"role": "Automation Engineer",
"level": "senior",
"yearsOfExperience": 8,
"pastIndustryExperience": ["Energy", "Manufacturing"],
"skills": ["Siemens TIA Portal", "SCADA", "IEC 61850"],
"certifications": ["ISO 9001 Auditor"],
"linkedin": "https://linkedin.com/in/anna-schmidt"
}
}Match employees against project requirements using Gemini.
| Field | Value |
|---|---|
| Content-Type | application/json |
Request:
{
"requirements": ["REQ-001 [HIGH*] SCADA integration: ..."],
"employees": [
{
"id": "emp-1",
"name": "Anna Schmidt",
"role": "Automation Engineer",
"skills": ["Siemens TIA Portal", "SCADA", "IEC 61850"]
}
],
"human_answer": "optional clarification string"
}Response:
{
"matches": [
{
"id": "emp-1",
"name": "Anna Schmidt",
"role": "Automation Engineer",
"match": 92,
"reason": "Direct match on all three core technical skills.",
"skills": ["Siemens TIA Portal", "SCADA", "IEC 61850"]
}
]
}Health check endpoint. Returns { "status": "ok" }.
Orchestrates RFP ingestion: accepts files, calls Python /api/analyze-multi, creates project state.
| Field | Value |
|---|---|
| Content-Type | multipart/form-data |
| Body | files — one or more RFP documents |
Response:
{
"status": "success",
"project": {
"id": "uuid",
"stage": "ANALYSIS",
"requirements": [...],
"clarificationQuestion": "...",
"thoughtLog": [...],
"createdAt": "...",
"updatedAt": "..."
}
}Adds human clarification, triggers employee matching, auto-selects PM.
Request:
{
"projectId": "uuid",
"humanAnswer": "We need someone with IEC 61850 experience"
}Response:
{
"status": "success",
"project": {
"matchCandidates": [...],
"selectedPM": { ... },
"thoughtLog": [...],
"status": "PLANNING"
}
}Sends push notifications to selected PM and team members via ntfy.sh.
Request:
{
"projectId": "uuid"
}Response:
{
"status": "success",
"notifications": [...],
"pm": { ... }
}Returns the full employee list from local storage.
Response:
{
"status": "success",
"employees": [...]
}Create or update an employee profile by ID.
Request: JSON employee object with fields matching the CandidateProfile schema.
Upload a CV PDF, extract structured profile via Gemini, store the file, detect duplicates.
| Field | Value |
|---|---|
| Content-Type | multipart/form-data |
| Body | file — PDF CV |
Response:
{
"status": "success",
"employee": { ... },
"cvUrl": "/cvs/filename.pdf",
"matches": []
}The core intelligence engine. Analyzes RFP/RFQ documents and extracts structured data.
How it works:
- Document Loading — Supports PDF (via pdfplumber or Gemini Files API for scanned docs), DOCX (python-docx), and plain text
- Chunking — For documents exceeding ~700K characters, splits into overlapping paragraph-boundary chunks
- Parallel Analysis — Sends 3 parallel Gemini calls (Part A: metadata + requirements, Part B: skills + deadlines, Part C: risks + compliance) for faster processing
- Merge & Deduplicate — Combines results, deduplicates requirements, renumbers IDs
- Structured Output — Returns a fully validated
RFPAnalysisPydantic model
Key files:
rfp_agent/agent.py—RFPAnalysisAgentclass withanalyze_file()andto_planning_agent_payload()rfp_agent/models.py— Pydantic models (RFPAnalysis,Requirement,SkillRequirement, etc.)rfp_agent/prompts.py— System, analysis, and merge prompts for Geminirfp_agent/document_loader.py— Multi-format document loading with fallbacks
Ranks employees against extracted requirements using Gemini's reasoning capabilities.
How it works:
- Receives requirements list + employee pool + optional human clarification
- Sends to Gemini for semantic matching (skills, experience, certifications)
- Returns ranked list with match scores (0-100) and reasoning
Extracts structured CandidateProfile data from uploaded CV PDFs using Gemini.
Extracted fields: name, email, location, role, level, years of experience, industry experience, skills, certifications, LinkedIn URL.
Automated pipeline for bulk CV ingestion from Google Drive into a CRM spreadsheet.
Flow:
- Authenticates with Google APIs (Drive + Sheets)
- Lists PDFs in a configured Google Drive "dropzone" folder
- Downloads and parses each CV with Gemini
- Appends structured data rows to a Google Sheets CRM
- Moves processed files to an archive folder
- Continues on per-file failures without stopping
Key files:
onboarding_agent/main.py— Pipeline orchestrationonboarding_agent/services/extractor.py— Gemini-powered CV parsingonboarding_agent/services/google_api.py— Google Drive/Sheets client
Sends push notifications to team members via ntfy.sh — a simple HTTP-based pub/sub notification service. No signup required.
RFPAnalysis
├── rfp_title string
├── client_name string | null
├── rfp_summary string — 2-3 sentence executive summary
├── project_scope string — detailed scope of work
├── submission_deadline string | null
├── project_duration string | null
├── estimated_team_size string | null
├── budget_constraints string | null
├── requirements[] Requirement
│ ├── id REQ-001, REQ-002, ...
│ ├── category functional | technical | operational | compliance | financial | resource
│ ├── title string
│ ├── description string
│ ├── priority high | medium | low
│ ├── is_mandatory bool
│ ├── skills_needed string[]
│ └── section_reference string | null
├── skills_required[] SkillRequirement
│ ├── skill string (granular, e.g. "OCPP 2.0.1" not "EV charging")
│ ├── category technical | domain | certification | soft_skill | language
│ ├── proficiency_level junior | mid | senior | expert
│ ├── quantity_needed int
│ ├── context string
│ └── related_requirement_ids string[]
├── deadlines[] Deadline
├── dependencies[] Dependency
├── risks[] Risk
├── compliance_norms[] ComplianceNorm
├── key_evaluation_criteria string[]
├── pitfalls string[]
├── analysis_notes string[]
└── confidence_score float (0.0–1.0)
A lightweight subset of RFPAnalysis optimized for downstream agent consumption:
{
"rfp_title": "...",
"client_name": "...",
"submission_deadline": "...",
"project_duration": "...",
"estimated_team_size": "...",
"skills_required": [...],
"high_priority_requirements": [...],
"dependencies": [...],
"compliance_norms": [...]
}| State | Description |
|---|---|
INGESTION |
Documents uploaded, awaiting analysis |
ANALYSIS |
RFP analyzed, clarification question generated |
PLANNING |
Employees matched, PM selected |
OUTREACH |
Notifications sent to team |
{
"firstName": "string",
"lastName": "string",
"email": "string",
"location": "string",
"role": "string",
"level": "junior | mid | senior | expert",
"yearsOfExperience": "number",
"pastIndustryExperience": ["string"],
"skills": ["string"],
"certifications": ["string"],
"linkedin": "string | null"
}Run the agent directly from the terminal without the web UI:
# Single document
python3 main.py rfp.pdf
# Multiple documents
python3 main.py rfp1.pdf rfp2.docx rfp3.pdf
# Save results to files
python3 main.py rfp1.pdf rfp2.pdf --output-dir results/ --planning-dir planning/
# Produces:
# results/rfp1.json — full analysis
# results/rfp2.json
# planning/rfp1_planning.json — slim payload for downstream agents
# planning/rfp2_planning.jsonweb/
├── server.py — FastAPI backend (port 8000)
├── main.py — CLI entry point for batch processing
├── requirements.txt — Python dependencies
├── package.json — Node.js dependencies
├── next.config.ts — Next.js configuration
├── tsconfig.json — TypeScript configuration
├── eslint.config.mjs — ESLint configuration
├── postcss.config.mjs — PostCSS/Tailwind configuration
│
├── rfp_agent/ — Core RFP analysis engine
│ ├── __init__.py — Package exports (RFPAnalysisAgent)
│ ├── agent.py — RFPAnalysisAgent class
│ ├── models.py — Pydantic data models
│ ├── prompts.py — Gemini prompt templates
│ └── document_loader.py — PDF/DOCX/TXT loader with fallbacks
│
├── onboarding_agent/ — Google Drive → Sheets CV pipeline
│ ├── main.py — Pipeline orchestration
│ ├── config.py — Environment configuration
│ ├── models.py — CandidateProfile schema
│ ├── requirements.txt — Additional Python deps
│ └── services/
│ ├── extractor.py — Gemini CV extraction
│ └── google_api.py — Google Drive/Sheets client
│
├── src/
│ ├── app/
│ │ ├── page.tsx — Main UI (upload, analysis, matching)
│ │ ├── layout.tsx — Root layout
│ │ ├── globals.css — Global styles
│ │ └── api/
│ │ ├── process-rfq/ — RFP ingestion orchestrator
│ │ ├── matchmaking/ — Employee matching + PM selection
│ │ ├── outreach/ — ntfy.sh notification sender
│ │ └── employees/ — Employee CRUD + CV extraction
│ │ ├── route.ts — GET all employees
│ │ ├── [id]/route.ts— POST upsert employee
│ │ └── extract/route.ts — POST CV upload + extraction
│ └── lib/
│ ├── gemini.ts — Frontend → FastAPI bridge
│ └── storage.ts — Local JSON persistence layer
│
├── data/
│ ├── employees.json — Employee database
│ └── projects.json — Project state store
│
└── public/
└── cvs/ — Uploaded CV file storage
| Package | Version | Purpose |
|---|---|---|
| fastapi | ≥ 0.111.0 | HTTP API framework |
| uvicorn | ≥ 0.29.0 | ASGI server |
| python-multipart | ≥ 0.0.9 | File upload handling |
| google-genai | ≥ 1.0.0 | Google Gemini AI SDK |
| pydantic | ≥ 2.0.0 | Data validation and schemas |
| pdfplumber | ≥ 0.10.0 | PDF text extraction |
| python-docx | ≥ 1.1.0 | DOCX document parsing |
| python-dotenv | ≥ 1.0.0 | Environment variable loading |
| rich | ≥ 13.0.0 | Terminal formatting (CLI mode) |
| typer | ≥ 0.12.0 | CLI argument parsing |
| Package | Version | Purpose |
|---|---|---|
| google-generativeai | ≥ 0.7.0 | Gemini for CV extraction |
| google-auth | ≥ 2.30.0 | Google API authentication |
| google-auth-oauthlib | ≥ 1.2.0 | OAuth2 flow |
| google-api-python-client | ≥ 2.130.0 | Google Drive/Sheets SDK |
| cryptography | 42.0.8 | Auth token handling |
| pydantic | ≥ 2.7.0 | Schema validation |
| python-dotenv | ≥ 1.0.1 | Environment variables |
| Package | Version | Purpose |
|---|---|---|
| next | 16.2.9 | React framework (App Router) |
| react | 19.2.4 | UI library |
| react-dom | 19.2.4 | React DOM renderer |
| framer-motion | 12.40.0 | Animation library |
| @google/generative-ai | 0.24.1 | Gemini client (frontend utilities) |
| lodash | 4.18.1 | Utility functions |
| uuid | 14.0.0 | Unique ID generation |
| typescript | 5.x | Type safety |
| tailwindcss | 4.x | Utility-first CSS |
| eslint | 9.x | Code linting |
This project was built for the Tacto Hackathon 2025.