Spaces:
Sleeping
Sleeping
File size: 8,471 Bytes
852e969 | 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | """Inference Script - Indian Traffic Signal OpenEnv
================================================
MANDATORY environment variables (injected by the validator):
API_BASE_URL The LiteLLM proxy endpoint.
API_KEY Your API key for the proxy.
MODEL_NAME The model identifier to use for inference.
STDOUT FORMAT (exact - do not deviate):
[START] task=<task_name> env=<benchmark> model=<model_name>
[STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
[END] success=<true|false> steps=<n> score=<0.000> rewards=<r1,r2,...,rn>
"""
import json
import os
import sys
from typing import List, Optional
from openai import OpenAI
from env import IndianTrafficEnv
from grader import grade_rollout
from models import TrafficAction, TrafficState
# -------------------------------------------------------------------
# MANDATORY: read from injected environment variables β no hardcoding.
# The validator checks that all LLM calls flow through API_BASE_URL.
# -------------------------------------------------------------------
API_BASE_URL: str = os.environ["API_BASE_URL"] # must be set by validator
API_KEY: str = os.environ.get("API_KEY") or os.environ.get("HF_TOKEN", "")
MODEL_NAME: str = os.environ.get("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct")
BENCHMARK: str = os.environ.get("BENCHMARK", "indian-traffic-signal-openenv")
SUCCESS_SCORE_THRESHOLD = 0.5
TASKS = ["single_intersection", "rush_hour", "emergency_priority"]
VALID_ACTIONS = [a.value for a in TrafficAction]
# Single shared client β always routed through the injected proxy URL.
_client = OpenAI(
base_url=API_BASE_URL,
api_key=API_KEY,
timeout=30.0, # generous timeout for proxy round-trips
max_retries=1,
)
# ---------------------------------------------------------------------------
# Logging helpers β exact format required by the validator
# ---------------------------------------------------------------------------
def log_start(task: str, env: str, model: str) -> None:
print(f"[START] task={task} env={env} model={model}", flush=True)
def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
error_val = error if error else "null"
print(
f"[STEP] step={step} action={action} reward={reward:.2f} "
f"done={str(done).lower()} error={error_val}",
flush=True,
)
def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
print(
f"[END] success={str(success).lower()} steps={steps} score={score:.3f} rewards={rewards_str}",
flush=True,
)
# ---------------------------------------------------------------------------
# Fallback policy β used only if the LLM call itself raises an exception
# ---------------------------------------------------------------------------
def _fallback_action(task_name: str, state: TrafficState) -> str:
"""Deterministic fallback β mirrors the baseline policy."""
if state.emergency_vehicle.present:
return TrafficAction.EMERGENCY_OVERRIDE.value
if state.pedestrian_count >= 16 and state.pedestrian_wait_time > 18:
return TrafficAction.PEDESTRIAN_CROSS.value
if state.time_since_last_phase_switch < 3 and state.current_signal_phase in (
TrafficAction.NS_GREEN,
TrafficAction.EW_GREEN,
TrafficAction.LEFT_PRIORITY,
):
return TrafficAction.EXTEND_GREEN.value
ns = state.lane_queues["N"].total + state.lane_queues["S"].total
ew = state.lane_queues["E"].total + state.lane_queues["W"].total
if task_name == "emergency_priority":
if abs(ns - ew) >= 10:
return TrafficAction.NS_GREEN.value if ns > ew else TrafficAction.EW_GREEN.value
return TrafficAction.NS_GREEN.value
if abs(ns - ew) >= 12:
return TrafficAction.NS_GREEN.value if ns > ew else TrafficAction.EW_GREEN.value
cycle = (state.tick // 8) % 4
return [
TrafficAction.NS_GREEN.value,
TrafficAction.EW_GREEN.value,
TrafficAction.LEFT_PRIORITY.value,
TrafficAction.PEDESTRIAN_CROSS.value,
][cycle]
# ---------------------------------------------------------------------------
# LLM call β ALWAYS goes through the injected proxy (API_BASE_URL / _client)
# ---------------------------------------------------------------------------
def get_action_from_llm(state: TrafficState, task_name: str) -> str:
"""Call the LLM via the injected proxy to choose a signal action."""
preferred = _fallback_action(task_name, state)
state_summary = {
"tick": state.tick,
"current_phase": state.current_signal_phase.value,
"time_since_switch": state.time_since_last_phase_switch,
"emergency": state.emergency_vehicle.model_dump(),
"pedestrian_count": state.pedestrian_count,
"pedestrian_wait": round(state.pedestrian_wait_time, 2),
"rain_level": round(state.rain_level, 3),
"lane_queues": {
lane: {"total": q.total, **q.model_dump()}
for lane, q in state.lane_queues.items()
},
}
system_prompt = (
"You are an AI traffic controller managing an Indian urban intersection. "
f"Task: {task_name}. "
f"Choose exactly one action from: {', '.join(VALID_ACTIONS)}. "
"Analyse the state and pick the best signal phase. "
f"Suggested action: {preferred}. "
"Reply with only the action name β no explanation, no punctuation."
)
user_prompt = f"Intersection state: {json.dumps(state_summary)}"
# This call MUST reach the proxy β do not wrap in a silent broad except.
response = _client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=0.0,
max_tokens=16,
stream=False,
)
action = (response.choices[0].message.content or "").strip().upper()
return action if action in VALID_ACTIONS else preferred
# ---------------------------------------------------------------------------
# Main inference loop
# ---------------------------------------------------------------------------
def run_inference() -> None:
if not API_KEY:
print("Warning: API_KEY / HF_TOKEN not set.", file=sys.stderr, flush=True)
for task_name in TASKS:
env = IndianTrafficEnv(task_id=task_name)
env.reset(seed=42, task_id=task_name)
rewards: List[float] = []
steps_taken = 0
success = False
score = 0.001
done = False
log_start(task=task_name, env=BENCHMARK, model=MODEL_NAME)
try:
step = 1
while not done:
state = env.get_state()
error: Optional[str] = None
try:
action_str = get_action_from_llm(state, task_name)
except Exception as exc:
# LLM call failed β log it, use fallback, keep running
action_str = _fallback_action(task_name, state)
error = f"llm_error:{type(exc).__name__}"
try:
traffic_action = TrafficAction(action_str)
except ValueError:
traffic_action = TrafficAction.ALL_RED
action_str = TrafficAction.ALL_RED.value
try:
_, reward, done, _ = env.step(traffic_action)
except Exception as exc:
reward = 0.0
done = True
error = str(exc)
rewards.append(reward)
steps_taken = step
log_step(step=step, action=action_str, reward=reward, done=done, error=error)
step += 1
grader_result = grade_rollout(task_id=task_name, seed=42)
score = float(grader_result.score)
success = score >= SUCCESS_SCORE_THRESHOLD
except Exception as exc:
print(f"Fatal error in task {task_name}: {exc}", file=sys.stderr, flush=True)
success = False
score = 0.001
finally:
log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
if __name__ == "__main__":
run_inference()
|