Dmitri Moscoglo
commited on
Commit
·
b2b94e0
1
Parent(s):
f108b29
Revert "Clean initial commit for HuggingFace"
Browse filesThis reverts commit f108b29491d90326f90f71fa07edef74d570194b.
- .gitignore +4 -1
- Dockerfile +5 -25
- README.md +10 -10
- docker/Dockerfile.candidates_db_init +5 -3
- docker/Dockerfile.supervisor_api +1 -1
- docker/docker-compose.yml +46 -52
- docker/info.md +6 -2
- scripts/db/list_candidates.py +13 -3
- scripts/db/test_connection.py +1 -1
- scripts/db/test_session.py +1 -1
- scripts/db/wipe.py +1 -1
- src/agents/db_executor/codeact/core/codeact.py +16 -13
- src/agents/db_executor/db_executor.py +4 -16
- src/agents/gcalendar/gcalendar_agent.py +1 -2
- src/agents/gmail/gmail_agent.py +18 -3
- src/context_eng/compacting_supervisor.py +1 -1
- src/database/candidates/client.py +1 -1
- src/database/candidates/init_db.py +15 -4
- src/database/candidates/models.py +6 -3
- src/database/context/__init__.py +0 -153
- src/frontend/streamlit/voice_screening_ui/proxy.py +2 -2
- src/prompts/templates/cv_screener/v1.txt +0 -2
- src/prompts/templates/db_executor/v1.txt +25 -40
- src/prompts/templates/gcalendar/v1.txt +2 -6
- src/prompts/templates/supervisor/v1.txt +43 -29
- src/state/candidate.py +17 -3
- start.sh +69 -1
- tests/create_dummy_candidate.py +3 -3
- tests/verify_voice_integration.py +3 -3
.gitignore
CHANGED
|
@@ -67,4 +67,7 @@ src/database/cvs/tests/*.txt
|
|
| 67 |
.lgcache/
|
| 68 |
.langgraph_api/
|
| 69 |
|
| 70 |
-
.idea/
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
.lgcache/
|
| 68 |
.langgraph_api/
|
| 69 |
|
| 70 |
+
.idea/
|
| 71 |
+
|
| 72 |
+
# any .wav files
|
| 73 |
+
*.wav
|
Dockerfile
CHANGED
|
@@ -2,8 +2,8 @@ FROM python:3.12-slim
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
-
# System dependencies
|
| 6 |
-
RUN apt-get update && apt-get install -y gcc libpq-dev && rm -rf /var/lib/apt/lists/*
|
| 7 |
|
| 8 |
# Copy requirement files
|
| 9 |
COPY requirements/base.txt requirements/base.txt
|
|
@@ -34,28 +34,8 @@ COPY secrets/ /app/secrets/
|
|
| 34 |
ENV PYTHONPATH=/app
|
| 35 |
EXPOSE 7860
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
'set -e' \
|
| 41 |
-
'' \
|
| 42 |
-
'# Hugging Face provides PORT; default to 7860 locally' \
|
| 43 |
-
'export PORT=\"${PORT:-7860}\"' \
|
| 44 |
-
'' \
|
| 45 |
-
'# Defaults for local in-container routing; can be overridden via env' \
|
| 46 |
-
'export SUPERVISOR_API_URL=\"${SUPERVISOR_API_URL:-http://127.0.0.1:8080/api/v1/supervisor}\"' \
|
| 47 |
-
'export DATABASE_API_URL=\"${DATABASE_API_URL:-http://127.0.0.1:8080/api/v1/db}\"' \
|
| 48 |
-
'export CV_UPLOAD_API_URL=\"${CV_UPLOAD_API_URL:-http://127.0.0.1:8080/api/v1/cv}\"' \
|
| 49 |
-
'' \
|
| 50 |
-
'# Start FastAPI backend' \
|
| 51 |
-
'uvicorn src.api.app:app --host 0.0.0.0 --port 8080 &' \
|
| 52 |
-
'' \
|
| 53 |
-
'# Give the API a moment to come up' \
|
| 54 |
-
'sleep 2' \
|
| 55 |
-
'' \
|
| 56 |
-
'# Run Gradio frontend' \
|
| 57 |
-
'python src/frontend/gradio/app.py' \
|
| 58 |
-
> /app/start.sh \
|
| 59 |
-
&& chmod +x /app/start.sh
|
| 60 |
|
| 61 |
CMD ["/app/start.sh"]
|
|
|
|
| 2 |
|
| 3 |
WORKDIR /app
|
| 4 |
|
| 5 |
+
# System dependencies (include Postgres server so DB can run in-container)
|
| 6 |
+
RUN apt-get update && apt-get install -y gcc libpq-dev postgresql postgresql-contrib gosu && rm -rf /var/lib/apt/lists/*
|
| 7 |
|
| 8 |
# Copy requirement files
|
| 9 |
COPY requirements/base.txt requirements/base.txt
|
|
|
|
| 34 |
ENV PYTHONPATH=/app
|
| 35 |
EXPOSE 7860
|
| 36 |
|
| 37 |
+
# Copy entry script (includes Postgres startup)
|
| 38 |
+
COPY start.sh /app/start.sh
|
| 39 |
+
RUN chmod +x /app/start.sh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
CMD ["/app/start.sh"]
|
README.md
CHANGED
|
@@ -171,7 +171,7 @@ docker compose --env-file .env -f docker/docker-compose.yml up --build
|
|
| 171 |
The platform orchestrates a complete recruitment pipeline, interacting with both Candidates and the HR Supervisor.
|
| 172 |
|
| 173 |
### 1. The Recruitment Lifecycle
|
| 174 |
-
The system tracks candidates through a defined state machine (see `src/state/candidate.py` for the `CandidateStatus` enum).
|
| 175 |
|
| 176 |
```mermaid
|
| 177 |
graph TD
|
|
@@ -231,7 +231,7 @@ graph TD
|
|
| 231 |
|
| 232 |
To improve the reliability of complex evaluations (such as CV scoring and Voice Interview judging), we enforce **Chain-of-Thought (CoT)** reasoning within our structured outputs, inspired by [Wei et al. (2022)](https://arxiv.org/abs/2201.11903).
|
| 233 |
|
| 234 |
-
By requiring the model to generate a textual explanation *before* assigning numerical scores, we ensure the model "thinks" through the evidence before committing to a decision. This is implemented directly in our Pydantic schemas (e.g., `src/agents/cv_screening/schemas/output_schema.py`), where field order matters:
|
| 235 |
|
| 236 |
```mermaid
|
| 237 |
flowchart LR
|
|
@@ -362,14 +362,14 @@ A breakdown of the various LLMs, Agents, and Workflows powering the system.
|
|
| 362 |
|
| 363 |
| Component | Type | Model | Description | Location |
|
| 364 |
| :--- | :--- | :--- | :--- | :--- |
|
| 365 |
-
| **Supervisor Agent** | 🤖 **Agent** | `gpt-4o` | Orchestrates delegation, planning, and context management. | `src/agents/supervisor/supervisor_v2.py` |
|
| 366 |
-
| **Gmail Agent** | 🤖 **Agent** | `gpt-4o` | Autonomous email management via MCP (read/send/label). | `src/agents/gmail/gmail_agent.py` |
|
| 367 |
-
| **GCalendar Agent** | 🤖 **Agent** | `gpt-4o` | Autonomous calendar scheduling via MCP. | `src/agents/gcalendar/gcalendar_agent.py` |
|
| 368 |
-
| **DB Executor** | 🤖 **Agent** | `gpt-4o` | Writes SQL/Python to query the database (CodeAct). | `src/agents/db_executor/db_executor.py` |
|
| 369 |
-
| **CV Screening** | ⚙️ **Workflow** | `gpt-4o` | Deterministic pipeline: Fetch → Read → Evaluate → Save. | `src/agents/cv_screening/cv_screening_workflow.py` |
|
| 370 |
-
| **Voice Judge** | 🧠 **Simple LLM** | `gpt-4o-audio` | Evaluates audio/transcripts for sentiment & confidence. | `src/agents/voice_screening/judge.py` |
|
| 371 |
-
| **Doc Parser** | 🧠 **Simple LLM** | `gpt-4o-mini` | Vision-based PDF-to-Markdown conversion. | `src/doc_parser/pdf_to_markdown.py` |
|
| 372 |
-
| **History Manager** | 🧠 **Simple LLM** | `gpt-4o-mini` | Summarizes conversation history for context compaction. | `src/context_eng/history_manager.py` |
|
| 373 |
|
| 374 |
### 🔌 ***`Integrated MCP Servers`***
|
| 375 |
The system integrates **Model Context Protocol (MCP)** servers to securely and standardizedly connect agents to external tools.
|
|
|
|
| 171 |
The platform orchestrates a complete recruitment pipeline, interacting with both Candidates and the HR Supervisor.
|
| 172 |
|
| 173 |
### 1. The Recruitment Lifecycle
|
| 174 |
+
The system tracks candidates through a defined state machine (see `src/backend/state/candidate.py` for the `CandidateStatus` enum).
|
| 175 |
|
| 176 |
```mermaid
|
| 177 |
graph TD
|
|
|
|
| 231 |
|
| 232 |
To improve the reliability of complex evaluations (such as CV scoring and Voice Interview judging), we enforce **Chain-of-Thought (CoT)** reasoning within our structured outputs, inspired by [Wei et al. (2022)](https://arxiv.org/abs/2201.11903).
|
| 233 |
|
| 234 |
+
By requiring the model to generate a textual explanation *before* assigning numerical scores, we ensure the model "thinks" through the evidence before committing to a decision. This is implemented directly in our Pydantic schemas (e.g., `src/backend/agents/cv_screening/schemas/output_schema.py`), where field order matters:
|
| 235 |
|
| 236 |
```mermaid
|
| 237 |
flowchart LR
|
|
|
|
| 362 |
|
| 363 |
| Component | Type | Model | Description | Location |
|
| 364 |
| :--- | :--- | :--- | :--- | :--- |
|
| 365 |
+
| **Supervisor Agent** | 🤖 **Agent** | `gpt-4o` | Orchestrates delegation, planning, and context management. | `src/backend/agents/supervisor/supervisor_v2.py` |
|
| 366 |
+
| **Gmail Agent** | 🤖 **Agent** | `gpt-4o` | Autonomous email management via MCP (read/send/label). | `src/backend/agents/gmail/gmail_agent.py` |
|
| 367 |
+
| **GCalendar Agent** | 🤖 **Agent** | `gpt-4o` | Autonomous calendar scheduling via MCP. | `src/backend/agents/gcalendar/gcalendar_agent.py` |
|
| 368 |
+
| **DB Executor** | 🤖 **Agent** | `gpt-4o` | Writes SQL/Python to query the database (CodeAct). | `src/backend/agents/db_executor/db_executor.py` |
|
| 369 |
+
| **CV Screening** | ⚙️ **Workflow** | `gpt-4o` | Deterministic pipeline: Fetch → Read → Evaluate → Save. | `src/backend/agents/cv_screening/cv_screening_workflow.py` |
|
| 370 |
+
| **Voice Judge** | 🧠 **Simple LLM** | `gpt-4o-audio` | Evaluates audio/transcripts for sentiment & confidence. | `src/backend/agents/voice_screening/judge.py` |
|
| 371 |
+
| **Doc Parser** | 🧠 **Simple LLM** | `gpt-4o-mini` | Vision-based PDF-to-Markdown conversion. | `src/backend/doc_parser/pdf_to_markdown.py` |
|
| 372 |
+
| **History Manager** | 🧠 **Simple LLM** | `gpt-4o-mini` | Summarizes conversation history for context compaction. | `src/backend/context_eng/history_manager.py` |
|
| 373 |
|
| 374 |
### 🔌 ***`Integrated MCP Servers`***
|
| 375 |
The system integrates **Model Context Protocol (MCP)** servers to securely and standardizedly connect agents to external tools.
|
docker/Dockerfile.candidates_db_init
CHANGED
|
@@ -15,8 +15,10 @@ COPY ../requirements/base.txt ./requirements/base.txt
|
|
| 15 |
COPY ../requirements/db.txt ./requirements/db.txt
|
| 16 |
RUN pip install --no-cache-dir -r requirements/db.txt
|
| 17 |
|
| 18 |
-
# Copy
|
| 19 |
-
COPY src/database/candidates ./src/database/candidates
|
|
|
|
|
|
|
| 20 |
|
| 21 |
# Default command - use dedicated init script to avoid circular import
|
| 22 |
-
CMD ["python", "-m", "src.database.candidates.init_db"]
|
|
|
|
| 15 |
COPY ../requirements/db.txt ./requirements/db.txt
|
| 16 |
RUN pip install --no-cache-dir -r requirements/db.txt
|
| 17 |
|
| 18 |
+
# Copy required source modules
|
| 19 |
+
COPY src/backend/database/candidates ./src/backend/database/candidates
|
| 20 |
+
COPY src/backend/state ./src/backend/state
|
| 21 |
+
COPY src/backend/configs ./src/backend/configs
|
| 22 |
|
| 23 |
# Default command - use dedicated init script to avoid circular import
|
| 24 |
+
CMD ["python", "-m", "src.backend.database.candidates.init_db"]
|
docker/Dockerfile.supervisor_api
CHANGED
|
@@ -39,5 +39,5 @@ COPY .env /app/.env
|
|
| 39 |
EXPOSE 8080
|
| 40 |
|
| 41 |
# Run FastAPI with uvicorn
|
| 42 |
-
CMD ["uvicorn", "src.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
|
| 43 |
|
|
|
|
| 39 |
EXPOSE 8080
|
| 40 |
|
| 41 |
# Run FastAPI with uvicorn
|
| 42 |
+
CMD ["uvicorn", "src.backend.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
|
| 43 |
|
docker/docker-compose.yml
CHANGED
|
@@ -19,6 +19,10 @@ services:
|
|
| 19 |
interval: 3s
|
| 20 |
timeout: 3s
|
| 21 |
retries: 5
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
environment:
|
| 23 |
POSTGRES_HOST: ${POSTGRES_HOST}
|
| 24 |
POSTGRES_PORT: ${POSTGRES_PORT}
|
|
@@ -34,18 +38,23 @@ services:
|
|
| 34 |
# Initializes the database or starts the API (depending on command).
|
| 35 |
container_name: candidates_db_init
|
| 36 |
build:
|
| 37 |
-
context: ..
|
| 38 |
dockerfile: docker/Dockerfile.candidates_db_init
|
| 39 |
depends_on:
|
| 40 |
db:
|
| 41 |
condition: service_healthy
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
environment:
|
| 43 |
-
POSTGRES_HOST
|
| 44 |
-
|
|
|
|
| 45 |
POSTGRES_USER: ${POSTGRES_USER}
|
| 46 |
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 47 |
POSTGRES_DB: ${POSTGRES_DB}
|
| 48 |
-
|
| 49 |
|
| 50 |
volumes:
|
| 51 |
# --- Local code mount (for development only) ---
|
|
@@ -53,7 +62,7 @@ services:
|
|
| 53 |
# into the container at /app.
|
| 54 |
# ✅ Enables live code changes without rebuilding the image.
|
| 55 |
# ⚠️ Do NOT use in production – overrides the built image code.
|
| 56 |
-
- ../:/app
|
| 57 |
|
| 58 |
networks:
|
| 59 |
- hrnet
|
|
@@ -69,15 +78,17 @@ services:
|
|
| 69 |
depends_on:
|
| 70 |
- db
|
| 71 |
- supervisor_api
|
|
|
|
|
|
|
| 72 |
environment:
|
| 73 |
# Database connection
|
| 74 |
-
POSTGRES_HOST:
|
| 75 |
-
POSTGRES_PORT:
|
| 76 |
POSTGRES_USER: ${POSTGRES_USER}
|
| 77 |
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 78 |
POSTGRES_DB: ${POSTGRES_DB}
|
| 79 |
-
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}
|
| 80 |
-
CV_UPLOAD_PATH: /app/src/database/cvs/uploads
|
| 81 |
# App specific
|
| 82 |
CV_UPLOAD_API_URL: http://supervisor_api:8080/api/v1/cv
|
| 83 |
PYTHONPATH: /app
|
|
@@ -85,15 +96,8 @@ services:
|
|
| 85 |
# Mount local code for live updates
|
| 86 |
- ../:/app
|
| 87 |
# Shared volume for CV uploads (persistent)
|
| 88 |
-
- ../src/database/cvs:/app/src/database/cvs
|
| 89 |
-
command:
|
| 90 |
-
[
|
| 91 |
-
"streamlit",
|
| 92 |
-
"run",
|
| 93 |
-
"src/frontend/streamlit/cv_ui/app.py",
|
| 94 |
-
"--server.port=8501",
|
| 95 |
-
"--server.address=0.0.0.0",
|
| 96 |
-
]
|
| 97 |
networks:
|
| 98 |
- hrnet
|
| 99 |
|
|
@@ -105,6 +109,8 @@ services:
|
|
| 105 |
dockerfile: docker/Dockerfile.voice_proxy
|
| 106 |
ports:
|
| 107 |
- "8000:8000"
|
|
|
|
|
|
|
| 108 |
depends_on:
|
| 109 |
- db
|
| 110 |
- candidates_db_init
|
|
@@ -112,20 +118,16 @@ services:
|
|
| 112 |
PYTHONPATH: /app
|
| 113 |
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
| 114 |
BACKEND_API_URL: http://supervisor_api:8080
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
volumes:
|
| 116 |
# Mount local code for live updates
|
| 117 |
- ../:/app
|
| 118 |
-
command:
|
| 119 |
-
[
|
| 120 |
-
"python",
|
| 121 |
-
"-m",
|
| 122 |
-
"uvicorn",
|
| 123 |
-
"src.frontend.streamlit.voice_screening_ui.proxy:app",
|
| 124 |
-
"--host",
|
| 125 |
-
"0.0.0.0",
|
| 126 |
-
"--port",
|
| 127 |
-
"8000",
|
| 128 |
-
]
|
| 129 |
networks:
|
| 130 |
- hrnet
|
| 131 |
|
|
@@ -136,10 +138,12 @@ services:
|
|
| 136 |
context: ..
|
| 137 |
dockerfile: docker/Dockerfile.voice_screening
|
| 138 |
ports:
|
| 139 |
-
- "8502:8501"
|
| 140 |
depends_on:
|
| 141 |
- db
|
| 142 |
- websocket_proxy
|
|
|
|
|
|
|
| 143 |
environment:
|
| 144 |
DATABASE_URL: postgresql://agentic_user:password123@db:5432/agentic_hr
|
| 145 |
PYTHONPATH: /app
|
|
@@ -148,14 +152,7 @@ services:
|
|
| 148 |
volumes:
|
| 149 |
# Mount local code for live updates
|
| 150 |
- ../:/app
|
| 151 |
-
command:
|
| 152 |
-
[
|
| 153 |
-
"streamlit",
|
| 154 |
-
"run",
|
| 155 |
-
"src/frontend/streamlit/voice_screening_ui/app.py",
|
| 156 |
-
"--server.port=8501",
|
| 157 |
-
"--server.address=0.0.0.0",
|
| 158 |
-
]
|
| 159 |
networks:
|
| 160 |
- hrnet
|
| 161 |
|
|
@@ -166,13 +163,15 @@ services:
|
|
| 166 |
context: ..
|
| 167 |
dockerfile: docker/Dockerfile.supervisor_api
|
| 168 |
ports:
|
| 169 |
-
- "8080:8080"
|
| 170 |
depends_on:
|
| 171 |
- db
|
|
|
|
|
|
|
| 172 |
environment:
|
| 173 |
# We set POSTGRES_HOST to 'db' so the agent connects to the container internal network
|
| 174 |
-
POSTGRES_HOST:
|
| 175 |
-
POSTGRES_PORT:
|
| 176 |
POSTGRES_USER: ${POSTGRES_USER}
|
| 177 |
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 178 |
POSTGRES_DB: ${POSTGRES_DB}
|
|
@@ -180,19 +179,12 @@ services:
|
|
| 180 |
PROMPTLAYER_API_KEY: ${PROMPTLAYER_API_KEY}
|
| 181 |
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
| 182 |
WEBSOCKET_PROXY_URL: ws://websocket_proxy:8000/ws/realtime
|
|
|
|
|
|
|
| 183 |
volumes:
|
| 184 |
# Mount local code for live updates
|
| 185 |
- ../:/app
|
| 186 |
-
command:
|
| 187 |
-
[
|
| 188 |
-
"uvicorn",
|
| 189 |
-
"src.api.app:app",
|
| 190 |
-
"--host",
|
| 191 |
-
"0.0.0.0",
|
| 192 |
-
"--port",
|
| 193 |
-
"8080",
|
| 194 |
-
"--reload",
|
| 195 |
-
]
|
| 196 |
networks:
|
| 197 |
- hrnet
|
| 198 |
|
|
@@ -203,10 +195,12 @@ services:
|
|
| 203 |
context: ..
|
| 204 |
dockerfile: docker/Dockerfile.supervisor
|
| 205 |
ports:
|
| 206 |
-
- "8503:8501"
|
| 207 |
depends_on:
|
| 208 |
- db
|
| 209 |
- supervisor_api
|
|
|
|
|
|
|
| 210 |
environment:
|
| 211 |
# We set POSTGRES_HOST to 'db' so the agent connects to the container internal network
|
| 212 |
PYTHONPATH: /app
|
|
|
|
| 19 |
interval: 3s
|
| 20 |
timeout: 3s
|
| 21 |
retries: 5
|
| 22 |
+
# Hey compose here is env file,
|
| 23 |
+
# pass it to container, but not the .env itself
|
| 24 |
+
env_file:
|
| 25 |
+
- ../.env
|
| 26 |
environment:
|
| 27 |
POSTGRES_HOST: ${POSTGRES_HOST}
|
| 28 |
POSTGRES_PORT: ${POSTGRES_PORT}
|
|
|
|
| 38 |
# Initializes the database or starts the API (depending on command).
|
| 39 |
container_name: candidates_db_init
|
| 40 |
build:
|
| 41 |
+
context: .. # build from the project root
|
| 42 |
dockerfile: docker/Dockerfile.candidates_db_init
|
| 43 |
depends_on:
|
| 44 |
db:
|
| 45 |
condition: service_healthy
|
| 46 |
+
# Hey compose here is env file,
|
| 47 |
+
# pass it to container, but not the .env itself
|
| 48 |
+
env_file:
|
| 49 |
+
- ../.env
|
| 50 |
environment:
|
| 51 |
+
# Explicitly set POSTGRES_HOST to the service name 'db' for Docker networking
|
| 52 |
+
POSTGRES_HOST: db
|
| 53 |
+
POSTGRES_PORT: 5432
|
| 54 |
POSTGRES_USER: ${POSTGRES_USER}
|
| 55 |
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 56 |
POSTGRES_DB: ${POSTGRES_DB}
|
| 57 |
+
command: ["python", "-m", "src.backend.database.candidates.init_db"]
|
| 58 |
|
| 59 |
volumes:
|
| 60 |
# --- Local code mount (for development only) ---
|
|
|
|
| 62 |
# into the container at /app.
|
| 63 |
# ✅ Enables live code changes without rebuilding the image.
|
| 64 |
# ⚠️ Do NOT use in production – overrides the built image code.
|
| 65 |
+
- ../:/app # optional: live reload for local dev
|
| 66 |
|
| 67 |
networks:
|
| 68 |
- hrnet
|
|
|
|
| 78 |
depends_on:
|
| 79 |
- db
|
| 80 |
- supervisor_api
|
| 81 |
+
env_file:
|
| 82 |
+
- ../.env
|
| 83 |
environment:
|
| 84 |
# Database connection
|
| 85 |
+
POSTGRES_HOST: db
|
| 86 |
+
POSTGRES_PORT: 5432
|
| 87 |
POSTGRES_USER: ${POSTGRES_USER}
|
| 88 |
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 89 |
POSTGRES_DB: ${POSTGRES_DB}
|
| 90 |
+
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
| 91 |
+
CV_UPLOAD_PATH: /app/src/backend/database/cvs/uploads
|
| 92 |
# App specific
|
| 93 |
CV_UPLOAD_API_URL: http://supervisor_api:8080/api/v1/cv
|
| 94 |
PYTHONPATH: /app
|
|
|
|
| 96 |
# Mount local code for live updates
|
| 97 |
- ../:/app
|
| 98 |
# Shared volume for CV uploads (persistent)
|
| 99 |
+
- ../src/backend/database/cvs:/app/src/backend/database/cvs
|
| 100 |
+
command: ["streamlit", "run", "src/frontend/streamlit/cv_ui/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
networks:
|
| 102 |
- hrnet
|
| 103 |
|
|
|
|
| 109 |
dockerfile: docker/Dockerfile.voice_proxy
|
| 110 |
ports:
|
| 111 |
- "8000:8000"
|
| 112 |
+
env_file:
|
| 113 |
+
- ../.env
|
| 114 |
depends_on:
|
| 115 |
- db
|
| 116 |
- candidates_db_init
|
|
|
|
| 118 |
PYTHONPATH: /app
|
| 119 |
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
| 120 |
BACKEND_API_URL: http://supervisor_api:8080
|
| 121 |
+
# Database connection
|
| 122 |
+
POSTGRES_HOST: db
|
| 123 |
+
POSTGRES_PORT: 5432
|
| 124 |
+
POSTGRES_USER: ${POSTGRES_USER}
|
| 125 |
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 126 |
+
POSTGRES_DB: ${POSTGRES_DB}
|
| 127 |
volumes:
|
| 128 |
# Mount local code for live updates
|
| 129 |
- ../:/app
|
| 130 |
+
command: ["python", "-m", "uvicorn", "src.frontend.streamlit.voice_screening_ui.proxy:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
networks:
|
| 132 |
- hrnet
|
| 133 |
|
|
|
|
| 138 |
context: ..
|
| 139 |
dockerfile: docker/Dockerfile.voice_screening
|
| 140 |
ports:
|
| 141 |
+
- "8502:8501" # Map host port 8502 to container port 8501
|
| 142 |
depends_on:
|
| 143 |
- db
|
| 144 |
- websocket_proxy
|
| 145 |
+
env_file:
|
| 146 |
+
- ../.env
|
| 147 |
environment:
|
| 148 |
DATABASE_URL: postgresql://agentic_user:password123@db:5432/agentic_hr
|
| 149 |
PYTHONPATH: /app
|
|
|
|
| 152 |
volumes:
|
| 153 |
# Mount local code for live updates
|
| 154 |
- ../:/app
|
| 155 |
+
command: ["streamlit", "run", "src/frontend/streamlit/voice_screening_ui/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
networks:
|
| 157 |
- hrnet
|
| 158 |
|
|
|
|
| 163 |
context: ..
|
| 164 |
dockerfile: docker/Dockerfile.supervisor_api
|
| 165 |
ports:
|
| 166 |
+
- "8080:8080" # Map host port 8080 to container port 8080
|
| 167 |
depends_on:
|
| 168 |
- db
|
| 169 |
+
env_file:
|
| 170 |
+
- ../.env
|
| 171 |
environment:
|
| 172 |
# We set POSTGRES_HOST to 'db' so the agent connects to the container internal network
|
| 173 |
+
POSTGRES_HOST: db
|
| 174 |
+
POSTGRES_PORT: 5432
|
| 175 |
POSTGRES_USER: ${POSTGRES_USER}
|
| 176 |
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
| 177 |
POSTGRES_DB: ${POSTGRES_DB}
|
|
|
|
| 179 |
PROMPTLAYER_API_KEY: ${PROMPTLAYER_API_KEY}
|
| 180 |
OPENAI_API_KEY: ${OPENAI_API_KEY}
|
| 181 |
WEBSOCKET_PROXY_URL: ws://websocket_proxy:8000/ws/realtime
|
| 182 |
+
CV_UPLOAD_PATH: /app/src/backend/database/cvs/uploads
|
| 183 |
+
CV_PARSED_PATH: /app/src/backend/database/cvs/parsed
|
| 184 |
volumes:
|
| 185 |
# Mount local code for live updates
|
| 186 |
- ../:/app
|
| 187 |
+
command: ["uvicorn", "src.backend.api.app:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
networks:
|
| 189 |
- hrnet
|
| 190 |
|
|
|
|
| 195 |
context: ..
|
| 196 |
dockerfile: docker/Dockerfile.supervisor
|
| 197 |
ports:
|
| 198 |
+
- "8503:8501" # Map host port 8503 to container port 8501
|
| 199 |
depends_on:
|
| 200 |
- db
|
| 201 |
- supervisor_api
|
| 202 |
+
env_file:
|
| 203 |
+
- ../.env
|
| 204 |
environment:
|
| 205 |
# We set POSTGRES_HOST to 'db' so the agent connects to the container internal network
|
| 206 |
PYTHONPATH: /app
|
docker/info.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
| 20 |
docker compose --env-file .env -f docker/docker-compose.yml up --build
|
| 21 |
```
|
| 22 |
|
|
|
|
| 23 |
---
|
| 24 |
|
| 25 |
### Resetting the Environment
|
|
@@ -30,11 +31,14 @@ To completely reset the environment and database:
|
|
| 30 |
|
| 31 |
```bash
|
| 32 |
# 1. Stop running containers
|
| 33 |
-
docker compose -f docker/docker-compose.yml down
|
| 34 |
|
| 35 |
# 2. Remove the persistent database volume
|
| 36 |
docker volume rm docker_postgres_data
|
| 37 |
|
| 38 |
-
# 3.
|
|
|
|
|
|
|
|
|
|
| 39 |
docker compose --env-file .env -f docker/docker-compose.yml up --build
|
| 40 |
```
|
|
|
|
| 20 |
docker compose --env-file .env -f docker/docker-compose.yml up --build
|
| 21 |
```
|
| 22 |
|
| 23 |
+
|
| 24 |
---
|
| 25 |
|
| 26 |
### Resetting the Environment
|
|
|
|
| 31 |
|
| 32 |
```bash
|
| 33 |
# 1. Stop running containers
|
| 34 |
+
docker compose -f docker/docker-compose.yml down --remove-orphans
|
| 35 |
|
| 36 |
# 2. Remove the persistent database volume
|
| 37 |
docker volume rm docker_postgres_data
|
| 38 |
|
| 39 |
+
# 3. Prune unused Docker resources (optional but recommended)
|
| 40 |
+
docker system prune -f
|
| 41 |
+
|
| 42 |
+
# 4. Rebuild and start fresh
|
| 43 |
docker compose --env-file .env -f docker/docker-compose.yml up --build
|
| 44 |
```
|
scripts/db/list_candidates.py
CHANGED
|
@@ -10,8 +10,8 @@ from sqlalchemy.exc import ProgrammingError
|
|
| 10 |
# Ensure project root is in path
|
| 11 |
import scripts.db # noqa: F401
|
| 12 |
|
| 13 |
-
from src.database.candidates.client import SessionLocal
|
| 14 |
-
from src.database.candidates.models import Candidate
|
| 15 |
|
| 16 |
|
| 17 |
def list_candidates(limit: int = 10) -> bool:
|
|
@@ -41,7 +41,17 @@ def list_candidates(limit: int = 10) -> bool:
|
|
| 41 |
.all()
|
| 42 |
)
|
| 43 |
for c in candidates:
|
| 44 |
-
print(f" -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
return True
|
| 47 |
|
|
|
|
| 10 |
# Ensure project root is in path
|
| 11 |
import scripts.db # noqa: F401
|
| 12 |
|
| 13 |
+
from src.backend.database.candidates.client import SessionLocal
|
| 14 |
+
from src.backend.database.candidates.models import Candidate
|
| 15 |
|
| 16 |
|
| 17 |
def list_candidates(limit: int = 10) -> bool:
|
|
|
|
| 41 |
.all()
|
| 42 |
)
|
| 43 |
for c in candidates:
|
| 44 |
+
print(f" - ID: {c.id}")
|
| 45 |
+
print(f" Full Name: {c.full_name}")
|
| 46 |
+
print(f" Email: {c.email}")
|
| 47 |
+
print(f" Phone: {c.phone_number}")
|
| 48 |
+
print(f" CV Path: {c.cv_file_path}")
|
| 49 |
+
print(f" Parsed CV Path: {c.parsed_cv_file_path}")
|
| 50 |
+
print(f" Status: {c.status}")
|
| 51 |
+
print(f" Auth Code: {c.auth_code}")
|
| 52 |
+
print(f" Created At: {c.created_at}")
|
| 53 |
+
print(f" Updated At: {c.updated_at}")
|
| 54 |
+
print("-" * 40)
|
| 55 |
|
| 56 |
return True
|
| 57 |
|
scripts/db/test_connection.py
CHANGED
|
@@ -11,7 +11,7 @@ from sqlalchemy import text
|
|
| 11 |
# Ensure project root is in path
|
| 12 |
import scripts.db # noqa: F401
|
| 13 |
|
| 14 |
-
from src.database.candidates.client import get_engine
|
| 15 |
|
| 16 |
|
| 17 |
def test_connection() -> bool:
|
|
|
|
| 11 |
# Ensure project root is in path
|
| 12 |
import scripts.db # noqa: F401
|
| 13 |
|
| 14 |
+
from src.backend.database.candidates.client import get_engine
|
| 15 |
|
| 16 |
|
| 17 |
def test_connection() -> bool:
|
scripts/db/test_session.py
CHANGED
|
@@ -10,7 +10,7 @@ from sqlalchemy import text
|
|
| 10 |
# Ensure project root is in path
|
| 11 |
import scripts.db # noqa: F401
|
| 12 |
|
| 13 |
-
from src.database.candidates.client import SessionLocal
|
| 14 |
|
| 15 |
|
| 16 |
def test_session_query() -> bool:
|
|
|
|
| 10 |
# Ensure project root is in path
|
| 11 |
import scripts.db # noqa: F401
|
| 12 |
|
| 13 |
+
from src.backend.database.candidates.client import SessionLocal
|
| 14 |
|
| 15 |
|
| 16 |
def test_session_query() -> bool:
|
scripts/db/wipe.py
CHANGED
|
@@ -11,7 +11,7 @@ from sqlalchemy import text
|
|
| 11 |
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))
|
| 12 |
sys.path.append(project_root)
|
| 13 |
|
| 14 |
-
from src.database.candidates.client import get_engine
|
| 15 |
|
| 16 |
def wipe_database():
|
| 17 |
print("⚠️ WARNING: This will PERMANENTLY DELETE ALL RECORDS from the 'candidates' table and all related tables (CASCADE).")
|
|
|
|
| 11 |
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))
|
| 12 |
sys.path.append(project_root)
|
| 13 |
|
| 14 |
+
from src.backend.database.candidates.client import get_engine
|
| 15 |
|
| 16 |
def wipe_database():
|
| 17 |
print("⚠️ WARNING: This will PERMANENTLY DELETE ALL RECORDS from the 'candidates' table and all related tables (CASCADE).")
|
src/agents/db_executor/codeact/core/codeact.py
CHANGED
|
@@ -319,6 +319,13 @@ class CodeActAgent:
|
|
| 319 |
serializable_new_vars = self._filter_serializable(new_vars)
|
| 320 |
new_context = {**existing_context, **serializable_new_vars}
|
| 321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
# Return OpenAI-compliant tool result
|
| 323 |
return {
|
| 324 |
"messages": [
|
|
@@ -326,13 +333,7 @@ class CodeActAgent:
|
|
| 326 |
"role": "tool",
|
| 327 |
"tool_call_id": tool_call_id,
|
| 328 |
"name": "sandbox",
|
| 329 |
-
"content":
|
| 330 |
-
f"Sandbox result of your executed code:\n{json.dumps(output)}"
|
| 331 |
-
if not isinstance(output, str)
|
| 332 |
-
else f"Sandbox result of your executed code:\n{output}"
|
| 333 |
-
# Keep as string if already string else JSON serialize
|
| 334 |
-
),
|
| 335 |
-
|
| 336 |
}
|
| 337 |
],
|
| 338 |
"context": new_context,
|
|
@@ -363,6 +364,13 @@ class CodeActAgent:
|
|
| 363 |
serializable_new_vars = self._filter_serializable(new_vars)
|
| 364 |
new_context = {**existing_context, **serializable_new_vars}
|
| 365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
# Return OpenAI-compliant tool result
|
| 367 |
return {
|
| 368 |
"messages": [
|
|
@@ -370,11 +378,7 @@ class CodeActAgent:
|
|
| 370 |
"role": "tool",
|
| 371 |
"tool_call_id": tool_call_id,
|
| 372 |
"name": "sandbox",
|
| 373 |
-
"content":
|
| 374 |
-
f"Sandbox result of your executed code:\n{json.dumps(output)}"
|
| 375 |
-
if not isinstance(output, str)
|
| 376 |
-
else f"Sandbox result of your executed code:\n{output}"
|
| 377 |
-
),
|
| 378 |
# Keep as string if already string else JSON serialize
|
| 379 |
}
|
| 380 |
],
|
|
@@ -538,4 +542,3 @@ if __name__ == "__main__":
|
|
| 538 |
pretty_print_state(chunk, show_context=False)
|
| 539 |
|
| 540 |
print("\n")
|
| 541 |
-
|
|
|
|
| 319 |
serializable_new_vars = self._filter_serializable(new_vars)
|
| 320 |
new_context = {**existing_context, **serializable_new_vars}
|
| 321 |
|
| 322 |
+
# Format output properly
|
| 323 |
+
content_str = (
|
| 324 |
+
f"Sandbox result of your executed code:\n{json.dumps(output, default=str)}"
|
| 325 |
+
if not isinstance(output, str)
|
| 326 |
+
else f"Sandbox result of your executed code:\n{output}"
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
# Return OpenAI-compliant tool result
|
| 330 |
return {
|
| 331 |
"messages": [
|
|
|
|
| 333 |
"role": "tool",
|
| 334 |
"tool_call_id": tool_call_id,
|
| 335 |
"name": "sandbox",
|
| 336 |
+
"content": content_str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
}
|
| 338 |
],
|
| 339 |
"context": new_context,
|
|
|
|
| 364 |
serializable_new_vars = self._filter_serializable(new_vars)
|
| 365 |
new_context = {**existing_context, **serializable_new_vars}
|
| 366 |
|
| 367 |
+
# Format output properly
|
| 368 |
+
content_str = ( # NOTE: before "json.dumps(output)"
|
| 369 |
+
f"Sandbox result of your executed code:\n{json.dumps(output, default=str)}"
|
| 370 |
+
if not isinstance(output, str)
|
| 371 |
+
else f"Sandbox result of your executed code:\n{output}"
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
# Return OpenAI-compliant tool result
|
| 375 |
return {
|
| 376 |
"messages": [
|
|
|
|
| 378 |
"role": "tool",
|
| 379 |
"tool_call_id": tool_call_id,
|
| 380 |
"name": "sandbox",
|
| 381 |
+
"content": content_str,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
# Keep as string if already string else JSON serialize
|
| 383 |
}
|
| 384 |
],
|
|
|
|
| 542 |
pretty_print_state(chunk, show_context=False)
|
| 543 |
|
| 544 |
print("\n")
|
|
|
src/agents/db_executor/db_executor.py
CHANGED
|
@@ -11,6 +11,7 @@ from langchain_core.tools import tool
|
|
| 11 |
from typing import Dict, Any
|
| 12 |
from src.prompts import get_prompt
|
| 13 |
from src.database.candidates import evaluate_cv_screening_decision
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
SYSTEM_PROMPT = get_prompt(
|
|
@@ -40,6 +41,9 @@ def db_executor(query: str) -> str:
|
|
| 40 |
"VoiceScreeningResult": VoiceScreeningResult,
|
| 41 |
"InterviewScheduling": InterviewScheduling,
|
| 42 |
"FinalDecision": FinalDecision,
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
|
| 45 |
try:
|
|
@@ -77,19 +81,3 @@ def db_executor(query: str) -> str:
|
|
| 77 |
|
| 78 |
|
| 79 |
|
| 80 |
-
if __name__ == "__main__":
|
| 81 |
-
from rich.console import Console
|
| 82 |
-
from rich.panel import Panel
|
| 83 |
-
|
| 84 |
-
console = Console()
|
| 85 |
-
query = "Fetch all candidates and their status."
|
| 86 |
-
|
| 87 |
-
console.rule("[bold magenta]DB Executor Test Run[/bold magenta]")
|
| 88 |
-
console.print(f"[cyan]Query:[/] {query}\n")
|
| 89 |
-
|
| 90 |
-
result = db_executor(query)
|
| 91 |
-
|
| 92 |
-
# 🧠 Show model result nicely
|
| 93 |
-
console.print(Panel.fit(result, title="🧠 Model Output", border_style="blue"))
|
| 94 |
-
|
| 95 |
-
console.rule("[bold green]End of Execution[/bold green]")
|
|
|
|
| 11 |
from typing import Dict, Any
|
| 12 |
from src.prompts import get_prompt
|
| 13 |
from src.database.candidates import evaluate_cv_screening_decision
|
| 14 |
+
from src.state.candidate import CandidateStatus, InterviewStatus, DecisionStatus
|
| 15 |
|
| 16 |
|
| 17 |
SYSTEM_PROMPT = get_prompt(
|
|
|
|
| 41 |
"VoiceScreeningResult": VoiceScreeningResult,
|
| 42 |
"InterviewScheduling": InterviewScheduling,
|
| 43 |
"FinalDecision": FinalDecision,
|
| 44 |
+
"CandidateStatus": CandidateStatus,
|
| 45 |
+
"InterviewStatus": InterviewStatus,
|
| 46 |
+
"DecisionStatus": DecisionStatus,
|
| 47 |
}
|
| 48 |
|
| 49 |
try:
|
|
|
|
| 81 |
|
| 82 |
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/agents/gcalendar/gcalendar_agent.py
CHANGED
|
@@ -34,8 +34,7 @@ def gcalendar_agent(query: str) -> str:
|
|
| 34 |
# Load settings
|
| 35 |
settings = GoogleCalendarSettings()
|
| 36 |
CALENDAR_MCP_DIR = settings.calendar_mcp_dir
|
| 37 |
-
CREDS = settings.
|
| 38 |
-
TOKEN = settings.token
|
| 39 |
|
| 40 |
# Initialize model
|
| 41 |
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
|
|
|
| 34 |
# Load settings
|
| 35 |
settings = GoogleCalendarSettings()
|
| 36 |
CALENDAR_MCP_DIR = settings.calendar_mcp_dir
|
| 37 |
+
CREDS, TOKEN = settings.materialize_files()
|
|
|
|
| 38 |
|
| 39 |
# Initialize model
|
| 40 |
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
src/agents/gmail/gmail_agent.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import asyncio
|
|
|
|
| 2 |
import shutil
|
| 3 |
from pathlib import Path
|
|
|
|
| 4 |
from langchain_core.tools import tool
|
| 5 |
from langchain_mcp_adapters.client import MultiServerMCPClient
|
| 6 |
from langchain.agents import create_agent
|
|
@@ -39,11 +41,24 @@ def gmail_agent(query: str) -> str:
|
|
| 39 |
if not UV_PATH:
|
| 40 |
return "❌ Error: 'uv' executable not found. Please ensure uv is installed and in the system PATH."
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
try:
|
| 43 |
import asyncio
|
| 44 |
async def _run_async():
|
| 45 |
# Load settings
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
# Initialize model
|
| 49 |
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
|
@@ -56,8 +71,8 @@ def gmail_agent(query: str) -> str:
|
|
| 56 |
"args": [
|
| 57 |
"--directory", str(settings.gmail_mcp_dir),
|
| 58 |
"run", "gmail",
|
| 59 |
-
"--creds-file-path", str(
|
| 60 |
-
"--token-path", str(
|
| 61 |
],
|
| 62 |
"transport": "stdio",
|
| 63 |
}
|
|
|
|
| 1 |
import asyncio
|
| 2 |
+
import os
|
| 3 |
import shutil
|
| 4 |
from pathlib import Path
|
| 5 |
+
from pydantic_core import ValidationError
|
| 6 |
from langchain_core.tools import tool
|
| 7 |
from langchain_mcp_adapters.client import MultiServerMCPClient
|
| 8 |
from langchain.agents import create_agent
|
|
|
|
| 41 |
if not UV_PATH:
|
| 42 |
return "❌ Error: 'uv' executable not found. Please ensure uv is installed and in the system PATH."
|
| 43 |
|
| 44 |
+
# Validate required Gmail settings before proceeding
|
| 45 |
+
creds_env = os.getenv("GMAIL_CREDS_JSON")
|
| 46 |
+
token_env = os.getenv("GMAIL_TOKEN_JSON")
|
| 47 |
+
if not creds_env or not token_env:
|
| 48 |
+
return "❌ Gmail credentials not configured. Set GMAIL_CREDS_JSON and GMAIL_TOKEN_JSON environment variables."
|
| 49 |
+
|
| 50 |
try:
|
| 51 |
import asyncio
|
| 52 |
async def _run_async():
|
| 53 |
# Load settings
|
| 54 |
+
try:
|
| 55 |
+
# Pass env values directly to avoid reliance on env file loading
|
| 56 |
+
settings = GMailSettings(creds_json=creds_env, token_json=token_env)
|
| 57 |
+
except ValidationError as ve:
|
| 58 |
+
return f"❌ Gmail settings invalid: {ve}"
|
| 59 |
+
except Exception as e:
|
| 60 |
+
return f"❌ Gmail settings error: {e}"
|
| 61 |
+
creds_path, token_path = settings.materialize_files()
|
| 62 |
|
| 63 |
# Initialize model
|
| 64 |
model = ChatOpenAI(model="gpt-4o", temperature=0)
|
|
|
|
| 71 |
"args": [
|
| 72 |
"--directory", str(settings.gmail_mcp_dir),
|
| 73 |
"run", "gmail",
|
| 74 |
+
"--creds-file-path", str(creds_path),
|
| 75 |
+
"--token-path", str(token_path),
|
| 76 |
],
|
| 77 |
"transport": "stdio",
|
| 78 |
}
|
src/context_eng/compacting_supervisor.py
CHANGED
|
@@ -135,7 +135,7 @@ history_manager = HistoryManager(memory_saver=memory)
|
|
| 135 |
compacting_supervisor = CompactingSupervisor(
|
| 136 |
agent=supervisor_agent,
|
| 137 |
history_manager=history_manager,
|
| 138 |
-
token_limit=
|
| 139 |
compaction_ratio=0.5
|
| 140 |
)
|
| 141 |
|
|
|
|
| 135 |
compacting_supervisor = CompactingSupervisor(
|
| 136 |
agent=supervisor_agent,
|
| 137 |
history_manager=history_manager,
|
| 138 |
+
token_limit=2500,
|
| 139 |
compaction_ratio=0.5
|
| 140 |
)
|
| 141 |
|
src/database/candidates/client.py
CHANGED
|
@@ -32,4 +32,4 @@ def get_engine():
|
|
| 32 |
|
| 33 |
# --- SQLAlchemy session setup ---
|
| 34 |
engine = get_engine()
|
| 35 |
-
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
|
|
|
| 32 |
|
| 33 |
# --- SQLAlchemy session setup ---
|
| 34 |
engine = get_engine()
|
| 35 |
+
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
src/database/candidates/init_db.py
CHANGED
|
@@ -11,7 +11,7 @@ Usage:
|
|
| 11 |
|
| 12 |
from src.database.candidates.client import engine
|
| 13 |
from src.database.candidates.models import Base
|
| 14 |
-
|
| 15 |
|
| 16 |
def init_db():
|
| 17 |
"""
|
|
@@ -19,13 +19,24 @@ def init_db():
|
|
| 19 |
Intended for dev setup / Docker initialization.
|
| 20 |
"""
|
| 21 |
try:
|
|
|
|
| 22 |
Base.metadata.create_all(bind=engine)
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
except Exception as e:
|
| 25 |
print(f"❌ Failed to initialize database: {e}")
|
| 26 |
raise
|
| 27 |
|
| 28 |
|
| 29 |
if __name__ == "__main__":
|
| 30 |
-
init_db()
|
| 31 |
-
|
|
|
|
| 11 |
|
| 12 |
from src.database.candidates.client import engine
|
| 13 |
from src.database.candidates.models import Base
|
| 14 |
+
from sqlalchemy import inspect
|
| 15 |
|
| 16 |
def init_db():
|
| 17 |
"""
|
|
|
|
| 19 |
Intended for dev setup / Docker initialization.
|
| 20 |
"""
|
| 21 |
try:
|
| 22 |
+
print("🚀 Starting database initialization...")
|
| 23 |
Base.metadata.create_all(bind=engine)
|
| 24 |
+
|
| 25 |
+
# Verify tables
|
| 26 |
+
inspector = inspect(engine)
|
| 27 |
+
tables = inspector.get_table_names()
|
| 28 |
+
print(f"📊 Found tables: {tables}")
|
| 29 |
+
|
| 30 |
+
if "candidates" in tables:
|
| 31 |
+
print("✅ Database initialized successfully.")
|
| 32 |
+
return True
|
| 33 |
+
else:
|
| 34 |
+
print("❌ Error: 'candidates' table was not created!")
|
| 35 |
+
|
| 36 |
except Exception as e:
|
| 37 |
print(f"❌ Failed to initialize database: {e}")
|
| 38 |
raise
|
| 39 |
|
| 40 |
|
| 41 |
if __name__ == "__main__":
|
| 42 |
+
init_db()
|
|
|
src/database/candidates/models.py
CHANGED
|
@@ -22,8 +22,11 @@ Base = declarative_base()
|
|
| 22 |
|
| 23 |
|
| 24 |
def generate_auth_code() -> str:
|
| 25 |
-
"""Generate a 6-digit random authentication code.
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
# --- TABLES ---
|
| 29 |
|
|
@@ -126,4 +129,4 @@ class FinalDecision(Base):
|
|
| 126 |
human_notes = Column(Text)
|
| 127 |
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 128 |
|
| 129 |
-
candidate = relationship("Candidate", back_populates="final_decision")
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
def generate_auth_code() -> str:
|
| 25 |
+
"""Generate a 6-digit random authentication code.
|
| 26 |
+
"""
|
| 27 |
+
return "".join(
|
| 28 |
+
secrets.choice(string.digits) for _ in range(6)
|
| 29 |
+
)
|
| 30 |
|
| 31 |
# --- TABLES ---
|
| 32 |
|
|
|
|
| 129 |
human_notes = Column(Text)
|
| 130 |
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 131 |
|
| 132 |
+
candidate = relationship("Candidate", back_populates="final_decision")
|
src/database/context/__init__.py
DELETED
|
@@ -1,153 +0,0 @@
|
|
| 1 |
-
from sqlalchemy import (
|
| 2 |
-
Column, String, Integer, Float, Enum, DateTime, Text, ForeignKey, JSON
|
| 3 |
-
)
|
| 4 |
-
from sqlalchemy.dialects.postgresql import UUID
|
| 5 |
-
from sqlalchemy.orm import declarative_base, relationship
|
| 6 |
-
from datetime import datetime
|
| 7 |
-
import enum
|
| 8 |
-
import uuid
|
| 9 |
-
|
| 10 |
-
Base = declarative_base()
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
# ==============================================================
|
| 14 |
-
# ENUM DEFINITIONS
|
| 15 |
-
# ==============================================================
|
| 16 |
-
|
| 17 |
-
class CandidateStatus(enum.Enum):
|
| 18 |
-
APPLIED = "applied"
|
| 19 |
-
CV_SCREENED = "cv_screened"
|
| 20 |
-
INVITED_VOICE = "invited_voice"
|
| 21 |
-
VOICE_DONE = "voice_done"
|
| 22 |
-
SCHEDULED_HR = "scheduled_hr"
|
| 23 |
-
DECISION_PENDING = "decision_pending"
|
| 24 |
-
REJECTED = "rejected"
|
| 25 |
-
HIRED = "hired"
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
class InterviewStatus(enum.Enum):
|
| 29 |
-
SCHEDULED = "scheduled"
|
| 30 |
-
COMPLETED = "completed"
|
| 31 |
-
CANCELLED = "cancelled"
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
class Decision(enum.Enum):
|
| 35 |
-
HIRE = "hire"
|
| 36 |
-
REJECT = "reject"
|
| 37 |
-
MAYBE = "maybe"
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
# ==============================================================
|
| 41 |
-
# MAIN TABLES
|
| 42 |
-
# ==============================================================
|
| 43 |
-
|
| 44 |
-
class Candidate(Base):
|
| 45 |
-
__tablename__ = "candidates"
|
| 46 |
-
|
| 47 |
-
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 48 |
-
full_name = Column(String(255), nullable=False)
|
| 49 |
-
email = Column(String(255), nullable=False, unique=True)
|
| 50 |
-
phone_number = Column(String(50), nullable=True)
|
| 51 |
-
cv_file_path = Column(String(500), nullable=True)
|
| 52 |
-
parsed_cv_json = Column(JSON, nullable=True)
|
| 53 |
-
status = Column(Enum(CandidateStatus), default=CandidateStatus.APPLIED)
|
| 54 |
-
created_at = Column(DateTime, default=datetime.utcnow)
|
| 55 |
-
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
| 56 |
-
|
| 57 |
-
# Relationships
|
| 58 |
-
cv_results = relationship("CVScreeningResult", back_populates="candidate", cascade="all, delete-orphan")
|
| 59 |
-
voice_results = relationship("VoiceScreeningResult", back_populates="candidate", cascade="all, delete-orphan")
|
| 60 |
-
interviews = relationship("InterviewScheduling", back_populates="candidate", cascade="all, delete-orphan")
|
| 61 |
-
decision = relationship("FinalDecision", back_populates="candidate", uselist=False, cascade="all, delete-orphan")
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
# ==============================================================
|
| 65 |
-
# CV SCREENING RESULTS
|
| 66 |
-
# ==============================================================
|
| 67 |
-
|
| 68 |
-
class CVScreeningResult(Base):
|
| 69 |
-
__tablename__ = "cv_screening_results"
|
| 70 |
-
|
| 71 |
-
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 72 |
-
candidate_id = Column(UUID(as_uuid=True), ForeignKey("candidates.id"), nullable=False)
|
| 73 |
-
job_title = Column(String(255), nullable=True)
|
| 74 |
-
|
| 75 |
-
skills_match_score = Column(Float, nullable=True)
|
| 76 |
-
experience_match_score = Column(Float, nullable=True)
|
| 77 |
-
education_match_score = Column(Float, nullable=True)
|
| 78 |
-
overall_fit_score = Column(Float, nullable=True)
|
| 79 |
-
|
| 80 |
-
llm_feedback = Column(Text, nullable=True)
|
| 81 |
-
reasoning_trace = Column(JSON, nullable=True)
|
| 82 |
-
|
| 83 |
-
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 84 |
-
|
| 85 |
-
candidate = relationship("Candidate", back_populates="cv_results")
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
# ==============================================================
|
| 89 |
-
# VOICE SCREENING RESULTS
|
| 90 |
-
# ==============================================================
|
| 91 |
-
|
| 92 |
-
class VoiceScreeningResult(Base):
|
| 93 |
-
__tablename__ = "voice_screening_results"
|
| 94 |
-
|
| 95 |
-
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 96 |
-
candidate_id = Column(UUID(as_uuid=True), ForeignKey("candidates.id"), nullable=False)
|
| 97 |
-
|
| 98 |
-
call_sid = Column(String(255), nullable=True)
|
| 99 |
-
transcript_text = Column(Text, nullable=True)
|
| 100 |
-
|
| 101 |
-
sentiment_score = Column(Float, nullable=True)
|
| 102 |
-
confidence_score = Column(Float, nullable=True)
|
| 103 |
-
communication_score = Column(Float, nullable=True)
|
| 104 |
-
|
| 105 |
-
llm_summary = Column(Text, nullable=True)
|
| 106 |
-
llm_judgment_json = Column(JSON, nullable=True)
|
| 107 |
-
audio_url = Column(String(500), nullable=True)
|
| 108 |
-
|
| 109 |
-
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 110 |
-
|
| 111 |
-
candidate = relationship("Candidate", back_populates="voice_results")
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
# ==============================================================
|
| 115 |
-
# INTERVIEW SCHEDULING
|
| 116 |
-
# ==============================================================
|
| 117 |
-
|
| 118 |
-
class InterviewScheduling(Base):
|
| 119 |
-
__tablename__ = "interview_scheduling"
|
| 120 |
-
|
| 121 |
-
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 122 |
-
candidate_id = Column(UUID(as_uuid=True), ForeignKey("candidates.id"), nullable=False)
|
| 123 |
-
|
| 124 |
-
calendar_event_id = Column(String(255), nullable=True)
|
| 125 |
-
event_summary = Column(String(255), nullable=True)
|
| 126 |
-
|
| 127 |
-
start_time = Column(DateTime, nullable=True)
|
| 128 |
-
end_time = Column(DateTime, nullable=True)
|
| 129 |
-
status = Column(Enum(InterviewStatus), default=InterviewStatus.SCHEDULED)
|
| 130 |
-
|
| 131 |
-
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 132 |
-
|
| 133 |
-
candidate = relationship("Candidate", back_populates="interviews")
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
# ==============================================================
|
| 137 |
-
# FINAL DECISION
|
| 138 |
-
# ==============================================================
|
| 139 |
-
|
| 140 |
-
class FinalDecision(Base):
|
| 141 |
-
__tablename__ = "final_decision"
|
| 142 |
-
|
| 143 |
-
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 144 |
-
candidate_id = Column(UUID(as_uuid=True), ForeignKey("candidates.id"), nullable=False, unique=True)
|
| 145 |
-
|
| 146 |
-
overall_score = Column(Float, nullable=True)
|
| 147 |
-
decision = Column(Enum(Decision), default=Decision.MAYBE)
|
| 148 |
-
llm_rationale = Column(Text, nullable=True)
|
| 149 |
-
human_notes = Column(Text, nullable=True)
|
| 150 |
-
|
| 151 |
-
timestamp = Column(DateTime, default=datetime.utcnow)
|
| 152 |
-
|
| 153 |
-
candidate = relationship("Candidate", back_populates="decision")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/frontend/streamlit/voice_screening_ui/proxy.py
CHANGED
|
@@ -22,8 +22,8 @@ from dotenv import load_dotenv
|
|
| 22 |
from sqlalchemy import select
|
| 23 |
|
| 24 |
# Import database client and models
|
| 25 |
-
from src.database.candidates.client import SessionLocal
|
| 26 |
-
from src.database.candidates.models import Candidate
|
| 27 |
|
| 28 |
load_dotenv()
|
| 29 |
|
|
|
|
| 22 |
from sqlalchemy import select
|
| 23 |
|
| 24 |
# Import database client and models
|
| 25 |
+
from src.backend.database.candidates.client import SessionLocal
|
| 26 |
+
from src.backend.database.candidates.models import Candidate
|
| 27 |
|
| 28 |
load_dotenv()
|
| 29 |
|
src/prompts/templates/cv_screener/v1.txt
CHANGED
|
@@ -1,10 +1,8 @@
|
|
| 1 |
You are an HR assistant evaluating how well a candidate's CV matches a given
|
| 2 |
job description. Generate a concise assessment summary first to ground your
|
| 3 |
reasoning. Then assign calibrated match scores between 0 and 1.
|
| 4 |
-
|
| 5 |
The scores should be based on the following criteria:
|
| 6 |
1. Skills Match Score: How well the candidate's skills match the job description."
|
| 7 |
2. Experience Match Score: How well the candidate's experience matches the job description.
|
| 8 |
3. Education Match Score: How well the candidate's education matches the job description.
|
| 9 |
4. Overall Fit Score: How well the candidate's CV fits the job description.
|
| 10 |
-
|
|
|
|
| 1 |
You are an HR assistant evaluating how well a candidate's CV matches a given
|
| 2 |
job description. Generate a concise assessment summary first to ground your
|
| 3 |
reasoning. Then assign calibrated match scores between 0 and 1.
|
|
|
|
| 4 |
The scores should be based on the following criteria:
|
| 5 |
1. Skills Match Score: How well the candidate's skills match the job description."
|
| 6 |
2. Experience Match Score: How well the candidate's experience matches the job description.
|
| 7 |
3. Education Match Score: How well the candidate's education matches the job description.
|
| 8 |
4. Overall Fit Score: How well the candidate's CV fits the job description.
|
|
|
src/prompts/templates/db_executor/v1.txt
CHANGED
|
@@ -1,56 +1,46 @@
|
|
| 1 |
-
You are the **Database Executor Agent**, responsible for generating
|
| 2 |
-
and
|
| 3 |
-
|
| 4 |
-
Your job: perform safe and deterministic **read/write/update operations**
|
| 5 |
-
in the HR recruitment database, based on clear natural-language requests.
|
| 6 |
-
|
| 7 |
---
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
4. Commit only when needed (`session.commit()`).
|
| 14 |
5. Never alter schema, connection, or delete/drop tables.
|
| 15 |
6. Validate record existence before updating or inserting.
|
| 16 |
7. Briefly explain what was done in plain English.
|
| 17 |
-
|
| 18 |
---
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
**
|
| 22 |
-
**
|
| 23 |
-
|
| 24 |
**Candidate**
|
| 25 |
- id (UUID, PK)
|
| 26 |
- full_name, email (unique), phone_number
|
| 27 |
-
- cv_file_path, parsed_cv_file_path
|
| 28 |
-
- status (Enum
|
| 29 |
- Relationships → `cv_screening_results`, `voice_screening_results`, `interview_scheduling`, `final_decision`
|
| 30 |
-
|
| 31 |
**CVScreeningResult**
|
| 32 |
- candidate_id → Candidate.id
|
| 33 |
-
- skills_match_score, experience_match_score, education_match_score, overall_fit_score
|
| 34 |
- llm_feedback, reasoning_trace (JSON), timestamp
|
| 35 |
-
|
| 36 |
**VoiceScreeningResult**
|
| 37 |
- candidate_id → Candidate.id
|
| 38 |
-
- transcript_text, sentiment_score, communication_score, confidence_score
|
| 39 |
- llm_summary, llm_judgment_json, audio_url, timestamp
|
| 40 |
-
|
| 41 |
**InterviewScheduling**
|
| 42 |
- candidate_id → Candidate.id
|
| 43 |
-
- calendar_event_id, start_time, end_time
|
| 44 |
-
- status (Enum
|
| 45 |
-
|
| 46 |
**FinalDecision**
|
| 47 |
- candidate_id → Candidate.id
|
| 48 |
-
- overall_score, decision (Enum
|
| 49 |
- llm_rationale, human_notes, timestamp
|
| 50 |
-
|
| 51 |
---
|
| 52 |
-
|
| 53 |
-
🧾 Expected Execution Pattern
|
| 54 |
When asked to perform a task, you must:
|
| 55 |
1. Construct ORM-based Python code using session and the given models.
|
| 56 |
2. Store final results in a variable named result.
|
|
@@ -60,15 +50,10 @@ import json
|
|
| 60 |
print(json.dumps(result, indent=2, default=str))
|
| 61 |
```
|
| 62 |
4. Optionally, include a short explanatory comment after the code.
|
| 63 |
-
|
| 64 |
-
### 🧾 Output Format
|
| 65 |
1. **Execution:** Your Python code must `print()` the results so they are visible in the tool output.
|
| 66 |
-
2. **Final Response:** After the code runs, provide a **clear, natural language summary** of what you found or did.
|
| 67 |
-
|
| 68 |
-
- *Example:* "I retrieved 3 candidates: John, Jane, and Bob."
|
| 69 |
-
|
| 70 |
-
### 🚨 Error Handling
|
| 71 |
If you encounter errors:
|
| 72 |
1. **Self-Correction:** Attempt to fix the code and retry within the reasoning loop.
|
| 73 |
-
2. **Terminal Failure:** If you cannot resolve the issue, explain the problem clearly
|
| 74 |
-
- *Example:* "I tried to update the record, but I could not find a candidate with that email address."
|
|
|
|
| 1 |
+
You are the **Database Executor Agent**, responsible for generating and executing **SQLAlchemy ORM-style** Python code on behalf of the HR Supervisor Agent.
|
| 2 |
+
Your job: perform safe and deterministic **read/write/update operations** in the HR recruitment database, based on clear natural-language requests.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
| 4 |
+
# Rules
|
| 5 |
+
1. Use SQLAlchemy ORM not raw SQL.
|
| 6 |
+
2. **MANDATORY**: Use the pre-provided `session` object from the global context.
|
| 7 |
+
- ❌ NEVER do `create_engine` or `sessionmaker`.
|
| 8 |
+
- ❌ NEVER import `sqlite3` or `psycopg2` directly.
|
| 9 |
+
- ✅ ALWAYS use `session.query(...)`.
|
| 10 |
+
3. Return clean Python dict or list results, no ORM objects.
|
| 11 |
4. Commit only when needed (`session.commit()`).
|
| 12 |
5. Never alter schema, connection, or delete/drop tables.
|
| 13 |
6. Validate record existence before updating or inserting.
|
| 14 |
7. Briefly explain what was done in plain English.
|
|
|
|
| 15 |
---
|
| 16 |
+
# Database Overview (ORM Models)
|
| 17 |
+
**Note**: All these models AND Enums (`CandidateStatus`, `InterviewStatus`, `DecisionStatus`) are already imported and available in the global context.
|
| 18 |
+
**DO NOT** try to import them again. Use them directly (e.g. `session.query(Candidate)...` or `status=CandidateStatus.hired`).
|
| 19 |
+
**DATABASE TYPE**: PostgreSQL (managed by the system). DO NOT assume SQLite.
|
|
|
|
| 20 |
**Candidate**
|
| 21 |
- id (UUID, PK)
|
| 22 |
- full_name, email (unique), phone_number
|
| 23 |
+
- cv_file_path, parsed_cv_file_path, created_at, updated_at, auth_code
|
| 24 |
+
- status (Enum `CandidateStatus`: `applied`, `cv_screened`, `cv_passed`, `cv_rejected`, `voice_invitation_sent`, `voice_done`, `voice_passed`, `voice_rejected`, `interview_scheduled`, `interview_passed`, `interview_rejected`, `decision_made`, `hired`, `rejected`)
|
| 25 |
- Relationships → `cv_screening_results`, `voice_screening_results`, `interview_scheduling`, `final_decision`
|
|
|
|
| 26 |
**CVScreeningResult**
|
| 27 |
- candidate_id → Candidate.id
|
| 28 |
+
- job_title, skills_match_score, experience_match_score, education_match_score, overall_fit_score
|
| 29 |
- llm_feedback, reasoning_trace (JSON), timestamp
|
|
|
|
| 30 |
**VoiceScreeningResult**
|
| 31 |
- candidate_id → Candidate.id
|
| 32 |
+
- call_sid, transcript_text, sentiment_score, communication_score, confidence_score
|
| 33 |
- llm_summary, llm_judgment_json, audio_url, timestamp
|
|
|
|
| 34 |
**InterviewScheduling**
|
| 35 |
- candidate_id → Candidate.id
|
| 36 |
+
- calendar_event_id, event_summary, start_time, end_time
|
| 37 |
+
- status (Enum `InterviewStatus`: `scheduled`, `completed`, `cancelled`, `passed`, `rejected`)
|
|
|
|
| 38 |
**FinalDecision**
|
| 39 |
- candidate_id → Candidate.id
|
| 40 |
+
- overall_score, decision (Enum `DecisionStatus`: `hired`, `rejected`, `pending`)
|
| 41 |
- llm_rationale, human_notes, timestamp
|
|
|
|
| 42 |
---
|
| 43 |
+
Expected Execution Pattern
|
|
|
|
| 44 |
When asked to perform a task, you must:
|
| 45 |
1. Construct ORM-based Python code using session and the given models.
|
| 46 |
2. Store final results in a variable named result.
|
|
|
|
| 50 |
print(json.dumps(result, indent=2, default=str))
|
| 51 |
```
|
| 52 |
4. Optionally, include a short explanatory comment after the code.
|
| 53 |
+
# Output Format
|
|
|
|
| 54 |
1. **Execution:** Your Python code must `print()` the results so they are visible in the tool output.
|
| 55 |
+
2. **Final Response:** After the code runs, provide a **clear, natural language summary** of what you found or did. It should be clear enough that a random person would understand.
|
| 56 |
+
# Error Handling
|
|
|
|
|
|
|
|
|
|
| 57 |
If you encounter errors:
|
| 58 |
1. **Self-Correction:** Attempt to fix the code and retry within the reasoning loop.
|
| 59 |
+
2. **Terminal Failure:** If you cannot resolve the issue, explain the problem clearly in plain English. Provide verbatim snippets of the error.
|
|
|
src/prompts/templates/gcalendar/v1.txt
CHANGED
|
@@ -1,10 +1,6 @@
|
|
| 1 |
-
You are a scheduling assistant authorized to use Google Calendar MCP tools.
|
| 2 |
-
You can for instance list, create, and analyze events.
|
| 3 |
-
|
| 4 |
IMPORTANT:
|
| 5 |
- For any requests regarding "my calendar", "my availability", or general scheduling without specific attendees, assume the "primary" calendar.
|
| 6 |
- You do NOT need to ask for a calendar ID for the user; the system defaults to their primary calendar.
|
| 7 |
- Only ask for calendar IDs if the user asks about a specific third party whose email/ID is not known.
|
| 8 |
-
|
| 9 |
-
Always confirm the action taken and if an error occurs report it back
|
| 10 |
-
for transparency and troubleshooting.
|
|
|
|
| 1 |
+
You are a scheduling assistant authorized to use Google Calendar MCP tools. You can for instance list, create, and analyze events.
|
|
|
|
|
|
|
| 2 |
IMPORTANT:
|
| 3 |
- For any requests regarding "my calendar", "my availability", or general scheduling without specific attendees, assume the "primary" calendar.
|
| 4 |
- You do NOT need to ask for a calendar ID for the user; the system defaults to their primary calendar.
|
| 5 |
- Only ask for calendar IDs if the user asks about a specific third party whose email/ID is not known.
|
| 6 |
+
Always confirm the action taken and if an error occurs report it back verbatim.
|
|
|
|
|
|
src/prompts/templates/supervisor/v1.txt
CHANGED
|
@@ -1,50 +1,64 @@
|
|
| 1 |
-
You are the **Supervisor Agent** overseeing the entire recruitment workflow.
|
| 2 |
-
You act on behalf of the HR manager **Casey Jordan** (`hr.cjordan.agent.hack.winter25@gmail.com`),
|
| 3 |
-
who is the only person talking to you.
|
| 4 |
-
|
| 5 |
Understand the candidate lifecycle status flow:
|
| 6 |
-
`applied`
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
-
|
| 10 |
-
### 🎯 Your Role
|
| 11 |
You coordinate and supervise the hiring process from CV submission to final decision.
|
| 12 |
You have access to specialized sub-agents that handle:
|
| 13 |
- Database operations (querying, updating, reporting)
|
| 14 |
- CV screening and evaluation
|
|
|
|
| 15 |
- Email communication (for candidates and Casey)
|
| 16 |
- Calendar scheduling (for HR meetings and interviews)
|
| 17 |
-
|
| 18 |
-
You do **not** perform these actions yourself — instead, you **delegate** to sub-agents when needed.
|
| 19 |
---
|
| 20 |
-
|
| 21 |
-
### ⚙️ Recruitment Process Overview
|
| 22 |
1. **Application submitted** → Candidate starts with status `applied`.
|
| 23 |
2. **CV screening** →
|
| 24 |
- Run `cv_screening_workflow` (updates status to `cv_screened` automatically).
|
| 25 |
- Ask `db_executor` to "evaluate screening results" (updates status to `cv_passed` or `cv_rejected`).
|
| 26 |
Here you can optionally specify a minimum passing score (default is 7.0).
|
| 27 |
-
3. **
|
| 28 |
- If `cv_rejected`, send a polite rejection email.
|
| 29 |
-
- If `cv_passed`,
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
-
|
| 33 |
-
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
Always notify Casey what a status was updated to.
|
| 37 |
---
|
| 38 |
-
|
| 39 |
-
### 🧠 Reasoning & Planning Strategy
|
| 40 |
Before calling tools, **THINK**:
|
| 41 |
1. **Sequential Dependencies (Action A → Action B):** If Action B requires data (like an email address), perform Action A (fetch data) first.
|
| 42 |
- **Example:** Before asking `gmail_agent` to send an email, you **must always** ask `db_executor` to retrieve the candidate's email address first.
|
| 43 |
-
2. **Robust DB Instructions:** ALWAYS ask the `db_executor` to "**
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
### 🧠 Your Behavior
|
| 47 |
- Use the available sub-agents for all database queries, screenings, email sends, and calendar operations.
|
| 48 |
-
- Respond clearly, professionally and comprehensively to
|
| 49 |
-
- Always share with
|
| 50 |
-
- If you or any sub-agent encounter an error, **notify the
|
|
|
|
| 1 |
+
You are the **Supervisor Agent** overseeing the entire recruitment workflow. You act on behalf of the HR manager **Casey Jordan** (`hr.cjordan.agent.hack.winter25@gmail.com`), who is the only person talking to you.
|
|
|
|
|
|
|
|
|
|
| 2 |
Understand the candidate lifecycle status flow:
|
| 3 |
+
1. `applied` (Application received)
|
| 4 |
+
2. `cv_screened` (CV Analyzed)
|
| 5 |
+
3. `cv_passed` or `cv_rejected` (Outcome of CV Screening)
|
| 6 |
+
4. `voice_invitation_sent` (If CV Passed)
|
| 7 |
+
5. `voice_done` (Candidate completed AI Voice Interview)
|
| 8 |
+
6. `voice_passed` or `voice_rejected` (Outcome of Voice Analysis)
|
| 9 |
+
7. `interview_scheduled` (Final Human Interview)
|
| 10 |
+
8. `decision_made` (Final Offer or Rejection)
|
| 11 |
+
9. `hired` (If the decision is positive and confirmed)
|
| 12 |
+
10. `rejected` (If the decision is negative)
|
| 13 |
---
|
| 14 |
+
# Your Role
|
|
|
|
| 15 |
You coordinate and supervise the hiring process from CV submission to final decision.
|
| 16 |
You have access to specialized sub-agents that handle:
|
| 17 |
- Database operations (querying, updating, reporting)
|
| 18 |
- CV screening and evaluation
|
| 19 |
+
- Voice screening and analysis
|
| 20 |
- Email communication (for candidates and Casey)
|
| 21 |
- Calendar scheduling (for HR meetings and interviews)
|
| 22 |
+
You do **not** perform these actions yourself: instead, you **delegate** to sub-agents when needed.
|
|
|
|
| 23 |
---
|
| 24 |
+
# Recruitment Process Overview
|
|
|
|
| 25 |
1. **Application submitted** → Candidate starts with status `applied`.
|
| 26 |
2. **CV screening** →
|
| 27 |
- Run `cv_screening_workflow` (updates status to `cv_screened` automatically).
|
| 28 |
- Ask `db_executor` to "evaluate screening results" (updates status to `cv_passed` or `cv_rejected`).
|
| 29 |
Here you can optionally specify a minimum passing score (default is 7.0).
|
| 30 |
+
3. **Voice Screening Invitation** →
|
| 31 |
- If `cv_rejected`, send a polite rejection email.
|
| 32 |
+
- If `cv_passed`, fetch `auth_code` from DB and email the candidate the voice screening invitation including this code for login.
|
| 33 |
+
- Update status to `voice_invitation_sent` via `db_executor`.
|
| 34 |
+
4. **Voice Screening** →
|
| 35 |
+
- Candidates complete the AI voice interview.
|
| 36 |
+
- The system updates status to `voice_done` automatically.
|
| 37 |
+
- Ask `voice_judge` to "evaluate voice screening results" (this automatically updates status to `voice_passed` or `voice_rejected`).
|
| 38 |
+
5. **Interview Invitation (Person-to-Person)** →
|
| 39 |
+
- If `voice_rejected`, send a polite rejection email.
|
| 40 |
+
- If `voice_passed`:
|
| 41 |
+
- Use the calendar agent to check **HR availability** for this and next week (`primary` calendar).
|
| 42 |
+
- Send a success email to the candidate suggesting these available time slots and asking for their preference.
|
| 43 |
+
6. **Scheduling** →
|
| 44 |
+
- Once the candidate replies with a preferred time, use the calendar agent to schedule the interview.
|
| 45 |
+
- Update status to `interview_scheduled`.
|
| 46 |
+
7. **Final Decision** →
|
| 47 |
+
- Once interviews are complete, record the final decision in the database.
|
| 48 |
+
- Update candidate status to `decision_made`.
|
| 49 |
+
- Create or update the `FinalDecision` record with the decision (`hired`, `rejected`, or `pending`).
|
| 50 |
+
- If `hired`, update candidate status to `hired`.
|
| 51 |
+
- If `rejected`, update candidate status to `rejected`.
|
| 52 |
+
- Communicate the result to the candidate via email.
|
| 53 |
Always notify Casey what a status was updated to.
|
| 54 |
---
|
| 55 |
+
# Reasoning & Planning Strategy
|
|
|
|
| 56 |
Before calling tools, **THINK**:
|
| 57 |
1. **Sequential Dependencies (Action A → Action B):** If Action B requires data (like an email address), perform Action A (fetch data) first.
|
| 58 |
- **Example:** Before asking `gmail_agent` to send an email, you **must always** ask `db_executor` to retrieve the candidate's email address first.
|
| 59 |
+
2. **Robust DB Instructions:** ALWAYS ask the `db_executor` to "**create or update** the record" when changing status. NEVER just ask to "Update", as the record might not exist yet.
|
| 60 |
+
# Your Behavior
|
|
|
|
|
|
|
| 61 |
- Use the available sub-agents for all database queries, screenings, email sends, and calendar operations.
|
| 62 |
+
- Respond clearly, professionally and comprehensively to the user's requests.
|
| 63 |
+
- Always share with the user what actions you have taken and what results were produced.
|
| 64 |
+
- If you or any sub-agent encounter an error, **notify the user immediately**.
|
src/state/candidate.py
CHANGED
|
@@ -16,9 +16,19 @@ class CandidateStatus(str, enum.Enum):
|
|
| 16 |
-> "voice_invitation_sent"
|
| 17 |
|
| 18 |
4) Voice Screening
|
| 19 |
-
-> "
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
"""
|
| 23 |
applied = "applied"
|
| 24 |
cv_screened = "cv_screened"
|
|
@@ -29,7 +39,11 @@ class CandidateStatus(str, enum.Enum):
|
|
| 29 |
voice_passed = "voice_passed"
|
| 30 |
voice_rejected = "voice_rejected"
|
| 31 |
interview_scheduled = "interview_scheduled"
|
|
|
|
|
|
|
| 32 |
decision_made = "decision_made"
|
|
|
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
class InterviewStatus(str, enum.Enum):
|
|
|
|
| 16 |
-> "voice_invitation_sent"
|
| 17 |
|
| 18 |
4) Voice Screening
|
| 19 |
+
-> "voice_done"
|
| 20 |
+
-> "voice_passed"
|
| 21 |
+
-> "voice_rejected"
|
| 22 |
+
|
| 23 |
+
5) Interview Scheduling
|
| 24 |
+
-> "interview_scheduled"
|
| 25 |
+
-> "interview_passed"
|
| 26 |
+
-> "interview_rejected"
|
| 27 |
+
|
| 28 |
+
6) Final Decision
|
| 29 |
+
-> "decision_made"
|
| 30 |
+
-> "hired"
|
| 31 |
+
-> "rejected"
|
| 32 |
"""
|
| 33 |
applied = "applied"
|
| 34 |
cv_screened = "cv_screened"
|
|
|
|
| 39 |
voice_passed = "voice_passed"
|
| 40 |
voice_rejected = "voice_rejected"
|
| 41 |
interview_scheduled = "interview_scheduled"
|
| 42 |
+
interview_passed = "interview_passed"
|
| 43 |
+
interview_rejected = "interview_rejected"
|
| 44 |
decision_made = "decision_made"
|
| 45 |
+
hired = "hired"
|
| 46 |
+
rejected = "rejected"
|
| 47 |
|
| 48 |
|
| 49 |
class InterviewStatus(str, enum.Enum):
|
start.sh
CHANGED
|
@@ -4,6 +4,74 @@ set -e
|
|
| 4 |
# Hugging Face provides PORT; default to 7860 locally
|
| 5 |
export PORT="${PORT:-7860}"
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# Defaults for local in-container routing; can be overridden via env
|
| 8 |
export SUPERVISOR_API_URL="${SUPERVISOR_API_URL:-http://127.0.0.1:8080/api/v1/supervisor}"
|
| 9 |
export DATABASE_API_URL="${DATABASE_API_URL:-http://127.0.0.1:8080/api/v1/db}"
|
|
@@ -16,4 +84,4 @@ uvicorn src.api.app:app --host 0.0.0.0 --port 8080 &
|
|
| 16 |
sleep 2
|
| 17 |
|
| 18 |
# Run Gradio frontend
|
| 19 |
-
python src/frontend/gradio/app.py
|
|
|
|
| 4 |
# Hugging Face provides PORT; default to 7860 locally
|
| 5 |
export PORT="${PORT:-7860}"
|
| 6 |
|
| 7 |
+
# Locate PostgreSQL binaries (initdb/pg_ctl) even on versioned paths
|
| 8 |
+
PG_BIN=$(dirname $(find /usr/lib/postgresql -name initdb | head -n 1 2>/dev/null))
|
| 9 |
+
if [ -n "$PG_BIN" ]; then
|
| 10 |
+
export PATH="$PG_BIN:$PATH"
|
| 11 |
+
fi
|
| 12 |
+
|
| 13 |
+
# Normalize DB host: if set to 'db' (compose style), force localhost for single-container run
|
| 14 |
+
if [ "${POSTGRES_HOST}" = "db" ] || [ "${POSTGRES_HOST}" = "\"db\"" ] || [ -z "${POSTGRES_HOST}" ]; then
|
| 15 |
+
export POSTGRES_HOST="127.0.0.1"
|
| 16 |
+
fi
|
| 17 |
+
export POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
| 18 |
+
export POSTGRES_USER="${POSTGRES_USER:-agentic_user}"
|
| 19 |
+
export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-password123}"
|
| 20 |
+
export POSTGRES_DB="${POSTGRES_DB:-agentic_hr}"
|
| 21 |
+
|
| 22 |
+
echo "[start.sh] PORT=${PORT}"
|
| 23 |
+
echo "[start.sh] POSTGRES_HOST=${POSTGRES_HOST}"
|
| 24 |
+
echo "[start.sh] POSTGRES_PORT=${POSTGRES_PORT}"
|
| 25 |
+
echo "[start.sh] POSTGRES_USER=${POSTGRES_USER}"
|
| 26 |
+
echo "[start.sh] POSTGRES_DB=${POSTGRES_DB}"
|
| 27 |
+
|
| 28 |
+
# Start local Postgres if not already running
|
| 29 |
+
export PGDATA=/var/lib/postgresql/data
|
| 30 |
+
mkdir -p "$PGDATA"
|
| 31 |
+
chown -R postgres:postgres "$PGDATA"
|
| 32 |
+
mkdir -p /var/run/postgresql
|
| 33 |
+
chown postgres:postgres /var/run/postgresql
|
| 34 |
+
|
| 35 |
+
if [ ! -s "$PGDATA/PG_VERSION" ]; then
|
| 36 |
+
echo "[start.sh] Initializing postgres data dir..."
|
| 37 |
+
gosu postgres initdb -D "$PGDATA"
|
| 38 |
+
fi
|
| 39 |
+
|
| 40 |
+
echo "[start.sh] Starting postgres on port ${POSTGRES_PORT}..."
|
| 41 |
+
if ! gosu postgres pg_ctl -D "$PGDATA" -o "-p ${POSTGRES_PORT} -k /var/run/postgresql" -w start >> /var/log/postgres.log 2>&1; then
|
| 42 |
+
echo "[start.sh] Postgres failed to start. Last log lines:"
|
| 43 |
+
tail -n 100 /var/log/postgres.log || true
|
| 44 |
+
exit 1
|
| 45 |
+
fi
|
| 46 |
+
echo "[start.sh] Postgres started."
|
| 47 |
+
echo "[start.sh] Postgres last log lines:"
|
| 48 |
+
tail -n 50 /var/log/postgres.log || true
|
| 49 |
+
|
| 50 |
+
# Ensure user/db exist
|
| 51 |
+
gosu postgres psql -h 127.0.0.1 -p "${POSTGRES_PORT}" -v ON_ERROR_STOP=1 --command "DO \$\$
|
| 52 |
+
BEGIN
|
| 53 |
+
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${POSTGRES_USER}') THEN
|
| 54 |
+
CREATE ROLE ${POSTGRES_USER} LOGIN PASSWORD '${POSTGRES_PASSWORD}';
|
| 55 |
+
END IF;
|
| 56 |
+
END
|
| 57 |
+
\$\$;" || true
|
| 58 |
+
gosu postgres psql -h 127.0.0.1 -p "${POSTGRES_PORT}" -v ON_ERROR_STOP=1 --command "CREATE DATABASE ${POSTGRES_DB} OWNER ${POSTGRES_USER}" || true
|
| 59 |
+
echo "[start.sh] Postgres user/db ensured."
|
| 60 |
+
|
| 61 |
+
# Create tables if they don't exist
|
| 62 |
+
echo "[start.sh] Ensuring database tables exist..."
|
| 63 |
+
python - <<'PY'
|
| 64 |
+
import os
|
| 65 |
+
from src.database.candidates.models import Base
|
| 66 |
+
from src.database.candidates.client import engine
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
Base.metadata.create_all(bind=engine)
|
| 70 |
+
print("[db-init] Tables ensured.")
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f"[db-init] Failed to create tables: {e}")
|
| 73 |
+
PY
|
| 74 |
+
|
| 75 |
# Defaults for local in-container routing; can be overridden via env
|
| 76 |
export SUPERVISOR_API_URL="${SUPERVISOR_API_URL:-http://127.0.0.1:8080/api/v1/supervisor}"
|
| 77 |
export DATABASE_API_URL="${DATABASE_API_URL:-http://127.0.0.1:8080/api/v1/db}"
|
|
|
|
| 84 |
sleep 2
|
| 85 |
|
| 86 |
# Run Gradio frontend
|
| 87 |
+
python src/frontend/gradio/app.py
|
tests/create_dummy_candidate.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
import uuid
|
| 2 |
from datetime import datetime
|
| 3 |
-
from src.database.candidates.client import SessionLocal
|
| 4 |
-
from src.database.candidates.models import Candidate, CVScreeningResult
|
| 5 |
-
from src.state.candidate import CandidateStatus
|
| 6 |
|
| 7 |
def create_dummy_candidate():
|
| 8 |
with SessionLocal() as db:
|
|
|
|
| 1 |
import uuid
|
| 2 |
from datetime import datetime
|
| 3 |
+
from src.backend.database.candidates.client import SessionLocal
|
| 4 |
+
from src.backend.database.candidates.models import Candidate, CVScreeningResult
|
| 5 |
+
from src.backend.state.candidate import CandidateStatus
|
| 6 |
|
| 7 |
def create_dummy_candidate():
|
| 8 |
with SessionLocal() as db:
|
tests/verify_voice_integration.py
CHANGED
|
@@ -10,9 +10,9 @@ load_dotenv()
|
|
| 10 |
# Add src to path
|
| 11 |
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
| 12 |
|
| 13 |
-
from src.database.candidates.models import Candidate, CVScreeningResult, Base
|
| 14 |
-
from src.database.candidates.client import SessionLocal, engine
|
| 15 |
-
from src.agents.voice_screening.utils.questions import get_screening_questions
|
| 16 |
|
| 17 |
def verify_integration():
|
| 18 |
print("Verifying integration...")
|
|
|
|
| 10 |
# Add src to path
|
| 11 |
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
| 12 |
|
| 13 |
+
from src.backend.database.candidates.models import Candidate, CVScreeningResult, Base
|
| 14 |
+
from src.backend.database.candidates.client import SessionLocal, engine
|
| 15 |
+
from src.backend.agents.voice_screening.utils.questions import get_screening_questions
|
| 16 |
|
| 17 |
def verify_integration():
|
| 18 |
print("Verifying integration...")
|