AI-powered system for extracting structured information from Request for Proposal (RFP) PDF documents using a locally hosted vision model, FastAPI, React, vLLM, and NVIDIA NeMo Guardrails.
The project is designed to run locally without depending on the team's development server or external AI APIs.
- Abdullah Alqahtani — https://github.com/Abdullah-Alqhtani
- Amr Alghamidi — https://github.com/AmrAlghamidi
- Turki Alotaibi — https://github.com/turki-alotaibi9
RFP AI provides an end-to-end pipeline for uploading an RFP PDF, validating it against security controls, extracting structured information, and displaying the results through a web interface.
RFP PDF
↓
React / Vite
↓
FastAPI
↓
PDF Security Checks
↓
NeMo Guardrails
↓
Local vLLM Model
↓
Structured JSON
↓
Post-Inference Security Validation
↓
Web Interface
- PDF upload and processing
- Structured RFP information extraction
- Local vision-model inference
- OpenAI-compatible vLLM API
- NVIDIA NeMo Guardrails
- Prompt-injection detection
- Pre-inference security validation
- Post-inference security validation
- Structured JSON output
- Benchmark evaluation
- Field-level accuracy reporting
- Kubernetes deployment manifests
- Prometheus and Grafana monitoring
- React/Vite frontend
- Python
- FastAPI
- Uvicorn
- PyMuPDF
- pypdf
- Requests
- Pydantic
- NVIDIA NeMo Guardrails
- vLLM 0.29.0
- Lift Vision Model
- OpenAI-compatible API
- React
- Vite
- JavaScript
- CSS
- Docker
- Kubernetes
- NVIDIA GPU
- Prometheus
- Grafana
.
├── benchmark/
│ ├── model_client.py
│ ├── model_endpoint.py
│ ├── pdf_parser.py
│ ├── prompts.py
│ ├── run_benchmark.py
│ ├── schema.py
│ └── test_model_client.py
│
├── benchmark_outputs/
│
├── data/
│ ├── ground_truth/
│ └── rfps/
│
├── frontend/
│ ├── public/
│ ├── src/
│ ├── package.json
│ ├── package-lock.json
│ └── vite.config.js
│
├── guardrails_config/
│ ├── config.yml
│ └── rails.co
│
├── monitoring/
│ ├── compose.yaml
│ ├── dashboard.json
│ └── prometheus.yml
│
├── scripts/
│ └── start_vllm.sh
│
├── Dockerfile.web
├── lift-k8s.yaml
├── rfp-web-k8s.yaml
├── requirements.txt
├── requirements-vllm.txt
├── .env.example
├── web_container.py
└── README.md
The current extraction schema contains 17 fields:
- Submission Deadline
- Deadline for Questions
- RFP Contact
- Submission Method
- Contract Term
- Scope of Deliverables
- Mandatory Submission Requirements
- Mandatory Technical Requirements
- Evaluation Criteria
- Minimum Score Threshold
- Pricing Requirements
- Minimum Insurance Requirements
- Vendor Experience / Qualifications
- Number of References Required
- Data Security / Privacy Requirements
- Data Hosting / Residency Requirements
- Vendor Demonstration Requirement
The schema is defined in:
benchmark/schema.py
For the full local extraction workflow, install:
- Linux recommended for local GPU inference
- Python 3.10+
- Node.js 20+
- npm
- Git
- NVIDIA GPU
- Working NVIDIA drivers
- A compatible local vision model
The repository does not include model weights.
The frontend and backend can be started without a running model, but actual RFP extraction requires a working vLLM server and a compatible model.
git clone https://github.com/Dem4t/team6-AI_Runners-RFP_AI.git
cd team6-AI_Runners-RFP_AICreate a virtual environment:
python3 -m venv .venvActivate it:
source .venv/bin/activateUpgrade pip:
python -m pip install --upgrade pipInstall backend dependencies:
pip install -r requirements.txtInstall vLLM separately:
pip install -r requirements-vllm.txtVerify the installation:
python -m vllm --versionThe repository uses vLLM 0.29.0 for the local model-serving setup.
Copy the environment template:
cp .env.example .envThe .env file contains the model configuration.
Example:
MODEL_BASE_URL=http://localhost:8000/v1
MODEL_NAME=/home/ubuntu/models/liftThe backend expects an OpenAI-compatible vLLM endpoint.
Default:
http://localhost:8000/v1
This must match the model path/name that vLLM serves.
Example:
MODEL_NAME=/home/ubuntu/models/liftOn another machine, replace it with the actual local model path:
MODEL_NAME=/path/to/your/modelDo not commit .env.
The repository provides a startup script:
bash scripts/start_vllm.shThe script starts vLLM using:
python -m vllm serveThis avoids depending on a globally installed vllm executable.
The default configuration is:
Host: 0.0.0.0
Port: 8000
Dtype: bfloat16
GPU Memory Utilization: 0.85
Maximum Model Length: 32768
The script reads the model configuration from .env.
Verify that the server is running:
curl http://localhost:8000/v1/modelsA successful response should contain the models exposed by the vLLM server.
Open a second terminal.
Navigate to the project:
cd team6-AI_Runners-RFP_AIActivate the virtual environment:
source .venv/bin/activateStart FastAPI:
cd benchmark
uvicorn model_endpoint:app --host 0.0.0.0 --port 8081The backend will be available at:
http://localhost:8081
Swagger API documentation:
http://localhost:8081/docs
Health check:
http://localhost:8081/health
Open a third terminal.
Navigate to the frontend:
cd team6-AI_Runners-RFP_AI/frontendInstall frontend dependencies:
npm ciStart Vite:
npm run devThe frontend will be available at:
http://localhost:5173
The Vite development server proxies API requests to:
http://localhost:8081
This means the frontend does not contain a hard-coded team-server IP.
| Component | Address |
|---|---|
| Frontend | http://localhost:5173 |
| FastAPI Backend | http://localhost:8081 |
| vLLM | http://localhost:8000/v1 |
| Prometheus | http://localhost:9090 |
| Grafana | http://localhost:3000 |
Once all services are running:
Browser
│
▼
http://localhost:5173
│
│ /api
▼
http://localhost:8081
│
│ OpenAI-compatible API
▼
http://localhost:8000/v1
│
▼
Local Vision Model
The user only needs to open:
http://localhost:5173
and upload an RFP PDF.
GET /healthExample:
{
"status": "active",
"model": "/home/ubuntu/models/lift",
"endpoint": "http://localhost:8000/v1",
"guardrails_active": true
}POST /api/v1/extractUpload a PDF using the multipart form field:
file
Example response:
{
"status": "success",
"filename": "example.pdf",
"latency_seconds": 12.4,
"tokens_used": 2048,
"extracted_fields": {
"submission_deadline": "...",
"rfp_contact": "...",
"scope_of_deliverables": "..."
}
}The extraction pipeline uses multiple security layers.
The uploaded PDF is inspected before model inference.
The system extracts text from the PDF for security analysis.
The API checks the document against known malicious instruction patterns.
Examples include:
- Ignore previous instructions
- Disregard the schema
- Bypass security
- Print the system prompt
- Write a Python script
When a detected pattern matches, the request is blocked.
The project uses NVIDIA NeMo Guardrails for semantic security validation.
Configuration:
guardrails_config/config.yml
guardrails_config/rails.co
Guardrails are applied before model inference.
The generated extraction is checked again before being returned to the frontend.
If a security violation is detected, the API blocks the response.
Benchmark RFP documents are stored in:
data/rfps/
Ground-truth results are stored in:
data/ground_truth/
Run the benchmark:
python3 benchmark/run_benchmark.pyResults are generated in:
benchmark_outputs/
The field-level report is:
benchmark_outputs/field_accuracy_report.json
Benchmark results can include:
- Field accuracy
- Per-RFP accuracy
- Latency
- Input tokens
- Output tokens
- Total tokens
- Field-level failures
Monitoring is optional for local development.
The project includes:
- Prometheus
- Grafana
- Node Exporter metrics
- NVIDIA DCGM metrics
- vLLM metrics
Start the monitoring stack:
cd monitoring
docker compose up -dPrometheus:
http://localhost:9090
Grafana:
http://localhost:3000
The Grafana dashboard is stored in:
monitoring/dashboard.json
The monitoring stack can display metrics such as:
- GPU utilization
- GPU memory
- GPU temperature
- GPU power usage
- CPU utilization
- RAM usage
- Network traffic
- vLLM request activity
- Waiting requests
- Completed requests
- Generated tokens
- Prompt tokens
- Token throughput
- Cache activity
- Preemptions
The repository contains a Docker build for the web application:
Dockerfile.web
Build the image:
docker build -f Dockerfile.web -t rfp-web:0.1 .The application container exposes:
8081
The model endpoint is configured through:
MODEL_BASE_URL
Example:
MODEL_BASE_URL=http://localhost:8000/v1When running inside Docker, make sure the container can reach the machine or container hosting vLLM.
The repository includes Kubernetes manifests for environments with:
- Kubernetes
- NVIDIA GPU support
- A local model available to the cluster
Files:
lift-k8s.yaml
rfp-web-k8s.yaml
These manifests are not required for normal local development.
The Kubernetes deployment uses the vLLM service internally rather than the local development ports described above.
The main environment variables are:
MODEL_BASE_URL=http://localhost:8000/v1
MODEL_NAME=/home/ubuntu/models/liftMachine-specific configuration should be placed in .env.
Example for another machine:
MODEL_BASE_URL=http://localhost:8000/v1
MODEL_NAME=/path/to/your/modelDo not commit:
- API keys
- passwords
- private endpoints
.envfiles- model weights
Make sure the virtual environment is active:
source .venv/bin/activateInstall the project vLLM dependency:
pip install -r requirements-vllm.txtVerify:
python -m vllm --versionThen start:
bash scripts/start_vllm.shCheck the configured model path:
echo $MODEL_NAMECheck that the path exists:
ls -ld "$MODEL_NAME"Or set it manually:
export MODEL_NAME=/path/to/your/modelThen:
bash scripts/start_vllm.shCheck:
curl http://localhost:8000/v1/modelsAlso check whether port 8000 is listening:
ss -lntp | grep 8000First verify vLLM:
curl http://localhost:8000/v1/modelsThen verify the backend:
curl http://localhost:8081/healthCheck the configured endpoint:
echo $MODEL_BASE_URLIt should normally be:
http://localhost:8000/v1
Check the backend:
curl http://localhost:8081/healthIf it is working, restart Vite:
cd frontend
npm run devThen open:
http://localhost:5173
Make sure these files exist:
guardrails_config/config.yml
guardrails_config/rails.co
Then verify the Python environment:
source .venv/bin/activate
pip install -r requirements.txt ┌──────────────────────┐
│ React / Vite │
│ Frontend :5173 │
└──────────┬───────────┘
│
│ /api
▼
┌──────────────────────┐
│ FastAPI │
│ Backend :8081 │
└──────────┬───────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Security Layer │ │ PDF Processing │
│ Regex + NeMo │ │ PyMuPDF / pypdf │
└────────┬────────┘ └────────┬────────┘
│ │
└──────────────┬─────────────┘
▼
┌──────────────────────┐
│ vLLM │
│ :8000/v1 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Lift Vision Model │
└──────────┬───────────┘
│
▼
Structured JSON
The repository is intentionally configured for local development.
Default services:
Frontend
localhost:5173
Backend
localhost:8081
vLLM
localhost:8000
Prometheus
localhost:9090
Grafana
localhost:3000
No team-server IP address is required for normal local development.
Model weights are intentionally not included in the Git repository because they are large and environment-specific.
Each developer must provide a compatible local model.
The model path is configured using:
MODEL_NAME=/path/to/your/modelvLLM is intentionally maintained as a separate dependency from the main backend requirements.
Backend dependencies:
requirements.txt
Model-serving dependency:
requirements-vllm.txt
This keeps the API environment separate from the GPU/model-serving environment while still providing a reproducible local setup.
For a complete local setup:
cd team6-AI_Runners-RFP_AI
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-vllm.txt
cp .env.example .envSet the correct model path in .env, then:
bash scripts/start_vllm.shcd team6-AI_Runners-RFP_AI
source .venv/bin/activate
cd benchmark
uvicorn model_endpoint:app --host 0.0.0.0 --port 8081cd team6-AI_Runners-RFP_AI/frontend
npm ci
npm run devOpen:
http://localhost:5173
RFP AI — AI Runners
Repository:
team6-AI_Runners-RFP_AI
The project focuses on:
- AI-assisted RFP extraction
- Local model serving
- Prompt-injection protection
- AI security
- Benchmarking
- Observability
- Infrastructure deployment
- Local-first development