# File: src/call_manager.py # Purpose: Initiate outbound AI calls via Bland.ai — no webhooks needed, # Bland.ai handles the full conversation loop internally. import httpx from typing import Dict, Any, List from config import ( BLAND_API_KEY, BLAND_API_URL, INTRO_MESSAGE, MAX_QUESTIONS, ) # In-memory session store keyed by Bland call_id _sessions: Dict[str, Dict[str, Any]] = {} HEADERS = { "authorization": BLAND_API_KEY, "Content-Type": "application/json", } def initiate_call( candidate_phone: str, session_id: int, candidate_name: str, resume_skills: List[str], ) -> str: """ Place an outbound AI call via Bland.ai. Bland handles STT, LLM, TTS and conversation loop internally. Returns the Bland call_id. """ # Build personalised prompt from resume skills skills_context = "" if resume_skills: skills_str = ", ".join(resume_skills[:5]) skills_context = ( f"The candidate has experience with: {skills_str}. " f"Ask relevant questions about these skills during the interview. " ) prompt = ( f"You are an expert AI technical recruiter conducting a phone screening interview with {candidate_name}. " f"{skills_context}" "Your job is to: " "1. Introduce yourself as an AI recruitment assistant. " "2. Ask one clear question at a time. " "3. Listen carefully and ask follow-up questions when answers are vague. " "4. Cover these topics: background and experience, most challenging project, " "debugging approach, production deployment experience, handling class imbalance, " "version control experience, and career goals. " "5. Keep a professional, warm, and encouraging tone. " "6. After covering all topics, thank the candidate and end the call. " "Never reveal you are evaluating the candidate. Keep responses under 3 sentences." ) payload = { "phone_number": candidate_phone, "task": prompt, "model": "enhanced", "language": "en", "voice": "june", "max_duration": 15, "answered_by_enabled": True, "wait_for_greeting": True, "record": True, "amd": False, "interruption_threshold": 100, "temperature": 0.4, "webhook": None, "metadata": { "session_id": session_id, "candidate_name": candidate_name, }, } response = httpx.post( f"{BLAND_API_URL}/calls", headers=HEADERS, json=payload, timeout=30, ) response.raise_for_status() data = response.json() call_id = data.get("call_id", "") _sessions[call_id] = { "session_id": session_id, "candidate_name": candidate_name, "status": "active", } return call_id def get_call_status(call_id: str) -> Dict[str, Any]: """Fetch call status and transcript from Bland.ai.""" response = httpx.get( f"{BLAND_API_URL}/calls/{call_id}", headers=HEADERS, timeout=30, ) response.raise_for_status() return response.json() def get_call_transcript(call_id: str) -> List[Dict[str, str]]: """ Fetch and format the call transcript from Bland.ai. Returns list of {speaker, text} dicts. """ data = get_call_status(call_id) transcripts = data.get("transcripts", []) result = [] for t in transcripts: speaker = "AI" if t.get("user") == "assistant" else "Candidate" text = t.get("text", "").strip() if text: result.append({"speaker": speaker, "text": text}) return result def get_session(call_id: str) -> Dict[str, Any]: return _sessions.get(call_id, {}) def remove_session(call_id: str): _sessions.pop(call_id, None)