Dhanushkumarps
clean hackathon submission without venv
b9f590a
Raw
History Blame Contribute Delete
5.39 kB
"""
client.py — OpenEnv client for the Customer Support Environment.
Translates between Python objects and the JSON API so that external scripts
(e.g. run_baseline.py) can interact with the running environment server
without dealing with raw HTTP.
"""
from typing import Any, Dict, Optional
try:
from openenv.core.env_client import EnvClient
from openenv.core.client_types import StepResult
except ImportError:
from typing import Generic, TypeVar
from pydantic import BaseModel
ActionType = TypeVar("ActionType")
ObsType = TypeVar("ObsType")
StateType = TypeVar("StateType")
class EnvClient(Generic[ActionType, ObsType, StateType]):
pass
class StepResult(BaseModel):
observation: Any = None
reward: float = 0.0
done: bool = False
info: dict = {}
from models import SupportAction, SupportObservation, SupportState
class SupportEnvClient(EnvClient[SupportAction, SupportObservation, SupportState]):
"""Client for the Customer Support OpenEnv.
Wraps the JSON API with typed Python objects for actions, observations,
and state.
Usage::
env = SupportEnvClient(base_url="http://localhost:8000")
obs = env.reset(task_name="easy")
result = env.step(SupportAction(message="refund", intent="classify"))
print(result.observation.reward)
"""
# ------------------------------------------------------------------ #
# Serialisation helpers
# ------------------------------------------------------------------ #
def _step_payload(self, action: SupportAction) -> Dict[str, Any]:
"""Convert a SupportAction into the JSON dict expected by POST /step.
Args:
action: The agent's action object.
Returns:
Dict with session_id, message, and intent ready for the API.
"""
return {
"session_id": self.session_id,
"message": action.message,
"intent": action.intent,
}
# ------------------------------------------------------------------ #
# Deserialisation helpers
# ------------------------------------------------------------------ #
def _parse_observation(self, payload: Dict[str, Any]) -> SupportObservation:
"""Parse a raw server response dict into a SupportObservation.
Handles missing keys gracefully by falling back to sensible defaults.
Args:
payload: The JSON dict returned by the server (usually nested
under an ``"observation"`` key).
Returns:
A fully-populated SupportObservation instance.
"""
# The server wraps the observation under an "observation" key
obs_data = payload.get("observation", payload)
return SupportObservation(
conversation=obs_data.get("conversation", []),
customer_query=obs_data.get("customer_query", ""),
task_name=obs_data.get("task_name", "easy"),
info=obs_data.get("info", None),
done=obs_data.get("done", False),
reward=obs_data.get("reward", None),
)
def _parse_result(self, payload: Dict[str, Any]) -> StepResult:
"""Parse a raw server response into a StepResult.
Extracts the observation, reward, done flag, and any extra info
from the API response.
Args:
payload: The JSON dict returned by POST /step.
Returns:
A StepResult containing the observation, reward, done, and info.
"""
observation = self._parse_observation(payload)
return StepResult(
observation=observation,
reward=observation.reward if observation.reward is not None else 0.0,
done=observation.done,
info={
"session_id": payload.get("session_id", self.session_id),
},
)
# ------------------------------------------------------------------ #
# High-level API
# ------------------------------------------------------------------ #
def reset(
self,
task_name: str = "easy",
seed: Optional[int] = None,
) -> SupportObservation:
"""Start a new episode on the server.
Sends POST /reset with the requested task tier and optional seed,
stores the returned session_id, and returns the opening observation.
Args:
task_name: Difficulty tier — ``"easy"``, ``"medium"``, or ``"hard"``.
seed: Optional RNG seed for reproducible scenario selection.
Returns:
The opening SupportObservation with the customer's first message.
"""
request_body: Dict[str, Any] = {
"task_name": task_name,
}
# Include session_id if we already have one (re-use session)
if hasattr(self, "session_id") and self.session_id:
request_body["session_id"] = self.session_id
# Include seed only if provided
if seed is not None:
request_body["seed"] = seed
# POST /reset and parse the response
response = self._post("/reset", json=request_body)
payload = response.json()
# Store the session_id for subsequent calls
self.session_id = payload.get("session_id", "")
return self._parse_observation(payload)