| """ |
| OpenEnv typed models for Customer Support Inbox environment. |
| Implements Observation, Action, Reward per OpenEnv spec. |
| """ |
| from __future__ import annotations |
|
|
| from enum import Enum |
| from typing import Any, Dict, List, Optional |
| from pydantic import BaseModel, Field |
|
|
|
|
| |
|
|
| class TicketCategory(str, Enum): |
| BILLING = "billing" |
| TECHNICAL = "technical" |
| SHIPPING = "shipping" |
| RETURNS = "returns" |
| GENERAL = "general" |
| ACCOUNT = "account" |
| COMPLAINT = "complaint" |
|
|
|
|
| class TicketPriority(str, Enum): |
| LOW = "low" |
| MEDIUM = "medium" |
| HIGH = "high" |
| URGENT = "urgent" |
|
|
|
|
| class TicketStatus(str, Enum): |
| OPEN = "open" |
| IN_PROGRESS = "in_progress" |
| WAITING_CUSTOMER = "waiting_customer" |
| ESCALATED = "escalated" |
| RESOLVED = "resolved" |
| CLOSED = "closed" |
|
|
|
|
| class ActionType(str, Enum): |
| CLASSIFY = "classify" |
| RESPOND = "respond" |
| ESCALATE = "escalate" |
| RESOLVE = "resolve" |
| REQUEST_INFO = "request_info" |
| TAG = "tag" |
| ASSIGN = "assign" |
|
|
|
|
| class SentimentLabel(str, Enum): |
| POSITIVE = "positive" |
| NEUTRAL = "neutral" |
| NEGATIVE = "negative" |
| ANGRY = "angry" |
|
|
|
|
| |
|
|
| class Message(BaseModel): |
| """A single message in a ticket conversation.""" |
| role: str = Field(..., description="'customer' or 'agent'") |
| content: str = Field(..., description="Message text") |
| timestamp: str = Field(..., description="ISO 8601 timestamp") |
| metadata: Dict[str, Any] = Field(default_factory=dict) |
|
|
|
|
| class CustomerProfile(BaseModel): |
| """Basic customer profile information.""" |
| customer_id: str |
| name: str |
| email: str |
| account_tier: str = Field(default="standard", description="standard | premium | enterprise") |
| total_orders: int = Field(default=0) |
| previous_tickets: int = Field(default=0) |
| sentiment: SentimentLabel = Field(default=SentimentLabel.NEUTRAL) |
|
|
|
|
| class TicketState(BaseModel): |
| """Full internal state of a support ticket.""" |
| ticket_id: str |
| subject: str |
| body: str |
| category: Optional[TicketCategory] = None |
| priority: Optional[TicketPriority] = None |
| status: TicketStatus = TicketStatus.OPEN |
| tags: List[str] = Field(default_factory=list) |
| assigned_to: Optional[str] = None |
| conversation: List[Message] = Field(default_factory=list) |
| customer: CustomerProfile = Field(...) |
| created_at: str = Field(...) |
| updated_at: str = Field(...) |
| sla_deadline: str = Field(...) |
| resolution_notes: Optional[str] = None |
| escalation_reason: Optional[str] = None |
| turn_count: int = Field(default=0, description="Number of agent actions taken") |
| max_turns: int = Field(default=10, description="Maximum allowed turns before episode ends") |
| task_id: str = Field(default="task1") |
| task_metadata: Dict[str, Any] = Field(default_factory=dict) |
|
|
|
|
| |
|
|
| class Observation(BaseModel): |
| """ |
| OpenEnv Observation β what the agent sees each step. |
| Contains all information needed to decide the next action. |
| """ |
| |
| ticket_id: str = Field(..., description="Unique ticket identifier") |
| subject: str = Field(..., description="Ticket subject line") |
| body: str = Field(..., description="Original customer message") |
| status: TicketStatus = Field(..., description="Current ticket status") |
| category: Optional[TicketCategory] = Field(None, description="Assigned category (None if unclassified)") |
| priority: Optional[TicketPriority] = Field(None, description="Assigned priority (None if unset)") |
| tags: List[str] = Field(default_factory=list, description="Applied tags") |
|
|
| |
| customer: CustomerProfile = Field(..., description="Customer profile info") |
|
|
| |
| conversation: List[Message] = Field(default_factory=list, description="Full message thread") |
|
|
| |
| turn_count: int = Field(..., description="Actions taken so far this episode") |
| turns_remaining: int = Field(..., description="Actions left before forced termination") |
| time_to_sla: str = Field(..., description="Human-readable time remaining on SLA") |
|
|
| |
| task_id: str = Field(..., description="Current task identifier") |
| task_description: str = Field(..., description="What the agent must accomplish") |
| task_objectives: List[str] = Field(default_factory=list, description="Specific success criteria") |
|
|
| |
| available_actions: List[str] = Field(..., description="Valid ActionTypes for current state") |
|
|
| |
| last_action_result: Optional[str] = Field(None, description="Outcome message from previous step") |
| last_reward: float = Field(default=0.0, description="Reward earned on last step") |
|
|
| |
| episode_done: bool = Field(default=False, description="Whether the episode has ended") |
| info: Dict[str, Any] = Field(default_factory=dict, description="Extra diagnostic info") |
|
|
|
|
| class Action(BaseModel): |
| """ |
| OpenEnv Action β what the agent can do each step. |
| Not all fields are required; depends on action_type. |
| """ |
| action_type: ActionType = Field(..., description="The type of action to perform") |
|
|
| |
| category: Optional[TicketCategory] = Field(None, description="Category to assign (for CLASSIFY)") |
| priority: Optional[TicketPriority] = Field(None, description="Priority to assign (for CLASSIFY)") |
|
|
| |
| response_text: Optional[str] = Field(None, description="Message to send to customer (for RESPOND/REQUEST_INFO)") |
|
|
| |
| escalation_reason: Optional[str] = Field(None, description="Reason for escalation (for ESCALATE)") |
| escalation_team: Optional[str] = Field(None, description="Team to escalate to: 'tier2' | 'billing_specialist' | 'engineering'") |
|
|
| |
| resolution_notes: Optional[str] = Field(None, description="Resolution summary (for RESOLVE)") |
| resolution_category: Optional[str] = Field(None, description="How was it resolved: 'fixed' | 'refunded' | 'explained' | 'workaround'") |
|
|
| |
| tags: Optional[List[str]] = Field(None, description="Tags to add (for TAG)") |
|
|
| |
| assigned_to: Optional[str] = Field(None, description="Department/agent to assign (for ASSIGN)") |
|
|
|
|
| class RewardBreakdown(BaseModel): |
| """Detailed breakdown of reward components.""" |
| classification_accuracy: float = Field(default=0.0, ge=0.0, le=1.0) |
| response_quality: float = Field(default=0.0, ge=0.0, le=1.0) |
| resolution_completeness: float = Field(default=0.0, ge=0.0, le=1.0) |
| sla_compliance: float = Field(default=0.0, ge=0.0, le=1.0) |
| efficiency: float = Field(default=0.0, ge=0.0, le=1.0) |
| customer_sentiment_improvement: float = Field(default=0.0, ge=0.0, le=1.0) |
| escalation_appropriateness: float = Field(default=0.0, ge=0.0, le=1.0) |
| step_penalty: float = Field(default=0.0, ge=-1.0, le=0.0) |
|
|
|
|
| class Reward(BaseModel): |
| """ |
| OpenEnv Reward β returned with each step. |
| Provides fine-grained partial credit signals. |
| """ |
| score: float = Field(..., ge=0.0, le=1.0, description="Overall step reward (0.0β1.0)") |
| cumulative_score: float = Field(default=0.0, ge=0.0, le=1.0, description="Accumulated reward this episode") |
| done: bool = Field(..., description="Whether this action ended the episode") |
| breakdown: RewardBreakdown = Field(default_factory=RewardBreakdown, description="Per-component reward details") |
| feedback: str = Field(default="", description="Human-readable reward explanation") |
| task_complete: bool = Field(default=False, description="Whether the task objective was fully met") |
|
|