ragavrida commited on
Commit
caf52d5
·
1 Parent(s): d39bc9a

fix: add pyrightconfig, update pyproject packages, polish inference & models

Browse files
Dockerfile CHANGED
@@ -3,14 +3,17 @@ FROM python:3.11-slim
3
  # HuggingFace Spaces expects port 7860
4
  WORKDIR /app
5
 
6
- # Install dependencies (openenv-core brings FastAPI, uvicorn, pydantic)
 
 
 
7
  COPY requirements.txt .
8
- RUN pip install --no-cache-dir -r requirements.txt
9
 
10
  # Copy environment code
11
  COPY . .
12
 
13
  EXPOSE 7860
14
 
15
- # Use uvicorn to serve the OpenEnv app on HF Spaces port
16
- CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
 
3
  # HuggingFace Spaces expects port 7860
4
  WORKDIR /app
5
 
6
+ # Upgrade pip first for better retry/timeout handling
7
+ RUN pip install --upgrade pip
8
+
9
+ # Install dependencies with retries and longer timeout for slow networks
10
  COPY requirements.txt .
11
+ RUN pip install --no-cache-dir --timeout=300 --retries=5 -r requirements.txt
12
 
13
  # Copy environment code
14
  COPY . .
15
 
16
  EXPOSE 7860
17
 
18
+ # Use python -m uvicorn to ensure it's found regardless of PATH
19
+ CMD ["python", "-m", "uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
TROUBLESHOOTING.md ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Inference.py Troubleshooting Guide
2
+
3
+ ## What Was Fixed
4
+
5
+ The original `inference.py` had an unhandled exception because it lacked proper error handling around:
6
+
7
+ 1. **Environment connection failures** - Docker image or server URL not reachable
8
+ 2. **Missing API credentials** - No validation of required environment variables
9
+ 3. **Network/parsing errors** - LLM calls and JSON parsing could fail silently
10
+ 4. **Environment step failures** - No recovery when env.step() fails
11
+
12
+ ## Changes Made
13
+
14
+ ### 1. Main Function Error Handling
15
+ - Added validation for required `API_KEY`/`HF_TOKEN`
16
+ - Wrapped environment connection in try-except with debug logging
17
+ - Added per-task error handling to continue even if one task fails
18
+ - Added traceback printing for debugging
19
+ - Ensured proper cleanup in finally block
20
+
21
+ ### 2. Run Task Error Handling
22
+ - Wrapped LLM calls in try-except
23
+ - Added error handling for env.step() failures
24
+ - Added traceback printing for task-level errors
25
+ - Ensured log_end() is always called (required by validator)
26
+
27
+ ## How to Test Locally
28
+
29
+ ### Prerequisites
30
+ ```bash
31
+ # Install dependencies
32
+ pip install -r requirements.txt
33
+
34
+ # Set required environment variables
35
+ export HF_TOKEN="your_token_here"
36
+ export MODEL_NAME="openai/gpt-4o-mini"
37
+ export API_BASE_URL="https://router.huggingface.co/v1"
38
+ ```
39
+
40
+ ### Option 1: Test with Docker Image
41
+ ```bash
42
+ export IMAGE_NAME="your-docker-image-name"
43
+ python inference.py
44
+ ```
45
+
46
+ ### Option 2: Test with Running Server
47
+ ```bash
48
+ # Start the server in one terminal
49
+ python -m server.app
50
+
51
+ # In another terminal, run inference
52
+ export SPACE_URL="http://localhost:8000"
53
+ python inference.py
54
+ ```
55
+
56
+ ### Option 3: Use Test Script
57
+ ```bash
58
+ ./test_inference.sh
59
+ ```
60
+
61
+ ## Common Issues & Solutions
62
+
63
+ ### Issue 1: "Failed to connect to environment"
64
+ **Cause:** Docker image not available or server not running
65
+
66
+ **Solutions:**
67
+ - If using Docker: Ensure image exists with `docker images`
68
+ - If using server: Start server first with `python -m server.app`
69
+ - Check network connectivity to SPACE_URL
70
+
71
+ ### Issue 2: "API_KEY or HF_TOKEN environment variable is required"
72
+ **Cause:** Missing authentication credentials
73
+
74
+ **Solution:**
75
+ ```bash
76
+ export HF_TOKEN="your_huggingface_token"
77
+ # or
78
+ export API_KEY="your_api_key"
79
+ ```
80
+
81
+ ### Issue 3: LLM calls timing out
82
+ **Cause:** Network issues or API rate limits
83
+
84
+ **Solution:**
85
+ - The script now retries up to 3 times with exponential backoff
86
+ - Check your API quota and rate limits
87
+ - Verify API_BASE_URL is correct
88
+
89
+ ### Issue 4: JSON parsing errors
90
+ **Cause:** LLM returns malformed JSON
91
+
92
+ **Solution:**
93
+ - The script now has robust JSON parsing with fallbacks
94
+ - If parsing fails, it uses default actions
95
+ - Check stderr for [DEBUG] messages showing what the LLM returned
96
+
97
+ ## Validation Checklist
98
+
99
+ Before submitting, verify:
100
+
101
+ - [ ] All required environment variables are set
102
+ - [ ] Dependencies are installed (`pip install -r requirements.txt`)
103
+ - [ ] Script runs without exceptions locally
104
+ - [ ] Output contains [START], [STEP], and [END] lines
105
+ - [ ] Environment container is reachable (Docker or server)
106
+ - [ ] No unhandled exceptions in stderr
107
+
108
+ ## Expected Output Format
109
+
110
+ ```
111
+ [START] task=severity-labeling env=code-review-env model=openai/gpt-4o-mini
112
+ [STEP] step=1 action=label_severity:medium reward=0.50 done=false error=null
113
+ [STEP] step=2 action=label_severity:high reward=1.00 done=true error=null
114
+ [END] success=true steps=2 score=0.750 rewards=0.50,1.00
115
+ ```
116
+
117
+ ## Debug Mode
118
+
119
+ To see detailed debug output:
120
+ ```bash
121
+ python inference.py 2>&1 | tee inference.log
122
+ ```
123
+
124
+ This captures both stdout (required format) and stderr (debug messages) to a file.
client.py CHANGED
@@ -39,10 +39,13 @@ class CodeReviewEnv(EnvClient[CodeReviewAction, CodeReviewObservation, CodeRevie
39
  """Parse the server's JSON response into a typed StepResult."""
40
  obs_data = payload.get("observation", payload.get("data", payload))
41
  observation = CodeReviewObservation(**obs_data)
 
 
 
42
  return StepResult(
43
  observation=observation,
44
- reward=observation.reward,
45
- done=observation.done,
46
  )
47
 
48
  def _parse_state(self, payload: Dict[str, Any]) -> CodeReviewState:
 
39
  """Parse the server's JSON response into a typed StepResult."""
40
  obs_data = payload.get("observation", payload.get("data", payload))
41
  observation = CodeReviewObservation(**obs_data)
42
+ # reward and done live at the top level of the payload, not inside observation
43
+ reward = payload.get("reward", getattr(observation, "reward", 0.0))
44
+ done = payload.get("done", getattr(observation, "done", False))
45
  return StepResult(
46
  observation=observation,
47
+ reward=reward,
48
+ done=done,
49
  )
50
 
51
  def _parse_state(self, payload: Dict[str, Any]) -> CodeReviewState:
inference.py CHANGED
@@ -41,6 +41,7 @@ import json
41
  import os
42
  import re
43
  import textwrap
 
44
  from typing import Any, Dict, List, Optional
45
 
46
  from openai import OpenAI
@@ -48,10 +49,33 @@ from openai import OpenAI
48
  from models import CodeReviewAction, CodeReviewObservation, CodeReviewState
49
  from client import CodeReviewEnv
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  # ─── Configuration ────────────────────────────────────────────────────────────
52
 
53
  IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME") or os.getenv("IMAGE_NAME") # If using from_docker_image()
54
- API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
55
 
56
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
57
  MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-4o-mini")
@@ -60,6 +84,48 @@ TEMPERATURE = 0.0
60
  MAX_TOKENS = 300
61
  SUCCESS_SCORE_THRESHOLD = 0.3
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  # ─── Structured Logging (exact spec format) ──────────────────────────────────
65
 
@@ -88,24 +154,35 @@ def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> No
88
 
89
  def call_llm(client: OpenAI, system_prompt: str, user_prompt: str, max_retries: int = 3) -> str:
90
  """Call the LLM using OpenAI Client with retry. Returns response text."""
 
 
 
 
91
  for attempt in range(max_retries):
92
- try:
93
- completion = client.chat.completions.create(
94
- model=MODEL_NAME,
95
- messages=[
96
- {"role": "system", "content": system_prompt},
97
- {"role": "user", "content": user_prompt},
98
- ],
99
- temperature=TEMPERATURE,
100
- max_tokens=MAX_TOKENS,
101
- stream=False,
102
- )
103
- return (completion.choices[0].message.content or "").strip()
104
- except Exception as exc:
105
- print(f"[DEBUG] Attempt {attempt+1}/{max_retries} failed: {exc}", file=sys.stderr, flush=True)
106
- if attempt < max_retries - 1:
107
- import time
108
- time.sleep(2 ** attempt)
 
 
 
 
 
 
 
109
  return ""
110
 
111
 
@@ -263,7 +340,7 @@ async def run_task(env: CodeReviewEnv, llm_client: OpenAI, task: str) -> float:
263
  log_start(task=config["task_name"], env=BENCHMARK, model=MODEL_NAME)
264
 
265
  try:
266
- result = await env.reset(seed=42)
267
  obs = result.observation
268
 
269
  for step in range(1, config["max_steps"] + 1):
@@ -271,9 +348,13 @@ async def run_task(env: CodeReviewEnv, llm_client: OpenAI, task: str) -> float:
271
  break
272
 
273
  # Get LLM response
274
- user_prompt = config["format_obs"](obs)
275
- response = call_llm(llm_client, config["system_prompt"], user_prompt)
276
- parsed = parse_json_response(response)
 
 
 
 
277
 
278
  # Build action
279
  if parsed and parsed.get("action_type"):
@@ -284,16 +365,23 @@ async def run_task(env: CodeReviewEnv, llm_client: OpenAI, task: str) -> float:
284
  # Ensure valid action fields
285
  try:
286
  action = CodeReviewAction(**action_dict)
287
- except Exception:
 
288
  action_dict = config["default_action"](obs)
289
  action = CodeReviewAction(**action_dict)
290
 
291
  # Step
292
- result = await env.step(action)
293
- obs = result.observation
294
- reward = result.reward or 0.0
295
- done = result.done
296
- error = None
 
 
 
 
 
 
297
 
298
  rewards.append(reward)
299
  steps_taken = step
@@ -317,6 +405,8 @@ async def run_task(env: CodeReviewEnv, llm_client: OpenAI, task: str) -> float:
317
 
318
  except Exception as exc:
319
  print(f"[DEBUG] Task {task} error: {exc}", file=sys.stderr, flush=True)
 
 
320
 
321
  finally:
322
  log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
@@ -326,32 +416,52 @@ async def run_task(env: CodeReviewEnv, llm_client: OpenAI, task: str) -> float:
326
 
327
  # ─── Main ────────────────────────────────────────────────────────────────────
328
 
329
- async def main() -> None:
330
- llm_client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
331
-
332
- # Connect to environment via Docker image or HF Space
333
- if IMAGE_NAME:
334
- env = await CodeReviewEnv.from_docker_image(IMAGE_NAME)
335
- else:
336
- # Fallback: connect to running server
337
- space_url = os.getenv("SPACE_URL", "https://ragavrida-code-review-env.hf.space")
338
- env = CodeReviewEnv(base_url=space_url)
339
-
340
  try:
 
 
 
 
 
 
341
  scores = {}
 
 
342
  for task in ["easy", "medium", "hard"]:
343
- score = await run_task(env, llm_client, task)
344
- scores[task] = score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
 
346
  composite = sum(scores.values()) / len(scores)
347
  print(f"\n[SUMMARY] composite={composite:.3f} easy={scores['easy']:.3f} medium={scores['medium']:.3f} hard={scores['hard']:.3f}", file=sys.stderr, flush=True)
348
 
349
- finally:
350
- try:
351
- await env.close()
352
- except Exception as e:
353
- print(f"[DEBUG] env.close() error: {e}", file=sys.stderr, flush=True)
 
354
 
355
 
356
  if __name__ == "__main__":
357
- asyncio.run(main())
 
41
  import os
42
  import re
43
  import textwrap
44
+ import inspect
45
  from typing import Any, Dict, List, Optional
46
 
47
  from openai import OpenAI
 
49
  from models import CodeReviewAction, CodeReviewObservation, CodeReviewState
50
  from client import CodeReviewEnv
51
 
52
+ # ─── .env loading (no extra dependency) ───────────────────────────────────────
53
+
54
+ def _load_dotenv(dotenv_path: str) -> None:
55
+ """Load KEY=VALUE pairs from a .env file into os.environ (without overriding)."""
56
+ try:
57
+ with open(dotenv_path, "r", encoding="utf-8") as f:
58
+ for raw_line in f:
59
+ line = raw_line.strip()
60
+ if not line or line.startswith("#"):
61
+ continue
62
+ if "=" not in line:
63
+ continue
64
+ key, value = line.split("=", 1)
65
+ key = key.strip()
66
+ value = value.strip().strip("\"'") # tolerate simple quoting
67
+ if key:
68
+ os.environ.setdefault(key, value)
69
+ except FileNotFoundError:
70
+ return
71
+
72
+
73
+ _load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
74
+
75
  # ─── Configuration ────────────────────────────────────────────────────────────
76
 
77
  IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME") or os.getenv("IMAGE_NAME") # If using from_docker_image()
78
+ API_KEY = os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY")
79
 
80
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
81
  MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-4o-mini")
 
84
  MAX_TOKENS = 300
85
  SUCCESS_SCORE_THRESHOLD = 0.3
86
 
87
+ def _maybe_disable_proxies() -> None:
88
+ """
89
+ OpenEnv's websocket client will honor HTTP(S)/SOCKS proxy env vars.
90
+ For local runs, misconfigured proxies are a common source of connection failure.
91
+ Set USE_PROXY=1 to keep proxy env vars enabled.
92
+ """
93
+ if os.getenv("USE_PROXY", "").strip().lower() in {"1", "true", "yes", "on"}:
94
+ return
95
+ for k in (
96
+ "ALL_PROXY",
97
+ "HTTPS_PROXY",
98
+ "HTTP_PROXY",
99
+ "SOCKS_PROXY",
100
+ "SOCKS5_PROXY",
101
+ "all_proxy",
102
+ "https_proxy",
103
+ "http_proxy",
104
+ "socks_proxy",
105
+ "socks5_proxy",
106
+ ):
107
+ os.environ.pop(k, None)
108
+
109
+ # Ensure local proxy bypass includes the default HF Space host.
110
+ host = os.getenv("SPACE_URL", "https://ragavrida-code-review-env.hf.space")
111
+ try:
112
+ host = host.split("://", 1)[1].split("/", 1)[0]
113
+ except Exception:
114
+ host = "ragavrida-code-review-env.hf.space"
115
+ for k in ("NO_PROXY", "no_proxy"):
116
+ cur = os.getenv(k, "")
117
+ parts = [p.strip() for p in cur.split(",") if p.strip()]
118
+ if host not in parts:
119
+ parts.append(host)
120
+ os.environ[k] = ",".join(parts)
121
+
122
+
123
+ async def _maybe_await(value: Any) -> Any:
124
+ """Await value if it's awaitable, else return it."""
125
+ if inspect.isawaitable(value):
126
+ return await value
127
+ return value
128
+
129
 
130
  # ─── Structured Logging (exact spec format) ──────────────────────────────────
131
 
 
154
 
155
  def call_llm(client: OpenAI, system_prompt: str, user_prompt: str, max_retries: int = 3) -> str:
156
  """Call the LLM using OpenAI Client with retry. Returns response text."""
157
+ model_candidates = [MODEL_NAME]
158
+ if "/" in MODEL_NAME:
159
+ model_candidates.append(MODEL_NAME.split("/", 1)[1])
160
+
161
  for attempt in range(max_retries):
162
+ for model in model_candidates:
163
+ try:
164
+ completion = client.chat.completions.create(
165
+ model=model,
166
+ messages=[
167
+ {"role": "system", "content": system_prompt},
168
+ {"role": "user", "content": user_prompt},
169
+ ],
170
+ temperature=TEMPERATURE,
171
+ max_tokens=MAX_TOKENS,
172
+ stream=False,
173
+ )
174
+ return (completion.choices[0].message.content or "").strip()
175
+ except Exception as exc:
176
+ print(
177
+ f"[DEBUG] Attempt {attempt+1}/{max_retries} failed (model={model}): {exc}",
178
+ file=sys.stderr,
179
+ flush=True,
180
+ )
181
+ # Try next candidate model (if any) before sleeping/retrying.
182
+ continue
183
+ if attempt < max_retries - 1:
184
+ import time
185
+ time.sleep(2 ** attempt)
186
  return ""
187
 
188
 
 
340
  log_start(task=config["task_name"], env=BENCHMARK, model=MODEL_NAME)
341
 
342
  try:
343
+ result = await _maybe_await(env.reset(seed=42))
344
  obs = result.observation
345
 
346
  for step in range(1, config["max_steps"] + 1):
 
348
  break
349
 
350
  # Get LLM response
351
+ try:
352
+ user_prompt = config["format_obs"](obs)
353
+ response = call_llm(llm_client, config["system_prompt"], user_prompt)
354
+ parsed = parse_json_response(response)
355
+ except Exception as e:
356
+ print(f"[DEBUG] LLM call failed at step {step}: {e}", file=sys.stderr, flush=True)
357
+ parsed = None
358
 
359
  # Build action
360
  if parsed and parsed.get("action_type"):
 
365
  # Ensure valid action fields
366
  try:
367
  action = CodeReviewAction(**action_dict)
368
+ except Exception as e:
369
+ print(f"[DEBUG] Action validation failed: {e}", file=sys.stderr, flush=True)
370
  action_dict = config["default_action"](obs)
371
  action = CodeReviewAction(**action_dict)
372
 
373
  # Step
374
+ try:
375
+ result = await _maybe_await(env.step(action))
376
+ obs = result.observation
377
+ reward = result.reward or 0.0
378
+ done = result.done
379
+ error = None
380
+ except Exception as e:
381
+ print(f"[DEBUG] env.step() failed: {e}", file=sys.stderr, flush=True)
382
+ reward = 0.0
383
+ done = True
384
+ error = str(e)
385
 
386
  rewards.append(reward)
387
  steps_taken = step
 
405
 
406
  except Exception as exc:
407
  print(f"[DEBUG] Task {task} error: {exc}", file=sys.stderr, flush=True)
408
+ import traceback
409
+ traceback.print_exc(file=sys.stderr)
410
 
411
  finally:
412
  log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
 
416
 
417
  # ─── Main ────────────────────────────────────────────────────────────────────
418
 
419
+ async def main() -> int:
 
 
 
 
 
 
 
 
 
 
420
  try:
421
+ # Initialize LLM client
422
+ if not API_KEY:
423
+ raise ValueError("HF_TOKEN or OPENAI_API_KEY (or API_KEY) environment variable is required")
424
+
425
+ llm_client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
426
+
427
  scores = {}
428
+ _maybe_disable_proxies()
429
+ space_url = os.getenv("SPACE_URL", "https://ragavrida-code-review-env.hf.space")
430
  for task in ["easy", "medium", "hard"]:
431
+ env = None
432
+ try:
433
+ # Fresh env per task to avoid reusing a closed ws connection.
434
+ if IMAGE_NAME:
435
+ print(f"[DEBUG] Connecting to Docker image: {IMAGE_NAME}", file=sys.stderr, flush=True)
436
+ env = await CodeReviewEnv.from_docker_image(IMAGE_NAME)
437
+ else:
438
+ print(f"[DEBUG] Connecting to server: {space_url}", file=sys.stderr, flush=True)
439
+ env = CodeReviewEnv(base_url=space_url)
440
+
441
+ score = await run_task(env, llm_client, task)
442
+ scores[task] = score
443
+ except Exception as e:
444
+ print(f"[ERROR] Task {task} failed: {e}", file=sys.stderr, flush=True)
445
+ scores[task] = 0.0
446
+ finally:
447
+ if env is not None:
448
+ try:
449
+ close_result = env.close()
450
+ if inspect.isawaitable(close_result):
451
+ await close_result
452
+ except Exception as e:
453
+ print(f"[DEBUG] env.close() error: {e}", file=sys.stderr, flush=True)
454
 
455
  composite = sum(scores.values()) / len(scores)
456
  print(f"\n[SUMMARY] composite={composite:.3f} easy={scores['easy']:.3f} medium={scores['medium']:.3f} hard={scores['hard']:.3f}", file=sys.stderr, flush=True)
457
 
458
+ except Exception as e:
459
+ print(f"[ERROR] Main execution failed: {e}", file=sys.stderr, flush=True)
460
+ import traceback
461
+ traceback.print_exc(file=sys.stderr)
462
+ return 1
463
+ return 0
464
 
465
 
466
  if __name__ == "__main__":
467
+ raise SystemExit(asyncio.run(main()))
models.py CHANGED
@@ -58,7 +58,7 @@ class CodeReviewObservation(Observation):
58
  Adds code-review-specific fields.
59
  """
60
 
61
- model_config = ConfigDict(extra="forbid")
62
 
63
  pr_id: str = Field(default="", description="Pull request identifier")
64
  title: str = Field(default="", description="PR title")
 
58
  Adds code-review-specific fields.
59
  """
60
 
61
+ model_config = ConfigDict(extra="allow")
62
 
63
  pr_id: str = Field(default="", description="Pull request identifier")
64
  title: str = Field(default="", description="PR title")
pyproject.toml CHANGED
@@ -7,11 +7,11 @@ name = "code-review-env"
7
  version = "1.0.0"
8
  description = "OpenEnv-compliant RL environment for software code review"
9
  readme = "README.md"
10
- requires-python = ">=3.10"
11
  license = {text = "BSD-3-Clause"}
12
 
13
  dependencies = [
14
- "openenv-core>=0.2.0",
15
  "pydantic>=2.0",
16
  "pyyaml>=6.0",
17
  "python-ulid>=2.0",
@@ -43,6 +43,9 @@ include = [
43
  "analysis*",
44
  "world_model*",
45
  "server*",
 
 
 
46
  ]
47
 
48
  [tool.pytest.ini_options]
 
7
  version = "1.0.0"
8
  description = "OpenEnv-compliant RL environment for software code review"
9
  readme = "README.md"
10
+ requires-python = ">=3.10,<3.14"
11
  license = {text = "BSD-3-Clause"}
12
 
13
  dependencies = [
14
+ "openenv>=0.1.13",
15
  "pydantic>=2.0",
16
  "pyyaml>=6.0",
17
  "python-ulid>=2.0",
 
43
  "analysis*",
44
  "world_model*",
45
  "server*",
46
+ "models*",
47
+ "client*",
48
+ "baseline*",
49
  ]
50
 
51
  [tool.pytest.ini_options]
pyrightconfig.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "executionEnvironments": [
3
+ {
4
+ "root": ".",
5
+ "extraPaths": ["."]
6
+ }
7
+ ],
8
+ "venvPath": ".",
9
+ "venv": ".venv"
10
+ }
requirements.txt CHANGED
@@ -6,3 +6,6 @@ scipy>=1.10
6
  numpy>=1.24
7
  openai>=1.0
8
  websockets>=12.0
 
 
 
 
6
  numpy>=1.24
7
  openai>=1.0
8
  websockets>=12.0
9
+ python-socks>=2.0
10
+ uvicorn>=0.27.0
11
+ fastapi>=0.109.0
scripts/validate-submission.sh ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ #
3
+ # validate-submission.sh — OpenEnv Submission Validator
4
+ #
5
+ # Checks that your HF Space is live, Docker image builds, and openenv validate passes.
6
+ #
7
+ # Prerequisites:
8
+ # - Docker: https://docs.docker.com/get-docker/
9
+ # - openenv-core: pip install openenv-core
10
+ # - curl (usually pre-installed)
11
+ #
12
+ # Run:
13
+ # curl -fsSL https://raw.githubusercontent.com/<owner>/<repo>/main/scripts/validate-submission.sh | bash -s -- <ping_url> [repo_dir]
14
+ #
15
+ # Or download and run locally:
16
+ # chmod +x validate-submission.sh
17
+ # ./validate-submission.sh <ping_url> [repo_dir]
18
+ #
19
+ # Arguments:
20
+ # ping_url Your HuggingFace Space URL (e.g. https://your-space.hf.space)
21
+ # repo_dir Path to your repo (default: current directory)
22
+ #
23
+ # Examples:
24
+ # ./validate-submission.sh https://my-team.hf.space
25
+ # ./validate-submission.sh https://my-team.hf.space ./my-repo
26
+ #
27
+
28
+ set -uo pipefail
29
+
30
+ DOCKER_BUILD_TIMEOUT=600
31
+ if [ -t 1 ]; then
32
+ RED='\033[0;31m'
33
+ GREEN='\033[0;32m'
34
+ YELLOW='\033[1;33m'
35
+ BOLD='\033[1m'
36
+ NC='\033[0m'
37
+ else
38
+ RED='' GREEN='' YELLOW='' BOLD='' NC=''
39
+ fi
40
+
41
+ run_with_timeout() {
42
+ local secs="$1"; shift
43
+ if command -v timeout &>/dev/null; then
44
+ timeout "$secs" "$@"
45
+ elif command -v gtimeout &>/dev/null; then
46
+ gtimeout "$secs" "$@"
47
+ else
48
+ "$@" &
49
+ local pid=$!
50
+ ( sleep "$secs" && kill "$pid" 2>/dev/null ) &
51
+ local watcher=$!
52
+ wait "$pid" 2>/dev/null
53
+ local rc=$?
54
+ kill "$watcher" 2>/dev/null
55
+ wait "$watcher" 2>/dev/null
56
+ return $rc
57
+ fi
58
+ }
59
+
60
+ portable_mktemp() {
61
+ local prefix="${1:-validate}"
62
+ mktemp "${TMPDIR:-/tmp}/${prefix}-XXXXXX" 2>/dev/null || mktemp
63
+ }
64
+
65
+ CLEANUP_FILES=()
66
+ cleanup() { rm -f "${CLEANUP_FILES[@]+"${CLEANUP_FILES[@]}"}"; }
67
+ trap cleanup EXIT
68
+
69
+ PING_URL="${1:-}"
70
+ REPO_DIR="${2:-.}"
71
+
72
+ if [ -z "$PING_URL" ]; then
73
+ printf "Usage: %s <ping_url> [repo_dir]\n" "$0"
74
+ printf "\n"
75
+ printf " ping_url Your HuggingFace Space URL (e.g. https://your-space.hf.space)\n"
76
+ printf " repo_dir Path to your repo (default: current directory)\n"
77
+ exit 1
78
+ fi
79
+
80
+ if ! REPO_DIR="$(cd "$REPO_DIR" 2>/dev/null && pwd)"; then
81
+ printf "Error: directory '%s' not found\n" "${2:-.}"
82
+ exit 1
83
+ fi
84
+ PING_URL="${PING_URL%/}"
85
+ export PING_URL
86
+ PASS=0
87
+
88
+ log() { printf "[%s] %b\n" "$(date -u +%H:%M:%S)" "$*"; }
89
+ pass() { log "${GREEN}PASSED${NC} -- $1"; PASS=$((PASS + 1)); }
90
+ fail() { log "${RED}FAILED${NC} -- $1"; }
91
+ hint() { printf " ${YELLOW}Hint:${NC} %b\n" "$1"; }
92
+ stop_at() {
93
+ printf "\n"
94
+ printf "${RED}${BOLD}Validation stopped at %s.${NC} Fix the above before continuing.\n" "$1"
95
+ exit 1
96
+ }
97
+
98
+ printf "\n"
99
+ printf "${BOLD}========================================${NC}\n"
100
+ printf "${BOLD} OpenEnv Submission Validator${NC}\n"
101
+ printf "${BOLD}========================================${NC}\n"
102
+ log "Repo: $REPO_DIR"
103
+ log "Ping URL: $PING_URL"
104
+ printf "\n"
105
+
106
+ log "${BOLD}Step 1/3: Pinging HF Space${NC} ($PING_URL/reset) ..."
107
+
108
+ CURL_OUTPUT=$(portable_mktemp "validate-curl")
109
+ CLEANUP_FILES+=("$CURL_OUTPUT")
110
+ HTTP_CODE=$(curl -s -o "$CURL_OUTPUT" -w "%{http_code}" -X POST \
111
+ -H "Content-Type: application/json" -d '{}' \
112
+ "$PING_URL/reset" --max-time 30 2>"$CURL_OUTPUT" || printf "000")
113
+
114
+ if [ "$HTTP_CODE" = "200" ]; then
115
+ pass "HF Space is live and responds to /reset"
116
+ elif [ "$HTTP_CODE" = "000" ]; then
117
+ fail "HF Space not reachable (connection failed or timed out)"
118
+ hint "Check your network connection and that the Space is running."
119
+ hint "Try: curl -s -o /dev/null -w '%%{http_code}' -X POST $PING_URL/reset"
120
+ stop_at "Step 1"
121
+ else
122
+ fail "HF Space /reset returned HTTP $HTTP_CODE (expected 200)"
123
+ hint "Make sure your Space is running and the URL is correct."
124
+ hint "Try opening $PING_URL in your browser first."
125
+ stop_at "Step 1"
126
+ fi
127
+
128
+ log "${BOLD}Step 2/3: Running docker build${NC} ..."
129
+
130
+ if ! command -v docker &>/dev/null; then
131
+ fail "docker command not found"
132
+ hint "Install Docker: https://docs.docker.com/get-docker/"
133
+ stop_at "Step 2"
134
+ fi
135
+
136
+ if [ -f "$REPO_DIR/Dockerfile" ]; then
137
+ DOCKER_CONTEXT="$REPO_DIR"
138
+ elif [ -f "$REPO_DIR/server/Dockerfile" ]; then
139
+ DOCKER_CONTEXT="$REPO_DIR/server"
140
+ else
141
+ fail "No Dockerfile found in repo root or server/ directory"
142
+ stop_at "Step 2"
143
+ fi
144
+
145
+ log " Found Dockerfile in $DOCKER_CONTEXT"
146
+
147
+ BUILD_OK=false
148
+ BUILD_OUTPUT=$(run_with_timeout "$DOCKER_BUILD_TIMEOUT" docker build "$DOCKER_CONTEXT" 2>&1) && BUILD_OK=true
149
+
150
+ if [ "$BUILD_OK" = true ]; then
151
+ pass "Docker build succeeded"
152
+ else
153
+ fail "Docker build failed (timeout=${DOCKER_BUILD_TIMEOUT}s)"
154
+ printf "%s\n" "$BUILD_OUTPUT" | tail -20
155
+ stop_at "Step 2"
156
+ fi
157
+
158
+ log "${BOLD}Step 3/3: Running openenv validate${NC} ..."
159
+
160
+ if ! command -v openenv &>/dev/null; then
161
+ fail "openenv command not found"
162
+ hint "Install it: pip install openenv-core"
163
+ stop_at "Step 3"
164
+ fi
165
+
166
+ VALIDATE_OK=false
167
+ VALIDATE_OUTPUT=$(cd "$REPO_DIR" && openenv validate 2>&1) && VALIDATE_OK=true
168
+
169
+ if [ "$VALIDATE_OK" = true ]; then
170
+ pass "openenv validate passed"
171
+ [ -n "$VALIDATE_OUTPUT" ] && log " $VALIDATE_OUTPUT"
172
+ else
173
+ fail "openenv validate failed"
174
+ printf "%s\n" "$VALIDATE_OUTPUT"
175
+ stop_at "Step 3"
176
+ fi
177
+
178
+ printf "\n"
179
+ printf "${BOLD}========================================${NC}\n"
180
+ printf "${GREEN}${BOLD} All 3/3 checks passed!${NC}\n"
181
+ printf "${GREEN}${BOLD} Your submission is ready to submit.${NC}\n"
182
+ printf "${BOLD}========================================${NC}\n"
183
+ printf "\n"
184
+
185
+ exit 0
test_inference.sh ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Test script for inference.py
3
+ # Run this locally before submitting to catch errors early
4
+
5
+ set -e
6
+
7
+ echo "=== Testing inference.py locally ==="
8
+
9
+ # Check required environment variables
10
+ if [ -z "$HF_TOKEN" ] && [ -z "$API_KEY" ]; then
11
+ echo "ERROR: HF_TOKEN or API_KEY environment variable is required"
12
+ exit 1
13
+ fi
14
+
15
+ # Set defaults for testing
16
+ export API_BASE_URL="${API_BASE_URL:-https://router.huggingface.co/v1}"
17
+ export MODEL_NAME="${MODEL_NAME:-openai/gpt-4o-mini}"
18
+
19
+ # Option 1: Test with Docker image (if available)
20
+ if [ -n "$IMAGE_NAME" ]; then
21
+ echo "Testing with Docker image: $IMAGE_NAME"
22
+ python inference.py
23
+ exit $?
24
+ fi
25
+
26
+ # Option 2: Test with running server
27
+ if [ -n "$SPACE_URL" ]; then
28
+ echo "Testing with server: $SPACE_URL"
29
+ python inference.py
30
+ exit $?
31
+ fi
32
+
33
+ # Option 3: Start local server first
34
+ echo "No IMAGE_NAME or SPACE_URL set."
35
+ echo "Please either:"
36
+ echo " 1. Set IMAGE_NAME to your Docker image name"
37
+ echo " 2. Set SPACE_URL to your running server URL"
38
+ echo " 3. Start the server locally first with: python -m server.app"
39
+ exit 1
uv.lock CHANGED
The diff for this file is too large to render. See raw diff