Spaces:
Paused
Paused
Download sensorflow/hillclimb/interviewer.py from whassan/SensorflowStudio: direct link, hf CLI and curl.
- Browser
- Download file 9.47 kB
-
https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/hillclimb/interviewer.py
- Command line
-
hf download hf://spaces/whassan/SensorflowStudio/sensorflow/hillclimb/interviewer.py
-
curl -L -o interviewer.py https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/hillclimb/interviewer.py
9.47 kB
| """AI interviewer: stateful adaptive sessions (technical / management / hybrid). | |
| There is NO fixed question list. Questions come from the competency-keyed | |
| exercise generator; after every answer the evaluation contract drives the | |
| sequencing policy: | |
| - weakness detected (score <= 2) -> targeted follow-up probe from the | |
| exercise's follow_up_questions | |
| - strong answer (score >= 4) -> escalate difficulty, or switch | |
| dimension once difficulty is maxed | |
| - shallow-but-polished (score 3 | |
| with polish markers but thin | |
| concept coverage) -> depth probe on a missed criterion | |
| - otherwise -> advance to a new competency | |
| The transcript is stored as an Evidence artifact. | |
| """ | |
| from __future__ import annotations | |
| import random | |
| from typing import Dict, List, Optional | |
| from pydantic import BaseModel, Field | |
| from sensorflow.hillclimb import content, evaluate | |
| from sensorflow.hillclimb.blueprint import competency_index, load_blueprint | |
| from sensorflow.hillclimb.models import (EvaluationResult, Evidence, Store, get_store, | |
| new_id, now_iso) | |
| MODES = { | |
| "technical": [1, 2], | |
| "management": [3], | |
| "hybrid": [1, 2, 3, 4], | |
| } | |
| POLISH_MARKERS = ["first", "second", "third", "in summary", "to summarize", "overall", | |
| "furthermore", "moreover", "in conclusion", "let me walk", "great question"] | |
| class InterviewTurn(BaseModel): | |
| index: int | |
| question: str | |
| question_type: str # opening | probe | depth_probe | escalate | advance | |
| competency_id: str | |
| exercise_id: str | |
| difficulty: int | |
| answer: Optional[str] = None | |
| evaluation: Optional[EvaluationResult] = None | |
| timestamp: str = Field(default_factory=now_iso) | |
| class InterviewSession(BaseModel): | |
| session_id: str = Field(default_factory=lambda: new_id("iv")) | |
| user_id: str = "default" | |
| mode: str = "hybrid" | |
| seed: int = 7 | |
| status: str = "active" # active | complete | |
| turns: List[InterviewTurn] = Field(default_factory=list) | |
| asked_competencies: List[str] = Field(default_factory=list) | |
| probe_count_on_current: int = 0 | |
| evidence_id: Optional[str] = None | |
| def _pool(mode: str, store: Store) -> List[str]: | |
| bp = load_blueprint(store) | |
| phases = MODES.get(mode, MODES["hybrid"]) | |
| return [c.id for c in bp.competencies if c.phase in phases] | |
| def _pick_competency(session: InterviewSession, store: Store, | |
| avoid_dimension_of: Optional[str] = None) -> str: | |
| bp = load_blueprint(store) | |
| idx = competency_index(bp) | |
| pool = [c for c in _pool(session.mode, store) if c not in session.asked_competencies] | |
| if not pool: | |
| pool = _pool(session.mode, store) | |
| if avoid_dimension_of and avoid_dimension_of in idx: | |
| dim = idx[avoid_dimension_of].dimension | |
| switched = [c for c in pool if idx[c].dimension != dim] | |
| if switched: | |
| pool = switched | |
| rng = random.Random(session.seed + len(session.turns) * 131) | |
| return rng.choice(sorted(pool)) | |
| def _new_exercise_turn(session: InterviewSession, competency_id: str, difficulty: int, | |
| question_type: str, store: Store) -> InterviewTurn: | |
| ex = content.generate_exercise(competency_id, difficulty=difficulty, | |
| seed=session.seed + len(session.turns) * 977, | |
| store=store, use_llm=False) | |
| session.asked_competencies.append(competency_id) | |
| session.probe_count_on_current = 0 | |
| return InterviewTurn(index=len(session.turns), question=ex.scenario, | |
| question_type=question_type, competency_id=competency_id, | |
| exercise_id=ex.exercise_id, difficulty=difficulty) | |
| def start_interview(mode: str = "hybrid", user_id: str = "default", seed: int = 7, | |
| store: Optional[Store] = None) -> InterviewSession: | |
| store = store or get_store() | |
| if mode not in MODES: | |
| raise ValueError(f"unknown mode '{mode}' (technical|management|hybrid)") | |
| session = InterviewSession(user_id=user_id, mode=mode, seed=seed) | |
| first_comp = _pick_competency(session, store) | |
| session.turns.append(_new_exercise_turn(session, first_comp, difficulty=2, | |
| question_type="opening", store=store)) | |
| store.put("interviews", session.session_id, session) | |
| return session | |
| def _is_polished_but_shallow(result: EvaluationResult, answer: str) -> bool: | |
| low = answer.lower() | |
| polish = sum(1 for m in POLISH_MARKERS if m in low) | |
| concept_strengths = [s for s in result.strengths | |
| if not s.lower().startswith(("quantifies", "states explicit tradeoff"))] | |
| return polish >= 2 and len(concept_strengths) <= 2 | |
| def submit_answer(session_id: str, answer: str, use_llm: bool = True, | |
| store: Optional[Store] = None) -> InterviewSession: | |
| store = store or get_store() | |
| raw = store.get("interviews", session_id) | |
| if raw is None: | |
| raise ValueError(f"unknown interview session '{session_id}'") | |
| session = InterviewSession(**raw) | |
| if session.status != "active": | |
| raise ValueError("interview already complete") | |
| current = session.turns[-1] | |
| ex = content.get_exercise(current.exercise_id, store) | |
| result = evaluate.evaluate_response(ex, answer, use_llm=use_llm) | |
| current.answer = answer | |
| current.evaluation = result | |
| # ------------------------------------------------------ sequencing policy | |
| next_turn: Optional[InterviewTurn] = None | |
| if result.score <= 2 and session.probe_count_on_current < 2 and ex.follow_up_questions: | |
| probe_idx = min(session.probe_count_on_current, len(ex.follow_up_questions) - 1) | |
| session.probe_count_on_current += 1 | |
| next_turn = InterviewTurn(index=len(session.turns), | |
| question=ex.follow_up_questions[probe_idx], | |
| question_type="probe", competency_id=current.competency_id, | |
| exercise_id=current.exercise_id, difficulty=current.difficulty) | |
| elif result.score >= 4: | |
| if current.difficulty < 3: | |
| next_turn = _new_exercise_turn(session, current.competency_id, | |
| difficulty=current.difficulty + 1, | |
| question_type="escalate", store=store) | |
| else: | |
| comp = _pick_competency(session, store, avoid_dimension_of=current.competency_id) | |
| next_turn = _new_exercise_turn(session, comp, difficulty=3, | |
| question_type="escalate", store=store) | |
| elif result.score == 3 and _is_polished_but_shallow(result, answer): | |
| missed = result.weaknesses[0] if result.weaknesses else "the mechanism behind your answer" | |
| next_turn = InterviewTurn( | |
| index=len(session.turns), | |
| question=(f"That was well structured, but let's go deeper. Specifically: " | |
| f"{missed.rstrip('.').lower()}. Explain the underlying mechanism with a " | |
| f"concrete example and real numbers."), | |
| question_type="depth_probe", competency_id=current.competency_id, | |
| exercise_id=current.exercise_id, difficulty=current.difficulty) | |
| else: | |
| comp = _pick_competency(session, store) | |
| next_turn = _new_exercise_turn(session, comp, difficulty=2, | |
| question_type="advance", store=store) | |
| session.turns.append(next_turn) | |
| store.put("interviews", session.session_id, session) | |
| return session | |
| def end_interview(session_id: str, store: Optional[Store] = None) -> InterviewSession: | |
| store = store or get_store() | |
| raw = store.get("interviews", session_id) | |
| if raw is None: | |
| raise ValueError(f"unknown interview session '{session_id}'") | |
| session = InterviewSession(**raw) | |
| if session.status == "complete": | |
| return session | |
| session.status = "complete" | |
| answered = [t for t in session.turns if t.evaluation is not None] | |
| per_comp: Dict[str, List[int]] = {} | |
| for t in answered: | |
| per_comp.setdefault(t.competency_id, []).append(t.evaluation.score) | |
| avg = (sum(t.evaluation.score for t in answered) / len(answered)) if answered else 0.0 | |
| ev = Evidence( | |
| user_id=session.user_id, | |
| competency_ids=list(per_comp.keys()), | |
| artifact_type="interview_transcript", | |
| source=f"AI interview ({session.mode})", | |
| summary=(f"{len(answered)} answers across {len(per_comp)} competencies, " | |
| f"avg score {avg:.1f}/5."), | |
| quotes=[t.evaluation.evidence[0] for t in answered if t.evaluation.evidence][:4], | |
| score=round(avg, 2), | |
| confidence=min(0.85, 0.3 + 0.1 * len(answered)), | |
| payload={"session_id": session.session_id, | |
| "per_competency": {k: sum(v) / len(v) for k, v in per_comp.items()}, | |
| "transcript": [t.model_dump() for t in session.turns]}, | |
| ) | |
| store.put("evidence", ev.evidence_id, ev) | |
| session.evidence_id = ev.evidence_id | |
| store.put("interviews", session.session_id, session) | |
| return session | |
| def get_interview(session_id: str, store: Optional[Store] = None) -> Optional[InterviewSession]: | |
| raw = (store or get_store()).get("interviews", session_id) | |
| return InterviewSession(**raw) if raw else None | |