owenkaplinsky
Clean initial commit for HuggingFace
363cda9
# API Layer πŸ”Œ
> FastAPI backend decoupling agents from frontends.
## Quick Start
```bash
# Local
uvicorn src.api.app:app --reload --port 8080
# Docker
docker compose --env-file .env -f docker/docker-compose.yml up supervisor_api
```
**Docs:** http://localhost:8080/docs
## Endpoints
### Supervisor Agent `/api/v1/supervisor`
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/chat` | Batch response with context compaction |
| POST | `/chat/stream` | SSE streaming with context compaction ⚠️ |
| POST | `/raw/chat` | Batch response, direct agent (no compaction) |
| POST | `/raw/chat/stream` | SSE streaming, direct agent ⚠️ |
| POST | `/new` | Create new chat session |
| GET | `/health` | Health check |
⚠️ **Note:** Streaming endpoints have known issues. Use batch endpoints (`/chat` or `/raw/chat`) for reliable operation.
**With vs Raw endpoints:**
- `/chat` and `/chat/stream` use `CompactingSupervisor` wrapper (auto context management)
- `/raw/chat` and `/raw/chat/stream` bypass wrapper (direct agent access, useful for debugging)
**Streaming (SSE) events:**
```
event: token β†’ {"content": "Hello"}
event: done β†’ {"thread_id": "abc123", "token_count": 150}
event: error β†’ {"error": "Something went wrong"}
```
### CV Upload `/api/v1/cv`
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/submit` | Submit application + CV |
| GET | `/health` | Health check |
**Submit flow:**
1. Save CV file to disk
2. Register candidate in DB
3. Parse CV β†’ Markdown (GPT-4 Vision)
4. Update parsed path in DB
### Database `/api/v1/db`
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/query` | Flexible query any table |
| GET | `/candidates` | List candidates with filters |
| GET | `/candidates/{id}` | Get full candidate profile by UUID |
| GET | `/candidates/email/{email}` | Get full candidate profile by email |
| GET | `/cv-screening` | List CV screening results |
| GET | `/voice-screening` | List voice screening results |
| GET | `/interviews` | List interview scheduling |
| GET | `/decisions` | List final decisions |
| GET | `/stats` | Database statistics |
| GET | `/health` | Health check |
**Full Candidate Profile** (`/candidates/{id}` and `/candidates/email/{email}`):
Returns ALL data for a candidate including related records (by default `include_relations=true`):
- **Base fields:** id, full_name, email, phone_number, cv_file_path, parsed_cv_file_path, status, created_at, updated_at
- **cv_screening_results:** list of CV screening scores and feedback
- **voice_screening_results:** list of voice screening transcripts and scores
- **interview_scheduling:** list of scheduled interviews
- **final_decision:** hiring decision with rationale (if any)
Use `?include_relations=false` to fetch only base candidate fields.
**Flexible Query Example:**
```json
POST /api/v1/db/query
{
"table": "candidates",
"filters": {"status": "applied"},
"fields": ["id", "full_name", "email"],
"include_relations": true,
"limit": 10,
"offset": 0,
"sort_by": "created_at",
"sort_order": "desc"
}
```
**Supported filter operators:**
- `$eq`, `$ne`: equality/inequality
- `$gt`, `$gte`, `$lt`, `$lte`: comparisons
- `$in`, `$nin`: list membership
- `$like`, `$ilike`: pattern matching
## Structure
```
src/api/
β”œβ”€β”€ app.py ← FastAPI app + CORS + router mounting
β”œβ”€β”€ routers/
β”‚ β”œβ”€β”€ supervisor.py ← Chat endpoints (regular + streaming)
β”‚ β”œβ”€β”€ cv_upload.py ← CV submission endpoint
β”‚ └── database.py ← Flexible database query endpoints
└── schemas/
β”œβ”€β”€ supervisor_chat.py ← ChatRequest, ChatResponse
β”œβ”€β”€ cv_upload.py ← SubmitResponse
└── database.py ← QueryRequest, QueryResponse, etc.
```
## SDK Clients
Frontends use SDK clients instead of raw HTTP:
```python
# Supervisor
from src.sdk import SupervisorClient
client = SupervisorClient()
for chunk in client.stream("Show candidates", thread_id):
print(chunk.content)
# CV Upload
from src.sdk import CVUploadClient
client = CVUploadClient()
response = client.submit(name, email, phone, cv_file, filename)
# Database Queries
from src.sdk import DatabaseClient
db = DatabaseClient()
# List candidates
candidates = db.get_candidates(status="applied", include_relations=True)
for c in candidates.data:
print(c["full_name"], c["status"])
# Get full candidate profile by email
profile = db.get_candidate_by_email("ada@example.com")
print(profile.data["cv_screening_results"])
# Flexible query with filters
results = db.query(
table="cv_screening_results",
filters={"overall_fit_score": {"$gte": 0.8}},
sort_by="overall_fit_score",
sort_order="desc"
)
# Get database stats
stats = db.get_stats()
print(stats.stats["candidates"]["by_status"])
```
## Environment
| Variable | Default | Used By |
|----------|---------|---------|
| `OPENAI_API_KEY` | required | Validated at startup |
| `CV_UPLOAD_PATH` | `src/database/cvs/uploads` | CV router |
| `CV_PARSED_PATH` | `src/database/cvs/parsed` | CV router |
| `POSTGRES_*` | varies | Database connection |
## TODO
- [ ] Voice agent router
- [x] Candidate database router