Download app.py from doublechocolatecup/ai-interview-simulator: direct link, hf CLI and curl.
- Browser
- Download file 8.19 kB
-
https://huggingface.co/spaces/doublechocolatecup/ai-interview-simulator/resolve/main/app.py
- Command line
-
hf download hf://spaces/doublechocolatecup/ai-interview-simulator/app.py
-
curl -L -o app.py https://huggingface.co/spaces/doublechocolatecup/ai-interview-simulator/resolve/main/app.py
8.19 kB
| """ | |
| app.py β Hugging Face Spaces entry point for the AI Interview Simulator. | |
| Exposes: | |
| 1. A FastAPI REST API that implements the OpenEnv HTTP spec | |
| (POST /reset, POST /step, GET /state, GET /tasks, GET /health) | |
| 2. A Gradio UI so humans and evaluators can interact with the environment | |
| directly in the browser. | |
| Both run on the same process via gr.mount_gradio_app. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from typing import Any, Dict, List, Optional | |
| import gradio as gr | |
| import uvicorn | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from interview_env import InterviewEnv, InterviewAction, TASK_REGISTRY | |
| from interview_env.models import InterviewObservation, InterviewState, StepResult | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Global environment registry (one per session) | |
| # In production you'd use proper session management. | |
| # For hackathon / HF Space a single shared env is fine. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| _envs: Dict[str, InterviewEnv] = {} | |
| def _get_or_create_env(task_id: str) -> InterviewEnv: | |
| if task_id not in _envs: | |
| _envs[task_id] = InterviewEnv(task_id=task_id) | |
| return _envs[task_id] | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # FastAPI app | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| api = FastAPI( | |
| title="AI Interview Simulator β OpenEnv", | |
| description=( | |
| "An OpenEnv-compliant environment that simulates real job interviews. " | |
| "Agents learn to answer interview questions and receive structured feedback." | |
| ), | |
| version="1.0.0", | |
| ) | |
| api.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| class ResetRequest(BaseModel): | |
| task_id: str = "junior_behavioral" | |
| class StepRequest(BaseModel): | |
| task_id: str | |
| response: str | |
| def health(): | |
| return {"status": "ok", "environment": "ai-interview-simulator"} | |
| def list_tasks(): | |
| return { | |
| tid: { | |
| "name": t.name if hasattr(t, "name") else tid, | |
| "difficulty": TASK_REGISTRY[tid].difficulty, | |
| "questions": TASK_REGISTRY[tid].total_questions, | |
| "description": TASK_REGISTRY[tid].description, | |
| } | |
| for tid, t in TASK_REGISTRY.items() | |
| } | |
| def reset_env(req: ResetRequest): | |
| if req.task_id not in TASK_REGISTRY: | |
| raise HTTPException(status_code=400, detail=f"Unknown task_id: {req.task_id}") | |
| env = _get_or_create_env(req.task_id) | |
| obs = env.reset() | |
| return obs.model_dump() | |
| def step_env(req: StepRequest): | |
| if req.task_id not in _envs: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"No active session for task '{req.task_id}'. Call /reset first.", | |
| ) | |
| env = _envs[req.task_id] | |
| action = InterviewAction(response=req.response) | |
| result = env.step(action) | |
| return result.model_dump() | |
| def get_state(task_id: str = "junior_behavioral"): | |
| if task_id not in _envs: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"No active session for task '{task_id}'. Call /reset first.", | |
| ) | |
| return _envs[task_id].state().model_dump() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Gradio UI | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _format_history(history: List) -> str: | |
| if not history: | |
| return "" | |
| lines = [] | |
| for h in history: | |
| lines.append(f"**Q:** {h[0]}") | |
| lines.append(f"**A:** {h[1]}") | |
| lines.append("---") | |
| return "\n".join(lines) | |
| def gradio_reset(task_id: str): | |
| env = _get_or_create_env(task_id) | |
| obs = env.reset() | |
| question = obs.current_question | |
| status = ( | |
| f"**Interview started!**\n\n" | |
| f"**Role:** {obs.job_title}\n\n" | |
| f"**Company:** {obs.company_context}\n\n" | |
| f"**Difficulty:** {obs.difficulty}\n\n" | |
| f"**Questions:** {obs.total_questions}" | |
| ) | |
| return question, [], status, "" | |
| def gradio_step(task_id: str, response: str, history: List, current_q: str): | |
| if not response.strip(): | |
| return current_q, history, "β οΈ Please type a response before submitting.", "" | |
| env = _get_or_create_env(task_id) | |
| action = InterviewAction(response=response) | |
| result = env.step(action) | |
| history = history + [[current_q, response]] | |
| if result.done: | |
| summary = result.info.get("episode_summary", {}) | |
| final = summary.get("final_score", 0.0) | |
| passed = "β PASSED" if summary.get("passed") else "β Did not pass" | |
| status = ( | |
| f"**Interview Complete!** {passed}\n\n" | |
| f"**Final Score:** {final:.0%}\n\n" | |
| f"**Feedback:** {result.info.get('feedback', '')}" | |
| ) | |
| return result.observation.current_question, history, status, "" | |
| else: | |
| feedback = result.info.get("feedback", "") | |
| score = result.info.get("grader_breakdown", {}).get("total_score", 0.0) | |
| status = ( | |
| f"**Question {result.info['question_number'] - 1} Score:** {score:.0%}\n\n" | |
| f"**Feedback:** {feedback}\n\n" | |
| f"**Progress:** {result.info['question_number'] - 1}/{result.info['total_questions']} answered" | |
| ) | |
| return result.observation.current_question, history, status, "" | |
| def build_gradio_ui(): | |
| task_choices = list(TASK_REGISTRY.keys()) | |
| with gr.Blocks( | |
| title="π€ AI Interview Simulator", | |
| theme=gr.themes.Soft(), | |
| ) as demo: | |
| gr.Markdown( | |
| """ | |
| # π€ AI Interview Simulator β OpenEnv | |
| Practice real job interviews with structured AI feedback. | |
| Select a difficulty, start the interview, and answer each question. | |
| """ | |
| ) | |
| with gr.Row(): | |
| task_dd = gr.Dropdown( | |
| choices=task_choices, | |
| value=task_choices[0], | |
| label="Interview Task", | |
| ) | |
| reset_btn = gr.Button("π Start / Restart Interview", variant="primary") | |
| status_md = gr.Markdown("Click **Start** to begin the interview.") | |
| question_box = gr.Textbox( | |
| label="π Current Question", | |
| interactive=False, | |
| lines=3, | |
| ) | |
| response_box = gr.Textbox( | |
| label="βοΈ Your Answer", | |
| placeholder="Type your answer hereβ¦", | |
| lines=5, | |
| ) | |
| submit_btn = gr.Button("π€ Submit Answer", variant="secondary") | |
| chat_hist = gr.Chatbot(label="Interview Transcript", height=400) | |
| # Wiring | |
| reset_btn.click( | |
| fn=gradio_reset, | |
| inputs=[task_dd], | |
| outputs=[question_box, chat_hist, status_md, response_box], | |
| ) | |
| submit_btn.click( | |
| fn=gradio_step, | |
| inputs=[task_dd, response_box, chat_hist, question_box], | |
| outputs=[question_box, chat_hist, status_md, response_box], | |
| ) | |
| return demo | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Mount Gradio on FastAPI | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| demo = build_gradio_ui() | |
| app = gr.mount_gradio_app(api, demo, path="/") | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("PORT", 7860)) | |
| uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False) | |