File size: 3,853 Bytes
61f1214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""
Typed Pydantic models for the AI Interview Simulator OpenEnv environment.
Defines Observation, Action, Reward, and State structures.
"""
from __future__ import annotations

from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


# ──────────────────────────────────────────────
# Enumerations
# ──────────────────────────────────────────────

class InterviewStage(str, Enum):
    INTRO       = "intro"
    BEHAVIORAL  = "behavioral"
    TECHNICAL   = "technical"
    SITUATIONAL = "situational"
    CLOSING     = "closing"
    COMPLETE    = "complete"


class DifficultyLevel(str, Enum):
    EASY   = "easy"
    MEDIUM = "medium"
    HARD   = "hard"


# ──────────────────────────────────────────────
# Core OpenEnv Models
# ──────────────────────────────────────────────

class QAPair(BaseModel):
    """A single question + the candidate's answer."""
    question:      str
    answer:        str
    stage:         InterviewStage
    question_score: Optional[float] = None   # filled after grading


class InterviewObservation(BaseModel):
    """
    Observation returned by reset() and step().
    Contains everything the agent needs to formulate its next response.
    """
    task_id:           str
    job_title:         str
    company_context:   str
    difficulty:        DifficultyLevel

    current_question:  str
    question_number:   int                    # 1-indexed
    total_questions:   int
    stage:             InterviewStage

    previous_qa:       List[QAPair] = Field(default_factory=list)
    cumulative_score:  float = 0.0
    time_elapsed:      float = 0.0            # seconds since reset
    interview_complete: bool = False


class InterviewAction(BaseModel):
    """
    Action submitted by the agent β€” the candidate's spoken response.
    """
    response: str = Field(
        ...,
        min_length=1,
        description="The candidate's answer to the current interview question.",
    )


class InterviewReward(BaseModel):
    """
    Detailed reward breakdown for a single step.
    All sub-scores are in [0.0, 1.0]; total_score is their weighted sum.
    """
    total_score:      float   # 0.0 – 1.0, primary signal
    relevance:        float   # Does the answer address the question?
    structure:        float   # Organised response (STAR / step-by-step)?
    depth:            float   # Specificity, examples, numbers
    professionalism:  float   # Tone, vocabulary, no red flags

    feedback:         str     # Human-readable grader feedback


class InterviewState(BaseModel):
    """
    Full internal state β€” returned by state().
    Useful for debugging, checkpointing, and OpenEnv validators.
    """
    task_id:           str
    difficulty:        DifficultyLevel
    job_title:         str
    company_context:   str

    questions:         List[str]             # All questions in order
    stages:            List[InterviewStage]  # Stage per question
    current_index:     int                   # Which question we're on

    qa_history:        List[QAPair]
    per_question_scores: List[float]

    episode_done:      bool
    total_steps:       int
    start_time:        float
    cumulative_reward: float


class StepResult(BaseModel):
    """Return type of env.step()."""
    observation: InterviewObservation
    reward:      float                    # scalar for RL loop
    done:        bool
    info:        Dict[str, Any] = Field(default_factory=dict)