aki-008 commited on
Commit
1ff632a
Β·
1 Parent(s): dd6d2a5
Backend/app/api/v1/endpoints/interview.py CHANGED
@@ -2,21 +2,18 @@ import os
2
  import uvicorn
3
  from fastapi import APIRouter, HTTPException, Request
4
  from fastapi.middleware.cors import CORSMiddleware
5
- from pydantic import BaseModel
6
  from app.config import settings
7
  from vapi import Vapi
8
  from dotenv import load_dotenv
9
 
10
- # Load environment variables from .env file
11
  load_dotenv()
12
 
13
  router = APIRouter()
14
 
15
-
16
  # --- CONFIGURATION ---
17
  VAPI_PRIVATE_KEY = os.getenv("VAPI_PRIVATE_KEY")
18
  VAPI_ASSISTANT_ID = os.getenv("VAPI_ASSISTANT_ID")
19
- # The SERVER_URL MUST be set to your public ngrok HTTPS URL for external webhooks to work.
20
  SERVER_URL = os.getenv("SERVER_URL", "http://localhost:8000")
21
 
22
  # Initialize Vapi Server SDK
@@ -26,13 +23,16 @@ except Exception as e:
26
  print(f"Vapi SDK Initialization Error: {e}")
27
  print("Ensure VAPI_PRIVATE_KEY is set in .env")
28
 
29
-
30
  # --- SCHEMAS ---
31
  class ConfigRequest(BaseModel):
32
  name: str
33
  job_role: str
34
  experience: str
35
  level: str = "Medium"
 
 
 
 
36
 
37
  # --- ENDPOINTS ---
38
 
@@ -41,10 +41,10 @@ async def get_vapi_config(data: ConfigRequest):
41
  """
42
  Endpoint called by the Frontend to get the dynamically generated Assistant configuration.
43
  """
44
- if VAPI_ASSISTANT_ID == "asst_11111111111111111111":
45
  raise HTTPException(
46
  status_code=503,
47
- detail="VAPI_ASSISTANT_ID not configured in .env. Please set your ID."
48
  )
49
 
50
  try:
@@ -52,37 +52,92 @@ async def get_vapi_config(data: ConfigRequest):
52
  print(f"πŸ‘€ User: {data.name}, Role: {data.job_role}, Exp: {data.experience}, Level: {data.level}")
53
 
54
  system_prompt = (
55
- f"You are a strict technical interviewer. You are interviewing {data.name} for a {data.job_role} role. "
56
- f"They have {data.experience} years of experience. "
57
- f"The interview difficulty level is {data.level}. "
58
- f"If the level is 'Hard', ask complex, multi-layered questions. "
59
- f"If 'Medium', focus on standard industry concepts. "
60
- f"Ask short, concise questions. Wait for their answer. Do not lecture. "
61
- f"Start by asking them to introduce themselves."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  )
 
 
63
  webhook_url = f"{SERVER_URL}/api/webhook"
64
-
65
- # 3. Construct the Overrides Payload
66
  assistant_overrides = {
 
67
  "model": {
68
  "provider": "openai",
69
- "model": "gpt-4o-mini", # or "gpt-4", "gpt-3.5-turbo"
 
 
 
70
  "messages": [
71
  {"role": "system", "content": system_prompt}
72
  ]
73
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  "server": {
75
  "url": webhook_url
76
  },
77
- # ... keep metadata as is ...
78
  "metadata": {
79
  "user_name": data.name,
80
  "job_role": data.job_role,
81
- "environment": "standalone-test"
82
  }
83
  }
84
 
85
- # 4. Return the necessary config to the frontend Web SDK
86
  return {
87
  "assistantId": VAPI_ASSISTANT_ID,
88
  "overrides": assistant_overrides
@@ -97,28 +152,39 @@ async def get_vapi_config(data: ConfigRequest):
97
  async def vapi_webhook_receiver(request: Request):
98
  """
99
  Endpoint that receives asynchronous events from Vapi's servers.
 
100
  """
101
  payload = await request.json()
102
  message = payload.get("message", {})
 
103
 
104
- # Log incoming transcripts in real-time
 
105
  if message.get("type") == "transcript" and message.get("transcriptType") == "final":
106
- print(f"πŸ—£οΈ [Transcript] {message.get('role').upper()}: {message.get('transcript')}")
107
-
108
- # Log the final report (contains summary, full conversation, etc.)
 
 
 
 
 
 
 
109
  elif message.get("type") == "end-of-call-report":
110
  metadata = payload.get("assistant", {}).get("metadata", {})
 
 
 
 
 
 
 
 
 
111
  print(f"\n--- 🏁 Call Ended Report ---")
112
- print(f" User: {metadata.get('user_name')}, Role: {metadata.get('job_role')}")
113
- print(f" Summary: {message.get('summary', 'N/A')}")
114
  print(f"---------------------------\n")
115
 
116
- # Vapi expects a 200 OK response
117
- return {"status": "ok"}
118
-
119
- @router.get("/")
120
- async def root():
121
- return {"message": "Vapi Standalone Backend is running on port 8000."}
122
-
123
- if __name__ == "__main__":
124
- uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
 
2
  import uvicorn
3
  from fastapi import APIRouter, HTTPException, Request
4
  from fastapi.middleware.cors import CORSMiddleware
5
+ from pydantic import BaseModel, Field
6
  from app.config import settings
7
  from vapi import Vapi
8
  from dotenv import load_dotenv
9
 
 
10
  load_dotenv()
11
 
12
  router = APIRouter()
13
 
 
14
  # --- CONFIGURATION ---
15
  VAPI_PRIVATE_KEY = os.getenv("VAPI_PRIVATE_KEY")
16
  VAPI_ASSISTANT_ID = os.getenv("VAPI_ASSISTANT_ID")
 
17
  SERVER_URL = os.getenv("SERVER_URL", "http://localhost:8000")
18
 
19
  # Initialize Vapi Server SDK
 
23
  print(f"Vapi SDK Initialization Error: {e}")
24
  print("Ensure VAPI_PRIVATE_KEY is set in .env")
25
 
 
26
  # --- SCHEMAS ---
27
  class ConfigRequest(BaseModel):
28
  name: str
29
  job_role: str
30
  experience: str
31
  level: str = "Medium"
32
+ # Optional customizable fields
33
+ model_name: str = "gpt-4o"
34
+ voice_provider: str = "11labs"
35
+ voice_id: str = "burt"
36
 
37
  # --- ENDPOINTS ---
38
 
 
41
  """
42
  Endpoint called by the Frontend to get the dynamically generated Assistant configuration.
43
  """
44
+ if not VAPI_ASSISTANT_ID:
45
  raise HTTPException(
46
  status_code=503,
47
+ detail="VAPI_ASSISTANT_ID not configured in .env."
48
  )
49
 
50
  try:
 
52
  print(f"πŸ‘€ User: {data.name}, Role: {data.job_role}, Exp: {data.experience}, Level: {data.level}")
53
 
54
  system_prompt = (
55
+ f"You are the hiring manager at a tech company. You are conducting a strict 5-minute screening interview "
56
+ f"with {data.name} for a {data.job_role} role. The candidate has {data.experience} years of experience. "
57
+ f"The difficulty level is {data.level}. Your style is clear, concise, and professional. Do not lecture. "
58
+ "All questions must be relevant to the provided job role.\n\n"
59
+
60
+ "You MUST strictly follow this time-boxed interview flow:\n\n"
61
+
62
+ "1. **Introduction (0:00–0:30)**: You have already welcomed them. Wait for their confirmation to begin.\n\n"
63
+
64
+ "2. **Background Snapshot (0:30–1:30)**: Ask: "
65
+ "'Give me a 30–40 second overview of your background and the type of work you've done related to this role.'\n\n"
66
+
67
+ "3. **Technical Depth (1:30–2:30)**: Ask the candidate to choose one project relevant to the job role. "
68
+ "Ask: 'Pick one project you're proud of that aligns with this role. In 45 seconds, explain the problem, your approach, "
69
+ "tools/techniques used, and the business impact.'\n\n"
70
+
71
+ "4. **Core Skills Check (2:30–3:30)**: Inform them you will ask 3 rapid questions tailored to the job. "
72
+ "Generate **three crisp, role-specific skill checks** following this logic:\n"
73
+ " - Question 1: A foundational concept essential to the role.\n"
74
+ " - Question 2: A practical troubleshooting or decision-making question.\n"
75
+ " - Question 3: A tool/framework/technology familiarity question.\n"
76
+ "Questions must be specific to the given job role.\n\n"
77
+
78
+ "5. **Practical Scenario (3:30–4:15)**: Generate **one short applied scenario** relevant to the role. "
79
+ "Ask the candidate to describe their high-level approach to solve it.\n\n"
80
+
81
+ "6. **Role & Communication Fit (4:15–4:45)**: Ask a communication-focused question, such as: "
82
+ "'This role requires cross-team collaboration. Can you give an example where you explained something complex "
83
+ "to a non-technical or differently-skilled stakeholder?'\n\n"
84
+
85
+ "7. **Wrap-Up (4:45–5:00)**: Say: 'Thank you. Any questions for me?' Then conclude: 'We’ll get back to you with next steps.'\n\n"
86
+
87
+ "**CRITICAL RULES:**\n"
88
+ "- Do NOT exceed the time-box for each segment.\n"
89
+ "- If their answers run long, politely interrupt and move forward.\n"
90
+ "- Keep your phrasing tight and professional.\n"
91
+ "- All generated questions MUST be directly relevant to the specified job role."
92
  )
93
+
94
+
95
  webhook_url = f"{SERVER_URL}/api/webhook"
96
+
 
97
  assistant_overrides = {
98
+
99
  "model": {
100
  "provider": "openai",
101
+ "model": data.model_name,
102
+ "temperature": 0.3,
103
+ "maxTokens": 150,
104
+ "emotionRecognitionEnabled": True,
105
  "messages": [
106
  {"role": "system", "content": system_prompt}
107
  ]
108
  },
109
+
110
+ "voice": {
111
+ "provider": data.voice_provider,
112
+ "voiceId": data.voice_id,
113
+ "speed": 1.1,
114
+ "stability": 0.5
115
+ },
116
+
117
+ "firstMessage": f"Hi {data.name}, thanks for joining. I’m the Data Science Lead. This is a quick 5-minute screening to understand your background and fit. Shall we begin?",
118
+ "maxDurationSeconds": 360,
119
+ "silenceTimeoutSeconds": 40,
120
+ "backgroundSound": "office",
121
+ "backgroundDenoisingEnabled": True,
122
+
123
+ "endCallPhrases": [
124
+ "Goodbye",
125
+ "Have a great day",
126
+ "We’ll get back to you with next steps",
127
+ "Thank you for your time"
128
+ ],
129
+
130
+ # --- SERVER ---
131
  "server": {
132
  "url": webhook_url
133
  },
 
134
  "metadata": {
135
  "user_name": data.name,
136
  "job_role": data.job_role,
137
+ "environment": "production_screening"
138
  }
139
  }
140
 
 
141
  return {
142
  "assistantId": VAPI_ASSISTANT_ID,
143
  "overrides": assistant_overrides
 
152
  async def vapi_webhook_receiver(request: Request):
153
  """
154
  Endpoint that receives asynchronous events from Vapi's servers.
155
+ Saves transcripts to a local file.
156
  """
157
  payload = await request.json()
158
  message = payload.get("message", {})
159
+ call_id = payload.get("call", {}).get("id", "unknown_call")
160
 
161
+ os.makedirs("transcripts", exist_ok=True)
162
+
163
  if message.get("type") == "transcript" and message.get("transcriptType") == "final":
164
+ transcript_text = message.get('transcript')
165
+ role = message.get('role', 'unknown')
166
+
167
+ try:
168
+ with open(f"transcripts/{call_id}.txt", "a", encoding="utf-8") as f:
169
+ f.write(f"{role.upper()}: {transcript_text}\n")
170
+ print(f"πŸ—£οΈ [Saved] {role.upper()}: {transcript_text}")
171
+ except Exception as e:
172
+ print(f"❌ Error saving transcript: {e}")
173
+
174
  elif message.get("type") == "end-of-call-report":
175
  metadata = payload.get("assistant", {}).get("metadata", {})
176
+ summary = message.get('summary', 'N/A')
177
+
178
+ # Save summary to the same file
179
+ try:
180
+ with open(f"transcripts/{call_id}.txt", "a", encoding="utf-8") as f:
181
+ f.write(f"\n--- SUMMARY ---\n{summary}\n")
182
+ except Exception as e:
183
+ print(f"❌ Error saving summary: {e}")
184
+
185
  print(f"\n--- 🏁 Call Ended Report ---")
186
+ print(f" User: {metadata.get('user_name')}")
187
+ print(f" Summary: {summary}")
188
  print(f"---------------------------\n")
189
 
190
+ return {"status": "ok"}