meta_ai_TeamTitans / client.py
shreyashahu0143's picture
fixed task passing logic in cilent and interference
1a37b3f
Raw
History Blame Contribute Delete
7.61 kB
"""
client.py — HTTP Client (The Waiter)
=====================================
PURPOSE:
This is what inference.py (the AI agent) imports and calls.
It handles ALL HTTP communication so inference.py never has to think about URLs or JSON.
The client inherits from HTTPEnvClient (OpenEnv base class) and implements 3 methods:
1. _step_payload(action) → Convert Python action → JSON dict for POST /step
2. _parse_result(response) → Convert JSON response → EmailObservation Python object
3. _parse_state(response) → Convert JSON response → State Python object
The base class handles: sending HTTP requests, error handling, retry logic.
CONNECTS TO:
← inference.py (imports reset_env, step_env, get_state)
→ server/app.py (sends requests to /reset, /step, /state endpoints)
← models.py (EmailObservation, State, StepResponse types)
HOW IT WORKS:
inference.py calls: client.step_env(action=1`)
client.py does: POST http://localhost:7860/step {"action": 1}
server returns: {"observation": {...}, "reward": 4.2, "done": false, "info": {...}}
client.py returns: StepResponse(observation=EmailObservation(...), reward=4.2, ...)
OWNER: LLM Engineer
"""
import os
import requests
from typing import Optional
from models import EmailObservation, State, StepResponse
# Server URL — override via ENV_SERVER_URL environment variable
SERVER_URL = os.getenv("ENV_SERVER_URL", "http://localhost:7860")
# ---------------------------------------------------------------------------
# PUBLIC FUNCTIONS (what inference.py calls)
# ---------------------------------------------------------------------------
def reset_env(task_config: Optional[dict] = None, task_id: int = 1) -> EmailObservation:
"""
Start a new episode.
Args:
task_config: Optional dict to override episode settings.
Example: {"num_emails": 20, "vip_count": 5, "time_budget": 480}
If None, uses server defaults (Task 1 settings).
Returns:
EmailObservation: The first email to evaluate.
Example (inference.py):
obs = reset_env()
print(f"First email: {obs.subject} from {obs.sender}")
"""
payload = {"task_id": task_id, "config": task_config or {}}
response = _post("/reset", payload)
return _parse_observation(response)
def step_env(action: int) -> StepResponse:
"""
Take one action and get the result.
Args:
action: 0 = IGNORE, 1 = RESPOND
Returns:
StepResponse with:
.observation → next EmailObservation (what to see next)
.reward → float reward for this action
.done → True if episode is over
.info → dict with debug info (time_cost, relationship_delta, etc.)
Example (inference.py):
result = step_env(action=1)
if result.done:
print(f"Episode ended! Final reward: {result.reward}")
else:
next_email = result.observation
"""
response = _post("/step", {"action": action})
return _parse_step_response(response)
def get_state() -> State:
"""
Get the full internal state (God-mode view).
Use this for:
- Grader: to check relationship health at end of episode
- Debugging: to verify what the environment sees vs what agent sees
Returns:
State: Full environment state including all emails and relationships.
Example (grader.py):
final_state = get_state()
avg_health = sum(r.health for r in final_state.relationships.values()) / len(final_state.relationships)
"""
response = _get("/state")
return _parse_state(response)
def health_check() -> bool:
"""
Check if the server is running.
Call this before starting inference to avoid confusing errors.
Returns:
True if server is up, False otherwise.
"""
try:
response = requests.get(f"{SERVER_URL}/health", timeout=5)
return response.status_code == 200
except requests.ConnectionError:
return False
# ---------------------------------------------------------------------------
# PRIVATE HELPERS (the 3 translation methods from OpenEnv spec)
# ---------------------------------------------------------------------------
def _step_payload(action: int) -> dict:
"""
METHOD 1: Action → JSON
Convert Python action integer into the JSON payload for POST /step.
"""
return {"action": action}
def _parse_result(response_json: dict) -> EmailObservation:
"""
METHOD 2: JSON → Observation
Convert the JSON response from POST /step into an EmailObservation Python object.
"""
return _parse_observation(response_json.get("observation", response_json))
def _parse_state(response_json: dict) -> State:
"""
METHOD 3: JSON → State
Convert the JSON response from GET /state into a State Python object.
"""
# Rebuild relationship objects
relationships = {}
for sender, rel_data in response_json.get("relationships", {}).items():
from models import Relationship
relationships[sender] = Relationship(**rel_data)
# Rebuild email objects
from models import Email
inbox = [Email(**e) for e in response_json.get("inbox", [])]
return State(
inbox=inbox,
current_email_index=response_json.get("current_email_index", 0),
relationships=relationships,
current_timestep=response_json.get("current_timestep", 0),
time_budget_remaining=response_json.get("time_budget_remaining", 480),
total_time_spent=response_json.get("total_time_spent", 0),
emails_handled=response_json.get("emails_handled", 0),
)
def _parse_observation(data: dict) -> EmailObservation:
"""Helper: dict → EmailObservation"""
return EmailObservation(
email_id=data["email_id"],
sender=data["sender"],
subject=data["subject"],
body=data["body"],
sender_importance=data["sender_importance"],
email_length=data["email_length"],
relationship_score=data["relationship_score"],
time_budget_remaining=data["time_budget_remaining"],
emails_remaining=data["emails_remaining"],
)
def _parse_step_response(data: dict) -> StepResponse:
"""Helper: dict → StepResponse"""
return StepResponse(
observation=_parse_observation(data["observation"]),
reward=data["reward"],
done=data["done"],
info=data.get("info", {}),
)
def _post(endpoint: str, payload: dict) -> dict:
"""Send a POST request and return parsed JSON."""
url = f"{SERVER_URL}{endpoint}"
try:
response = requests.post(url, json=payload, timeout=30)
response.raise_for_status()
return response.json()
except requests.ConnectionError:
raise ConnectionError(
f"Cannot connect to server at {SERVER_URL}. "
"Is the server running? Start it with: uvicorn server.app:app --port 8000"
)
except requests.HTTPError as e:
raise RuntimeError(f"Server returned error: {e.response.status_code}{e.response.text}")
def _get(endpoint: str) -> dict:
"""Send a GET request and return parsed JSON."""
url = f"{SERVER_URL}{endpoint}"
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response.json()
except requests.ConnectionError:
raise ConnectionError(f"Cannot connect to server at {SERVER_URL}.")