meta_ai_TeamTitans / models.py
shreyashahu0143's picture
3 of the major bugs fixed
b81a142
Raw
History Blame Contribute Delete
8.21 kB
"""
models.py — Type-safe data contracts for the Email Triage RL Environment
=========================================================================
PURPOSE:
Defines EXACTLY what data looks like when flowing between:
- server/environment.py (the kitchen that creates data)
- client.py (the waiter that carries data)
- inference.py (the AI that reads data)
- grader.py (the food critic that scores)
Everyone imports from here. Change here → change everywhere.
CHANGES FROM ORIGINAL:
- estimated_response_time max raised from 120 → 180
(email_bank.json has a 180-min email; old cap caused Pydantic validation error)
- All other fields unchanged
"""
from __future__ import annotations
from enum import IntEnum, Enum
from typing import Dict, List, Optional
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# 1. ENUMS
# ---------------------------------------------------------------------------
class SenderImportance(str, Enum):
"""
The "tier" of a sender. Controls relationship decay rates and reward weights.
VIP → Boss, Team Lead, Key Client (ignore_penalty = -20, respond_boost = +15)
NORMAL → Teammates, Collaborators (ignore_penalty = -10, respond_boost = +10)
SPAM → Newsletters, Cold Outreach (ignore_penalty = 0, respond_boost = 0)
"""
VIP = "VIP"
NORMAL = "Normal"
SPAM = "Spam"
class Action(IntEnum):
"""
The two actions the AI agent can take each step.
IGNORE (0) → Skip this email. Saves time but damages relationship.
RESPOND (1) → Reply to this email. Costs time but boosts relationship.
"""
IGNORE = 0
RESPOND = 1
# ---------------------------------------------------------------------------
# 2. EMAIL
# ---------------------------------------------------------------------------
class Email(BaseModel):
"""
Represents a single email in the inbox.
KEY DESIGN DECISIONS:
- base_priority (1-10): inherent importance. NOT always correlated with
sender_importance (a VIP can send a low-priority "lunch?" email).
- estimated_response_time: HIDDEN from the agent — it sees email_length instead.
This is Blind Spot #1.
- times_ignored: tracks ignore count per email; urgency escalates.
- is_followup: True if this email exists because a previous one was ignored.
- parent_email_id: links follow-up to its original email.
"""
email_id: int
sender: str = Field(..., description="Full email address, e.g. boss@company.com")
sender_domain: str = Field(..., description="'internal', 'external', or 'vip-domain'")
subject: str
body: str = Field(..., description="Full email body text")
# Importance & Priority
sender_importance: str = Field(..., description="VIP | Normal | Spam")
base_priority: int = Field(..., ge=1, le=10, description="Inherent priority 1-10")
# Time tracking (Blind Spot #1) — max 180 to support long audit emails
estimated_response_time: int = Field(
..., ge=5, le=180,
description="Minutes to respond. HIDDEN from agent — it sees email_length instead."
)
# Episode tracking
received_at_timestep: int = Field(default=0)
times_ignored: int = Field(default=0)
sender_urgency_multiplier: float = Field(
default=1.0,
description="Escalates by +0.5 each time this sender is ignored"
)
# Follow-up tracking
is_followup: bool = Field(
default=False,
description="True if generated because a previous email from this sender was ignored"
)
parent_email_id: Optional[int] = Field(
default=None,
description="email_id of the original email if this is a follow-up"
)
bank_email_id: Optional[int] = None
# ---------------------------------------------------------------------------
# 3. RELATIONSHIP
# ---------------------------------------------------------------------------
class Relationship(BaseModel):
"""
Tracks the health of our working relationship with each sender.
health: Current score (0-100). Starts at 75.
importance_weight: VIP=3, Normal=2, Spam=0. Used in health_penalty formula.
degradation_rate: Health lost per ignore. Pulled from RELATIONSHIP_CONFIG.
is_angry: True if VIP was ignored. Triggers follow-up generation.
interaction_count: Count of times we responded to this sender.
DECAY TABLE (from RELATIONSHIP_CONFIG below):
VIP: ignore_penalty=-20, respond_boost=+15
Normal: ignore_penalty=-10, respond_boost=+10
Spam: ignore_penalty=0, respond_boost=0
"""
sender_email: str
health: float = Field(default=75.0, ge=0.0, le=100.0)
importance: str = Field(..., description="VIP | Normal | Spam")
importance_weight: int = Field(..., ge=0, le=3)
degradation_rate: float = Field(...)
is_angry: bool = Field(default=False)
interaction_count: int = Field(default=0)
# ---------------------------------------------------------------------------
# 4. STATE (God-mode view — used by grader and for debugging)
# ---------------------------------------------------------------------------
class State(BaseModel):
"""
Complete snapshot of the environment.
IMPORTANT: environment.py must return a DEEP COPY (not live references).
Use copy.deepcopy() before returning.
"""
inbox: List[Email] = Field(..., description="All emails in this episode")
current_email_index: int = Field(default=0)
relationships: Dict[str, Relationship] = Field(
..., description="Maps sender_email → Relationship object"
)
current_timestep: int = Field(default=0)
time_budget_remaining: int = Field(default=480)
total_time_spent: int = Field(default=0)
emails_handled: int = Field(default=0)
# ---------------------------------------------------------------------------
# 5. OBSERVATION (What the agent actually sees — partial view)
# ---------------------------------------------------------------------------
class EmailObservation(BaseModel):
"""
What the LLM agent sees at each step. A LIMITED view of State.
HIDDEN from agent (intentional partial observability):
- estimated_response_time (agent sees email_length as proxy only)
- other senders' relationship scores
- future emails in the inbox
- relationship degradation rates
"""
email_id: int
sender: str
subject: str
body: str
sender_importance: str = Field(..., description="VIP | Normal | Spam")
email_length: int = Field(..., description="Character count. Proxy for time cost.")
relationship_score: float = Field(..., description="Health with THIS sender only (0-100)")
time_budget_remaining: int = Field(...)
emails_remaining: int = Field(...)
# ---------------------------------------------------------------------------
# 6. STEP RESPONSE
# ---------------------------------------------------------------------------
class StepResponse(BaseModel):
"""
The full response from POST /step.
"""
observation: EmailObservation = Field(
..., description="The NEXT email to act on (or final observation if done=True)"
)
reward: float = Field(...)
done: bool = Field(...)
info: dict = Field(default_factory=dict)
# ---------------------------------------------------------------------------
# 7. RELATIONSHIP CONFIG (constants — imported by environment.py)
# ---------------------------------------------------------------------------
RELATIONSHIP_CONFIG = {
"VIP": {
"ignore_penalty": -20,
"respond_boost": +15,
"followup_multiplier": 1.5,
"importance_weight": 3,
},
"Normal": {
"ignore_penalty": -10,
"respond_boost": +10,
"followup_multiplier": 1.2,
"importance_weight": 2,
},
"Spam": {
"ignore_penalty": 0,
"respond_boost": 0,
"followup_multiplier": 1.0,
"importance_weight": 0,
},
}